Skip to content

6. 组件通信

Vue3组件通信和Vue2的区别:

  • 移出事件总线,使用mitt代替。
  • vuex换成了pinia
  • .sync优化到了v-model里面了。
  • $listeners所有的东西,合并到$attrs中了。
  • $children被砍掉了。

常见搭配形式:

image-20231119185900990

案例基本框架

组件间通讯

我是父元素,演示props

我是子元素

案例目录结构如下

txt
│  App.vue
│  main.ts

├─assets
│      main.css     

├─router
│      index.ts

└─views
    ├─01-props
    │      Child.vue
    │      Father.vue

    ├─02-emit
    │      Child.vue
    │      Father.vue

    ├─03-mitt
    │      Child.vue
    │      Father.vue

    ├─04-v-model
    │      Child.vue
    │      Father.vue

    ├─05-attrs
    │      Child.vue
    │      Father.vue

    ├─06-refs-parent
    │      Child.vue
    │      Father.vue

    ├─07-provice-inject
    │      Child.vue
    │      Father.vue

    ├─08-pinia
    │      Child.vue
    │      Father.vue

    └─09-slot
            Child.vue
            Father.vue

每个知识点对一个的父子组件

vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="childComponent">
        我是子元素
   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="Child"> 
import { ref   } from 'vue'			
 

</script> 
 
<!-- 3 定义样式 -->
<style scoped> 
 
</style>
vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="fatherComponent">
       <h3>我是父元素,演示 props</h3>
       <Child></Child>
   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="Father"> 
import { ref   } from 'vue'	
	
 
 import Child from './Child.vue'
</script> 
 
<!-- 3 定义样式 -->
<style scoped> 
 
</style>

框架应用其他的文件

css
/* 定义一些变量,统一样式 */
:root {
  --fc-bg-father: #52acb3;
  --fc-bg-child: #acca8c;  
}

*,html,body{
  margin:0;
  padding: 0;
}

/* 父组件样式 */
.fatherComponent{
  padding: 10px;
  background-color: var(--fc-bg-father);
  box-shadow: 0 0 3px pink;
  border-radius: 5px;
}

/* 子组件样式 */
.childComponent{
  margin: 10px;
  padding: 10px;
  background-color: var(--fc-bg-child);
  box-shadow: 0 0 4px rgb(208, 197, 199);
  border-radius: 5px;
  min-height: 100px;
}
ts
import { createRouter, createWebHistory } from 'vue-router'


const router = createRouter({
    history: createWebHistory(),
    routes: [
        {
            path: '/props',
            component: () => import('@/views/01-props/Father.vue')
        },
        {
            path: '/emit',
            component: () => import('@/views/02-emit/Father.vue')
        },
        {
            path: '/mitt',
            component: () => import('@/views/03-mitt/Father.vue')
        }, {
            path: '/v-model',
            component: () => import('@/views/04-v-model/Father.vue')
        },{
            path:'/attrs',
            component:()=>import('@/views/05-attrs/Father.vue')
        },{
            path:'/refs-parent',
            component:()=>import('@/views/06-refs-parent/Father.vue')
        },{
            path:'/provice-inject',
            component:()=>import('@/views/07-provice-inject/Father.vue')
        },{
            path:'/pinia',
            component:()=>import('@/views/08-pinia/Father.vue')
        },{
            path:'/slot',
            component:()=>import('@/views/09-slot/Father.vue')
        }

    ]
})

export default router;
ts
//使用自定义全局样式
import './assets/main.css'

import { createApp } from 'vue'
import App from './App.vue'

//引入ElementPlus组件库
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

//引入路由
import router from '@/router'

const app = createApp(App)

app.use(router)
//注册全局组件
app.use(ElementPlus)

app.mount('#app')
vue
<script setup lang="ts">
//自定义组件,包含了导航菜单


</script>

<template>
    <el-container class="root_container">
        <el-header class="header">组件间通讯 </el-header>
        <el-container>
            <el-aside width="200px" class="aside">
                <el-menu active-text-color="#ffd04b" background-color="#545c64" class="el-menu-vertical-demo"
                    default-active="2" text-color="#fff" router>

                    <el-menu-item index="/props">
                        <el-icon>
                            <document />
                        </el-icon>
                        <span>1.Props方式</span>
                    </el-menu-item>
                    <el-menu-item index="/emit">
                        <el-icon> <document />   </el-icon>
                        <span>2.自定义事件</span>
                    </el-menu-item>
                    <el-menu-item index="/mitt">
                        <el-icon> <document />   </el-icon>
                        <span>3.mitt第三方库</span>
                    </el-menu-item>
                    <el-menu-item index="/v-model">
                        <el-icon> <document />   </el-icon>
                        <span>4.v-model</span>
                    </el-menu-item>
                    <el-menu-item index="/attrs">
                        <el-icon> <document />   </el-icon>
                        <span>5.$attrs</span>
                    </el-menu-item>
                    <el-menu-item index="/refs-parent">
                        <el-icon> <document />   </el-icon>
                        <span>6.$refs-$parent</span>
                    </el-menu-item>
                    <el-menu-item index="/provice-inject">
                        <el-icon> <document />   </el-icon>
                        <span>7.provice-inject</span>
                    </el-menu-item>
                    <el-menu-item index="/pinia">
                        <el-icon> <document />   </el-icon>
                        <span>8.pinia</span>
                    </el-menu-item>
                    <el-menu-item index="/slot">
                        <el-icon> <document />   </el-icon>
                        <span>8.slot插槽</span>
                    </el-menu-item>
                </el-menu>
            </el-aside>
            <el-main class="main">
                <RouterView></RouterView>
            </el-main>
        </el-container>
    </el-container>
</template>

<style scoped>
.root_container {
    height: 100vh;
}

.header {
    background-color: #909b89;
    line-height: 60px;
}

.aside {
    background-color: #e4dbdb;
}
</style>
ts
import { createRouter, createWebHistory } from 'vue-router'


const router = createRouter({
    history: createWebHistory(),
    routes: [
        {
            path: '/props',
            component: () => import('@/views/01-props/Father.vue')
        },
        {
            path: '/emit',
            component: () => import('@/views/02-emit/Father.vue')
        },
        {
            path: '/mitt',
            component: () => import('@/views/03-mitt/Father.vue')
        }, {
            path: '/v-model',
            component: () => import('@/views/04-v-model/Father.vue')
        },{
            path:'/attrs',
            component:()=>import('@/views/05-attrs/Father.vue')
        },{
            path:'/refs-parent',
            component:()=>import('@/views/06-refs-parent/Father.vue')
        },{
            path:'/provice-inject',
            component:()=>import('@/views/07-provice-inject/Father.vue')
        },{
            path:'/pinia',
            component:()=>import('@/views/08-pinia/Father.vue')
        },{
            path:'/slot',
            component:()=>import('@/views/09-slot/Father.vue')
        }

    ]
})

export default router;

6.1. 【props】

概述:props是使用频率最高的一种通信方式,常用与 :父 ↔ 子

  • 父传子:属性值是非函数
  • 子传父:属性值是函数

父组件:

vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="fatherComponent">
       <h3>我是父元素,演示 props</h3>

       <div v-if="myWords">
              <ul>
               <li v-for="(word,index) in   myWords">{{index}} {{ word  }}</li>
              </ul> 
       </div>

       <Child :money="100" :otherMoney="{ CNY: 999, USD: 50.6 }" :getluckyWords="getluckyWords"></Child>
   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="Father">
import { ref ,reactive} from 'vue'
import Child from './Child.vue'

let  myWords = reactive<string[]>([])

//定义方法
function getluckyWords(words:string) {
   console.log("拜年话:", words)
   myWords.unshift(words)
}


</script> 

<!-- 3 定义样式 -->
<style scoped></style>

子组件

vue
<template>
  <div class="child">
    <h3>子组件</h3>
		<h4>我的玩具:{{ toy }}</h4>
		<h4>父给我的车:{{ car }}</h4>
		<button @click="getToy(toy)">玩具给父亲</button>
  </div>
</template>

<script setup lang="ts" name="Child">
	import { ref } from "vue";
	const toy = ref('奥特曼')
	
	defineProps(['car','getToy'])
</script>

6.2. 【自定义事件

  1. 概述:自定义事件常用于:子 => 父。
  2. 注意区分好:原生事件、自定义事件。
  • 原生事件:
    • 事件名是特定的(clickmosueenter等等)
    • 事件对象$event: 是包含事件相关信息的对象(pageXpageYtargetkeyCode
  • 自定义事件:
    • 事件名是任意名称
    • 事件对象$event: 是调用emit时所提供的数据,可以是任意类型!!!
  1. 示例:
vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="fatherComponent">
       <h3>我是父元素,演示 自定义事件 emit</h3>

       <div v-if="myWords">
              <ul>
               <li v-for="(word,index) in   myWords">{{index}} {{ word  }}</li>
              </ul> 
       </div>

       <Child @get-lucky-words="getluckyWords($event)"></Child>
   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="Father">
import { ref ,reactive} from 'vue'
import Child from './Child.vue'

let  myWords = reactive<string[]>([])

//定义方法
function getluckyWords(words:string) {
   // console.log("event  ",event)
   // console.log("使用自定义事件,拜年话:", words)
   myWords.unshift(words)
}


</script> 

<!-- 3 定义样式 -->
<style scoped></style>
vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="childComponent">

        我是子元素

    
       <button type="button" @click="getluckyWords('吉祥如意,万事顺遂,身体健康,心想事成--使用方法触发')">拜年1</button>
       <button type="button" @click="$emit('get-lucky-words','吉祥如意,万事顺遂,身体健康,心想事成【直接触发事件】')">拜年2</button>


   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="Child"> 
import { ref   } from 'vue'			

//定义事件
const emit = defineEmits(['get-lucky-words'])

function getluckyWords(words:string){
  //产生事件
  emit('get-lucky-words',words,666)
}


</script> 
 
<!-- 3 定义样式 -->
<style scoped> 
 
</style>

6.3. 【mitt】

第三方支持的消息总线,github仓库 developit/mitt: 🥊 Tiny 200 byte functional event emitter / pubsub. (github.com)

概述:与消息订阅与发布(pubsub)功能类似,可以实现任意组件间通信。

安装mitt

shell
npm i mitt

新建文件:src\utils\emitter.ts

javascript
// 引入mitt 
import mitt from "mitt";

// 创建emitter
const emitter = mitt()

/*
  // 绑定事件
  emitter.on('abc',(value)=>{
    console.log('abc事件被触发',value)
  })
  emitter.on('xyz',(value)=>{
    console.log('xyz事件被触发',value)
  })

  setInterval(() => {
    // 触发事件
    emitter.emit('abc',666)
    emitter.emit('xyz',777)
  }, 1000);

  setTimeout(() => {
    // 清理事件
    emitter.all.clear()
  }, 3000); 
*/

// 创建并暴露mitt
export default emitter

接收数据的组件中:绑定事件、同时在销毁前解绑事件:

typescript
import emitter from "@/utils/emitter";
import { onUnmounted } from "vue";

// 绑定事件
emitter.on('send-toy',(value)=>{
  console.log('send-toy事件被触发',value)
})

onUnmounted(()=>{
  // 解绑事件
  emitter.off('send-toy')
})

【第三步】:提供数据的组件,在合适的时候触发事件

javascript
import emitter from "@/utils/emitter";

function sendToy(){
  // 触发事件
  emitter.emit('send-toy',toy.value)
}

注意这个重要的内置关系,总线依赖着这个内置关系

6.4.【v-model】传送门

在组件中使用v-model进行通讯使用方式子组件有两种写法,

  • 从 Vue 3.4 开始,推荐的实现方式是使用 defineModel() 宏:
  • Vue 3.4之前 参考 ElementPlus中的el-input组件
    • defineProps(['modelValue'])
    • defineEmits(['update:modelValue'])
  1. 概述:实现 父↔子 之间相互通信。

  2. 前序知识 —— v-model的本质

    [组件 v-model 的底层机制),](https://cn.vuejs.org/guide/components/v-model.html#under-the-hood ,此处还可以参考【表单输入绑定

    vue
    <!-- 使用v-model指令 -->
    <input type="text" v-model="userName">
    
    <!-- v-model的本质是下面这行代码 -->
    <input 
      type="text" 
      :value="userName" 
      @input="userName =(<HTMLInputElement>$event.target).value"
    >
  3. 组件标签上的v-model的本质::moldeValueupdate:modelValue事件。

    vue
    <!-- 1 定义组件模版 --> 
    <template>
       <div class="fatherComponent">
          <h3>我是父元素,演示 v-model</h3>
    
          <!-- 表单输入绑定  :https://cn.vuejs.org/guide/essentials/forms.html#form-input-bindings -->
          <!-- <input type="text" v-model="username"> -->
          
          <!-- 上面的 v-model 本质是 ↓ -->
          <input :value="username"  @input="event => username =( event.target as HTMLInputElement).value">
    
          <!-- 在自定义组件中使用v-model -->
          
          <!-- <MyInput :modelValue="username"  @update:modelValue="event => username = event"></MyInput> -->
    
          <MyInput v-model="username"></MyInput> 
    
    
    
       </div>
    </template> 
    
    <!-- 2 定义组件逻辑 -->
    <script setup lang="ts" name="Father">
    import { ref } from 'vue'
    import MyInput from './MyInput.vue'
    
    
    //定义数据
    let username = ref('admin')
    
    
    </script> 
     
    <!-- 3 定义样式 -->
    <style scoped></style>

    MyInput组件中:

3.4之前的写法

vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="childComponent">
        <input type="text" :value="modelValue" @input="inputFun($event)">
   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="MyInput"> 
import { ref   } from 'vue'			

defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])


function inputFun(event:Event){
   
   console.log(
     '%c event: ',
     'background-color: #3756d4; padding: 4px 8px; border-radius: 2px; font-size: 14px; color: #fff; font-weight: 700;',
     (event.target as HTMLInputElement).value
   )
   emit('update:modelValue',(event.target as HTMLInputElement).value)
}


</script> 
 
<!-- 3 定义样式 -->
<style scoped> 
 
</style>

3.4+之后的写法defineModel

vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="childComponent">
      使用model的方式
        <input type="text" :value="model" @input="inputFun($event)">
   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="MyInput"> 
import { ref   } from 'vue'

//定义v-model绑定的数据
const model = defineModel()
console.log(model); //model 的类型  CustomRefImpl 


//定义方法
function inputFun(event:Event){
   model.value = (event.target as HTMLInputElement).value
}


</script> 
 
<!-- 3 定义样式 -->
<style scoped> 
 
</style>

组件上除了使用默认的v-model绑定的modelValue属性之外还可以有【参数】、【多个 v-model 绑定】、【v-model 修饰符】等知识点,可以点击链接查看官网信息。

6.5.【$attrs 】

官网传送门

  1. 概述:$attrs用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)。

  2. 具体说明:$attrs是一个对象,包含所有父组件传入的标签属性。

    注意:$attrs会自动排除props中声明的属性(可以认为声明过的 props 被子组件自己“消费”了)

父组件:

vue
<template>
  <div class="father">
    <h3>父组件</h3>
		<Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA"/>
  </div>
</template>

<script setup lang="ts" name="Father">
	import Child from './Child.vue'
	import { ref } from "vue";
	let a = ref(1)
	let b = ref(2)
	let c = ref(3)
	let d = ref(4)

	function updateA(value){
		a.value = value
	}
</script>

子组件:

vue
<template>
	<div class="child">
		<h3>子组件</h3>
		<GrandChild v-bind="$attrs"/>
	</div>
</template>

<script setup lang="ts" name="Child">
	import GrandChild from './GrandChild.vue'
</script>

孙组件:

vue
<template>
	<div class="grand-child">
		<h3>孙组件</h3>
		<h4>a:{{ a }}</h4>
		<h4>b:{{ b }}</h4>
		<h4>c:{{ c }}</h4>
		<h4>d:{{ d }}</h4>
		<h4>x:{{ x }}</h4>
		<h4>y:{{ y }}</h4>
		<button @click="updateA(666)">点我更新A</button>
	</div>
</template>

<script setup lang="ts" name="GrandChild">
	defineProps(['a','b','c','d','x','y','updateA'])
</script>

6.6. 【$refs、$parent】

  1. 概述:

    • $refs用于 :父→子。
    • $parent用于:子→父。
  2. 原理如下:

    属性说明
    $refs值为对象,包含所有被ref属性标识的DOM元素或组件实例。
    $parent值为对象,当前组件的父组件实例对象。

我是父元素,演示refs-parent

所有的合计:0

  1. 请战英雄列表
我是英雄:张飞, 战斗力:800
我是英雄:关羽, 战斗力:600
我是英雄:刘备, 战斗力:900
vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="fatherComponent" style=" margin: 10px;
               padding: 10px;
               background-color: #d2e0a5;
               box-shadow: 0 0 4px rgb(236, 214, 218);
               border-radius: 5px;
               min-height: 100px;">
      <h3>我是父元素,演示refs-parent</h3>

      <h4>所有的合计:{{ allAounter }}</h4>

      <button type="button" @click="getChild($refs)" style="border-radius: 2px;background-color: aliceblue;padding:3px;margin:10px">访问实例对象(访问自组件) </button>
      <ol>
        <li style="font-weight: 900;">请战英雄列表</li>
        <li v-for="hero in heros">
          {{ hero }} 申请出战.....
        </li>
      </ol>

      <Child ref="ref1" name="张飞" :initMax="800"></Child>
      <Child ref="ref2" name="关羽" :initMax="600"></Child>
      <Child ref="ref3" name="刘备" :initMax="900"></Child>
      
   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="Father">
import { reactive, ref } from 'vue'

import Child from './Child.vue'

let ref1 = ref()
let ref2 = ref()
let ref3 = ref()


//请战英雄列表
const heros = reactive<string[]>([])


let allAounter = ref(0)

const getChild = (refs: {    [key:string]:any})=>{
      
      // console.log(
      //   '%c ref1: ',
      //   'background-color: #3756d4; padding: 4px 8px; border-radius: 2px; font-size: 14px; color: #fff; font-weight: 700;',
      //   ref1
      // )


      // ref1.value.counter++;

      // //计算所有counter
      
      // console.log(
      //   '%c refs: ',
      //   'background-color: #3756d4; padding: 4px 8px; border-radius: 2px; font-size: 14px; color: #fff; font-weight: 700;',
      //   refs
      // )

      for (const key in refs) {
        //获取每个组件的实例
        const child = refs[key];
      
        //  console.log(
        //    '%c key: ',
        //    'background-color: #3756d4; padding: 4px 8px; border-radius: 2px; font-size: 14px; color: #fff; font-weight: 700;',
        //    key,refs[key]
        //  )
         
        let {counter,maxPlus} = child;

        //  console.log(
        //    '%c child: ',
        //    'background-color: #3756d4; padding: 4px 8px; border-radius: 2px; font-size: 14px; color: #fff; font-weight: 700;',
        //    child
        //  )
        //调用组件的方法也可以传递数据
         child.maxPlus()
         
      }


}

function collectChildName(name:string){
  console.log(name+" 申请出战");
  heros.unshift(name)
}


defineExpose({collectChildName})

</script> 
 
<!-- 3 定义样式 -->
<style scoped></style>
vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="childComponent" style=" margin: 10px;
               padding: 10px;
               background-color: #acca8c;
               box-shadow: 0 0 4px rgb(208, 197, 199);
               border-radius: 5px;
               min-height: 100px;">
        我是英雄:{{ name }}, 战斗力:{{ max }}

        <button type="button" @click="counter = counter+1" style="border-radius: 2px;background-color: aliceblue;padding:3px;margin:10px"> 我是计数器,点我加1 --{{counter  }}</button>

        <button @click="invokeParentFun($parent)" style="border-radius: 2px;background-color: aliceblue;padding:3px;margin:10px">{{ name }}申请出战</button> 

   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="Child"> 
import { ref   } from 'vue'			
let counter = ref(0)

let {name,initMax} = defineProps(['name','initMax'])
let max = ref(initMax?initMax:100)

function maxPlus(){
   max.value++
}


//调用$parent组件实例方法 向父组件传递数据
function invokeParentFun(parent:{collectChildName:Function}){
   
   console.log(
     '%c parent: ',
     'background-color: #3756d4; padding: 4px 8px; border-radius: 2px; font-size: 14px; color: #fff; font-weight: 700;',
     parent
   )

   parent.collectChildName(name)
}


//对外暴露的数据
defineExpose({counter,maxPlus})



</script> 
 
<!-- 3 定义样式 -->
<style scoped> 
 
</style>

6.7. 【provide、inject】

  1. 概述:实现祖孙组件直接通信

  2. 具体使用:

    • 在祖先组件中通过provide配置向后代组件提供数据
    • 在后代组件中通过inject配置来声明接收数据
  3. 具体编码:

    【第一步】父组件中,使用provide提供数据

    vue
    <!-- 1 定义组件模版 --> 
    <template>
       <div style="padding: 10px;   background-color: #52acb3; box-shadow: 0 0 3px pink; border-radius: 5px;">
          <h3>我是父元素,provice-parent</h3>
          <p>
             <div class="block">
                学校宣传处
                <input type="text" v-model="propagandaMessage" /> <button type="button" @click="publicMessage">发布</button>
                <ol>
                   <li v-for="msg in  allMsg">
                      已发布消息:{{ msg }}
                   </li>
                </ol>
             </div>
             <div class="block">
                   蓝桥杯报名处
                   <ol>
                      <li v-for="sign in  signUpList">
                         已报名,学号 {{ sign }}
                      </li>
                   </ol>
             </div>
          </p>
          <ClassRoom></ClassRoom>
       </div>
    </template> 
    
    <!-- 2 定义组件逻辑 -->
    <script setup lang="ts" name="School">
    import { ref, reactive, provide } from 'vue'
    import ClassRoom from './ClassRoom.vue'
    
    let propagandaMessage = ref('')
    let allMsg = reactive<string[]>([])
    
    let signUpList:number[] = reactive([])
    
    
    //方法
    //发布学校通知
    function publicMessage() {
       allMsg.push(propagandaMessage.value)
       // propagandaMessage.value = ''
    }
    
    //用于接受孙孙节点发送过来的数据
    function signUp(studentNum:number){
       signUpList.push(studentNum)
    }
    
    provide('allMsg', allMsg)
    provide('signUp', signUp)
    
    
    </script> 
     
    <!-- 3 定义样式 -->
    <style scoped>
    .block{
       display: inline-block;
       width: 45%;
       margin: 0 5px;
       vertical-align: top;
       border: solid 1px grey;
       padding-left: 10px;
    
    }
    </style>

    注意:子组件中不用编写任何东西,是不受到任何打扰的

    【第二步】孙组件中使用inject配置项接受数据。

    vue
    <!-- 1 定义组件模版 --> 
    <template>
       <div class="student">
          学生xxx,学号:{{ number }} ,<button type="button" @click="signUp(number)">我要报名</button>
    
          <ul>
             <li v-for="msg in  allMsg">
                已发布消息:{{ msg }}
             </li>
          </ul>
       </div>
    </template> 
    
    <!-- 2 定义组件逻辑 -->
    <script setup lang="ts" name="Student">
    import { inject, ref } from 'vue'
    
    defineProps(['number'])
    
    const allMsg = inject('allMsg',[''])
    const signUp = inject('signUp',new Function())
    
    
    </script> 
     
    <!-- 3 定义样式 -->
    <style scoped> .student {
        display: inline-block;
        width: 300px;
        height: 300px;
        border: solid 1px grey;
        border-radius: 3px;
        background-color: azure;
        margin: 5px 10px;
     }
    </style>

6.8. 【pinia】

参考之前pinia部分的讲解

6.9. 【slot】

官网传送门

1. 默认插槽

插槽图示

vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="fatherComponent">
    <h3>我是父元素,演示slot插槽</h3>
       <MyTable>
            用户列表
       </MyTable>
       <MyTable v-slot:default = "角色列表">
           
       </MyTable>
   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="Father"> 
import { reactive, ref   } from 'vue'			

 import MyTable from './MyTable.vue'
</script> 
 
<!-- 3 定义样式 -->
<style scoped> 
 
</style>
vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="tableApp">
        
        <div class="header"><slot></slot> </div> 
        <div>其他内容</div>
        <div>其他内容...</div>
        <div>其他内容...</div>
        <div>其他内容...</div>
        <div>其他内容...</div>
        <div>其他内容...</div>
        <div>其他内容...</div>
   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="Child"> 
import { ref   } from 'vue'			
defineProps(['tableData'])

</script> 
 
<!-- 3 定义样式 -->
<style scoped> 
   .tableApp{
      background-color: skyblue;
      border-radius: 5px;
      box-shadow: 0 0 10px;
      margin:5px;
      padding:5px;
      display: inline-block;
      min-width:200px ;
      min-height: 200px;
      vertical-align: top;
   }

   .header, .footer{
      font-weight: 800;
      margin: 5px 0;

   }
</style>

在外部没有提供任何内容的情况下,可以为插槽指定默认内容。比如有这样一个

<slot> 默认内容,当外部没有提供内容时显示 </slot>

2. 具名插槽

有时在一个组件中包含多个插槽出口是很有用的。可以给插槽取名字,带 name 的插槽被称为具名插槽 (named slots)<slot name="header"></slot>。没有提供 name<slot> 出口会隐式地命名为“default”。 <slot></slot>相当于<slot name="default"></slot>

具名插槽图示

vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="fatherComponent">
      <h3>我是父元素,演示slot插槽</h3>
      <MyTable >
         用户列表
      </MyTable>


      <MyTable v-slot:default>
         角色列表
      </MyTable>

      <MyTable >
        <template v-slot:default>
            菜单列表
        </template>
        <template v-slot:footer>
            共计xxx菜单
        </template>
      </MyTable>

   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="Father">
import { ref } from 'vue'
import MyTable from './MyTable.vue'
</script> 
 
<!-- 3 定义样式 -->
<style scoped></style>
vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="tableApp">
        
        <div class="header"><slot></slot> </div> 
        <div>其他内容</div>
        <div>其他内容...</div>
        <div>其他内容...</div>
        <div>其他内容...</div>
        <div>其他内容...</div>
        <div>其他内容...</div>
        <div>其他内容...</div>
        <div class="footer"><slot name="footer"></slot> </div> 

   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="Child"> 
import { ref   } from 'vue'			
defineProps(['tableData'])

</script> 
 
<!-- 3 定义样式 -->
<style scoped> 
   .tableApp{
      background-color: skyblue;
      border-radius: 5px;
      box-shadow: 0 0 10px;
      margin:5px;
      padding:5px;
      display: inline-block;
      min-width:200px ;
      min-height: 200px;
      vertical-align: top;
   }

   .header, .footer{
      font-weight: 800;
      margin: 5px 0;
   }
</style>

3. 作用域插槽

参考 ElementPlus 的表格组件 自定义列模版 Table 表格 | Element Plus (element-plus.org)

image-20240219124759335

  1. 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(用户数据在Child组件中,但使用数据所遍历出来表格最后一列的结构由Father组件决定)

  2. 具体编码:

vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="fatherComponent">
      <h3>我是父元素,演示slot插槽</h3>
      <MyTable >
         用户列表
         <template #handle="scope" >
               <button type="button" style="background-color: rgb(235, 208, 135);">修改用户</button>
         </template>
      </MyTable>


      <MyTable>
         <template v-slot>
            角色管理
         </template>
         <template #handle="scope" >
               <input type="checkbox" name="" id="">删除角色
         </template>
      </MyTable>

      <MyTable >
         <template v-slot:default>
            菜单列表
         </template>
         <template v-slot:footer>
            共计xxx菜单
         </template>


         <template #handle="scope" >
            <input type="radio" name="" id=""/>菜单操作id:{{ scope.rowData.id }}
         </template>
      </MyTable>

   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="Father">
import { reactive, ref } from 'vue'
import MyTable from './MyTable.vue'




</script> 
 
<!-- 3 定义样式 -->
<style scoped></style>
vue
<!-- 1 定义组件模版 --> 
<template>
   <div class="tableApp">
        
        <div class="header"><slot></slot> </div> 
        <div>
            <table>
               <tr>
                  <td>id</td>
                  <td>姓名</td>
                  <td>年龄</td>
                  <td>操作</td>
               </tr>
               <tr v-for="(rowData ,index) in tableData">
                  <td>{{ rowData.id }}</td>
                  <td>{{ rowData.name }}</td>
                  <td>{{ rowData.age }}</td>
                  <td> <slot name="handle" :rowData="rowData"></slot></td>
               </tr>
            </table>
        </div>
        <div class="footer"><slot name="footer"></slot> </div> 

   </div>
</template> 

<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="Child"> 
import { ref  , reactive } from 'vue'			
// defineProps(['tableData'])

let tableData = reactive([
   { id: 'asdfsafd781', name: '公孙策', age: 18 },
   { id: 'asdfsafd782', name: '房玄龄', age: 20 },
   { id: 'asdfsafd783', name: '诸葛亮', age: 30 },
   { id: 'asdfsafd784', name: '杜仲', age: 40 },
])
</script> 
 
<!-- 3 定义样式 -->
<style scoped> 
   .tableApp{
      background-color: skyblue;
      border-radius: 5px;
      box-shadow: 0 0 10px;
      margin:5px;
      padding:5px;
      display: inline-block;
      min-width:200px ;
      min-height: 200px;
      vertical-align: top;
   }

   .header, .footer{
      font-weight: 800;
      margin: 5px 0;
   }

   table ,tr,td{
      border-collapse: collapse;
   }
   td{
      border: solid 1px grey;
      padding: 2px 5px;
   }
</style>

Released under the MIT License.