在通读了vue的官网文档后,我记录下了如下这些相对于2.x的变化之处。

1.创建应用实例的变化

之前一般是这样:

  1. let app = new Vue({
  2. // ...一些选项
  3. template: '',// 字符串模板
  4. render: h => h(App)// 单文件情况下
  5. })
  6. let vm = app.$mount('#app')
  7. app === vm// true

而现在变成这样:

  1. import { createApp } from 'vue'
  2. import App from './App.vue'
  3. let app = createApp({
  4. // ...组件选项
  5. })
  6. let app = createApp(App)// 单文件情况下
  7. let vm = app.mount('#app')
  8. app === vm // false

改成这样的最主要原因是为了避免对Vue的全局配置会影响每个创建的实例。

2.data选项变化

之前在非组件的情况下创建实例可以使用对象,但是现在所有情况下都只能使用一个返回对象的函数。

3.生命周期变化

beforeDestroy=>beforeUnmountdestroyed=>unmounted,另外新增了两个生命周期renderTrackedrenderTriggered,用来跟踪虚拟DOM重新渲染。

4.事件监听支持多个处理函数

在3.0中v-on指令可以绑定多个处理函数:

  1. <button @click="one(),two(),three($event)"></button>
  1. export default {
  2. methods: {
  3. one(){},
  4. two(){},
  5. three(){}
  6. }
  7. }

绑定多个函数时必须使用内联函数调用方式,即不能只写一个函数名。

5.实例多了一个数据选项:emits

显式声明该组件能触发的自定义事件,就像props属性一样,可以是简单的字符串数组,也可以是对象,同样的,对象类型的话可以用来定义校验,使用方法如下:

  1. export default {
  2. emits: ['change', 'select'],// 数组类型
  3. emits: {// 对象类型
  4. change: null,// 没有验证函数
  5. select: (arg) => {// 接收this.$emit('select', ..args)的args参数
  6. return true// 返回true或false代表事件参数是否有效,校验失败事件还是能正常触发,但是控制台会弹出一行警告信息
  7. }
  8. },
  9. methods: {
  10. emit() {
  11. this.$emit('change')
  12. this.$emit('select', 1, 2, 3)
  13. }
  14. }
  15. }

该声明是可选的。

6.新增了v-is指令

这个指令用来承担2.x版本里的特殊attributeis的部分功能。

在2.x里is可用在两个场景下,一是用于动态组件component来切换要渲染的组件,二是用于在使用DOM模板时的一些HTML元素的限制,比如ul元素里只能出现li元素,这样当ul里使用自定义组件时浏览器会认为是无效内容,此时可以使用is属性:

  1. <ul>
  2. <!--<my-component></my-component> x这样不行-->
  3. <li is="my-component"></li>
  4. </ul>

而在3.0版本is只能用在component上,上述功能需要使用v-is来代替:

  1. <ul>
  2. <li v-is="'my-component'"></li>
  3. </ul>

注意上述的单引号是必须的。

7.未声明的emits

因为新增了类似props的选项emits,如果某些传递给组件的属性并没有在props声明,那么可以通过$attrs属性来访问,事件监听器也一样:

  1. <!--父组件-->
  2. <sub-component @change="change" @select="select"></sub-component>
  1. // 子组件
  2. export default {
  3. emits: ['change'],
  4. created(){
  5. console.log(this.$attrs)// { onSelect: () => {} }
  6. },
  7. }

另外,在2.x中这些未声明的propsemits会直接继承到该组件的根节点上,比如:

  1. <!--父组件-->
  2. <sub-component class="warn"></sub-component>
  1. <!--子组件-->
  2. <div class="info"></div>
  1. <!--实际渲染结果-->
  2. <div class="info warn"></div>

但在3.x中组件支持多个根节点,当出现多个根节点时,属性将不会主动继承,需要手动给需要继承属性的组件进行绑定,如果一个都没绑定的话vue会给出警告:

  1. <template>
  2. <my-momponent class="bar" @change="change"></my-component>
  3. </template>
  1. <template>
  2. <div v-bind="$attrs"></div>
  3. <div></div>
  4. </template>

8.v-model的变化

在2.x中给一个组件自定义v-model一般是这样的:

  1. export default {
  2. model: {// v-model默认是利用名为value的prop及input事件,可使用model选项来修改
  3. prop: 'checked',
  4. event: 'change'
  5. },
  6. props: {
  7. checked: Boolean
  8. },
  9. methods: {
  10. emit() {
  11. this.$emit('change', true)
  12. }
  13. }
  14. }
  15. /*
  16. <my-component v-model="checked"></my-component>
  17. */

在3.x中v-model指令多了一个参数,比如:v-model:value="value",所以就不需要使用model选项了,vue会直接利用value属性及事件名update:value

  1. export default {
  2. props: {
  3. checked: Boolean
  4. },
  5. methods: {
  6. emit() {
  7. this.$emit('update:checked', true)
  8. }
  9. }
  10. }
  11. /*
  12. <my-component v-model:checked="checked"></my-component>
  13. */

当然你也可以省略value,这样会默认绑定到名为modelValueprop上:

  1. export default {
  2. props: {
  3. modelValue: Boolean
  4. },
  5. methods: {
  6. emit() {
  7. this.$emit('update:modelValue', true)
  8. }
  9. }
  10. }
  11. /*
  12. <my-component v-model="checked"></my-component>
  13. */

这样的一个好处是可以绑定多个v-model

  1. export default {
  2. props: {
  3. modelValue: Number,
  4. checked: Boolean,
  5. value: String
  6. },
  7. methods: {
  8. emit() {
  9. this.$emit('update:modelValue', 1)
  10. this.$emit('update:checked', true)
  11. this.$emit('update:value', 'abc')
  12. }
  13. }
  14. }
  15. /*
  16. <my-component v-model="count" v-model:checked="checked" v-model:value="value"></my-component>
  17. */

最后一点是3.x支持自定义v-model的修饰符,大致就是修饰符也能通过props获取到,然后可以根据修饰符存在与否进行一些对应的数据格式化操作:

  1. /*
  2. <my-component v-model.double="count" v-model:count2.double="count2"></my-component>
  3. */
  4. export default {
  5. props: {
  6. modelValue: Number,
  7. count2: Number,
  8. modelModifiers: Object,// 没有参数的v-model的修饰符数据,名称为modelModifiers,对象格式:{double: true},如果修饰符不存在为undefined
  9. count2Modifiers: Object// 带参数的v-model的修饰符数据名称为:参数+"Modifiers",对象格式:{double: true},如果修饰符不存在为undefined
  10. },
  11. methods: {
  12. emit() {
  13. // 在这里可以根据modelModifiers和count2Modifiers的值来判断是否要进行一些数据操作
  14. this.$emit('update:modelValue', xxx)
  15. this.$emit('update:value', xxx)
  16. }
  17. }
  18. }

9.响应式provide/reject

provide/reject默认是没有响应性的,父组件的provide值变化了,子组件使用reject接收的值不会相应更新,在2.0中,想要使它变成可响应比较麻烦,下面这种方式是不行的,父组件的count变化了子组件的count并不会变化:

  1. <template>
  2. <div>{{count}}</div>
  3. </template>
  4. <script>
  5. export default {
  6. inject: ['count']
  7. }
  8. </script>
  1. export default {
  2. provide() {
  3. return {
  4. count: this.count
  5. }
  6. },
  7. data: {
  8. count: 0
  9. }
  10. }

vue2.x文档里有个提示:

提示:provideinject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。

后半句我的理解是如果provide返回的对象的属性值是一个可响应对象的话,那么是可以的,比如:

  1. export default {
  2. provide() {
  3. return {
  4. count: this.countObj
  5. }
  6. },
  7. data: {
  8. countObj: {
  9. value: 0
  10. }
  11. }
  12. }

这样的话修改countObj.value的值,子组件会相应的更新,但是如果想像上面那样依赖count的值,即使你使用computed也是不行的:

  1. export default {
  2. provide() {
  3. return {
  4. count: this.countObj
  5. }
  6. },
  7. data: {
  8. count: 0
  9. },
  10. computed: {
  11. countObj() {
  12. return {
  13. value: this.count
  14. };
  15. }
  16. }
  17. }

那么就只能使用watchVue.observable方法来配合实现:

  1. let countState = Vue.observable({ value: 0 });
  2. export default {
  3. provide() {
  4. return {
  5. count: countState
  6. };
  7. },
  8. data: {
  9. count: 0
  10. },
  11. watch: {
  12. count(newVal) {
  13. countState.value = newVal
  14. }
  15. }
  16. }

但是在3.x中就比较简单了,可以直接使用组合式api里的computed方法:

  1. import {computed} from 'vue'
  2. export default {
  3. provide() {
  4. return {
  5. count: computed(() => {
  6. return this.count
  7. })
  8. };
  9. },
  10. data: {
  11. count: 0
  12. }
  13. }

后面这些在子组件里使用的时候都需要访问count.value属性。

10.异步组件

在2.x中,异步组件一般使用如下方法定义:

  1. // 全局
  2. Vue.component('async-component', () => import('./my-async-component'))
  3. // 局部
  4. {
  5. components: {
  6. 'async-component': () => import('./my-async-component')
  7. }
  8. }

在3.x中新增了一个函数defineAsyncComponent来做这件事情:

  1. import { defineAsyncComponent } from 'vue'
  2. const AsyncComp = defineAsyncComponent(() =>
  3. import('./components/AsyncComponent.vue')
  4. )
  5. // 全局
  6. app.component('async-component', AsyncComp)
  7. // 组件内
  8. {
  9. components: {
  10. 'AsyncComponent': AsyncComp
  11. }
  12. }

11.过渡class的变化

3.x和2.x一样,仍然有6个class,意义完全一样,唯一的变化只有v-enter->v-enter-fromv-leave->v-leave-from两个名字以及enter-class->enter-from-classleave-class->leave-from-class两个自定义类名的变化。

12.自定义指令变化

在2.x中提供了bindinsertedupdatecomponentUpdatedunbind五个指令,在3.x中新增了一个,一共有六个:

beforeMount(指令第一次绑定到元素并且还未挂载到父组件上调用,对应于bind,用来进行一些初始化操作)

mounted(绑定元素的父组件被挂载时调用,对应inserted,但是inserted的描述里说仅保证父组件存在但不一定被插入到文档中,mounted的描述里没有这句话)

beforeUpdate(在包含该组件的虚拟节点被更新前调用,对应update

updated(在包含该组件的虚拟节点及其所有子组件的虚拟节点都更新后调用,对应componentUpdated

beforeUnmount(在卸载绑定元素的父组件前调用,为新增钩子)

unmounted(指令与元素解除绑定且父组件已经卸载时调用,对应unbind

总的来说改名后的自定义钩子和vue本身的生命周期钩子趋于一致。

13.新增Teleport

在2.x中有一个常见的痛点:

  1. <div>
  2. <Dialog></Dialog>
  3. <Loading></Loading>
  4. </div>

在上述组件里包含了两个子组件,像这种弹窗或loading组件一般都是希望它们的DOM节点直接挂在body元素下,这样在样式尤其是层级上比较好控制,但是实际渲染出来是在这个div节点下的,那么就只能把这两个组件移到body下,但是逻辑上这两个组件又是属于该组件,所以就比较不爽。

在3.x中新增了teleport组件可以用来解决这个问题:

  1. <div>
  2. <teleport to="body">
  3. <Dialog></Dialog>
  4. </teleport>
  5. <teleport to="#xxx">
  6. <Loading></Loading>
  7. </teleport>
  8. </div>

直接将需要提到外层的组件放到teleport标签里,通过to属性来指定要挂载到的元素,to可以是有效的元素查询选择器,比如id选择器,类选择器等。

14.渲染函数的变化

在2.x中使用render函数需要使用注入的方法来创建虚拟节点,示例如下:

  1. Vue.component('my-component', {
  2. render(createElement) {
  3. return createElement('div', '我是文本')
  4. }
  5. })

在3.x中使用vue对象的静态方法来实现:

  1. Vue.component('my-component', {
  2. render() {
  3. return Vue.h('div', '我是文本')
  4. }
  5. })

h函数接收的参数和createElement基本都是tagpropschildren,但是props结构发生了很大变化,比如事件绑定:

  1. Vue.component('my-component', {
  2. render(createElement) {
  3. return createElement('div', {
  4. on: {
  5. 'click': this.clickCallback
  6. }
  7. })
  8. }
  9. })
  10. Vue.component('my-component', {
  11. render() {
  12. return Vue.h('div', {
  13. onClick: this.clickCallback
  14. })
  15. }
  16. })

在2.x中不支持v-model3.x中已经支持了,其他变化之处也很大,需要读者自己去详细了解,这一节的官方文档应该还需要完善,props的具体描述并未看到,但是大致的改变就是更加扁平化,比如2.x的结构:

  1. {
  2. class: ['xxx', 'xxx'],
  3. style: { color: '#fff' },
  4. attrs: { id: 'xxx' },
  5. domProps: { innerHTML: 'xxx' },
  6. on: { click: onClick },
  7. key: 'xxx'
  8. }

在3.x中变成这样:

  1. {
  2. class: ['xxx', 'xxx'],
  3. style: { color: '#fff' },
  4. id: 'xxx',
  5. innerHTML: 'xxx',
  6. onClick: onClick,
  7. key: 'xxx'
  8. }

15.插件开发的变化

在2.x中注册插件时调用插件的install方法时会注入Vue对象和参数对象,在3.x中因为将Vue上的全局属性和方法都移到了由createApp方法创建的实例app上,所以注册插件需要在createApp方法执行之后,另外注入功能时也会有一些细微的变化。

16.去掉了过滤器选项

在3.x中可以使用方法来实现该功能。

17.响应性原理变化

众所周知,在2.x中是使用Object.defineProperty来实现数据响应的,在3.x默认使用ES6Proxy来实现,并且在IE浏览器上使用Object.defineProperty进行降级。

另外在3.x中增加了很多可以用来给数据增加响应行功能的方法,比如:

  1. // 非原始值
  2. import {reactive} from 'vue'
  3. // 响应式状态
  4. const state = reactive({
  5. count: 1
  6. })
  7. // 原始值
  8. import {ref} from 'vue'
  9. // 响应式状态
  10. const count = ref(0)
  11. console.log(count.value)

此外还新增了computedwatch等等可以直接使用的方法,这些方法一般在使用组合式api的情况下使用。

18.新增响应式和组合式api

这个已经有非常多的文章详细的介绍它了,可以在掘金上搜索或直接去官网上看,此处不赘述。

19.ref的变化

在2.x中ref是用来访问组件实例或者是DOM元素的属性:

  1. <div ref="div">
  2. <ul>
  3. <li v-for="item in list" ref="liList"></li>
  4. </ul>
  5. <MyComponent ref="component"></MyComponent>
  6. </div>
  1. export default {
  2. mounted() {
  3. console.log(this.$refs.div, this.$refs.component)
  4. console.log(this.$refs.liList)// liList会自动是一个数组
  5. }
  6. }

其中当在循环里使用ref是不明确的,尤其是存在嵌套循环,所以在3.x中ref支持绑定到一个函数:

  1. <div ref="div">
  2. <ul>
  3. <li v-for="item in list" :ref="setLiList"></li>
  4. </ul>
  5. <MyComponent ref="component"></MyComponent>
  6. </div>
  1. export default {
  2. data() {
  3. return {
  4. liList: []
  5. }
  6. }
  7. mounted() {
  8. console.log(this.$refs.div, this.$refs.component)
  9. console.log(this.liList)
  10. },
  11. methods: {
  12. setLiList(el) {
  13. this.liList.push(el)
  14. }
  15. }
  16. }

20.Vue-Router变化

vue-router升级到了新版本,安装命令为:npm install vue-router@4

接下来使用一个简单的例子看一下2.x和3.x的区别:

  1. // 2.x
  2. import Vue from 'vue'
  3. import VueRouter from 'vue-router'
  4. Vue.use(VueRouter)
  5. const routes = [
  6. // ...
  7. ]
  8. const router = new VueRouter({
  9. // ...一些选项配置
  10. routes
  11. })
  12. const app = new Vue({
  13. router
  14. }).$mount('#app')
  1. // 3.x
  2. import Vue from 'vue'
  3. import VueRouter from 'vue-router@4'
  4. const routes = [
  5. // ...
  6. ]
  7. const router = VueRouter.createRouter({
  8. // ...一些选项配置
  9. routes
  10. })
  11. const app = Vue.createApp({})
  12. app.use(router)
  13. app.mount('#app')

除了创建路由的方式有变化外,其他也有很多细节变化,以及如何在组合式api中使用,笔者没看完,请自行阅读vue-router文档。

21.Vuex变化

除路由外,官方的状态管理库vuex也配套升级了新版本,安装:npm install vuex@next --save

同样以一个十分简单的例子看一下初始化的变化:

  1. // 2.x
  2. import Vue from 'vue'
  3. import Vuex from 'vuex'
  4. Vue.use(Vuex)
  5. const store = new Vuex.Store({
  6. state: {
  7. count: 0
  8. },
  9. mutations: {},
  10. actions: {},
  11. // ...
  12. })
  13. new Vue({
  14. store
  15. })
  1. // 3.x
  2. import {createApp} from 'vue'
  3. import {createStore} from 'vuex'
  4. const store = createStore({
  5. state() {
  6. return {
  7. count:0
  8. }
  9. },
  10. mutations: {},
  11. actions: {},
  12. // ...
  13. })
  14. const app = createApp({})
  15. app.use(store)

vuex的api基本没有大的变化,更多的可以去了解一下如何在组合式api中使用。

22.其他变化一览

  • $attrs里也包含classstyle

  • 移除了$children,如需访问子组件请使用ref

  • 移除了Vue实例的$on$emit$once方法,之前常见的使用方式现在需要自己实现或者使用其他事件库:

    1. import Vue from 'vue'
    2. Vue.prototype.$bus = new Vue()

    这一常见操作完全被干掉了,因为现在要增加全局功能的话需要通过应用实例的globalProperties属性:

    1. app.config.globalProperties.$bus = new OtherEvent()
  • 支持多个根节点:

    1. <template>
    2. <div></div>
    3. <Header></Header>
    4. </template>
  • 一些2.x的全局api都改成使用导出的方式进行使用,比如:import {nextTick} from 'vue',这样可以利于构建工具去掉无用代码

  • 使用template组件进行循环操作时,key属性可以需要直接设置在template标签上:

    1. <template>
    2. <template v-for="item in list" :key="item.id"></template>
    3. </template>

以上大部分内容在vue的官方升级指南中也提到了,有兴趣的也可以直接去看官方文档:https://v3.vuejs.org/guide/migration/introduction.html,以及中文版:https://v3.cn.vuejs.org/guide/migration/introduction.html,如果有任何错误的话欢迎指出。

一文看完vue3的变化之处的更多相关文章

  1. 一文看懂Vue3.0的优化

    1.源码优化: a.使用monorepo来管理源码 Vue.js 2.x 的源码托管在 src 目录,然后依据功能拆分出了 compiler(模板编译的相关代码).core(与平台无关的通用运行时代码 ...

  2. 发现在看完objc基本语法之后,还是看Apple文档比较有用。

    现在已经停止找中文资料了,因为很多例子已经过时,运行不出来. 看完objc基本语法以后,Apple的资料也看得懂了. 还是应该跟着Apple的入门指南开始学,今后也应该以Apple的文档为主.

  3. 看完SQL Server 2014 Q/A答疑集锦:想不升级都难!

    看完SQL Server 2014 Q/A答疑集锦:想不升级都难! 转载自:http://mp.weixin.qq.com/s/5rZCgnMKmJqeC7hbe4CZ_g 本期嘉宾为微软技术中心技术 ...

  4. c#代码 天气接口 一分钟搞懂你的博客为什么没人看 看完python这段爬虫代码,java流泪了c#沉默了 图片二进制转换与存入数据库相关 C#7.0--引用返回值和引用局部变量 JS直接调用C#后台方法(ajax调用) Linq To Json SqlServer 递归查询

    天气预报的程序.程序并不难. 看到这个需求第一个想法就是只要找到合适天气预报接口一切都是小意思,说干就干,立马跟学生沟通价格. ​ ​不过谈报价的过程中,差点没让我一口老血喷键盘上,话说我们程序猿的人 ...

  5. 一文看懂大数据的技术生态圈,Hadoop,hive,spark都有了

    一文看懂大数据的技术生态圈,Hadoop,hive,spark都有了 转载: 大数据本身是个很宽泛的概念,Hadoop生态圈(或者泛生态圈)基本上都是为了处理超过单机尺度的数据处理而诞生的.你可以把它 ...

  6. 【图解】你还在为 TCP 重传、滑动窗口、流量控制、拥塞控制发愁吗?看完图解就不愁了

    每日一句英语学习,每天进步一点点: 前言 前一篇「硬不硬你说了算!近 40 张图解被问千百遍的 TCP 三次握手和四次挥手面试题」得到了很多读者的认可,在此特别感谢你们的认可,大家都暖暖的. 来了,今 ...

  7. fish_redux使用详解---看完就会用!

    说句心里话,这篇文章,来来回回修改了很多次,如果认真看完这篇文章,还不会写fish_redux,请在评论里喷我. 前言 来学学难搞的fish_redux框架吧,这个框架,官方的文档真是一言难尽,比fl ...

  8. 花了30天才肝出来,史上最全面Java设计模式总结,看完再也不会忘

    本文所有内容均节选自<设计模式就该这样学> 序言 Design Patterns: Elements of Reusable Object-Oriented Software(以下简称&l ...

  9. 在知乎上看到 Web Socket这篇文章讲得确实挺好,从头看到尾都非常形象生动,一口气看完,没有半点模糊,非常不错

    在知乎上看到这篇文章讲得确实挺好,从头看到尾都非常形象生动,一口气看完,没有半点模糊,非常不错,所以推荐给大家,非常值得一读. 作者:Ovear链接:https://www.zhihu.com/que ...

随机推荐

  1. 学习打卡day14&&构建之法阅读笔记第二篇

    对于书中所提到的结对编程我还是有些许感受的,在大二上学期我就有和同学合作,共同完成编码.有时候可能是我来做非常非常简易的前端页面部分,然后给同学一个基础框架,让同学往框架里面填充,时而遇到问题我再来沟 ...

  2. node.js - mysql

    今天结束的挺早,因为今天的内容还可以不是很难,今天全程是学了一些关于mysql数据库和sql查询语句的内容包括在node终端里面怎么来连接数据库.经过今天的一个学习,我感觉离那个地步越来越近了,就是那 ...

  3. LCA的离线快速求法

    最常见的LCA(树上公共祖先)都是在线算法,往往带了一个log.有一种办法是转化为"+-1最值问题"得到O(n)+O(1)的复杂度,但是原理复杂,常数大.今天介绍一种允许离线时接近 ...

  4. 有意思的CVE-2022-0337复现

    前言 前两天在刷tw,看到了个比较有意思的一个CVE漏洞,价值奖励是10000美刀,比较好奇的是价值10000美刀的漏洞是什么样子的,漏洞利用就是需要在浏览器中进行用户交互才能触发该漏洞,但由于 Wi ...

  5. input 相关

    1.label 标签 for 属性同 input 标签 id 属性联系之一

  6. java、selenium、图片滑块验证,底部附本地可测试代码

    准备 本地Chrome版本对应WebDriver驱动:http://chromedriver.storage.googleapis.com/index.html maven包 <!-- sele ...

  7. .NET 中 GC 的模式与风格

    垃圾回收(GC)是托管语言必备的技术之一.GC 的性能是影响托管语言性能的关键.我们的 .NET 既能写桌面程序 (WINFROM , WPF) 又能写 web 程序 (ASP.NET CORE),甚 ...

  8. drools的简单入门案例

    一.背景 最近在学习规则引擎drools,此处简单记录一下drools的入门案例. 二.为什么要学习drools 假设我们存在如下场景: 在我们到商店购买衣服的时候,经常会发生这样的事情,购买1件不打 ...

  9. 好客租房45-react组件基础综合案例-6边界问题

    边界问题 //导入react import React from 'react' import ReactDOM from 'react-dom' //导入组件 // 约定1:类组件必须以大写字母开头 ...

  10. form表单与css选择器

    目录 form表单 action属性 input标签 lable标签 select标签 textarea标签 补充 网络请求方式 CSS简介 CSS基本选择器 组合选择器 属性选择器 分组与嵌套 伪类 ...