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也会遇到让人苦恼的问题:
-
命名冲突问题
-
不清楚暴露出来的变量的作用
-
逻辑重用到其他 component 经常遇到问题
关于上面经常出现的问题我就不一一举例了,使用过的小伙伴多多少少都会遇到。文章的重点不是Mixin,如果确实想知道的就留言啦~
所以,我们Vue3.x就推出了Composition API主要就是为了解决上面的问题,将零散分布的逻辑组合在一起来维护,并且还可以将单独的功能逻辑拆分成单独的文件。接下来我们就重点认识Composition API。
Composition API

setup
setup 是Vue3.x新增的一个选项, 他是组件内使用 Composition API的入口。
setup执行时机
我在学习过程中看到很多文章都说setup 是在 beforeCreate和created之间, 这个结论是错误的。实践是检验真理的唯一标准, 于是自己去检验了一下:
export default defineComponent ({beforeCreate() {console.log("----beforeCreate----");},created() {console.log("----created----");},setup() {console.log("----setup----");},
})

setup 执行时机是在beforeCreate之前执行,详细的可以看后面生命周期讲解。
::: warning 由于在执行setup 时尚未创建组件实例,因此在 setup 选项中没有 this。:::
setup 参数
使用setup时,它接受两个参数:
-
props: 组件传入的属性
-
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中最常用的三个属性:attrs、slot 和emit,分别对应Vue2.x中的 $attr属性、slot插槽 和$emit发射事件。并且这几个属性都是自动同步最新的值,所以我们每次使用拿到的都是最新值。
reactive、ref与toRefs
在vue2.x中, 定义数据都是在data中, 但是Vue3.x 可以使用reactive和ref来进行数据定义。
那么ref和reactive他们有什么区别呢?分别什么时候使用呢?说到这里,我又不得不提一下,看到很多网上文章说(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.count和obj.name绑定到页面上也是可以的;但是reactive函数确实可以代理一个对象, 但是不能代理基本类型,例如字符串、数字、boolean等。
接下来使用代码展示一下ref、reactive的使用:

运行效果:

上面的代码中,我们绑定到页面是通过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名称变更成beforeUnmount; destroyed 表更为 unmounted,作者说这么变更纯粹是为了更加语义化,因为一个组件是一个mount和unmount的过程。其他Vue2中的生命周期仍然保留。
上边生命周期图中并没包含全部的生命周期钩子, 还有其他的几个, 全部生命周期钩子如图所示:

我们可以看到beforeCreate和created被setup替换了(但是Vue3中你仍然可以使用, 因为Vue3是向下兼容的, 也就是你实际使用的是vue2的)。其次,钩子命名都增加了on; Vue3.x还新增用于调试的钩子函数onRenderTriggered和onRenderTricked
下面我们简单使用几个钩子, 方便大家学习如何使用,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)}},
});
执行结果首先打印一次state和year值;然后每隔一秒,打印state和year值。
从上面的代码可以看出, 并没有像watch一样需要先传入依赖,watchEffect会自动收集依赖, 只要指定一个回调函数。在组件初始化时, 会先执行一次来收集依赖, 然后当收集到的依赖中数据发生变化时, 就会再次执行回调函数。所以总结对比如下:
-
watchEffect 不需要手动传入依赖
-
watchEffect 会先执行一次用来自动收集依赖
-
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
-
Object.defineProperty只能劫持对象的属性, 而Proxy是直接代理对象
由于Object.defineProperty只能劫持对象属性,需要遍历对象的每一个属性,如果属性值也是对象,就需要递归进行深度遍历。但是Proxy直接代理对象, 不需要遍历操作
-
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 绑定到了当前的实例。虽然我们借助webpack的tree-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中具名插槽和作用域插槽分别使用slot和slot-scope来实现, 在Vue3.0中将slot和slot-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>相关文章:
Vue3.0 新特性以及使用变更总结
Vue3.0 在2020年9月正式发布了,也有许多小伙伴都热情的拥抱Vue3.0。去年年底我们新项目使用Vue3.0来开发,这篇文章就是在使用后的一个总结, 包含Vue3新特性的使用以及一些用法上的变更。 图片.png 为什么要升级Vue3 使用Vue2.x的小伙伴都熟悉…...
ToBeWritten之VSOC安全运营
也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 转移发布平台通知:将不再在CSDN博客发布新文章,敬…...
2023爱分析·一站式通信解决方案市场厂商评估报告:牛信云
[图片] 01 中国企业出海发展背景及阶段 出海背景:出海,对中国企业而言,并不陌生。从最初的贸易型出海,到制造业崛起,再到互联网、移动互联网产业腾飞,中国企业在出海道路上走的越发稳健。行业也从最初的家电…...
微信小程序消防知识每天学平台设计与实现
摘 要 消防是当下一个人都需要在日常生活中所高度重视的事项。消防安全关系到居民的日常生活的安全,通过学习消防知识能够提升人们在日常生活中对于灾难的防范。通过对当下的大学生进行调查研究后发现,现在的年轻人在消防意识上比较的单薄,对…...
Oracle跨库访问DBLINK
1. DBLINK的介绍 Oracle在进行跨库访问时,可以创建DBLINK实现,比如要将UAT的表数据灌入开发环境,则可以使用UAT库为数据源,通过DBLINK实现将查出的数据灌入开发库。 简而言之就是在当前数据库中访问另一个数据库中的表中的数据 2…...
【vue3.0 组合式API与选项式API是什么,有什么区别】
vue3.0 组合式API与选项式API是什么 Vue3.0中引入了组合式API(Composition API),同时保留了选项式API(Options API)。两种 API 风格都能够覆盖大部分的应用场景。它们只是同一个底层系统所提供的两套不同的接口。实际…...
React配置代理的5种方法
React配置代理的五种方法的介绍 使用create-react-app的代理配置: 使用场景:适用于使用create-react-app创建的React项目,特别是小型项目或快速原型开发。优点:配置简单,无需额外安装依赖,适合快速开发和简…...
皮卡丘靶场搭建遇到的问题大全
该博客记录我在安装皮卡丘靶场中遇到的一些问题。 1、 phpstudy_pro启动Mysql失败 自己电脑开启了mysql服务,使用winr,services.msc,找到自己的mysql服务,关闭。再次尝试使用phpstudy_pro启动mysql,成功解决。 2、皮…...
【C++】C++11的新特性(上)
引入 C11作为C标准的一个重要版本,引入了许多令人振奋的新特性,极大地丰富了这门编程语言的功能和表达能力。本章将为您介绍C11的一些主要变化和改进,为接下来的章节铺垫。 文章目录 引入 一、列表初始化 1、1 {} 初始化 1、2 std::initiali…...
ubuntu学习(四)----文件写入操作编程
1、write函数的详解 ssize_t write(int fd,const void*buf,size_t count); 参数说明: fd:是文件描述符(write所对应的是写,即就是1) buf:通常是一个字符串,需要写入的字符串 count:是每次写入的字节数…...
如何解决MySQL中的套接字错误
MySQL通过使用** socket文件**来管理到数据库服务器的连接,socket文件是一种特殊的文件,可以促进不同进程之间的通信。MySQL服务器的套接字文件名为mysqld.sock,在Ubuntu系统中,它通常存储在/var/run/mysqld/目录中。该文件由MySQ…...
socket
Socket是一种用于网络通信的编程接口,它提供了在计算机网络中进行数据传输的方法。通过Socket,可以在不同主机之间建立网络连接,并通过发送和接收数据来进行通信。在C语言中,可以使用Socket函数库(如BSD Socket或Winso…...
Python数据分析实战-判断一组序列(列表)的变化趋势(附源码和实现效果)
实现功能 判断一组序列(列表)的变化趋势 实现代码 from sklearn.linear_model import LinearRegression import numpy as np # 计算相邻两个数之间的差值的均值,并判断变化趋势。 def trend(lst):diff [lst[i1] - lst[i] for i in range(…...
Spring与MyBatis集成 AOP整合PageHelper插件
目录 1.什么是集成? 2.Spring与MyBatis集成 3.Spring与MyBatis集成的基本配置 4.AOP整合PageHelper插件 1.什么是集成? 集成是指将不同的组件、框架或系统整合到一起,使它们可以协同工作、相互调用、共享资源等。通过集成,可以…...
[Android 四大组件] --- BroadcastReceiver
1 BroadcastReceiver是什么 BroadcastReceiver(广播接收器)即广播,是一个全局的监听器。 Android 广播分为两个角色:广播发送者、广播接受者。 2 广播类型 广播按照类型分为两种,一种是全局广播,另一种…...
<C++> STL_容器适配器
1.容器适配器 适配器是一种设计模式,该种模式是将一个类的接口转换成客户希望的另外一个接口。 容器适配器是STL中的一种重要组件,用于提供不同的数据结构接口,以满足特定的需求和限制。容器适配器是基于其他STL容器构建的,通过…...
【25考研】- 整体规划及高数一起步
【25考研】- 整体规划及高数一起步 一、整体规划二、专业课870计算机应用基础参考网上考研学长学姐: 三、高数一典型题目、易错点及常用结论(一)典型题目(二)易错点(三)常用结论1.令tarctanx, 则…...
【Unity】常见的角色移动旋转
在Unity 3D游戏引擎中,可以使用不同的方式对物体进行旋转。以下是几种常见的旋转方式: 欧拉角(Euler Angles):欧拉角是一种常用的旋转表示方法,通过绕物体的 X、Y 和 Z 轴的旋转角度来描述物体的旋转。在Un…...
今天的小结
1、冒泡排序 冒泡排序(Bubble Sort)是一种简单的排序算法,它重复地遍历待排序的元素列表,比较相邻的元素并交换它们的位置,直到整个列表排序完成。冒泡排序的基本思想是通过不断交换相邻元素,将最大&#…...
了解 Socks 协议:它的过去、现在与未来
在网络世界的江湖中,有一名叫做 Socks 协议的高手,它凭借着一招“代理”绝技,在网络安全领域独步天下。今天,就让我们来了解一下这位神秘高手的过去、现在和未来。 在过去,互联网世界的江湖可谓是风起云涌,…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
elementUI点击浏览table所选行数据查看文档
项目场景: table按照要求特定的数据变成按钮可以点击 解决方案: <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...
JS红宝书笔记 - 3.3 变量
要定义变量,可以使用var操作符,后跟变量名 ES实现变量初始化,因此可以同时定义变量并设置它的值 使用var操作符定义的变量会成为包含它的函数的局部变量。 在函数内定义变量时省略var操作符,可以创建一个全局变量 如果需要定义…...
