Vue3 学习笔记 —— 局部/全局组件、递归组件、动态组件、异步组件

发布于:2022-12-14 ⋅ 阅读:(1002) ⋅ 点赞:(0)

目录

1. 局部/全局组件

1.1 局部组件

1.2 全局组件

2. 递归组件

2.1 什么是递归组件

2.1.1 官方文档

2.1.2 讲解视频分享【强推】

2.2 递归菜单树

2.2.1 定义公共数据类型接口

2.2.2 父组件中使用子组件

2.2.3 子组件中的第一个 script 标签

2.2.4 子组件中的第二个 script 标签

2.2.5 子组件中的 template 标签

2.2.6 最终效果

3. 动态组件 Component

3.1 什么是动态组件

3.2 实现 Tab 切换

3.2.1 使用动态组件时,控制台出现警告

3.2.2 动态组件 绑定字符串、绑定组件实例

4. 异步组件 suspense

4.1 为什么要使用异步组件

4.2 defineAsyncComponent 配合 import() 实现分包

4.3 使用异步组件 suspense


1. 局部/全局组件

1.1 局部组件

每次使用之前,都需要 import 导入组件,在 component:{} 中声明组件(不能直接使用组件)

1.2 全局组件

1.2.1 什么是全局组件

每个页面都有使用的组件,可以封装为全局组件

1.2.2 全局组件注册方法

在 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')

1.2.3 全局组件使用方法

任意 vue 文件中,直接使用标签即可,无需引入

2. 递归组件

2.1 什么是递归组件

2.1.1 官方文档

单文件组件https://cn.vuejs.org/api/sfc-script-setup.html#using-components

2.1.2 讲解视频分享【强推】

小满Vue3(第十五章 全局组件,局部组件,递归组件)_哔哩哔哩_bilibili小满Vue3(第十五章 全局组件,局部组件,递归组件)是Vue3 + vite + Ts + pinia + 实战 + 源码 +全栈的第19集视频,该合集共计111集,视频收藏或关注UP主,及时了解更多相关视频内容。https://www.bilibili.com/video/BV1dS4y1y7vd?p=19&vd_source=8bc01635b95dbe8ecd349b2c23b03a10

自己调用自己,通过 条件判断 结束递归(必写,防止内存泄漏)

2.2 递归菜单树

2.2.1 定义公共数据类型接口

type TreeList = {
  name: string;
  icon?: string;
  children?: TreeList[] | [];
};

2.2.2 父组件中使用子组件

定义菜单数据,传给子组件

监听子组件的点击事件,用于获取当前点击的菜单项

<template>
  <div class="layout">
    <Menu :data="data" @on-click="getItem"></Menu>
  </div>
</template>

<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;
};

2.2.3 子组件中的第一个 script 标签

此标签里,用于定义子组件 接受参数、抛出事件 等逻辑

<script setup lang="ts">
type Props<T> = {
  data?: T[] | [];
};

defineProps<Props<TreeList>>();

const emit = defineEmits(['on-click']);

const clickItem = (item: TreeList) => {
  emit('on-click', item);
};
</script>

2.2.4 子组件中的第二个 script 标签

用于定义组件名称,实现组件递归调用自身

由于 setup 中无法定义 name,因此才额外加了这样一个标签

<script lang="ts">
export default {
  name:"TreeItem"
}
</script>

2.2.5 子组件中的 template 标签

展示菜单名称的位置,绑定了子组件的点击事件,实现最外层菜单 点击打印 功能

使用第二个 script 标签里定义的 name,作为递归组件名称标签,并使用

递归组件的 data,也就是当前循环项的 children

递归结束的条件:v-if="item?.children?.length"  当没有 children 时,则停止递归

递归组件内部 emit 了 on-click 事件,因此在递归组件的标签上,要监听 on-click 并抛出

为了避免 子组件的点击事件 同时触发 父组件的点击事件,要给最外层添加 .stop 修饰符,阻止事件冒泡

<template>
  <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>
</template>

2.2.6 最终效果

3. 动态组件 Component

3.1 什么是动态组件

让多个组件,使用同一个挂载点,并动态切换

import A from './A.vue'
import B from './B.vue'

<component :is="A"></component>

3.2 实现 Tab 切换

3.2.1 使用动态组件时,控制台出现警告

出现警告的原因:

reactive 会进行 proxy 代理,组件代理之后 毫无用处;为了节省性能开销,Vue3 推荐使用shallowRef 或者 markRaw 跳过组件 proxy 代理

解决方案:引入 markRaw,并包裹动态组件,如下所示

<template>
  <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>
</template>

<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>

3.2.2 动态组件 绑定字符串、绑定组件实例

在 Vue2 中,绑定字符串,可以实现动态组件切换;

在 setup 语法糖中,绑定组件实例,才能实现动态组件切换;

4. 异步组件 suspense

4.1 为什么要使用异步组件

npm run build

执行上方代码,打包 vue3 项目,生成 dist 目录 

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

因此,在大型应用中,需要将应用分割成小一些的代码块,减少主包体积,进而解决白屏问题;此时,便可以利用到异步组件

4.2 defineAsyncComponent 配合 import() 实现分包

在 setup 语法糖中,直接使用 await,最终代码会被编译成 async setup() {}

子组件中,发送异步请求

<script setup>
  const post = await fetch(`/api/post/1`).then(data => data.json())
</script>

父组件中引用子组件时,通过 defineAsyncComponent 异步加载,配合 import()函数,便可实现分包

import { defineAsyncComponent } from "vue";

const Dialog = defineAsyncComponent(
  () => import("../../components/dialog/index.vue")
);

4.3 使用异步组件 suspense

此组件有两个插槽,都直接收一个 DOM 节点

在 default 插槽中的内容展示出来之前,会展示 fallback 中的内容

  <!-- 异步组件 -->
  <Suspense>
    <!-- 通过 defineAsyncComponent 异步加载,配合 import()函数 引入的子组件 -->
    <template #default>
      <Dialog>
        <template #default>
          <div>我在哪儿</div>
        </template>
      </Dialog>
    </template>

    <!-- 组件加载完成前,展示的内容 -->
    <template #fallback>
      <div>loading...</div>
    </template>
  </Suspense>