6. 组件通信
Vue3
组件通信和Vue2
的区别:
- 移出事件总线,使用
mitt
代替。
vuex
换成了pinia
。- 把
.sync
优化到了v-model
里面了。 - 把
$listeners
所有的东西,合并到$attrs
中了。 $children
被砍掉了。
常见搭配形式:

案例基本框架
我是父元素,演示props
案例目录结构如下
│ 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
每个知识点对一个的父子组件
<!-- 1 定义组件模版 -->
<template>
<div class="childComponent">
我是子元素
</div>
</template>
<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="Child">
import { ref } from 'vue'
</script>
<!-- 3 定义样式 -->
<style scoped>
</style>
<!-- 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>
框架应用其他的文件
/* 定义一些变量,统一样式 */
: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;
}
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;
//使用自定义全局样式
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')
<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>
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
是使用频率最高的一种通信方式,常用与 :父 ↔ 子。
- 若 父传子:属性值是非函数。
- 若 子传父:属性值是函数。
父组件:
<!-- 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>
子组件
<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. 【自定义事件】
- 概述:自定义事件常用于:子 => 父。
- 注意区分好:原生事件、自定义事件。
- 原生事件:
- 事件名是特定的(
click
、mosueenter
等等) - 事件对象
$event
: 是包含事件相关信息的对象(pageX
、pageY
、target
、keyCode
)
- 事件名是特定的(
- 自定义事件:
- 事件名是任意名称
事件对象$event: 是调用emit时所提供的数据,可以是任意类型!!!
- 示例:
<!-- 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>
<!-- 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
npm i mitt
新建文件:src\utils\emitter.ts
// 引入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
接收数据的组件中:绑定事件、同时在销毁前解绑事件:
import emitter from "@/utils/emitter";
import { onUnmounted } from "vue";
// 绑定事件
emitter.on('send-toy',(value)=>{
console.log('send-toy事件被触发',value)
})
onUnmounted(()=>{
// 解绑事件
emitter.off('send-toy')
})
【第三步】:提供数据的组件,在合适的时候触发事件
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'])
概述:实现 父↔子 之间相互通信。
前序知识 ——
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" >
组件标签上的
v-model
的本质::moldeValue
+update: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之前的写法
<!-- 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
<!-- 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 】
概述:
$attrs
用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)。具体说明:
$attrs
是一个对象,包含所有父组件传入的标签属性。注意:
$attrs
会自动排除props
中声明的属性(可以认为声明过的props
被子组件自己“消费”了)
父组件:
<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>
子组件:
<template>
<div class="child">
<h3>子组件</h3>
<GrandChild v-bind="$attrs"/>
</div>
</template>
<script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue'
</script>
孙组件:
<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】
概述:
$refs
用于 :父→子。$parent
用于:子→父。
原理如下:
属性 说明 $refs
值为对象,包含所有被 ref
属性标识的DOM
元素或组件实例。$parent
值为对象,当前组件的父组件实例对象。
我是父元素,演示refs-parent
所有的合计:0
- 请战英雄列表
<!-- 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>
<!-- 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】
概述:实现祖孙组件直接通信
具体使用:
- 在祖先组件中通过
provide
配置向后代组件提供数据 - 在后代组件中通过
inject
配置来声明接收数据
- 在祖先组件中通过
具体编码:
【第一步】父组件中,使用
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. 默认插槽
<!-- 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>
<!-- 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>
<!-- 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>
<!-- 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)
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(用户数据在
Child
组件中,但使用数据所遍历出来表格最后一列的结构由Father
组件决定)具体编码:
<!-- 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>
<!-- 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>
