【Vue3】全局组件,递归组件,动态组件,传送组件,缓存组件,异步组件等

组件使用

父子组件传参

  • 父组件通过v-bind绑定一个数据,然后子组件通过defineProps接受传过来的值
父传子
模板上直接使用
typescript 复制代码
<template>
  <div class="main">
    <div class="vt-test">子组件</div>
    <div>{{ title }}</div>
  </div>
</template>

<script setup lang="ts">
//接受父组件传过来的值
defineProps({
  title: {
    type: String,
    default: '默认值',
  },
});
</script>
js里面使用
typescript 复制代码
// 接受父组件传过来的值;
const props = defineProps({
  title: {
    type: String,
    default: '默认值',
  },
});
console.log('父组件传过来的值', props.title);
ts组件里面泛型接收
typescript 复制代码
//ts模式下接收采取泛型更方便
const prop = defineProps<{
  title: string;
}>();
console.log('父组件传过来的值', prop.title);
ts泛型里面设置默认值
typescript 复制代码
//ts模式下设置默认值
const prop = withDefaults(
  defineProps<{
    title: string;
    arr: number[];
  }>(),
  {
    arr: () => [1, 2, 3],
  }
);
console.log('父组件传过来的值', prop.title);
子传父
方式一,采用defineEmits
  • 子组件
typescript 复制代码
<template>
  <div class="main">
    <div class="vt-test">子组件</div>
    <button @click="send">点击给父组件传值</button>
  </div>
</template>

<script setup lang="ts">
//给父组件传值采用defineEmits
const emit = defineEmits(['on-click','on-getname']);
const send = () => {
  emit('on-click', '花神');
  emit('on-getname', '花神');
};
</script>
  • 父组件
typescript 复制代码
<template>
  <div class="vt-parents">父组件</div>
  <hr />
  <Helloworld :title="name" @on-click="getclick" @on-getname='getName'></Helloworld>
</template>

<script setup lang="ts">
import Helloworld from './components/HelloWorld.vue';
const name = '花神';
const getName = (value) => {
  console.log(value, '子组件传的值');
};
</script>
  • 子组件传多个事件
typescript 复制代码
//给父组件传值采用defineEmits
const emit = defineEmits(['on-click', 'on-getname']);
const send = () => {
  emit('on-click', '花神');
  emit('on-getname', '花神2');
};
const getname = () => {
  emit('on-getname', '花神2');
};
方式二,采用ts的泛型
typescript 复制代码
<script setup lang="ts">
//给父组件传值采用defineEmits
//有没有返回值,没有返回值定义void,不加也不报错
const emit = defineEmits<{
  (e: 'on-click', value: string): void;
  (e: 'on-getname', value: string): void;
}>();
const send = () => {
  emit('on-click', '花神');
};
const getname = () => {
  emit('on-getname', '花神2');
};
</script>
方式三,采用ref获取子组件内部暴露的数据和方法
子组件先暴露
  • defineExpose
父组件接收
  • 类型检测<InstanceType>不写也行

全局组件

  • 在main.ts文件里导入组件并挂载在全局中
  • 页面上直接使用即可
发现有报错,找不到模块"./App.vue"或其相应的类型声明
  • 解决思路:在项目根目录 env.d.ts 文件中,加入以下内容:
typescript 复制代码
/// <reference types="vite/client" />
declare module "*.vue" {
  import type { DefineComponent } from "vue"
  const vueComponent: DefineComponent<{}, {}, any>
  export default vueComponent
}
批量导入组件
  • 想一次性全部导入模块的所有变量就可以使用 * as 代表全部
  • Object.entries() ,将一个对象中可枚举属性的键名和键值按照二维数组的方式返回

可选链

  • 单问号,没有值返回undefined
  • 双问号,只判断undefined和null,0和false不处理

递归组件

  • 简单来说就是在组件中内使用组件本身
  • 一般用来实现树形菜单,多级菜单
  • 注意的是:要防止无限递归,造成调用栈溢出
步骤一,建立子组件Tree.vue
typescript 复制代码
<template>
  <div class="main" v-for="(item, index) in data" :key="index">
    <input type="checkbox" v-model="item.checked" /><span>{{ item.name }}</span>
    <Tree v-if="item?.children?.length" :data="item?.children"></Tree>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue';
//定义接收的数据类型
interface Tree {
  name: string;
  checked: boolean;
  children?: Tree[];
}
defineProps<{ data?: Tree[] }>();
</script>

<style scoped>
.main {
  margin-left: 30px;
}
</style>
步骤二,建立父组件展示最终页面
typescript 复制代码
<template>
  <div class="main"></div>
  <TreeVal :data="data"></TreeVal>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue';
import TreeVal from './components/Tree.vue';
interface Tree {
  name: string;
  checked: boolean;
  children?: Tree[];
}
//造自定义数据传递给子组件
const data = reactive<Tree[]>([
  {
    name: '1',
    checked: false,
    children: [
      {
        name: '1-1',
        checked: false,
        children: [
          {
            name: '1-1-1',
            checked: false,
            children: [],
          },
        ],
      },
    ],
  },
  {
    name: '2',
    checked: false,
    children: [],
  },
]);
</script>

<style scoped></style>
  • 最终展示页面
如何设置递归组件名name
  • 方式一:可以采用文件名本身
  • 方式二:可以页面内再建script里自定义组件名
  • 方式三:通过插件
防止点击冒泡和传递event对象

动态组件

  • 让多个组件使用同一个挂载点,并动态切换
  • 在挂载点使用component标签,然后使用v-bind :is="组件"
方式一
typescript 复制代码
<template>
  <div v-for="(item, index) in com" :key="index">
    <div>{{ item.name }}</div>
    <component :is="item.com"></component>
  </div>
</template>

<script lang="ts">
import A from './components/A.vue';
import B from './components/B.vue';
import C from './components/C.vue';

export default {
  components: {
    A,
    B,
    C,
  },
};
</script>
<script setup lang="ts">
import { ref, reactive } from 'vue';
//区别,此时使用的时候是字符串形式
const com = reactive([
  { name: 'A组件', com: "A" },
  { name: 'B组件', com: "B" },
  { name: 'C组件', com: "C" },
]);
</script>
方式二。Vue3写法
typescript 复制代码
<template>
  <div v-for="(item, index) in com" :key="index">
    <div>{{ item.name }}</div>
    <component :is="item.com"></component>
  </div>
</template>
typescript 复制代码
<script setup lang="ts">
import { ref, reactive } from 'vue';
import A from './components/A.vue';
import B from './components/B.vue';
import C from './components/C.vue';
//这边直接使用
const com = reactive([
  { name: 'A组件', com: A },
  { name: 'B组件', com: B },
  { name: 'C组件', com: C },
]);
</script>
  • 虽然展示,但有报错
  • 使用markRaw,到时候会多一个skip属性,reactive碰到这个属性,会跳过proxy代理
typescript 复制代码
<script setup lang="ts">
import { ref, reactive, markRaw } from 'vue';
import A from './components/A.vue';
import B from './components/B.vue';
import C from './components/C.vue';

const com = reactive([
  { name: 'A组件', com: markRaw(A) },
  { name: 'B组件', com: markRaw(B) },
  { name: 'C组件', com: markRaw(C) },
]);
</script>

Teleport 传送组件

  • Teleport是一种能够将我们的模板渲染至指定Dom节点,不受父级style,v-show等属性影响
  • 但data,prop数据依旧能够共用的技术
  • 主要为了解决,使Teleport节点挂载在其他指定的节点下,完全不受父级style样式的影响
  • 例如:想要将子组件的这个弹窗挂载到外层
  • 使用Teleport ,to挂载到想要展示的层级
typescript 复制代码
<template>
  <div class="main">
    <h3>子组件</h3>
    <!-- disabled设置为true,则to不生效,否则生效 -->
    <Teleport to="body" :disabled="true	">
      <div class="box">弹窗</div>
    </Teleport>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue';
</script>

<style scoped lang="less">
.main {
  position: relative;
  width: 500px;
  height: 30vh;
  margin-left: 30px;
  background: #888;
}
.box {
  background: yellow;
  width: 200px;
  height: 200px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
</style>

keep-alive缓存组件

  • 注意,keep-alive里面只能有一个子节点,不能放多个,多个的话需要if条件判断只显示一个
  • 有时候不希望组件被重新渲染影响使用体验,或者处于性能考验,避免多次重复渲染
  • 目的,想要切换组件的时候,将我之前输入的值保留
include
  • 缓存name匹配上的
  • 在vue3中,组件内部的组件name需要重新写,setup的语法糖不支持
  • 子组件
typescript 复制代码
<template>
  <div class="main">
    <h3>子组件</h3>
    <Teleport to="body" :disabled="true">
      <div class="box">弹窗</div>
    </Teleport>
    <input type="text" />
  </div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue';
</script>
//重写name,为了在父组件可以使用include缓存Avue
<script lang="ts">
export default {
  name: 'Avue',
};
</script>
  • 父组件
typescript 复制代码
<template>
  <div class="main">
    <!-- include,只缓存名字匹配的,例如,这次只缓存Avue,Bvue不缓存-->
    <keep-alive :include="['Avue']">
      <Avue v-if="istrue"></Avue>
      <Bvue v-else></Bvue>
    </keep-alive>
    <button @click="change">切换</button>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue';
import Avue from './components/A.vue';
import Bvue from './components/B.vue';
let istrue = ref('true');
const change = () => {
  istrue.value = !istrue.value;
};
</script>

<style scoped></style>
enclude
  • 不缓存name匹配上的
max,缓存最多数
  • 超出max,会剔除旧的,不活跃的,保留新组件进行缓存
keep-alive生命周期onActivated和onDeactivated
  • 初始化只会走一次
  • 子组件
typescript 复制代码
<template>
  <div class="main">
    <h3>子组件</h3>
    <Teleport to="body" :disabled="true">
      <div class="box">弹窗</div>
    </Teleport>
    <input type="text" />
  </div>
</template>

<script setup lang="ts">
import {
  ref,
  reactive,
  onMounted,
  onUnmounted,
  onActivated,
  onDeactivated,
} from 'vue';
onMounted(() => {
  console.log('初始化');
});
onActivated(() => {
  console.log('keeplive缓存生命周期初始化');
});
onDeactivated(() => {
  console.log('keeplive缓存生命周期卸载');
});
onUnmounted(() => {
  console.log('卸载');
});
</script>
<script lang="ts">
export default {
  name: 'Avue',
};
</script>
  • 刚进入
  • 切换后
  • 切回来

异步组件

  • defineAsyncComponent加载异步组件
  • 大型应用中,需要将应用分割成小一些的代码块,并且减少主包的体积
typescript 复制代码
<template>
  <div class="main">
    <Suspense>
      <template #default>
          <Syncvue></Syncvue>
      </template>
      <template #fallback>
        <!-- 放置骨架屏等 -->
      </template>
    </Suspense>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive,defineAsyncComponent} from 'vue';
const Syncvue =defineAsyncComponent(()=》{import('@/components/sync.vue')})
</script>

<style scoped></style>
Suspense
  • 它帮助我们处理异步组件,但它的作用远不止于此
  • 我们可以将异步组件的加载状态和占位内容进行统一管理。当异步组件加载完成时,会自动替换占位内容,从而实现平滑的过渡效果
  • Suspense 允许我们协调整个应用程序的加载状态,而不是一个页面上到处都是 loading
  • Suspense,把异步组件放入 default 槽,把回退加载状态放入 fallback 槽。
  • Suspense 机制还提供了错误处理的能力。当异步组件加载出错时,可以显示自定义的错误信息,以便及时通知用户
typescript 复制代码
<template>
  <Suspense>
    <template v-slot:default>
      <div>Loading...</div>
    </template>

    <template v-slot:error>
      <div>Failed to load component.</div>
    </template>

    <Syncvue />
  </Suspense>
</template>

<script>
import { defineAsyncComponent } from 'vue';

const Syncvue =defineAsyncComponent(()=》{import('@/components/sync.vue')})

export default {
  components: {
    Syncvue ,
  },
};
</script>
相关推荐
专注VB编程开发20年1 分钟前
VB.NET关于接口实现与简化设计的分析,封装其他类
java·前端·数据库
小妖66610 分钟前
css 中 content: “\e6d0“ 怎么变成图标的?
前端·css
L耀早睡1 小时前
mapreduce打包运行
大数据·前端·spark·mapreduce
HouGISer1 小时前
副业小程序YUERGS,从开发到变现
前端·小程序
outstanding木槿1 小时前
react中安装依赖时的问题 【集合】
前端·javascript·react.js·node.js
霸王蟹2 小时前
React中useState中更新是同步的还是异步的?
前端·javascript·笔记·学习·react.js·前端框架
霸王蟹2 小时前
React Hooks 必须在组件最顶层调用的原因解析
前端·javascript·笔记·学习·react.js
专注VB编程开发20年2 小时前
asp.net IHttpHandler 对分块传输编码的支持,IIs web服务器后端技术
服务器·前端·asp.net
爱分享的程序员3 小时前
全栈项目搭建指南:Nuxt.js + Node.js + MongoDB
前端
隐含3 小时前
webpack打包,把png,jpg等文件按照在src目录结构下的存储方式打包出来。解决同一命名的图片资源在打包之后,重复命名的图片就剩下一个图片了。
前端·webpack·node.js