【Vue3】12-局部组件 & 全局组件 & 递归组件

979 阅读1分钟

1. 局部组件

1.1 场景

一个页面的模块非常多,不想全写在一个页面里面,就拆成一个个局部组件,再引入使用

1.2 使用

<template>
    <!-- 使用组件 -->
    <head-component></head-component>
    <content-component></content-component>
    <foot-component></foot-component>
</template>

<script setup lang="ts">
    // 引入组件(无需注册)
    import HeadComponent from "..."
    import ContentComponent from "..."
    import FootComponent from "..."
</script>

2. 全局组件

2.1 场景

一个组件在全局范围内使用频率非常高,可以在全局引入并注册这个组件,在任何地方都能直接使用

2.2 全局单独引入并注册

// main.ts
// 引入
import GlobalComponentVue from '...'

// 注册
const app = create(App)
app.component('GlobalComponent', GlobalComponentVue)  // 参数为 组件名称 和 组件

2.3 全局批量引入并注册(以 Element-UI 为例)

// main.ts

import * as ElementPlusIconsVue from '@element-plus/icons-vue'

const app = create(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}

3. 递归组件

3.1 场景

比如树和多级菜单,就是递归出来的

3.2 使用

// App.vue
<template>
    <!-- 将数据传给子组件 -->
    <tree :data="data"></tree>
</template>

<script setup lang="ts">
import { reactive } from 'vue';
import Tree from './components/Tree.vue'

// 定义一个数据类型
interface Menu {
    name: string,
    checked: boolean,
    children?: Menu[]
}

// Mock 一些数据
const data = reactive<Menu[]>([
    {
        name: '1',
        checked: true,
    },
    {
        name: '2',
        checked: true,
        children: [
            {
                name: '2-1', 
                checked: false
            },
            {
                name: '2-2',
                checked: true
            }
        ]
    },
    {
        name: '3',
        checked: false,
        children: [
            {
                name: '3-1',
                checked: true,
                children: [
                    {
                        name: '3-1-1',
                        checked: false
                    }
                ]
            }
        ]
    },
])
</script>
// Tree.vue
<template>
    <div  v-for="item in data" :key="item.name" style="margin: 10px">
        <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">
// 在子组件中还是要定义同个数据类型,用于对接收数据的类型判定
interface Menu {
    name: string,
    checked: boolean,
    children?: Menu[]
}

const { data } = defineProps<{
    data: Menu[]  // 声明接收的数据类型
}>()
</script>

页面效果 ↓

image.png

如果递归时,组件名不想使用文件名,想更改组件的名称怎么办?

方式一:在递归组件中,加一个 script 标签(注意不要加 setup 属性),内部默认暴露一个对象,属性为一个 name
<script lang="ts">
    export default { name: "OwnName" }
</script>
方式二:使用插件 `unplugin-vue-define-options`
// 第一步:安装插件 npm i unplugin-vue-define-options

// 第二步:vite.config.ts 引入并注册
import DefineOptions from 'unplugin-vue-define-options/vite'
export default defineConfig({
  plugins: [DefineOptions()],
})

// 第三步:tsconfig.json 配置 types
{
  "compilerOptions": {
    "types": ["unplugin-vue-define-options/macros-global"]
  }
}

// 第四步:递归组件中使用 defineOptions
defineOptions({
    name: "OwnName"
})

3.3 绑定事件

<template>
    <!-- 绑定事件,并阻止冒泡,$event 是事件对象 -->
    <div @click.stop="clickTap(item, $event)"  v-for="item in data" :key="item.name" class="menu">
        <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">
interface Menu {
    name: string,
    checked: boolean,
    children?: Menu[]
}

const { data } = defineProps<{
    data: Menu[]
}>()


const clickTap = (item: Menu, e: any) => {
    console.log(item)
    console.log(e.target)  // 获取事件源对象
}