Vue3---父子组件间通信、组件间双向绑定的高级内容、插槽详解、动态组件、异步组件

115 阅读7分钟

#完整原文地址见简书www.jianshu.com/p/c8891bd3f… #更多完整Vue笔记目录敬请见《前端 Web 笔记 汇总目录(Updating)》



#本文内容提要

  • 父子组件可通过事件 进行通信

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

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

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

  • 结合$emitv-bindv-model 实现 父子组件通信(数据双向绑定)

  • 结合$emitv-bindv-model 实现 父子组件通信(多个字段的应用案例)

  • 自定义修饰符

    • 实验this.modelModifiers的作用
  • 下面在子组件的点击回调handleClick()中,通过this.modelModifiers.[自定义修饰符名]实现自定义修饰符逻辑

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

  • 注意,slot标签上是无法直接添加事件(修饰符)的,如有需要,可以在外层包裹一层标签,再加上事件

  • 插槽【传 字符串示例】

  • 插槽【传 自定义子组件 示例】

  • 插槽作用域问题

  • 插槽 UI默认值

  • 插槽的灵活拆分与应用【具名插槽】

  • v-slot指令的简写

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

  • v-for结合v-bindv-slot<slot>做列表渲染

  • 使用解构概念进行简写

  • 动态组件

    • 常规的利用双向绑定特性,通过点击事件切换UI的写法
    • 动态组件写法
  • 异步组件


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

代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello World! heheheheheheda</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="heheApp"></div>
</body>
<script>
    const app = Vue.createApp({
        data() {
            return {count: 1}
        },
        methods: {
            handleItemEvent() {
                this.count += 1;
            }
        },
        template: `
        <div>
            <counter :count="count" @he-he-da="handleItemEvent"/>
        </div>`
    });

    app.component('counter', {
        props: ['count'],
        methods: {
            handleItemClick() {
                this.$emit('heHeDa');
            }
        },
        template:`
        <div @click="handleItemClick">{{count}}</div>
        `
    });

    const vm = app.mount('#heheApp');
</script>
</html>

运行,点击组件:


####携带参数的事件 发送和监听回调 **`this.$emit()`可以添加参数位, 父组件的监听回调中, 则可加形参位 用于接收参数(如`handleItemEvent(param)`中的 `param`);**

代码:

<script>
    const app = Vue.createApp({
        data() {
            return {count: 1}
        },
        methods: {
            handleItemEvent(param) {
                this.count += param;
            }
        },
        template: `
        <div>
            <counter :count="count" @add-count="handleItemEvent"/>
        </div>`
    });

    app.component('counter', {
        props: ['count'],
        methods: {
            handleItemClick() {
                this.$emit('addCount', 8);
            }
        },
        template:`
        <div @click="handleItemClick">{{count}}</div>
        `
    });

    const vm = app.mount('#heheApp');
</script>

运行,点击效果:
子组件需 发送多个参数 亦可,只要在this.$emit()按需添加参数位, 父组件的监听回调中,添加对应的形参 去接收即可:

<script>
    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: `
        <div>
            <counter :count="count" @add-count="handleItemEvent"/>
        </div>`
    });

    app.component('counter', {
        props: ['count'],
        methods: {
            handleItemClick() {
                this.$emit('addCount', 8, 2, 6);
            }
        },
        template:`
        <div @click="handleItemClick">{{count}}</div>
        `
    });

    const vm = app.mount('#heheApp');
</script>

效果:


**当然 父组件 接收 子组件参数 后的 计算逻辑, 可以在 子组件传参 的时候 计算完成 再传给`this.$emit()`! 父组件接收时,直接 受值即可(`handleItemEvent(count)`);** ``` ``` 效果同上一个例子;
####使用 组件的`emits`板块 整理组件事件 **实际开发场景中,我们一个组件自定义的触发事件可能会很多, 我们不可能一个一个去梳理核实, 这个时候就可以使用 组件的`emits`板块 来整理组件的事件;
可以把组件中 `自定义到的事件`都写在这里,`方便梳理`,提高`可读性`, 或者把 `想要定义的事件` 写在这里, 如此一来,如果`忘记`编写对应的自定义事件, Vue系统会在运行时 给予`警告`:** ``` ``` **如果`忘记`编写对应的自定义事件,Vue系统会在运行时 给予`警告`:** ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec759841def646468ce00c20ffe2d7f5~tplv-k3u1fbpfcp-zoom-1.image)
####使用 组件`emits`板块的 `Object`形式 校验外传的参数值 **可以根据需要,使用 组件`emits`板块的 `Object`形式 校验外传的参数值, 如下,子组件的`emits`板块, ‘key’值定义对应的事件名,‘value’值定义一个校验函数,
返回`true`表示同意数值外传, 返回`false`表示不同意,会给出警告;** ``` ``` 运行,点击效果:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/62c368d6b831436786a6e72e6b028ff0~tplv-k3u1fbpfcp-zoom-1.image)
####结合`$emit`、`v-bind`与`v-model` 实现 父子组件通信(数据双向绑定) >**v-model可以实现数据字段与DOM节点内容的双向绑定, 也可以实现数据字段与数据字段之间的双向绑定; 而`v-bind`只能是实现`单向数据流`;**

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

<script>
    const app = Vue.createApp({
        data() {
            return {count: 1}
        },
        template: `
            <counter v-model="count"/>`
    });

    app.component('counter', {
        props: ['modelValue'],
        methods: {
            handleItemClick() {
                this.$emit('update:modelValue', this.modelValue + 16);
                console.log(vm.$data.count);
            }
        },
        template:`
        <div @click="handleItemClick">{{modelValue}}</div>
        `
    });

    const vm = app.mount('#heheApp');
</script>

效果:

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

<script>
    const app = Vue.createApp({
        data() {
            return {count: 1}
        },
        template: `
            <counter v-model:testField="count"/>`
    });

    app.component('counter', {
        props: ['testField'],
        methods: {
            handleItemClick() {
                this.$emit('update:testField', this.testField + 16);
                console.log(vm.$data.count);
            }
        },
        template:`
        <div @click="handleItemClick">{{testField}}</div>
        `
    });

    const vm = app.mount('#heheApp');
</script>

实现效果与上例相同;


####结合`$emit`、`v-bind`与`v-model` 实现 父子组件通信(多个字段的应用案例) **如下代码, 父组件的`count`与子组件承接的`testField`字段, 父组件的`count1`与子组件承接的`testField1`字段, 分别实现了双向绑定:** ``` ``` 效果:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4db33b76712f45d5af1679f859afa10d~tplv-k3u1fbpfcp-zoom-1.image)
####自定义修饰符 >**机制:在父组件调用处,在`v-model`后 使用`自定义修饰符`, >在`实现修饰符逻辑`的地方,如点击事件中, >通过`this.modelModifiers.[自定义修饰符名]`返回的`布尔值`, >判断用户是否使用了修饰符, >进而分别对使用与否做相应的处理; 另外`'modelModifiers'`板块中可以`指定默认值`(下代码指定为一个空对象`{}`);**

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

<script>
    const app = Vue.createApp({
        data() {
            return {
                char: 'a'
            }
        },
        template: `
            <counter v-model.captalize="char"/>`
    });

    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:`
        <div @click="handleClick">{{modelValue}}</div>
        `
    });

    const vm = app.mount('#heheApp');
</script>


#####下面在子组件的点击回调`handleClick()`中,通过`this.modelModifiers.[自定义修饰符名]`实现自定义修饰符逻辑 **实现效果即 点击之后使得对应的字符串 全变大写;** ``` ``` **效果:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7a27e4d7aeab4be48006d6bf7e771804~tplv-k3u1fbpfcp-zoom-1.image)**
####插槽【slot】【传组件示例】 >**使用关键 主要分两个部分: 自定义子组件: 在需要 被父组件`插入组件`的位置, 使用``标签对临时占位;
>父组件: 在调用`子组件标签对`时, 往`子组件标签对` 间 写上 要替换`子组件标签对`中``位置的组件
>【slot】的出现, 方便父子组件之间数据的传递, 方便DOM的传递;** ``` Hello World! heheheheheheda
``` 运行效果:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c0eab42d3c0443bd9c2c8ee1e98257d5~tplv-k3u1fbpfcp-zoom-1.image)
####注意,slot标签上是无法直接添加事件(修饰符)的,如有需要,可以在外层包裹一层标签,再加上事件 ``` ``` 运行,点击提交文本或按钮:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dfffba4783684a338a2c3031d2996c98~tplv-k3u1fbpfcp-zoom-1.image)
####插槽【传 字符串示例】 ``` ``` ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/34ede06b58e24b5396406e151eac824b~tplv-k3u1fbpfcp-zoom-1.image)
####插槽【传 自定义子组件 示例】 ``` ``` 运行:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/890a3be8384c4766993781809a133981~tplv-k3u1fbpfcp-zoom-1.image)
####插槽作用域问题 >虽然,`父组件`中 往`子组件`标签间 插入的组件 会替换`子组件`的插槽位, 但是` 父组件`中 往`子组件`标签间 插入的组件, 其所使用的数据字段,仍然是`父组件`的,而非`子组件`;
**父组件的template中 调用的数据是 父组件中的 data; 子组件的template中 调用的数据是 子组件中的 data;** ``` ``` ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/105c7b6047494047b351a839ef668427~tplv-k3u1fbpfcp-zoom-1.image)
####插槽 UI默认值 >**可以在子组件的`插槽标签`间 编写`默认值`, 如果父组件没有使用 组件 注入`插槽`, 则对应位置 会显示`默认值`:** ``` ``` 效果:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8803d4aa278b4775a80750499fefd564~tplv-k3u1fbpfcp-zoom-1.image)
####插槽的灵活拆分与应用【具名插槽】 - **使得插槽的 父组件注入部分 和 子组件占位部分,能够更加灵活的布局,
可以通过`v-slot:[插槽名]`来对一组插槽命名, 父组件定义之后 `插槽名`及其对应的`组件`之后,
子组件只需要在要占位的地方, 配合`name`属性 使用对应命名的标签, 即可将对应的父组件插槽组件占用过来;**
  • 父组件 的插槽注入部分的组件, 需要用