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

2021-03-23 14:47:28 浏览数 (1)

完整原文地址见简书https://cloud.tencent.com/developer/article/1805447

本文内容提要

  • 父子组件可通过事件 进行通信
  • 携带参数的事件 发送和监听回调
  • 使用 组件的**emits**板块 整理组件事件
  • 使用 组件**emits**板块的 Object**形式 校验外传的参数值**
  • 结合**$emit**、**v-bind**与**v-model** 实现 父子组件通信(数据双向绑定)
  • 结合**$emit**、**v-bind**与**v-model** 实现 父子组件通信(多个字段的应用案例)
  • 自定义修饰符
代码语言:txt复制
- **实验****`this.modelModifiers`****的作用**
代码语言:txt复制
- **常规的利用****`双向绑定特性`****,通过点击事件切换UI的写法**
- **动态组件写法**
父子组件可通过事件 进行通信

前面的笔记 —— 《Vue3 | 组件的定义及复用性、局部组件、全局组件、组件间传值及其校验、单项数据流、Non-props属性》,单向数据流的概念,

即子组件无法修改来自父组件的数据字段,

如果确要修改,可以使用下面说的方式进行通信:

首先,在子组件的UI点击回调方法中,调用**this.$emit('【自定义事件名】')**,

向外发送一个**事件**;

接着各级父组件会收到这个事件,

则在父组件中 调用 子组件标签处,

@【事件名】= "回调方法名"**的形式,**监听**该事件以及配置**回调方法**;**

回调方法**中即可 对 子组件意图修改 的 父组件数据字段 进行修改;**

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

代码:

代码语言:javascript复制
<!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**);**

代码:

代码语言:javascript复制
<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()**按需添加参数位,

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

代码语言:javascript复制
<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)**);

代码语言:javascript复制
<script>
    const app = Vue.createApp({
        data() {
            return {count: 1}
        },
        methods: {
            handleItemEvent(count) {
                this.count = count;
                console.log(this.count);
            }
        },
        template: `
        <div>
            <counter :count="count" @add-count="handleItemEvent"/>
        </div>`
    });

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

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

效果同上一个例子;

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

实际开发场景中,我们一个组件自定义的触发事件可能会很多,

我们不可能一个一个去梳理核实,

这个时候就可以使用 组件的**emits**板块 来整理组件的事件;

可以把组件中 自定义到的事件**都写在这里,**方便梳理**,提高**可读性**,**

或者把 想要定义的事件 写在这里,

如此一来,如果**忘记**编写对应的自定义事件,

Vue系统会在运行时 给予**警告**:

代码语言:javascript复制
<script>
    const app = Vue.createApp({
        data() {
            return {count: 1}
        },
        methods: {
            handleItemEvent(count) {
                this.count = count;
                console.log(this.count);
            }
        },
        template: `
        <div>
            <counter :count="count" @add-count="handleItemEvent"/>
        </div>`
    });

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

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

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

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

可以根据需要,使用 组件**emits**板块的 Object**形式 校验外传的参数值,**

如下,子组件的**emits**板块,

‘key’值定义对应的事件名,‘value’值定义一个校验函数,

返回**true**表示同意数值外传,

返回**false**表示不同意,会给出警告;

代码语言:javascript复制
<script>
    const app = Vue.createApp({
        data() {
            return {count: 1}
        },
        methods: {
            handleItemEvent(count) {
                this.count = count;
                console.log(this.count);
            }
        },
        template: `
        <div>
            <counter :count="count" @add-count="handleItemEvent"/>
        </div>`
    });

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

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

运行,点击效果:

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

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

若不自定义**承接的字段名**,则需要用**modelValue**作为默认的**承接字段名**;

同时,**$emit()**的一参默认为**update:modelValue**,二参为绑定的数据;

如下代码,

子组件 的承接变量**modelValue** 同父组件的**count**字段 双向绑定,

(实际上就是**v-model**的特性 —— 将 子组件的内容即**modelValue** 同 父组件的**数据字段**双向绑定)

然后显示在子组件的DOM中(**{{modelValue}}**):

代码语言:javascript复制
<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**的位置:

代码语言:javascript复制
<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>

实现效果与上例相同;

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

如下代码,

父组件的**count**与子组件承接的**testField**字段,

父组件的**count1**与子组件承接的**testField1**字段,

分别实现了双向绑定:

代码语言:javascript复制
<script>
    const app = Vue.createApp({
        data() {
            return {
                count: 1,
                count1: 1
            }
        },
        template: `
            <counter v-model:testField="count" v-model:testField1="count1"/>`
    });

    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:`
        <div @click="handleItemClick">{{testField}}</div>
        <div @click="handleItemClick1">{{testField1}}</div>
        `
    });

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

效果:

自定义修饰符

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

实验this.modelModifiers的作用

首先下面是一个空的处理,**'modelModifiers'**板块中**指定默认值**(下代码指定为一个空对象**{}**),

mounted**函数中打印 子组件**modelModifiers**属性的内容,**

代码如下,

运行后,可以见打印了一个对象**{captalize: true}**,

正是我们传入的自定义修饰符**.captalize**(这里未做处理)

【如果这里**v-model**不接修饰符,

console.log(this.modelModifiers);**将打印一个空对象**{}**】:**

代码语言:javascript复制
<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.[自定义修饰符名]实现自定义修饰符逻辑

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

代码语言:javascript复制
<script>
    const app = Vue.createApp({
        data() {
            return {
                testString: 'a'
            }
        },
        template: `
            <counter v-model.heheda="testString"/>`
    });

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

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

效果:

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

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

代码语言:javascript复制
<!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({
        template: `
            <myform>
                <div>提交</div>
            </myform>
            <myform>
                <button>提交</button>
            </myform>`
    });

    app.component('myform', {
        template:`
        <div>
            <input />
            <slot></slot>
            <br><br>
        </div>
        `
    });

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

运行效果:

注意,slot标签上是无法直接添加事件(修饰符)的,如有需要,可以在<slot>外层包裹一层<span>标签,再加上事件
代码语言:javascript复制
<script>
    const app = Vue.createApp({
        template: `
            <myform>
                <div>提交</div>
            </myform>
            <myform>
                <button>提交</button>
            </myform>`
    });

    app.component('myform', {
        methods: {
            handleClick() {
                console.log("heheda!!===");
            }
        },
        template:`
        <div>
            <input />
            <span @click="handleClick">
                <slot></slot>
            </span>    
            <br><br>
        </div>
        `
    });

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

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

插槽【传 字符串示例】
代码语言:javascript复制
<script>
    const app = Vue.createApp({
        template: `
            <myform>
                66666
            </myform>
            <myform>
                88888
            </myform>`
    });

    app.component('myform', {
        methods: {
            handleClick() {
                console.log("heheda!!===");
            }
        },
        template:`
        <div>
            <input />
            <span @click="handleClick">
                <slot></slot>
            </span>    
            <br><br>
        </div>
        `
    });

    const vm = app.mount('#heheApp');
</script>
插槽【传 自定义子组件 示例】
代码语言:javascript复制
<script>
    const app = Vue.createApp({
        template: `
            <myform>
                <test />
            </myform>
            <myform>
                88888
            </myform>`
    });

    app.component('test', {
        template: `<div>test component</div>`
    })

    app.component('myform', {
        methods: {
            handleClick() {
                console.log("heheda!!===");
            }
        },
        template:`
        <div>
            <input />
            <span @click="handleClick">
                <slot></slot>
            </span>    
            <br><br>
        </div>
        `
    });

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

运行:

插槽作用域问题

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

代码语言:javascript复制
<script>
    const app = Vue.createApp({
        data() {
            return {
                text: '提交'
            }
        },
        template: `
            <myform>
                <div>{{text}}</div>
            </myform>
            <myform>
                <button>{{text}}</button>
            </myform>`
    });

    app.component('myform', {
        methods: {
            handleClick() {
                console.log("heheda!!===");
            }
        },
        template:`
        <div>
            <input />
            <span @click="handleClick">
                <slot></slot>
            </span>    
            <br><br>
        </div>
        `
    });

    const vm = app.mount('#heheApp');
</script>
插槽 UI默认值

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

代码语言:javascript复制
<script>
    const app = Vue.createApp({
        data() {
            return {
                text: '提交'
            }
        },
        template: `
            <myform>
                <div>{{text}}</div>
            </myform>
            <myform>
                <button>{{text}}</button>
            </myform>
            <myform>
            </myform>`
    });

    app.component('myform', {
        methods: {
            handleClick() {
                console.log("heheda!!===");
            }
        },
        template:`
        <div>
            <input />
            <span @click="handleClick">
                <slot>default value</slot>
            </span>    
            <br><br>
        </div>
        `
    });

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

效果:

插槽的灵活拆分与应用【具名插槽】
  • 使得插槽的 父组件注入部分 和 子组件占位部分,能够更加灵活的布局,

可以通过**v-slot:[插槽名]**来对一组插槽命名,

父组件定义之后 插槽名**及其对应的**组件**之后,**

子组件只需要在要占位的地方,

配合**name**属性 使用对应命名的<slot>标签,

即可将对应的父组件插槽组件占用过来;

  • 父组件 的插槽注入部分的组件, 需要用<template>标签组包裹起来, 使用**v-slot:[插槽名]**命名一组插槽;
  • 子组件使用**<slot name="[插槽名]"></slot>**的形式,进行插槽组件块的临时占用;
代码语言:javascript复制
<script>
    const app = Vue.createApp({
        template: `
            <layout>
                <template v-slot:header>
                    <div>头部</div>
                </template>
                <template v-slot:footer>
                    <div>尾部</div>
                </template>
            </layout>`
    });

    app.component('layout', {
        template:`
        <div>
            <slot name="header"></slot>
            <div>content</div>
            <slot name="footer"></slot>
        </div>
        `
    });

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

效果:

v-slot指令的简写

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

代码语言:javascript复制
<script>
    const app = Vue.createApp({
        template: `
            <layout>
                <template #header>
                    <div>头部</div>
                </template>
                <template #footer>
                    <div>尾部</div>
                </template>
            </layout>`
    });

    app.component('layout', {
        template:`
        <div>
            <slot name="header"></slot>
            <div>content</div>
            <slot name="footer"></slot>
        </div>
        `
    });

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

实现的效果同上例;

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

下面在子组件中,

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

代码语言:javascript复制
<script>
    const app = Vue.createApp({
        template: `
            <test-list />`
    });

    app.component('test-list', {
        data(){
            return { 
                list: ["heheda", "xixi" , "lueluelue"]
            }
        },
        template:`
        <div>
            <div v-for="item in list">{{item}}</div>
        </div>
        `
    });

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

运行效果:

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

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

子组件使用**v-for**循环获取数据,

每一轮迭代 获得的子项数据,

通过**v-bind**设置到**占位的<slot>标签**中,

父组件中,在引用的 子组件标签上,

使用**v-slot**承接 子组件通过**v-bind**传来的所有数据字段,

同时将这些字段打包成一个类似**JSONObject**的**结构 字段**,

并为这个**字段** 指定一个**形参名**(如下代码中的**mySlotProps**);

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

拟填充插槽**的DOM组件中,**

使用方才 v-slot**指定的形参,用于**开箱取数据**:**

代码语言:javascript复制
<script>
    const app = Vue.createApp({
        template: `
            <test-list v-slot="mySlotProps"> 
                <div>{{mySlotProps.item}}</div>
            </test-list>`
    });

    app.component('test-list', {
        data(){
            return { 
                list: ["heheda", "xixi" , "lueluelue"]
            }
        },
        template:`
        <div>
            <slot v-for="item in list" :item="item" />
        </div>
        `
    });

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

运行效果同上例;

使用解构概念进行简写

使用**v-slot="{item}"**替代前面的**props**的结构逻辑形式;

意义是,把**mySlotProps**这个承接属性的字段,

里面的**item**属性直接**解构 剥取**出来,直接拿来用;

代码语言:javascript复制
<script>
    const app = Vue.createApp({
        template: `
            <test-list v-slot="{item}"> 
                <div>{{item}}</div>
            </test-list>`
    });

    app.component('test-list', {
        data(){
            return { 
                list: ["heheda", "xixi" , "lueluelue"]
            }
        },
        template:`
        <div>
            <slot v-for="item in list" :item="item" />
        </div>
        `
    });

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

运行效果同上例;

动态组件
常规的利用双向绑定特性,通过点击事件切换UI的写法:
代码语言:javascript复制
<script>
    const app = Vue.createApp({
        data() {
            return {
                currentItem: 'input-item'
            }
        },
        methods: {
            handlerClick() {
                this.currentItem === 'input-item'?
                this.currentItem = 'div-item': this.currentItem = 'input-item'
            }
        },
        template: `
            <input-item v-show="currentItem === 'input-item'" />
            <div-item v-show="currentItem === 'div-item'" />
            <button @click="handlerClick">切换DOM组件</button>`
    });

    app.component('input-item', {
        template:`
        <input />`
    });

    app.component('div-item', {
        template:`<div>heheda</div>`
    });

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

运行效果:

动态组件写法
  • 语法: 一般在父组件中, 使用占位标签**<component :is="[需显示的 子组件名]" />**, 效果即 占位位置,会显示 is**属性 指定组件名的子组件;** 另外, 使用**<keep-alive>**标签,包裹**<component :is="[需显示的 子组件名]" />**, 可以是切换组件的时候,能够缓存组件的数据, 如一个有**输入数据**的<input> 切换成一个其他组件 再切换 回来的时候, 可以保留一开始的**输入数据**:
代码语言:javascript复制
<script>
    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: `
            <keep-alive>
                <component :is="currentItem" />
            </keep-alive>
            <button @click="handlerClick">切换DOM组件</button>`
    });

    app.component('input-item', {
        template:`
        <input />`
    });

    app.component('div-item', {
        template:`<div>heheda</div>`
    });

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

运行效果:

初始为有输入数据的输入框:

点击切换为文本组件:

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

由于**<keep-alive>**的作用,数据缓存下来,没有丢失,

如果没加**<keep-alive>**,这里会是空的输入框:

异步组件

首先,

本文在此案例之前的所有案例,都是**同步组件**,

即随即渲染,一个线程运行;

下面是**异步(自定义子)组件**,

可以设定在某个时刻开始,延迟一个时延后,再执行渲染:

代码语言:javascript复制
<script>
    const app = Vue.createApp({
        template: `
            <div>
                <div-item />
                <my-async-item />
            </div>`        
    });

    app.component('div-item', {
        template:`<div>heheda heheda</div>`
    });

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

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

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

代码语言:javascript复制
    app.component('my-async-item', Vue.defineAsyncComponent(() => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve({
                    template: `<div>this is my async component</div>`
                })
            }, 4000)
        })
    }))

运行效果:

0 人点赞