Vue3 学习笔记 —— 父子组件传值、插槽、提供/注入、兄弟组件传值、mitt
目录
1. 父子组件传值
1.1 父组件给子组件传值 —— v-bind
1.2 子组件接收父组件的传值 —— defineProps
1.3 设置子组件接受参数的默认值 —— withDefaults
1.4 子组件给父组件传参(派发事件) —— defineEmits
1.5 子组件暴露给父组件内部属性 —— defineExpose
2. 插槽
2.1 什么是插槽
2.2 匿名插槽、具名插槽
2.3 作用域插槽
2.4 动态插槽
3. 提供/注入
3.1 提供/注入是什么
3.2 父组件暴露(提供)数据
3.2.1 provide 基本用法
3.2.2 provide 源码解析
3.3 子组件接收(注入)数据
3.3.1 inject 基本用法
3.3.2 inject 源码解析
4. 兄弟组件传值
4.1 EventBus 原理
4.1.1 使用 ts 实现一个简单的 EventBus
4.1.2 使用 4.1.1 的 EventBus
4.2 Mitt
4.2.1 安装 mitt
4.2.2 新建 mitt-bus.ts
4.2.3 使用 mitt 发送事件
4.2.4 使用 mitt 接收事件
4.2.5 在全局实例上使用 mitt
1. 父子组件传值
1.1 父组件给子组件传值 —— v-bind
传递字符串类型,不需要加 v-bind(:)
传递非字符串类型,需要加 v-bind(:)
<template><div class="layout"><Menu :data="data" title="我是标题"></Menu><div class="layout-right"><Header></Header><Content></Content></div></div>
</template><script setup lang="ts">
import Menu from "./Menu/index.vue";
import Header from "./Header/index.vue";
import Content from "./Content/index.vue";
import { reactive } from "vue";const data = reactive<number[]>([1, 2, 3]);
</script>
1.2 子组件接收父组件的传值 —— defineProps
如果是在 setup 语法糖中使用 defineProps,则无需引入,直接使用即可
使用了 ts:defineProps<type>();
未使用 ts:defineProps({});
// 使用了 ts 时的写法
<script setup lang="ts">
defineProps<{title: string;data: number[];
}>();
</script>// 未使用 ts 时的写法
<script setup>
defineProps({title: {default: "",type: string,},data: Array,
});
</script>
1.3 设置子组件接受参数的默认值 —— withDefaults
withDefaults 接受两个参数:
- defineProps<Props>()
- 一个对象(里面包含了所有默认值)
注意:
- 仅当使用 ts 时,才可以使用 withDefaults
- 设置默认值时,如果是引用类型,则要使用 () => {}、() => [] 的形式设置
type Props = {title?: string;data?: number[];
};withDefaults(defineProps<Props>(), {title: "bilibili",data: () => [1, 2, 3],
});
1.4 子组件给父组件传参(派发事件) —— defineEmits
defineEmits 接受一个数组,用于存储 事件名 数组
defineEmits 返回一个函数,也就是传说中的 emit
子组件定义派发事件:
<template><div class="menu"><button @click="clickTap">派发给父组件</button></div>
</template><script setup lang="ts">
import { reactive } from "vue";
const list = reactive<number[]>([4, 5, 6]);const emit = defineEmits(["on-click"]);
const clickTap = () => {emit("on-click", list);
};
</script>
父组件接受/监听子组件派发的事件:
<template><div class="layout"><Menu @on-click="getList"></Menu></div>
</template><script setup lang="ts">
import Menu from "./Menu/index.vue";
import { reactive } from "vue";const getList = (list: number[]) => {console.log(list, "父组件接受子组件");
};
</script>
1.5 子组件暴露给父组件内部属性 —— defineExpose
在父组件中,获取子组件的 DOM
<Menu ref="menus"></Menu>
const menus = ref<HTMLElement | null>(null);
console.log(menus.value);
打印之后,发现没有任何属性
父组件若想读取子组件内部的属性,需要在子组件内 把需要的属性 通过 defineExpose 暴露出去
const list = reactive<number[]>([4, 5, 6]);defineExpose({list,
});
这样做的目的:让父组件不能随意通过 DOM 修改子组件内的属性,父组件只能读取到子组件主动暴露出来的属性
2. 插槽
2.1 什么是插槽
子组件提供给父组件使用的占位符,用 <slot></slot> 表示
父组件可以使用任意代码填充 templete,填充的内容会替换子组件中的 <slot></slot>
插槽 | Vue3中文文档 - vuejsVue.js - The 渐进式 JavaScript 框架, Vue3中文文档 - Vue3最新动态https://www.javascriptc.com/vue3js/guide/component-slots.html#%E6%8F%92%E6%A7%BD%E5%86%85%E5%AE%B9
2.2 匿名插槽、具名插槽
具名插槽:有 name,父组件通过 #name 获取
匿名插槽:没有 name,父组件通过 #default 获取
<!-- 子组件 -->
<template><div><!-- 具名插槽 --><slot name="header"></slot><!-- 匿名插槽 --><slot></slot><slot name="footer"></slot></div>
</template><!-- 父组件 -->
<Dialog><!-- 简写 #header ;非简写 v-slot:header --><template v-slot:header><div>1</div></template><!-- 简写 #default ;非简写 v-slot --><template #default><div>2</div></template><template #footer><div>3</div></template>
</Dialog>
2.3 作用域插槽
子组件:给 slot 标签,动态绑定参数,派发给父组件使用
父组件:在 template 中使用解构赋值 #default="{ data, index }",获取子组件中的值
<!-- 子组件 -->
<template><!-- 动态绑定参数 派发给父组件使用 --><div v-for="(item, index) in 100" :key="index"><slot :data="item" :id="index"></slot></div>
</template><!-- 父组件 -->
<Dialog><!-- 通过解构的方式取值 --><template #default="{ data, index }"><div>{{ data }} -- {{ index }}</div></template>
</Dialog>
2.4 动态插槽
插槽可以是变量名
如果是 header,则填充到头部;如果是 footer,则填充到底部
<Dialog><template #[nameTest]><div>手可摘星辰</div></template></Dialog>const nameTest = ref('header')
3. 提供/注入
3.1 提供/注入是什么
提供 / 注入 | Vue3中文文档 - vuejsVue.js - The 渐进式 JavaScript 框架, Vue3中文文档 - Vue3最新动态https://www.javascriptc.com/vue3js/guide/component-provide-inject.html#%E5%A4%84%E7%90%86%E5%93%8D%E5%BA%94%E6%80%A7
provide:在祖先组件中,指定要暴露(提供)出去的属性
inject:在孙子组件中,指定要接收(注入)的属性
3.2 父组件暴露(提供)数据
3.2.1 provide 基本用法
provide 接收两个值
- key
- value:string、number、symbol
其中,value 默认是可以被子组件修改的,为了防止被修改,可以添加 readonly 属性
<template><div class="App"><h2> App.vue </h2><A></A></div>
</template><script setup lang='ts'>import { provide, ref } from 'vue'import A from './components/A.vue'const colorInApp = ref<string>('red')// 如果不想父组件的值被子组件修改,可以添加 readonly 属性provide('color', readonly(colorInApp))</script>
3.2.2 provide 源码解析
provide 只能在 setup 语法糖中使用,不能在 options 中使用
使用 原型链 的方式,实现 provide(举个栗子:var a = {name: 1}; var b = Object.create(a); 打印b 什么都没有,但是打印 b.name 可以打印出1)
export function provide<T>(key: InjectionKey<T> | string | number, value: T) {if (!currentInstance) {if (__DEV__) {// provide 只能在 setup 语法糖中使用,不能再 options 里使用warn(`provide() can only be used inside setup().`)}} else {// currentInstance 获取当前组件实例let provides = currentInstance.provides// 默认情况下,实例继承父类的 provides 对象// 如果当前组件有自己的 provides,那么他会使用 父provides 对象作为原型,创建自己的 provides// 在 inject 中只需要查询 原型链 即可// 读取父组件 providesconst parentProvides =currentInstance.parent && currentInstance.parent.providesif (parentProvides === provides) {// var a = {name: 1}; var b = Object.create(a); 打印b 什么都没有,但是打印 b.name 可以打印出1provides = currentInstance.provides = Object.create(parentProvides)}// 在新的对象上增加了这次的 providesprovides[key as string] = value}
}
3.3 子组件接收(注入)数据
补充:vue3 中可以使用 v-bind 绑定 setup 中的变量
3.3.1 inject 基本用法
inject 可以设置默认值(注意设置成响应式的)
inject 可以修改父组件中的 provide 值,注意响应式
<template><div><button @click="changeSth">改变 app.vue 中的 flag</button><div class="my-box">{{ flag }}</div></div>
</template><script setup lang='ts'>
import { inject, Ref, ref } from 'vue';// 使用 Ref 进行类型推论
// 如果接收的值为空,则设置默认值 ref('yellow')
const colorInB = inject<Ref<string>>('color', ref('yellow'));const changeSth = () => {// 如果无法修改,可以考虑改成 colorInB!.value = 'green';colorInB.value = 'green'
};
</script><style scoped lang="scss">
.my-box {// vue3 中可以使用 v-bind 绑定 setup 中的变量background: v-bind(colorInB)
}
</style>
3.3.2 inject 源码解析
查询父组件的 provides,如果读的到,则返回父组件的 provides
如果读不到(实例在根目录),则返回 appContext 的 provides
// inject 做了个函数重载
export function inject<T>(key: InjectionKey<T> | string): T | undefined
export function inject<T>(key: InjectionKey<T> | string,// 允许接收默认值defaultValue: T,treatDefaultAsFactory?: false
): T
export function inject<T>(key: InjectionKey<T> | string,defaultValue: T | (() => T),treatDefaultAsFactory: true
): T
export function inject(key: InjectionKey<any> | string,defaultValue?: unknown,treatDefaultAsFactory = false
) {// fallback to `currentRenderingInstance` so that this can be called in// a functional component// 读取当前组件的实例const instance = currentInstance || currentRenderingInstanceif (instance) {// #2400// to support `app.use` plugins,// fallback to appContext's `provides` if the instance is at root// inject,查询父组件的 provides,如果读的到,则返回父组件的 provides// 如果读不到(实例在根目录),则返回 appContext 的 providesconst provides =instance.parent == null? instance.vnode.appContext && instance.vnode.appContext.provides: instance.parent.providesif (provides && (key as string | symbol) in provides) {// TS doesn't allow symbol as index typereturn provides[key as string]} else if (arguments.length > 1) {return treatDefaultAsFactory && isFunction(defaultValue)? defaultValue.call(instance.proxy): defaultValue} else if (__DEV__) {warn(`injection "${String(key)}" not found.`)}} else if (__DEV__) {warn(`inject() can only be used inside setup() or functional components.`)}
}
4. 兄弟组件传值
简而言之,兄弟组件通信 有两种方式:
- 使用父组件充当桥梁,实现兄弟组件传参
- 使用 EventBus(JavaScript 发布订阅)
4.1 EventBus 原理
4.1.1 使用 ts 实现一个简单的 EventBus
type BusClass<T> = {emit: (name: T) => void// emit 中接收的参数,会传递给 callbackon: (name: T, callback: Function) => void
}type BusParams = string | number | symboltype List = {[key: BusParams]: Array<Function>
}class Bus<T extends BusParams> implements BusClass<T> {// 调度中心(是个对象)list: Listconstructor() {// 初始化 listthis.list = {};}/*** emit* @param name 自定义事件名称* @description 可传递的参数有多个,所以使用解构赋值 ...args*/emit(name: T, ...args: Array<any>) {let eventName: Array<Function> = this.list[name]eventName.forEach(ev => {ev.apply(this, args)})}/*** on* @param name 自定义事件名称* @param callback 自定义事件方法*/on(name: T, callback: Function) {// 如果事件已经注册过了,则直接去 list 中寻找;如果没注册过,则返回空数组let fn: Array<Function> = this.list[name] || [];fn.push(callback)this.list[name] = fn}
}export default new Bus<number>()
4.1.2 使用 4.1.1 的 EventBus
在 A 组件中,使用 emit 发出事件
在 B 组件中,使用 on 监听事件
// 兄弟组件A
import Bus form './bus.ts';// 发送事件
const handleEmit = () => {Bus.emit('a-data', 1, 2);
};// 兄弟组件B
<A @a-data="handleOn"></A>import Bus form './bus.ts';// 接收事件
const handleOn = () => {Bus.on('a-data', (data1, data2) => {console.log('data1 ===', data1);console.log('data2 ===', data2);});
};
4.2 Mitt
mitt - npmTiny 200b functional Event Emitter / pubsub.. Latest version: 3.0.0, last published: a year ago. Start using mitt in your project by running `npm i mitt`. There are 1346 other projects in the npm registry using mitt.https://www.npmjs.com/package/mitt
4.2.1 安装 mitt
npm install --save mitt
4.2.2 新建 mitt-bus.ts
import mitt from 'mitt';
export default mitt();
4.2.3 使用 mitt 发送事件
import mittBus from "@/utils/mitt-bus";mittBus.emit('refreshDraftList', '刷新草稿列表');
4.2.4 使用 mitt 接收事件
接收事件监听的页面,在卸载时,一定要记得调用 off 取消事件监听
import mittBus from "@/utils/mitt-bus";/*** 监听:刷新草稿列表页面* @desc 发送草稿表单后,会跳转回草稿列表页面,并重新获取草稿*/
mittBus.on('refreshDraftList', (data: string) => {console.log(data);// 请求:获取草稿列表getDraftList();
})/*** 页面卸载后,取消事件监听*/
onUnmounted(() => {mittBus.off('refreshDraftList');
})
4.2.5 在全局实例上使用 mitt
上面的 4.2.1-4.2.4 是单独使用 mitt,不是将 mitt 挂载到全局上,下面展示如何挂载到全局使用
修改 main.ts
import { createApp } from 'vue'
import App from './App.vue'
import mitt from 'mitt'const Mitts = mitt()// 必须要拓展 ComponentCustomProperties 类型,才能获得关于 mitt 的 ts 提示
declare module "vue" {export interface ComponentCustomProperties {$Bus: typeof Mitts}
}const app = createApp(App)// Vue3 挂载全局 API
app.config.globalProperties.$Bus = Mittsapp.mount('#app')
派发事件
<template><div><h1>我是A</h1><button @click="handleEmit1">handleEmit1</button><button @click="handleEmit2">handleEmit2</button></div>
</template><script setup lang='ts'>
import { getCurrentInstance } from 'vue'const instance = getCurrentInstance();const handleEmit1 = () => {instance?.proxy?.$Bus.emit('emitone', 100)
}const handleEmit2 = () => {instance?.proxy?.$Bus.emit('emittwo', 200)
}
</script>
添加事件监听
注意:如果是监听全部事件,那么会默认接收两个参数:事件名称、事件数据
最后会这么展示:
- 监听全部事件 === emitone 100
- 监听全部事件 === emittwo 200
<template><div><h1>我是B</h1></div>
</template><script setup lang='ts'>
import { getCurrentInstance } from 'vue'const instance = getCurrentInstance()instance?.proxy?.$Bus.on('emit-one', (num) => {console.log('监听一个事件 ===', num)
})instance?.proxy?.$Bus.on('*', (emitName, emitData) => {console.log('监听全部事件 ===', emitName, emitData)
})
</script>
移除事件监听
const testEmitOff = (num: number) => {console.log(num, '测试事件监听移除')
}// 添加事件监听
instance?.proxy?.$Bus.on('emitone', testEmitOff)
// 移除事件监听
instance?.proxy?.$Bus.off('emitone', testEmitOff)
清空所有事件监听
instance?.proxy?.$Bus.all.clear()
Vue3 学习笔记 —— 父子组件传值、插槽、提供/注入、兄弟组件传值、mitt相关推荐
- amazeui学习笔记二(进阶开发2)--Web组件简介Web Component
amazeui学习笔记二(进阶开发2)--Web组件简介Web Component 一.总结 1.amaze ui:amaze ui是一个web 组件, 由模板(hbs).样式(LESS).交互(JS ...
- 四、Vue组件化开发学习笔记——父子组件通信,父级向子级传值(props),子级向父级传值(自定义事件),slot插槽
一.父子组件的通信 在上一篇博文中,我们提到了子组件是不能引用父组件或者Vue实例的数据的. 但是,在开发中,往往一些数据确实需要从上层传递到下层: 比如在一个页面中,我们从服务器请求到了很多的数据. ...
- 转载于掘金的vue3学习笔记
声明:转载于掘金的文章,原文地址:https://juejin.cn/post/7005140118960865317/,本文只是自用. 一.简介 2020年9月18日,Vue.js发布3.0版本,代 ...
- 【Vue】菜头学前端 - vue3学习笔记
目录 简介 一.创建项目 1.使用vue-clli创建 2.使用Vite创建 二.Composition API(组合式API) 1.setup函数 2.ref函数和reactive函数 1.ref函 ...
- vue3学习笔记(ref, reactive, setup, hook...)
目录 一.搭建项目 二.常用的Composition API 1.ref函数(实现响应式) 2.reactive函数 3.vue2和vue3响应式的区别 4. setup参数 5.计算属性和监视 6. ...
- Vue3学习笔记:了解并使用Pinia状态管理
Vue3是一个流行的JavaScript框架,它允许您构建交互式的Web应用程序.Vue3中使用的状态管理工具是Vuex,但是有一种新的状态管理工具叫做Pinia,它提供了更简单.更轻量级的状态管理方 ...
- Vue 学习笔记(2)Vue 生命周期、组件
Vue Vue 生命周期 Vue 中组件(Component) 全局组件的开发 局部组件的开发 组件中 props 的使用 在组件上声明静态数据传递给组件内部 在组件上声明动态数据传递给组件内部 pr ...
- Vue3学习笔记 ----- Vite、Vetur、Vue-router@4、Pinia的使用
打包 – Vite 极速的服务启动,使用原生 ESM 文件,无需打包 轻量快速的热重载,始终极快的模块热重载(HMR) 丰富的功能,对 TypeScript.JSX.CSS 等支持开箱即用 传统打包方 ...
- Vue3 学习笔记 —— 破坏式更新、自定义指令 directive
目录 1. 什么叫破坏式更新? 2. Vue3 中的自定义指令 2.1 自定义指令的生命周期 2.1.1 Vue2 Vs Vue3 的自定义指令生命周期 2.1.2 自定义指令的生命周期中,接收的参数 ...
最新文章
- VMware9.0安装Ubuntu出现Software virtualization is incompatible 问题的解决
- 『第27天』Sunos(二)
- python3 安装包 源码包 下载慢问题 解决方法
- Discuz!NT论坛代码小分析
- 入门Web前端有哪些误区?该如何避免?
- Matlab中出现“无法打开电子表格,MATLAB报告了以下错误;错误:服务器出现意外情况”
- 预处理和typedef
- Vue中ESlint配置文件eslintrc.js文件详解
- Maven新建项目的JDK版本类型问题
- html 正则表达式密码判断,JS利用正则表达式实现简单的密码强弱判断实例
- vue项目引入sass
- 雪亮工程整体解决方案
- 施耐德SoMachine Basic中存在高危漏洞(CVE-2018-7783),可读取目标系统上的任意文件...
- Lambda表达式实现机制的分析
- As of Dart Sass 2.0.0 弃用 除法符号“/” element-ui自定义皮肤样式报错,但能运行
- 第五章(1.7)深度学习——常用的八种神经网络性能调优方案
- [他山之玉]轮值董事长郭平 2019年新年致辞
- [渝粤教育] 南通职业大学 艺术导论2021 参考 资料
- 多目标优化-测试问题及其Pareto前沿
- 用PS通道消除人物脸部斑点的磨皮, 有什么技巧
热门文章
- Django+vue+ElementUi 实现前后端分离项目
- 07:收集瓶盖赢大奖
- CIKM Competition数据挖掘竞赛夺冠算法-陈运文
- 软件测试笔试题(四)
- position:sticky介绍
- ZOJ 2100 Seeding ( DFS 经典回溯
- 分析大众点评产品成败的因素,从pest和波特五力出发。
- JFreeChart柱子显示数据
- 淘宝App惊现“内测版本弹屏”P0级事故!5张淘宝架构PPT,剖析事故始末
- 中国电子学会2022年06月份青少年软件编程Scratch图形化等级考试试卷三级真题(含答案)