Vue3.0 新特性以及使用变更总结

Vue3.0 在2020年9月正式发布了,也有许多小伙伴都热情的拥抱Vue3.0。去年年底我们新项目使用Vue3.0来开发,这篇文章就是在使用后的一个总结, 包含Vue3新特性的使用以及一些用法上的变更。

图片.png

为什么要升级Vue3

使用Vue2.x的小伙伴都熟悉,Vue2.x中所有数据都是定义在data中,方法定义在methods中的,并且使用this来调用对应的数据和方法。那Vue3.x中就可以不这么玩了, 具体怎么玩我们后续再说, 先说一下Vue2.x版本这么写有什么缺陷,所以才会进行升级变更的。

回顾Vue2.x实现加减

<template><div class="homePage"><p>count: {{ count }}</p><p>倍数: {{ multiple }}</p><div><button style="margin-right:10px" @click="increase">加1</button><button @click="decrease">减一</button></div></div>
</template><script>
export default {data() {return {count: 0,};},computed: {multiple() {return 2 * this.count;},},methods: {increase() {this.count++;},decrease() {this.count++;},},
};
</script>

上面代码只是实现了对count的加减以及显示倍数, 就需要分别在data、methods、computed中进行操作,当我们增加一个需求,就会出现下图的情况:

当我们业务复杂了就会大量出现上面的情况, 随着复杂度上升,就会出现这样一张图, 每个颜色的方块表示一个功能:

甚至一个功能还有会依赖其他功能,全搅合在一起。

当这个组件的代码超过几百行时,这时增加或者修改某个需求, 就要在data、methods、computed以及mounted中反复的跳转,这其中的的痛苦写过的都知道。

那我们就想啊, 如果可以按照逻辑进行分割,将上面这张图变成下边这张图,是不是就清晰很多了呢, 这样的代码可读性和可维护性都更高:

那么vue2.x版本给出的解决方案就是Mixin, 但是使用Mixin也会遇到让人苦恼的问题:

  1. 命名冲突问题

  2. 不清楚暴露出来的变量的作用

  3. 逻辑重用到其他 component 经常遇到问题

关于上面经常出现的问题我就不一一举例了,使用过的小伙伴多多少少都会遇到。文章的重点不是Mixin,如果确实想知道的就留言啦~

所以,我们Vue3.x就推出了Composition API主要就是为了解决上面的问题,将零散分布的逻辑组合在一起来维护,并且还可以将单独的功能逻辑拆分成单独的文件。接下来我们就重点认识Composition API

Composition API

setup

setup 是Vue3.x新增的一个选项, 他是组件内使用 Composition API的入口。

setup执行时机

我在学习过程中看到很多文章都说setup 是在 beforeCreatecreated之间, 这个结论是错误的。实践是检验真理的唯一标准, 于是自己去检验了一下:

export default defineComponent ({beforeCreate() {console.log("----beforeCreate----");},created() {console.log("----created----");},setup() {console.log("----setup----");},
})

setup 执行时机是在beforeCreate之前执行,详细的可以看后面生命周期讲解。

::: warning 由于在执行setup 时尚未创建组件实例,因此在 setup 选项中没有 this。:::

setup 参数

使用setup时,它接受两个参数:

  1. props: 组件传入的属性

  2. context

setup中接受的props是响应式的, 当传入新的props 时,会及时被更新。由于是响应式的, 所以不可以使用ES6解构,解构会消除它的响应式。

错误代码示例, 这段代码会让props不再支持响应式:

// demo.vue
export default defineComponent ({setup(props, context) {const { name } = propsconsole.log(name)},
})

那在开发中我们想要使用解构,还能保持props的响应式,有没有办法解决呢?大家可以思考一下,在后面toRefs学习的地方为大家解答。

接下来我们来说一下setup接受的第二个参数context,我们前面说了setup中不能访问Vue2中最常用的this对象,所以context中就提供了this中最常用的三个属性:attrsslot 和emit,分别对应Vue2.x中的 $attr属性、slot插槽 和$emit发射事件。并且这几个属性都是自动同步最新的值,所以我们每次使用拿到的都是最新值。

reactive、ref与toRefs

在vue2.x中, 定义数据都是在data中, 但是Vue3.x 可以使用reactiveref来进行数据定义。

那么refreactive他们有什么区别呢?分别什么时候使用呢?说到这里,我又不得不提一下,看到很多网上文章说(reactive用于处理对象的双向绑定,ref则处理js基础类型的双向绑定)。我其实不太赞同这样的说法,这样很容易初学者认为ref就能处理js基本类型, 比如ref也是可以定义对象的双向绑定的啊, 上段代码:

 setup() {const obj = ref({count:1, name:"张三"})setTimeout(() =>{obj.value.count = obj.value.count + 1obj.value.name = "李四"}, 1000)return{obj}}

我们将obj.countobj.name绑定到页面上也是可以的;但是reactive函数确实可以代理一个对象, 但是不能代理基本类型,例如字符串、数字、boolean等。

接下来使用代码展示一下refreactive的使用:

运行效果:

上面的代码中,我们绑定到页面是通过user.name,user.age;这样写感觉很繁琐,我们能不能直接将user中的属性解构出来使用呢?答案是不能直接对user进行结构, 这样会消除它的响应式, 这里就和上面我们说props不能使用ES6直接解构就呼应上了。那我们就想使用解构后的数据怎么办,解决办法就是使用toRefs

toRefs用于将一个reactive对象转化为属性全部为ref对象的普通对象。具体使用方式如下:

<template><div class="homePage"><p>第 {{ year }} 年</p><p>姓名: {{ nickname }}</p><p>年龄: {{ age }}</p></div>
</template><script>
import { defineComponent, reactive, ref ,toRefs} from "vue";
export default defineComponent({setup() {const year = ref(0);const user = reactive({ nickname: "xiaofan", age: 26, gender: "女" });setInterval(() =>{year.value ++user.age ++}, 1000)return {year,// 使用reRefs...toRefs(user)}},
});
</script>

生命周期钩子

我们可以直接看生命周期图来认识都有哪些生命周期钩子(图片是根据官网翻译后绘制的):

从图中我们可以看到Vue3.0新增了setup,这个在前面我们也详细说了, 然后是将Vue2.x中的beforeDestroy名称变更成beforeUnmountdestroyed 表更为 unmounted,作者说这么变更纯粹是为了更加语义化,因为一个组件是一个mountunmount的过程。其他Vue2中的生命周期仍然保留。

上边生命周期图中并没包含全部的生命周期钩子, 还有其他的几个, 全部生命周期钩子如图所示:

我们可以看到beforeCreatecreatedsetup替换了(但是Vue3中你仍然可以使用, 因为Vue3是向下兼容的, 也就是你实际使用的是vue2的)。其次,钩子命名都增加了on; Vue3.x还新增用于调试的钩子函数onRenderTriggeredonRenderTricked

下面我们简单使用几个钩子, 方便大家学习如何使用,Vue3.x中的钩子是需要从vue中导入的:

import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate,onUpdated, onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked, onRenderTriggered
} from "vue";export default defineComponent({// beforeCreate和created是vue2的beforeCreate() {console.log("------beforeCreate-----");},created() {console.log("------created-----");},setup() {console.log("------setup-----");// vue3.x生命周期写在setup中onBeforeMount(() => {console.log("------onBeforeMount-----");});onMounted(() => {console.log("------onMounted-----");});// 调试哪些数据发生了变化onRenderTriggered((event) =>{console.log("------onRenderTriggered-----",event);})},
});

关于生命周期相关的内容就介绍到这里,下面我们介绍一下Vue3.x中watch有什么不同。

watch 与 watchEffect 的用法

watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是惰性的,也就是说仅在侦听的源数据变更时才执行回调。

watch(source, callback, [options])

参数说明:

  • source:可以支持string,Object,Function,Array; 用于指定要侦听的响应式变量

  • callback: 执行的回调函数

  • options:支持deep、immediate 和 flush 选项。

接下来我会分别介绍这个三个参数都是如何使用的, 如果你对watch的使用不明白的请往下看:

侦听reactive定义的数据

import { defineComponent, ref, reactive, toRefs, watch } from "vue";
export default defineComponent({setup() {const state = reactive({ nickname: "xiaofan", age: 20 });setTimeout(() =>{state.age++},1000)// 修改age值时会触发 watch的回调watch(() => state.age,(curAge, preAge) => {console.log("新值:", curAge, "老值:", preAge);});return {...toRefs(state)}},
});

侦听ref定义的数据

const year = ref(0)setTimeout(() =>{year.value ++ 
},1000)watch(year, (newVal, oldVal) =>{console.log("新值:", newVal, "老值:", oldVal);
})

侦听多个数据

上面两个例子中,我们分别使用了两个watch, 当我们需要侦听多个数据源时, 可以进行合并, 同时侦听多个数据:

watch([() => state.age, year], ([curAge, preAge], [newVal, oldVal]) => {console.log("新值:", curAge, "老值:", preAge);console.log("新值:", newVal, "老值:", oldVal);
});

侦听复杂的嵌套对象

我们实际开发中,复杂数据随处可见, 比如:

const state = reactive({room: {id: 100,attrs: {size: "140平方米",type:"三室两厅"},},
});
watch(() => state.room, (newType, oldType) => {console.log("新值:", newType, "老值:", oldType);
}, {deep:true});

如果不使用第三个参数deep:true, 是无法监听到数据变化的。

前面我们提到,默认情况下,watch是惰性的, 那什么情况下不是惰性的, 可以立即执行回调函数呢?其实使用也很简单, 给第三个参数中设置immediate: true即可。关于flush配置,还在学习,后期会补充

stop 停止监听

我们在组件中创建的watch监听,会在组件被销毁时自动停止。如果在组件销毁之前我们想要停止掉某个监听, 可以调用watch()函数的返回值,操作如下:

const stopWatchRoom = watch(() => state.room, (newType, oldType) => {console.log("新值:", newType, "老值:", oldType);
}, {deep:true});setTimeout(()=>{// 停止监听stopWatchRoom()
}, 3000)

还有一个监听函数watchEffect,在我看来watch已经能满足监听的需求,为什么还要有watchEffect呢?虽然我没有get到它的必要性,但是还是要介绍一下watchEffect,首先看看它的使用和watch究竟有何不同。

import { defineComponent, ref, reactive, toRefs, watchEffect } from "vue";
export default defineComponent({setup() {const state = reactive({ nickname: "xiaofan", age: 20 });let year = ref(0)setInterval(() =>{state.age++year.value++},1000)watchEffect(() => {console.log(state);console.log(year);});return {...toRefs(state)}},
});

执行结果首先打印一次stateyear值;然后每隔一秒,打印stateyear值。

从上面的代码可以看出, 并没有像watch一样需要先传入依赖,watchEffect会自动收集依赖, 只要指定一个回调函数。在组件初始化时, 会先执行一次来收集依赖, 然后当收集到的依赖中数据发生变化时, 就会再次执行回调函数。所以总结对比如下:

  1. watchEffect 不需要手动传入依赖

  2. watchEffect 会先执行一次用来自动收集依赖

  3. watchEffect 无法获取到变化前的值, 只能获取变化后的值

::: danger 留一个思考题:如果定义一个非响应式的值, watch和watchEffect可以监听到值的变化吗?:::

上面介绍了Vue3 Composition API的部分内容,还有很多非常好用的API, 建议直接查看官网composition-api。

其实我们也能进行自定义封装。

自定义 Hooks

开篇的时候我们使用Vue2.x写了一个实现加减的例子, 这里可以将其封装成一个hook, 我们约定这些「自定义 Hook」以 use 作为前缀,和普通的函数加以区分。

useCount.ts 实现:

import { ref, Ref, computed } from "vue";type CountResultProps = {count: Ref<number>;multiple: Ref<number>;increase: (delta?: number) => void;decrease: (delta?: number) => void;
};export default function useCount(initValue = 1): CountResultProps {const count = ref(initValue);const increase = (delta?: number): void => {if (typeof delta !== "undefined") {count.value += delta;} else {count.value += 1;}};const multiple = computed(() => count.value *2 )const decrease = (delta?: number): void => {if (typeof delta !== "undefined") {count.value -= delta;} else {count.value -= 1;}};return {count,multiple,increase,decrease,};
}

接下来看一下在组件中使用useCount这个 hook:

<template><p>count: {{ count }}</p><p>倍数: {{ multiple }}</p><div><button @click="increase()">加1</button><button @click="decrease()">减一</button></div>
</template><script lang="ts">
import useCount from "../hooks/useCount";setup() {const { count, multiple, increase, decrease } = useCount(10);return {count,multiple,increase,decrease,};},
</script>

开篇Vue2.x实现,分散在data,method,computed等, 如果刚接手项目,实在无法快速将data字段和method关联起来,而Vue3的方式可以很明确的看出,将count相关的逻辑聚合在一起, 看起来舒服多了, 而且useCount还可以扩展更多的功能。

项目开发完之后,后续还会写一篇总结项目中使用到的「自定义Hooks的文章」,帮助大家更高效的开发, 关于Composition API和自定义Hooks就介绍到这里, 接下来简单介绍一下vue2.x与vue3响应式对比。

简单对比vue2.x与vue3.x响应式

其实在Vue3.x 还没有发布beta的时候, 很火的一个话题就是Vue3.x 将使用Proxy 取代Vue2.x 版本的 Object.defineProperty

没有无缘无故的爱,也没有无缘无故的恨。为何要将Object.defineProperty换掉呢,咋们可以简单聊一下。

我刚上手Vue2.x的时候就经常遇到一个问题,数据更新了啊,为何页面不更新呢?什么时候用$set更新,什么时候用$forceUpdate强制更新,你是否也一度陷入困境。后来的学习过程中开始接触源码,才知道一切的根源都是 Object.defineProperty

对这块想要深入了解的小伙伴可以看这篇文章 为什么Vue3.0不再使用defineProperty实现数据监听?要详细解释又是一篇文章,这里就简单对比一下Object.defineProperty 与Proxy

  1. Object.defineProperty只能劫持对象的属性, 而Proxy是直接代理对象

由于Object.defineProperty只能劫持对象属性,需要遍历对象的每一个属性,如果属性值也是对象,就需要递归进行深度遍历。但是Proxy直接代理对象, 不需要遍历操作

  1. Object.defineProperty对新增属性需要手动进行Observe

因为Object.defineProperty劫持的是对象的属性,所以新增属性时,需要重新遍历对象, 对其新增属性再次使用Object.defineProperty进行劫持。也就是Vue2.x中给数组和对象新增属性时,需要使用$set才能保证新增的属性也是响应式的, $set内部也是通过调用Object.defineProperty去处理的。

Teleport

Teleport是Vue3.x新推出的功能, 没听过这个词的小伙伴可能会感到陌生;翻译过来是传送的意思,可能还是觉得不知所以,没事下边我就给大家形象的描述一下。

Teleport 是什么呢?

Teleport 就像是哆啦A梦中的「任意门」,任意门的作用就是可以将人瞬间传送到另一个地方。有了这个认识,我们再来看一下为什么需要用到Teleport的特性呢,看一个小例子:

在子组件Header中使用到Dialog组件,我们实际开发中经常会在类似的情形下使用到 Dialog ,此时Dialog就被渲染到一层层子组件内部,处理嵌套组件的定位、z-index和样式都变得困难。

Dialog从用户感知的层面,应该是一个独立的组件,从dom结构应该完全剥离Vue顶层组件挂载的DOM;同时还可以使用到Vue组件内的状态(data或者props)的值。简单来说就是,即希望继续在组件内部使用Dialog,又希望渲染的DOM结构不嵌套在组件的DOM中

此时就需要Teleport上场,我们可以用<Teleport>包裹Dialog, 此时就建立了一个传送门,可以将Dialog渲染的内容传送到任何指定的地方。

接下来就举个小例子,看看Teleport的使用方式

Teleport的使用

我们希望Dialog渲染的dom和顶层组件是兄弟节点关系, 在index.html文件中定义一个供挂载的元素:

<body>
<div id="app"></div>
+ <div id="dialog"></div>
</body>

定义一个Dialog组件Dialog.vue, 留意 to 属性, 与上面的id选择器一致:

<template><teleport to="#dialog"><div class="dialog"><div class="dialog_wrapper"><div class="dialog_header" v-if="title"><slot name="header"><span>{{title}}</span></slot></div></div><div class="dialog_content"><slot></slot></div><div class="dialog_footer"><slot name="footer"></slot></div></div></teleport>
</template>

最后在一个子组件Header.vue中使用Dialog组件,这里主要演示 Teleport的使用,不相关的代码就省略了。header组件

<div class="header">...<navbar />
+    <Dialog v-if="dialogVisible"></Dialog>
</div>
...

Dom渲染效果如下:

图片.png

可以看到,我们使用 teleport 组件,通过 to 属性,指定该组件渲染的位置与 <div id="app"></div> 同级,也就是在 body 下,但是 Dialog 的状态 dialogVisible 又是完全由内部 Vue 组件控制.

Suspense

Suspense是Vue3.x中新增的特性, 那它有什么用呢?别急,我们通过Vue2.x中的一些场景来认识它的作用。

Vue2.x中应该经常遇到这样的场景:

<template>
<div><div v-if="!loading">...</div><div v-if="loading">加载中...</div>
</div>
</template>

在前后端交互获取数据时, 是一个异步过程,一般我们都会提供一个加载中的动画,当数据返回时配合v-if来控制数据显示。

如果你使用过vue-async-manager这个插件来完成上面的需求, 你对Suspense可能不会陌生,Vue3.x感觉就是参考了vue-async-manager.

Vue3.x新出的内置组件Suspense, 它提供两个template slot, 刚开始会渲染一个fallback状态下的内容, 直到到达某个条件后才会渲染default状态的正式内容, 通过使用Suspense组件进行展示异步渲染就更加的简单。:::warning 如果使用 Suspense, 要返回一个promise :::Suspense 组件的使用:

 <Suspense><template #default><async-component></async-component></template><template #fallback><div>Loading...</div></template></Suspense>

asyncComponent.vue:

<template>
<div><h4>这个是一个异步加载数据</h4><p>用户名:{{user.nickname}}</p><p>年龄:{{user.age}}</p>
</div>
</template><script>
import { defineComponent } from "vue"
import axios from "axios"
export default defineComponent({setup(){const rawData = await axios.get("http://xxx.xinp.cn/user")return {user: rawData.data}}
})
</script>

从上面代码来看,Suspense 只是一个带插槽的组件,只是它的插槽指定了default 和 fallback 两种状态。

片段(Fragment)

在 Vue2.x 中, template中只允许有一个根节点:

<template><div><span></span><span></span></div>
</template>

但是在 Vue3.x 中,你可以直接写多个根节点, 是不是很爽:

<template><span></span><span></span>
</template>

更好的 Tree-Shaking

Vue3.x 在考虑到 tree-shaking的基础上重构了全局和内部API, 表现结果就是现在的全局API需要通过 ES Module的引用方式进行具名引用, 比如在Vue2.x中,我们要使用 nextTick:

// vue2.x
import Vue from "vue"Vue.nextTick(()=>{...
})

Vue.nextTick() 是一个从 Vue 对象直接暴露出来的全局 API,其实 $nextTick() 只是 Vue.nextTick() 的一个简易包装,只是为了方便而把后者的回调函数的 this 绑定到了当前的实例。虽然我们借助webpacktree-shaking,但是不管我们实际上是否使用Vue.nextTick(),最终都会进入我们的生产代码, 因为 Vue实例是作为单个对象导出的, 打包器无法坚持出代码总使用了对象的哪些属性。

在 Vue3.x中改写成这样:

import { nextTick } from "vue"nextTick(() =>{...
})

受影响的 API

这是一个比较大的变化, 因为以前的全局 API 现在只能通过具名导入,这一更改会对以下API有影响:

  • Vue.nextTick

  • Vue.observable(用 Vue.reactive 替换)

  • Vue.version

  • Vue.compile(仅限完整版本时可用)

  • Vue.set(仅在 2.x 兼容版本中可用)

  • Vue.delete(与上同)

内置工具

出来上面的 API外, 还有许多内置的组件

:::warning 重要

以上仅适用于 ES Modules builds,用于支持 tree-shaking 的绑定器——UMD 构建仍然包括所有特性,并暴露 Vue 全局变量上的所有内容 (编译器将生成适当的输出,以使用全局外的 api 而不是导入)。:::

前面都是Vue3.0的一些新特性,后面着重介绍一下相对于Vue2.x来说, 有什么变更呢?

变更

slot 具名插槽语法

在Vue2.x中, 具名插槽的写法:

<!--  子组件中:-->
<slot name="title"></slot>

在父组件中使用:

<template slot="title"><h1>歌曲:成都</h1>
<template>

如果我们要在slot上面绑定数据,可以使用作用域插槽,实现如下:

// 子组件 
<slot name="content" :data="data"></slot>
export default {data(){return{data:["走过来人来人往","不喜欢也得欣赏","陪伴是最长情的告白"]}}
}
<!-- 父组件中使用 -->
<template slot="content" slot-scope="scoped"><div v-for="item in scoped.data">{{item}}</div>
<template>

在Vue2.x中具名插槽和作用域插槽分别使用slotslot-scope来实现, 在Vue3.0中将slotslot-scope进行了合并同意使用。

Vue3.0中v-slot

<!-- 父组件中使用 --><template v-slot:content="scoped"><div v-for="item in scoped.data">{{item}}</div>
</template><!-- 也可以简写成: -->
<template #content="{data}"><div v-for="item in data">{{item}}</div>
</template>

自定义指令

首先回顾一下 Vue 2 中实现一个自定义指令:

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {// 当被绑定的元素插入到 DOM 中时……inserted: function (el) {// 聚焦元素el.focus()}
})

在Vue 2 中, 自定义指令通过以下几个可选钩子创建:

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。

  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

  • unbind:只调用一次,指令与元素解绑时调用。

在Vue 3 中对自定义指令的 API进行了更加语义化的修改, 就如组件生命周期变更一样, 都是为了更好的语义化, 变更如下:

所以在Vue3 中, 可以这样来自定义指令:

const { createApp } from "vue"const app = createApp({})
app.directive('focus', {mounted(el) {el.focus()}
})

然后可以在模板中任何元素上使用新的 v-focus指令, 如下:

<input v-focus />

v-model 升级

在使用Vue 3 之前就了解到 v-model 发生了很大的变化, 使用过了之后才真正的get到这些变化, 我们先纵观一下发生了哪些变化, 然后再针对的说一下如何使用:

  • 变更:在自定义组件上使用v-model时, 属性以及事件的默认名称变了

  • 变更:v-bind.sync修饰符在 Vue 3 中又被去掉了, 合并到了v-model

  • 新增:同一组件可以同时设置多个 v-model

  • 新增:开发者可以自定义 v-model修饰符

有点懵?别着急,往下看 在Vue2 中, 在组件上使用 v-model其实就相当于传递了value属性, 并触发了input事件:

<!-- Vue 2 -->
<search-input v-model="searchValue"><search-input><!-- 相当于 -->
<search-input :value="searchValue" @input="searchValue=$event"><search-input>

这时v-model只能绑定在组件的value属性上,那我们就不开心了, 我们就像给自己的组件用一个别的属性,并且我们不想通过触发input来更新值,在.async出来之前,Vue 2 中这样实现:

// 子组件:searchInput.vue
export default {model:{prop: 'search',event:'change'}
}

修改后, searchInput 组件使用v-model就相当于这样:

<search-input v-model="searchValue"><search-input>
<!-- 相当于 -->
<search-input :search="searchValue" @change="searchValue=$event"><search-input>

但是在实际开发中,有些场景我们可能需要对一个 prop 进行“双向绑定”, 这里以最常见的 modal为例子:modal挺合适属性双向绑定的,外部可以控制组件的visible显示或者隐藏,组件内部关闭可以控制 visible属性隐藏,同时visible 属性同步传输到外部。组件内部, 当我们关闭modal时, 在子组件中以update:PropName模式触发事件:

this.$emit('update:visible', false)

然后在父组件中可以监听这个事件进行数据更新:

<modal :visible="isVisible" @update:visible="isVisible = $event"></modal>

此时我们也可以使用v-bind.async来简化实现:

<modal :visible.async="isVisible"></modal>

上面回顾了 Vue2 中v-model实现以及组件属性的双向绑定,那么在Vue 3 中应该怎样实现的呢?

在Vue3 中,在自定义组件上使用v-model,相当于传递一个modelValue 属性, 同时触发一个update:modelValue事件:

<modal v-model="isVisible"></modal><!-- 相当于 -->
<modal :modelValue="isVisible" @update:modelValue="isVisible = $event"></modal>

如果要绑定属性名, 只需要给v-model传递一个参数就行, 同时可以绑定多个v-model

<modal v-model:visible="isVisible" v-model:content="content"></modal><!-- 相当于 -->
<modal :visible="isVisible":content="content"@update:visible="isVisible"@update:content="content"
/>

不知道你有没有发现,这个写法完全没有.async什么事儿了, 所以啊,Vue 3 中又抛弃了.async写法, 统一使用v-model

异步组件

Vue3 中 使用 defineAsyncComponent 定义异步组件,配置选项 component 替换为 loader ,Loader 函数本身不再接收 resolve 和 reject 参数,且必须返回一个 Promise,用法如下:

<template><!-- 异步组件的使用 --><AsyncPage />
</tempate><script>
import { defineAsyncComponent } from "vue";export default {components: {// 无配置项异步组件AsyncPage: defineAsyncComponent(() => import("./NextPage.vue")),// 有配置项异步组件AsyncPageWithOptions: defineAsyncComponent({loader: () => import(".NextPage.vue"),delay: 200, timeout: 3000,errorComponent: () => import("./ErrorComponent.vue"),loadingComponent: () => import("./LoadingComponent.vue"),})},
}
</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/54329.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用Linux部署Kafka教程

目录 一、部署Zookeeper 1 拉取Zookeeper镜像 2 运行Zookeeper 二、部署Kafka 1 拉取Kafka镜像 2 运行Kafka 三、验证是否部署成功 1 进入到kafka容器中 2 创建topic 生产者 3 生产者发送消息 4 消费者消费消息 四、搭建kafka管理平台 五、SpringBoot整合Kafka 1…

大彩串口屏使用记录

写在最前面 屏幕型号 DC10600M070 IDE VisualTFT&#xff08;官方&#xff09; VSCode&#xff08;lua编程&#xff09; 用之前看一下官方那个1小时的视频教程就大概懂控件怎么用了&#xff0c;用官方的软件VisualTFT很简单 本文只是简单记录遇到的一些坑 lua编辑器 VisualTF…

内嵌功能强大、低功耗STM32WB55CEU7、STM32WB55CGU7 射频微控制器 - MCU, 48-UFQFN

一、概述&#xff1a; STM32WB55xx多协议无线和超低功耗器件内嵌功能强大的超低功耗无线电模块&#xff08;符合蓝牙 低功耗SIG规范5.0和IEEE 802.15.4-2011标准&#xff09;。该器件内含专用的Arm Cortex -M0&#xff0c;用于执行所有的底层实时操作。这些器件基于高性能Arm …

TensorFlow中slim包的具体用法

TensorFlow中slim包的具体用法 1、训练脚本文件&#xff08;该文件包含数据下载打包、模型训练&#xff0c;模型评估流程&#xff09;3、模型训练1、数据集相关模块&#xff1a;2、设置网络模型模块3、数据预处理模块4、定义损失loss5、定义优化器模块 本次使用的TensorFlow版本…

Redis五大数据类型

Redis五大数据类型 Redis-Key 官网&#xff1a;https://www.redis.net.cn/order/ 序号命令语法描述1DEL key该命令用于在 key 存在时删除 key2DUMP key序列化给定 key &#xff0c;并返回被序列化的值3EXISTS key检查给定 key 是否存在&#xff0c;存在返回1&#xff0c;否则返…

yolov8热力图可视化

安装pytorch_grad_cam pip install grad-cam自动化生成不同层的bash脚本 # 循环10次&#xff0c;将i的值从0到9 for i in $(seq 0 13) doecho "Running iteration $i";python yolov8_heatmap.py $i; done热力图生成python代码 import warnings warnings.filterwarn…

vscode流程图插件使用

vscode流程图插件使用 1.在vscode中点击左下角设置然后选择扩展。 2.在扩展中搜索Draw.io Integration&#xff0c;安装上面第一个插件。 3.安装插件后在工程中创建一个后缀为drawio的文件并且双击打开即可绘制流程图

2023-08-26 LeetCode每日一题(汇总区间)

2023-08-26每日一题 一、题目编号 228. 汇总区间二、题目链接 点击跳转到题目位置 三、题目描述 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖…

如何在地图上寻找最密集点的位置?

最近我在工作中遇到了一个小的需求点&#xff0c;大概是需要在地图上展示出一堆点中的点密度最密集的位置。最开始没想到好的方法&#xff0c;就使用了一个非常简单的策略——所有点的坐标求平均值&#xff0c;这个方法大部分的时候好用&#xff0c;因为大部分城市所有点位基本…

深度学习4. 循环神经网络 – Recurrent Neural Network | RNN

目录 循环神经网络 – Recurrent Neural Network | RNN 为什么需要 RNN &#xff1f;独特价值是什么&#xff1f; RNN 的基本原理 RNN 的优化算法 RNN 到 LSTM – 长短期记忆网络 从 LSTM 到 GRU RNN 的应用和使用场景 总结 百度百科维基百科 循环神经网络 – Recurre…

【手写promise——基本功能、链式调用、promise.all、promise.race】

文章目录 前言一、前置知识二、实现基本功能二、实现链式调用三、实现Promise.all四、实现Promise.race总结 前言 关于动机&#xff0c;无论是在工作还是面试中&#xff0c;都会遇到Promise的相关使用和原理&#xff0c;手写Promise也有助于学习设计模式以及代码设计。 本文主…

WPF基础入门-Class5-WPF命令

WPF基础入门 Class5-WPF命令 1、xaml编写一个button&#xff0c;Command绑定一个命令 <Grid><ButtonWidth"100"Height"40" Command"{Binding ShowCommand}"></Button> </Grid>2、编写一个model.cs namespace WPF_Le…

【LeetCode-面试经典150题-day15】

目录 104.二叉树的最大深度 100.相同的树 226.翻转二叉树 101.对称二叉树 105.从前序与中序遍历序列构造二叉树 106.从中序与后序遍历序列构造二叉树 117.填充每个节点的下一个右侧节点指针Ⅱ 104.二叉树的最大深度 题意&#xff1a; 给定一个二叉树 root &#xff0c;返回其…

STM32F103 4G Cat.1模块EC200S使用

一、简介 EC200S-CN 是移远通信最近推出的 LTE Cat 1 无线通信模块&#xff0c;支持最大下行速率 10Mbps 和最大上行速率 5Mbps&#xff0c;具有超高的性价比&#xff1b;同时在封装上兼容移远通信多网络制式 LTE Standard EC2x&#xff08;EC25、EC21、EC20 R2.0、EC20 R2.1&a…

用大白话来讲讲多线程的知识架构

感觉多线程的知识又多又杂&#xff0c;自从接触java&#xff0c;就在一遍一遍捋脉络和深入学习。现在将这次的学习成果展示如下。 什么是多线程&#xff1f; 操作系统运行一个程序&#xff0c;就是一个线程。同时运行多个程序&#xff0c;就是多线程。即在同一时间&#xff0…

基于FPGA的Lorenz混沌系统verilog开发,含testbench和matlab辅助测试程序

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 将vivado的仿真结果导入到matlab显示三维混沌效果&#xff1a; 2.算法运行软件版本 vivado2019.2 matlab2022a 3.部分核心程序 testbench如下所…

npm常用命令 + 前端常用的包管理工具 以及 npm淘宝镜像配置等

npm常用命令 前端常用的包管理工具 以及 npm淘宝镜像配置等 1. 前言1.1 NodeJs的下载安装1.2 windows上1.3 常用包管理工具 2. npm2.1 npm 的安装2.2 npm初始化包2.3 npm 安装、卸载包2.3.1 非全局安装2.3.1.1 单个包的安装2.3.1.1.1 默认版本安装2.3.1.1.2 指定版本安装 2.3.…

解除用户账户控制提醒

解决用户账户控制提醒 1. 前言2. 解决用户账户控制提醒2.1 控制面板2.2 注册表2.3 UAC服务 结束语 1. 前言 当我们使用电脑时&#xff0c;有时进行安装应用或者打开应用时&#xff0c;总会弹出一个提示框&#xff0c;要选择点击是否允许程序运行&#xff1b; 系统经常弹出用户…

【Git】测试持续集成——Git+Gitee+PyCharm

文章目录 概述一、使用Gitee1. 注册账号2. 绑定邮箱3. 新建仓库4. 查看项目地址 二、安装配置Git1. 下载安装包2. 校验是否安装成功。3. 配置Git4. Git命令5. Git实操 三、PyCharmGit1. 配置Git2. Clone项目3. 提交文件到服务器4. 从服务器拉取文件 概述 持续集成&#xff08;…

【javaweb】学习日记Day4 - Maven 依赖管理 Web入门

目录 一、Maven入门 - 管理和构建java项目的工具 1、IDEA如何构建Maven项目 2、Maven 坐标 &#xff08;1&#xff09;定义 &#xff08;2&#xff09;主要组成 3、IDEA如何导入和删除项目 二、Maven - 依赖管理 1、依赖配置 2、依赖传递 &#xff08;1&#xff09;查…