Vue3 您所在的位置:网站首页 vue多个组件之间通信 Vue3

Vue3

2023-11-17 20:17| 来源: 网络整理| 查看: 265

完整原文地址见简书https://www.jianshu.com/p/c8891bd3fe36

本文内容提要

父子组件可通过事件 进行通信携带参数的事件 发送和监听回调使用 组件的emits板块 整理组件事件使用 组件emits板块的 Object形式 校验外传的参数值结合$emit、v-bind与v-model 实现 父子组件通信(数据双向绑定)结合$emit、v-bind与v-model 实现 父子组件通信(多个字段的应用案例) 自定义修饰符 实验this.modelModifiers的作用下面在子组件的点击回调handleClick()中,通过this.modelModifiers.[自定义修饰符名]实现自定义修饰符逻辑插槽【slot】【传组件示例】注意,slot标签上是无法直接添加事件(修饰符)的,如有需要,可以在外层包裹一层标签,再加上事件插槽【传 字符串示例】插槽【传 自定义子组件 示例】插槽作用域问题插槽 UI默认值插槽的灵活拆分与应用【具名插槽】v-slot指令的简写普通的v-for例子 进行 列表渲染v-for结合v-bind、v-slot、做列表渲染使用解构概念进行简写 动态组件 常规的利用双向绑定特性,通过点击事件切换UI的写法动态组件写法异步组件父子组件可通过事件 进行通信

前面的笔记 —— 《Vue3 | 组件的定义及复用性、局部组件、全局组件、组件间传值及其校验、单项数据流、Non-props属性》,单向数据流的概念, 即子组件无法修改来自父组件的数据字段, 如果确要修改,可以使用下面说的方式进行通信: 首先,在子组件的UI点击回调方法中,调用this.$emit('【自定义事件名】'), 向外发送一个事件; 接着各级父组件会收到这个事件, 则在父组件中 调用 子组件标签处, 以 @【事件名】= "回调方法名"的形式,监听该事件以及配置回调方法; 回调方法中即可 对 子组件意图修改 的 父组件数据字段 进行修改;

注意, 触发事件的命名,用驼峰命名法(如下heHeDa); 监听事件的命名,用横杆间隔法(如下he-he-da)。

代码:

Hello World! heheheheheheda const app = Vue.createApp({ data() { return {count: 1} }, methods: { handleItemEvent() { this.count += 1; } }, template: ` ` }); app.component('counter', { props: ['count'], methods: { handleItemClick() { this.$emit('heHeDa'); } }, template:` {{count}} ` }); const vm = app.mount('#heheApp');

运行,点击组件:

携带参数的事件 发送和监听回调

this.$emit()可以添加参数位, 父组件的监听回调中, 则可加形参位 用于接收参数(如handleItemEvent(param)中的 param);

代码:

const app = Vue.createApp({ data() { return {count: 1} }, methods: { handleItemEvent(param) { this.count += param; } }, template: ` ` }); app.component('counter', { props: ['count'], methods: { handleItemClick() { this.$emit('addCount', 8); } }, template:` {{count}} ` }); const vm = app.mount('#heheApp');

运行,点击效果:

子组件需 发送多个参数 亦可,只要在this.$emit()按需添加参数位,

父组件的监听回调中,添加对应的形参 去接收即可:

const app = Vue.createApp({ data() { return {count: 1} }, methods: { handleItemEvent(param1, param2, param3) { this.count = this.count + param1 + param2 + param3; console.log(this.count); } }, template: ` ` }); app.component('counter', { props: ['count'], methods: { handleItemClick() { this.$emit('addCount', 8, 2, 6); } }, template:` {{count}} ` }); const vm = app.mount('#heheApp');

效果:

当然 父组件 接收 子组件参数 后的 计算逻辑, 可以在 子组件传参 的时候 计算完成 再传给this.$emit()! 父组件接收时,直接 受值即可(handleItemEvent(count));

const app = Vue.createApp({ data() { return {count: 1} }, methods: { handleItemEvent(count) { this.count = count; console.log(this.count); } }, template: ` ` }); app.component('counter', { props: ['count'], methods: { handleItemClick() { this.$emit('addCount', this.count + 16); } }, template:` {{count}} ` }); const vm = app.mount('#heheApp');

效果同上一个例子;

使用 组件的emits板块 整理组件事件

实际开发场景中,我们一个组件自定义的触发事件可能会很多, 我们不可能一个一个去梳理核实, 这个时候就可以使用 组件的emits板块 来整理组件的事件; 可以把组件中 自定义到的事件都写在这里,方便梳理,提高可读性, 或者把 想要定义的事件 写在这里, 如此一来,如果忘记编写对应的自定义事件, Vue系统会在运行时 给予警告:

const app = Vue.createApp({ data() { return {count: 1} }, methods: { handleItemEvent(count) { this.count = count; console.log(this.count); } }, template: ` ` }); app.component('counter', { props: ['count'], emits: ['hehehe'], methods: { handleItemClick() { this.$emit('addCount', this.count + 16); } }, template:` {{count}} ` }); const vm = app.mount('#heheApp');

如果忘记编写对应的自定义事件,Vue系统会在运行时 给予警告:

使用 组件emits板块的 Object形式 校验外传的参数值

可以根据需要,使用 组件emits板块的 Object形式 校验外传的参数值, 如下,子组件的emits板块, ‘key’值定义对应的事件名,‘value’值定义一个校验函数, 返回true表示同意数值外传, 返回false表示不同意,会给出警告;

const app = Vue.createApp({ data() { return {count: 1} }, methods: { handleItemEvent(count) { this.count = count; console.log(this.count); } }, template: ` ` }); app.component('counter', { props: ['count'], emits: { addCount: (count) => { if (count < 0) { return true; } return false; } }, methods: { handleItemClick() { this.$emit('addCount', this.count + 16); } }, template:` {{count}} ` }); const vm = app.mount('#heheApp');

运行,点击效果:

结合$emit、v-bind与v-model 实现 父子组件通信(数据双向绑定)

v-model可以实现数据字段与DOM节点内容的双向绑定, 也可以实现数据字段与数据字段之间的双向绑定; 而v-bind只能是实现单向数据流;

若不自定义承接的字段名,则需要用modelValue作为默认的承接字段名; 同时,$emit()的一参默认为update:modelValue,二参为绑定的数据; 如下代码, 子组件 的承接变量modelValue 同父组件的count字段 双向绑定, (实际上就是v-model的特性 —— 将 子组件的内容即modelValue 同 父组件的数据字段双向绑定) 然后显示在子组件的DOM中({{modelValue}}):

const app = Vue.createApp({ data() { return {count: 1} }, template: ` ` }); app.component('counter', { props: ['modelValue'], methods: { handleItemClick() { this.$emit('update:modelValue', this.modelValue + 16); console.log(vm.$data.count); } }, template:` {{modelValue}} ` }); const vm = app.mount('#heheApp');

效果:

当然也可以自定义字段名, 这种方式需要给v-model字段接一个字段名, 同时将这个字段名替代子组件中所有modelValue的位置:

const app = Vue.createApp({ data() { return {count: 1} }, template: ` ` }); app.component('counter', { props: ['testField'], methods: { handleItemClick() { this.$emit('update:testField', this.testField + 16); console.log(vm.$data.count); } }, template:` {{testField}} ` }); const vm = app.mount('#heheApp');

实现效果与上例相同;

结合$emit、v-bind与v-model 实现 父子组件通信(多个字段的应用案例)

如下代码, 父组件的count与子组件承接的testField字段, 父组件的count1与子组件承接的testField1字段, 分别实现了双向绑定:

const app = Vue.createApp({ data() { return { count: 1, count1: 1 } }, template: ` ` }); app.component('counter', { props: ['testField','testField1'], methods: { handleItemClick() { this.$emit('update:testField', this.testField + 16); console.log("vm.$data.count", vm.$data.count); }, handleItemClick1() { this.$emit('update:testField1', this.testField1 + 8); console.log("vm.$data.count1", vm.$data.count1); } }, template:` {{testField}} {{testField1}} ` }); const vm = app.mount('#heheApp');

效果:

自定义修饰符

机制:在父组件调用处,在v-model后 使用自定义修饰符, 在实现修饰符逻辑的地方,如点击事件中, 通过this.modelModifiers.[自定义修饰符名]返回的布尔值, 判断用户是否使用了修饰符, 进而分别对使用与否做相应的处理; 另外'modelModifiers'板块中可以指定默认值(下代码指定为一个空对象{});

实验this.modelModifiers的作用

首先下面是一个空的处理,'modelModifiers'板块中指定默认值(下代码指定为一个空对象{}), mounted函数中打印 子组件modelModifiers属性的内容, 代码如下, 运行后,可以见打印了一个对象{captalize: true}, 正是我们传入的自定义修饰符.captalize(这里未做处理) 【如果这里v-model不接修饰符, console.log(this.modelModifiers);将打印一个空对象{}】:

const app = Vue.createApp({ data() { return { char: 'a' } }, template: ` ` }); app.component('counter', { props: { 'modelValue': String, 'modelModifiers': { default: () => ({}) } }, mounted() { console.log(this.modelModifiers); }, methods: { handleClick() { this.$emit('update:modelValue', this.modelValue + 'h'); console.log("vm.$data.count", vm.$data.char); } }, template:` {{modelValue}} ` }); const vm = app.mount('#heheApp'); 下面在子组件的点击回调handleClick()中,通过this.modelModifiers.[自定义修饰符名]实现自定义修饰符逻辑

实现效果即 点击之后使得对应的字符串 全变大写;

const app = Vue.createApp({ data() { return { testString: 'a' } }, template: ` ` }); app.component('counter', { props: { 'modelValue': String, 'modelModifiers': { default: () => ({}) } }, mounted() { console.log(this.modelModifiers); }, methods: { handleClick() { let newValue = this.modelValue + 'h'; if(this.modelModifiers.heheda) { newValue = newValue.toUpperCase(); } this.$emit('update:modelValue', newValue); console.log("vm.$data.count", vm.$data.testString); } }, template:` {{modelValue}} ` }); const vm = app.mount('#heheApp');

效果:

插槽【slot】【传组件示例】

使用关键 主要分两个部分: 自定义子组件: 在需要 被父组件插入组件的位置, 使用标签对临时占位; 父组件: 在调用子组件标签对时, 往子组件标签对 间 写上 要替换子组件标签对中位置的组件 【slot】的出现, 方便父子组件之间数据的传递, 方便DOM的传递;

Hello World! heheheheheheda const app = Vue.createApp({ template: ` 提交 提交 ` }); app.component('myform', { template:` ` }); const vm = app.mount('#heheApp');

运行效果:

注意,slot标签上是无法直接添加事件(修饰符)的,如有需要,可以在外层包裹一层标签,再加上事件 const app = Vue.createApp({ template: ` 提交 提交 ` }); app.component('myform', { methods: { handleClick() { console.log("heheda!!==="); } }, template:` ` }); const vm = app.mount('#heheApp');

运行,点击提交文本或按钮:

插槽【传 字符串示例】 const app = Vue.createApp({ template: ` 66666 88888 ` }); app.component('myform', { methods: { handleClick() { console.log("heheda!!==="); } }, template:` ` }); const vm = app.mount('#heheApp'); 插槽【传 自定义子组件 示例】 const app = Vue.createApp({ template: ` 88888 ` }); app.component('test', { template: `test component` }) app.component('myform', { methods: { handleClick() { console.log("heheda!!==="); } }, template:` ` }); const vm = app.mount('#heheApp');

运行:

插槽作用域问题

虽然,父组件中 往子组件标签间 插入的组件 会替换子组件的插槽位, 但是父组件中 往子组件标签间 插入的组件, 其所使用的数据字段,仍然是父组件的,而非子组件; 父组件的template中 调用的数据是 父组件中的 data; 子组件的template中 调用的数据是 子组件中的 data;

const app = Vue.createApp({ data() { return { text: '提交' } }, template: ` {{text}} {{text}} ` }); app.component('myform', { methods: { handleClick() { console.log("heheda!!==="); } }, template:` ` }); const vm = app.mount('#heheApp'); 插槽 UI默认值

可以在子组件的插槽标签间 编写默认值, 如果父组件没有使用 组件 注入插槽, 则对应位置 会显示默认值:

const app = Vue.createApp({ data() { return { text: '提交' } }, template: ` {{text}} {{text}} ` }); app.component('myform', { methods: { handleClick() { console.log("heheda!!==="); } }, template:` default value ` }); const vm = app.mount('#heheApp');

效果:

插槽的灵活拆分与应用【具名插槽】使得插槽的 父组件注入部分 和 子组件占位部分,能够更加灵活的布局, 可以通过v-slot:[插槽名]来对一组插槽命名, 父组件定义之后 插槽名及其对应的组件之后, 子组件只需要在要占位的地方, 配合name属性 使用对应命名的标签, 即可将对应的父组件插槽组件占用过来;父组件 的插槽注入部分的组件, 需要用标签组包裹起来, 使用v-slot:[插槽名]命名一组插槽;子组件使用的形式,进行插槽组件块的临时占用; const app = Vue.createApp({ template: ` 头部 尾部 ` }); app.component('layout', { template:` content ` }); const vm = app.mount('#heheApp');

效果:

v-slot指令的简写

v-slot:[插槽名] 可以简写成 #[插槽名]

const app = Vue.createApp({ template: ` 头部 尾部 ` }); app.component('layout', { template:` content ` }); const vm = app.mount('#heheApp');

实现的效果同上例;

普通的v-for例子 进行 列表渲染

下面在子组件中, 使用v-for指令 循环 子组件的数据,创建DOM组件:

const app = Vue.createApp({ template: ` ` }); app.component('test-list', { data(){ return { list: ["heheda", "xixi" , "lueluelue"] } }, template:` {{item}} ` }); const vm = app.mount('#heheApp');

运行效果:

v-for结合v-bind、v-slot、做列表渲染

作用:给数据由子组件提供, 但列表UI实现 由父组件调用处提供, 类似于回调接口的设计逻辑!!!

子组件使用v-for循环获取数据, 每一轮迭代 获得的子项数据, 通过v-bind设置到占位的标签中, 父组件中,在引用的 子组件标签上, 使用v-slot承接 子组件通过v-bind传来的所有数据字段, 同时将这些字段打包成一个类似JSONObject的结构 字段, 并为这个字段 指定一个形参名(如下代码中的mySlotProps);

【注意! 前面是, 使用v-slot命名父组件中 拟填充插槽的组件, 子组件在标签上,通过name=使用 父组件的命名,灵活填充插槽; 而这里是, 的slot反而是起到了类似props的作用,而非之前的命名组件作用!】

在 拟填充插槽的DOM组件中, 使用方才 v-slot指定的形参,用于开箱取数据:

const app = Vue.createApp({ template: ` {{mySlotProps.item}} ` }); app.component('test-list', { data(){ return { list: ["heheda", "xixi" , "lueluelue"] } }, template:` ` }); const vm = app.mount('#heheApp');

运行效果同上例;

使用解构概念进行简写

使用v-slot="{item}"替代前面的props的结构逻辑形式; 意义是,把mySlotProps这个承接属性的字段, 里面的item属性直接解构 剥取出来,直接拿来用;

const app = Vue.createApp({ template: ` {{item}} ` }); app.component('test-list', { data(){ return { list: ["heheda", "xixi" , "lueluelue"] } }, template:` ` }); const vm = app.mount('#heheApp');

运行效果同上例;

动态组件常规的利用双向绑定特性,通过点击事件切换UI的写法: const app = Vue.createApp({ data() { return { currentItem: 'input-item' } }, methods: { handlerClick() { this.currentItem === 'input-item'? this.currentItem = 'div-item': this.currentItem = 'input-item' } }, template: ` 切换DOM组件` }); app.component('input-item', { template:` ` }); app.component('div-item', { template:`heheda` }); const vm = app.mount('#heheApp');

运行效果:

动态组件写法语法: 一般在父组件中, 使用占位标签, 效果即 占位位置,会显示 is属性 指定组件名的子组件; 另外, 使用标签,包裹, 可以是切换组件的时候,能够缓存组件的数据, 如一个有输入数据的 切换成一个其他组件 再切换 回来的时候, 可以保留一开始的输入数据: const app = Vue.createApp({ data() { return { currentItem: 'input-item' } }, methods: { handlerClick() { console.log("handlerClick ----- "); this.currentItem === 'input-item'? this.currentItem = 'div-item': this.currentItem = 'input-item' } }, template: ` 切换DOM组件` }); app.component('input-item', { template:` ` }); app.component('div-item', { template:`heheda` }); const vm = app.mount('#heheApp');

运行效果: 初始为有输入数据的输入框:

点击切换为文本组件:

再次点击,切换为有输入数据的输入框,

由于的作用,数据缓存下来,没有丢失,

如果没加,这里会是空的输入框:

异步组件

首先, 本文在此案例之前的所有案例,都是同步组件, 即随即渲染,一个线程运行; 下面是异步(自定义子)组件, 可以设定在某个时刻开始,延迟一个时延后,再执行渲染:

const app = Vue.createApp({ template: ` ` }); app.component('div-item', { template:`heheda heheda` }); app.component('my-async-item', Vue.defineAsyncComponent(() => { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ template: `this is my async component` }) }, 4000) }) })) const vm = app.mount('#heheApp');

关键代码【异步(自定义子)组件】:

app.component('my-async-item', Vue.defineAsyncComponent(() => { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ template: `this is my async component` }) }, 4000) }) }))

运行效果:



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有