目录
4.2 defineAsyncComponent 配合 import() 实现分包
每次使用之前,都需要 import 导入组件,在 component:{} 中声明组件(不能直接使用组件)
每个页面都有使用的组件,可以封装为全局组件
在 main.ts 中引入组件,通过 component() 函数注册全局组件
component() 函数接受两个参数:
具体步骤:在 createApp(App) 后添加 .component('Button', Button)
一定不要在 .mount('#app') 后面添加!!
- import { createApp } from 'vue'
- import App from './App.vue'
- import Button from './components/button.vue'
-
- // 注意顺序
- createApp(App).component('Button', Button).mount('#app')
任意 vue 文件中,直接使用标签即可,无需引入
单文件组件
https://cn.vuejs.org/api/sfc-script-setup.html#using-components
自己调用自己,通过 条件判断 结束递归(必写,防止内存泄漏)
- type TreeList = {
- name: string;
- icon?: string;
- children?: TreeList[] | [];
- };
定义菜单数据,传给子组件
监听子组件的点击事件,用于获取当前点击的菜单项
- <div class="layout">
- <Menu :data="data" @on-click="getItem">Menu>
- div>
-
- <script setup lang="ts">
- const data = reactive<TreeList[]>([
- {
- name: "no.1",
- children: [
- {
- name: "no.1-1",
- children: [
- {
- name: "no.1-1-1",
- },
- ],
- },
- ],
- },
- {
- name: "no.2",
- children: [
- {
- name: "no.2-1",
- },
- ],
- },
- {
- name: "no.3",
- },
- ]);
-
- const getItem = (item: TreeList) => {
- console.log('父组件中,点击子组件', item);
- };
- script>
其实一般接口返回的数据,不是直接组装好的树结构,要转换,可以用下面的方法
- export const jsonToTree = (data: any) => {
- // 初始化结果数组,并判断输入数据的格式
- const result: any = [];
- if (!Array.isArray(data)) {
- return result;
- }
- // 使用map,将当前对象的 category_id 与当前对象对应存储起来
- const map: any = {};
- data.forEach((item) => {
- map[item.category_id] = item;
- });
- data.forEach((item) => {
- const parent = map[item.parent_id];
- if (parent) {
- (parent.children || (parent.children = [])).push(item);
- } else {
- result.push(item);
- }
- });
- return result;
- };
此标签里,用于定义子组件 接受参数、抛出事件 等逻辑
用于定义组件名称,实现组件递归调用自身
由于 setup 中无法定义 name,因此才额外加了这样一个标签
展示菜单名称的位置,绑定了子组件的点击事件,实现最外层菜单 点击打印 功能
使用第二个 script 标签里定义的 name,作为递归组件名称标签,并使用
递归组件的 data,也就是当前循环项的 children
递归结束的条件:v-if="item?.children?.length" 当没有 children 时,则停止递归
递归组件内部 emit 了 on-click 事件,因此在递归组件的标签上,要监听 on-click 并抛出
为了避免 子组件的点击事件 同时触发 父组件的点击事件,要给最外层添加 .stop 修饰符,阻止事件冒泡
- <div style="margin-left: 10px">
- <div :key="index" v-for="(item, index) in data">
-
-
- <div @click.stop="clickItem(item)">{{ item.name }}div>
-
-
- <TreeItem
- v-if="item?.children?.length"
- :data="item.children"
- @on-click="clickItem"
- >TreeItem>
- div>
- div>

让多个组件,使用同一个挂载点,并动态切换
- import A from './A.vue'
- import B from './B.vue'
-
"A">

出现警告的原因:
reactive 会进行 proxy 代理,组件代理之后 毫无用处;为了节省性能开销,Vue3 推荐使用shallowRef 或者 markRaw 跳过组件 proxy 代理
解决方案:引入 markRaw,并包裹动态组件,如下所示
- <div>
- <div class="tab-container">
- <div
- v-for="(item, index) in tabList"
- :key="index"
- @click="changeTab(item)"
- >
- {{ item.name }}
- div>
- div>
-
-
- <component :is="componentId">component>
- div>
-
- <script setup lang="ts">
- import a from "./a.vue";
- import b from "./b.vue";
- import c from "./c.vue";
-
- import { markRaw, reactive, ref } from "vue";
-
- const componentId = ref(a);
-
- const tabList = reactive([
- {
- name: "第五人格",
- // 这里直接写组件,会有警告,使用 markRaw 清除警告
- com: markRaw(a),
- },
- {
- name: "全球高考",
- com: markRaw(b),
- },
- {
- name: "手可摘星辰",
- com: markRaw(c),
- },
- ]);
-
- const changeTab = (item) => {
- componentId.value = item.com;
- };
- script>
在 Vue2 中,绑定字符串,可以实现动态组件切换;
在 setup 语法糖中,绑定组件实例,才能实现动态组件切换;
npm run build
执行上方代码,打包 vue3 项目,生成 dist 目录

浏览器会通过 index.html 入口文件加载项目,当执行到下方代码时,就开始下载项目代码了……这个代码可能几十兆,很大,会导致一段时间的白屏

因此,在大型应用中,需要将应用分割成小一些的代码块,减少主包体积,进而解决白屏问题;此时,便可以利用到异步组件
在 setup 语法糖中,直接使用 await,最终代码会被编译成 async setup() {}
子组件中,发送异步请求
- const post = await fetch(`/api/post/1`).then(data => data.json())
父组件中引用子组件时,通过 defineAsyncComponent 异步加载,配合 import()函数,便可实现分包
- import { defineAsyncComponent } from "vue";
-
- const Dialog = defineAsyncComponent(
- () => import("../../components/dialog/index.vue")
- );
此组件有两个插槽,都直接收一个 DOM 节点
在 default 插槽中的内容展示出来之前,会展示 fallback 中的内容
-
- <Suspense>
-
- <template #default>
- <Dialog>
- <template #default>
- <div>我在哪儿div>
- template>
- Dialog>
- template>
-
-
- <template #fallback>
- <div>loading...div>
- template>
- Suspense>