目录

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相关推荐

  1. amazeui学习笔记二(进阶开发2)--Web组件简介Web Component

    amazeui学习笔记二(进阶开发2)--Web组件简介Web Component 一.总结 1.amaze ui:amaze ui是一个web 组件, 由模板(hbs).样式(LESS).交互(JS ...

  2. 四、Vue组件化开发学习笔记——父子组件通信,父级向子级传值(props),子级向父级传值(自定义事件),slot插槽

    一.父子组件的通信 在上一篇博文中,我们提到了子组件是不能引用父组件或者Vue实例的数据的. 但是,在开发中,往往一些数据确实需要从上层传递到下层: 比如在一个页面中,我们从服务器请求到了很多的数据. ...

  3. 转载于掘金的vue3学习笔记

    声明:转载于掘金的文章,原文地址:https://juejin.cn/post/7005140118960865317/,本文只是自用. 一.简介 2020年9月18日,Vue.js发布3.0版本,代 ...

  4. 【Vue】菜头学前端 - vue3学习笔记

    目录 简介 一.创建项目 1.使用vue-clli创建 2.使用Vite创建 二.Composition API(组合式API) 1.setup函数 2.ref函数和reactive函数 1.ref函 ...

  5. vue3学习笔记(ref, reactive, setup, hook...)

    目录 一.搭建项目 二.常用的Composition API 1.ref函数(实现响应式) 2.reactive函数 3.vue2和vue3响应式的区别 4. setup参数 5.计算属性和监视 6. ...

  6. Vue3学习笔记:了解并使用Pinia状态管理

    Vue3是一个流行的JavaScript框架,它允许您构建交互式的Web应用程序.Vue3中使用的状态管理工具是Vuex,但是有一种新的状态管理工具叫做Pinia,它提供了更简单.更轻量级的状态管理方 ...

  7. Vue 学习笔记(2)Vue 生命周期、组件

    Vue Vue 生命周期 Vue 中组件(Component) 全局组件的开发 局部组件的开发 组件中 props 的使用 在组件上声明静态数据传递给组件内部 在组件上声明动态数据传递给组件内部 pr ...

  8. Vue3学习笔记 ----- Vite、Vetur、Vue-router@4、Pinia的使用

    打包 – Vite 极速的服务启动,使用原生 ESM 文件,无需打包 轻量快速的热重载,始终极快的模块热重载(HMR) 丰富的功能,对 TypeScript.JSX.CSS 等支持开箱即用 传统打包方 ...

  9. Vue3 学习笔记 —— 破坏式更新、自定义指令 directive

    目录 1. 什么叫破坏式更新? 2. Vue3 中的自定义指令 2.1 自定义指令的生命周期 2.1.1 Vue2 Vs Vue3 的自定义指令生命周期 2.1.2 自定义指令的生命周期中,接收的参数 ...

最新文章

  1. VMware9.0安装Ubuntu出现Software virtualization is incompatible 问题的解决
  2. 『第27天』Sunos(二)
  3. python3 安装包 源码包 下载慢问题 解决方法
  4. Discuz!NT论坛代码小分析
  5. 入门Web前端有哪些误区?该如何避免?
  6. Matlab中出现“无法打开电子表格,MATLAB报告了以下错误;错误:服务器出现意外情况”
  7. 预处理和typedef
  8. Vue中ESlint配置文件eslintrc.js文件详解
  9. Maven新建项目的JDK版本类型问题
  10. html 正则表达式密码判断,JS利用正则表达式实现简单的密码强弱判断实例
  11. vue项目引入sass
  12. 雪亮工程整体解决方案
  13. 施耐德SoMachine Basic中存在高危漏洞(CVE-2018-7783),可读取目标系统上的任意文件...
  14. Lambda表达式实现机制的分析
  15. As of Dart Sass 2.0.0 弃用 除法符号“/” element-ui自定义皮肤样式报错,但能运行
  16. 第五章(1.7)深度学习——常用的八种神经网络性能调优方案
  17. [他山之玉]轮值董事长郭平 2019年新年致辞
  18. [渝粤教育] 南通职业大学 艺术导论2021 参考 资料
  19. 多目标优化-测试问题及其Pareto前沿
  20. 用PS通道消除人物脸部斑点的磨皮, 有什么技巧

热门文章

  1. Django+vue+ElementUi 实现前后端分离项目
  2. 07:收集瓶盖赢大奖
  3. CIKM Competition数据挖掘竞赛夺冠算法-陈运文
  4. 软件测试笔试题(四)
  5. position:sticky介绍
  6. ZOJ 2100 Seeding ( DFS 经典回溯
  7. 分析大众点评产品成败的因素,从pest和波特五力出发。
  8. JFreeChart柱子显示数据
  9. 淘宝App惊现“内测版本弹屏”P0级事故!5张淘宝架构PPT,剖析事故始末
  10. 中国电子学会2022年06月份青少年软件编程Scratch图形化等级考试试卷三级真题(含答案)