乐趣区

关于javascript:一文看完vue3的变化之处

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

1. 创立利用实例的变动

之前个别是这样:

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

而当初变成这样:

import {createApp} from 'vue'
import App from './App.vue'
let app = createApp({// ... 组件选项})
let app = createApp(App)// 单文件状况下
let vm = app.mount('#app')
app === vm // false

改成这样的最次要起因是为了防止对 Vue 的全局配置会影响每个创立的实例。

2.data选项变动

之前在非组件的状况下创立实例能够应用对象,然而当初所有状况下都只能应用一个返回对象的函数。

3. 生命周期变动

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

4. 事件监听反对多个处理函数

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

<button @click="one(),two(),three($event)"></button>
export default {
    methods: {one(){},
        two(){},
        three(){}
    }
}

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

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

显式申明该组件能触发的自定义事件,就像 props 属性一样,能够是简略的字符串数组,也能够是对象,同样的,对象类型的话能够用来定义校验,应用办法如下:

export default {emits: ['change', 'select'],// 数组类型
    emits: {// 对象类型
        change: null,// 没有验证函数
        select: (arg) => {// 接管 this.$emit('select', ..args)的 args 参数
            return true// 返回 true 或 false 代表事件参数是否无效,校验失败事件还是能失常触发,然而控制台会弹出一行正告信息
        }
    },
    methods: {emit() {this.$emit('change')
            this.$emit('select', 1, 2, 3)
        }
    }
}

该申明是可选的。

6. 新增了 v-is 指令

这个指令用来承当 2.x 版本里的非凡 attributeis的局部性能。

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

<ul>
    <!--<my-component></my-component> x 这样不行 -->
    <li is="my-component"></li>
</ul>

而在 3.0 版本 is 只能用在 component 上,上述性能须要应用 v-is 来代替:

<ul>
    <li v-is="'my-component'"></li>
</ul>

留神上述的单引号是必须的。

7. 未声明的emits

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

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

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

<!-- 父组件 -->
<sub-component class="warn"></sub-component>
<!-- 子组件 -->
<div class="info"></div>
<!-- 理论渲染后果 -->
<div class="info warn"></div>

但在 3.x 中组件反对多个根节点,当呈现多个根节点时,属性将不会被动继承,须要手动给须要继承属性的组件进行绑定,如果一个都没绑定的话 vue 会给出正告:

<template>
    <my-momponent class="bar" @change="change"></my-component>
</template>
<template>
    <div v-bind="$attrs"></div>
    <div></div>
</template>

8.v-model的变动

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

export default {
    model: {// v-model 默认是利用名为 value 的 prop 及 input 事件,可应用 model 选项来批改
        prop: 'checked',
        event: 'change'
    },
    props: {checked: Boolean},
    methods: {emit() {this.$emit('change', true)
        }
    }
}
/*
<my-component v-model="checked"></my-component>
*/

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

export default {
    props: {checked: Boolean},
    methods: {emit() {this.$emit('update:checked', true)
        }
    }
}
/*
<my-component v-model:checked="checked"></my-component>
*/

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

export default {
    props: {modelValue: Boolean},
    methods: {emit() {this.$emit('update:modelValue', true)
        }
    }
}
/*
<my-component v-model="checked"></my-component>
*/

这样的一个益处是能够绑定多个v-model

export default {
    props: {
        modelValue: Number,
        checked: Boolean,
        value: String
    },
    methods: {emit() {this.$emit('update:modelValue', 1)
            this.$emit('update:checked', true)
            this.$emit('update:value', 'abc')
        }
    }
}
/*
<my-component v-model="count" v-model:checked="checked" v-model:value="value"></my-component>
*/

最初一点是 3.x 反对自定义 v-model 的修饰符,大抵就是修饰符也能通过 props 获取到,而后能够依据修饰符存在与否进行一些对应的数据格式化操作:

/*
<my-component v-model.double="count" v-model:count2.double="count2"></my-component>
*/

export default {
    props: {
        modelValue: Number,
        count2: Number,
        modelModifiers: Object,// 没有参数的 v -model 的修饰符数据,名称为 modelModifiers,对象格局:{double: true},如果修饰符不存在为 undefined
        count2Modifiers: Object// 带参数的 v -model 的修饰符数据名称为:参数 +"Modifiers",对象格局:{double: true},如果修饰符不存在为 undefined
    },
    methods: {emit() {
            // 在这里能够依据 modelModifiers 和 count2Modifiers 的值来判断是否要进行一些数据操作
            this.$emit('update:modelValue', xxx)
            this.$emit('update:value', xxx)
        }
    }
}

9. 响应式provide/reject

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

<template>
    <div>{{count}}</div>
</template>
<script>
export default {inject: ['count']
}
</script>
export default {provide() {
        return {count: this.count}
    },
    data: {count: 0}
}

vue2.x 文档里有个提醒:

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

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

export default {provide() {
        return {count: this.countObj}
    },
    data: {
        countObj: {value: 0}
    }
}

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

export default {provide() {
        return {count: this.countObj}
    },
    data: {count: 0},
    computed: {countObj() {
            return {value: this.count};
        }
    }
}

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

let countState = Vue.observable({value: 0});

export default {provide() {
        return {count: countState};
    },
    data: {count: 0},
    watch: {count(newVal) {countState.value = newVal}
    }
}

然而在 3.x 中就比较简单了,能够间接应用组合式 api 里的 computed 办法:

import {computed} from 'vue'

export default {provide() {
        return {count: computed(() => {return this.count})
        };
    },
    data: {count: 0}
}

前面这些在子组件里应用的时候都须要拜访 count.value 属性。

10. 异步组件

在 2.x 中,异步组件个别应用如下办法定义:

// 全局
Vue.component('async-component', () => import('./my-async-component'))
// 部分
{
    components: {'async-component': () => import('./my-async-component')
    }
}

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

import {defineAsyncComponent} from 'vue'
const AsyncComp = defineAsyncComponent(() =>
  import('./components/AsyncComponent.vue')
)

// 全局
app.component('async-component', AsyncComp)
// 组件内
{
    components: {'AsyncComponent': AsyncComp}
}

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 中有一个常见的痛点:

<div>
    <Dialog></Dialog>
    <Loading></Loading>
</div>

在上述组件里蕴含了两个子组件,像这种弹窗或 loading 组件个别都是心愿它们的 DOM 节点间接挂在 body 元素下,这样在款式尤其是层级上比拟好管制,然而理论渲染进去是在这个 div 节点下的,那么就只能把这两个组件移到 body 下,然而逻辑上这两个组件又是属于该组件,所以就比拟不爽。

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

<div>
    <teleport to="body">
        <Dialog></Dialog>
    </teleport>
    <teleport to="#xxx">
        <Loading></Loading>
    </teleport>
</div>

间接将须要提到外层的组件放到 teleport 标签里,通过 to 属性来指定要挂载到的元素,to能够是无效的元素查问选择器,比方 id 选择器,类选择器等。

14. 渲染函数的变动

在 2.x 中应用 render 函数须要应用注入的办法来创立虚构节点,示例如下:

Vue.component('my-component', {render(createElement) {return createElement('div', '我是文本')
    }
})

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

Vue.component('my-component', {render() {return Vue.h('div', '我是文本')
    }
})

h函数接管的参数和 createElement 根本都是 tagpropschildren,然而props 构造产生了很大变动,比方事件绑定:

Vue.component('my-component', {render(createElement) {
        return createElement('div', {
            on: {'click': this.clickCallback}
        })
    }
})
Vue.component('my-component', {render() {
        return Vue.h('div', {onClick: this.clickCallback})
    }
})

在 2.x 中不反对 v-model3.x 中曾经反对了,其余变动之处也很大,须要读者本人去具体理解,这一节的官网文档应该还须要欠缺,props的具体形容并未看到,然而大抵的扭转就是更加扁平化,比方 2.x 的构造:

{class: ['xxx', 'xxx'],
  style: {color: '#fff'},
  attrs: {id: 'xxx'},
  domProps: {innerHTML: 'xxx'},
  on: {click: onClick},
  key: 'xxx'
}

在 3.x 中变成这样:

{class: ['xxx', 'xxx'],
      style: {color: '#fff'},
    id: 'xxx',
    innerHTML: 'xxx',
    onClick: onClick,
    key: 'xxx'
}

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 中减少了很多能够用来给数据减少响应行性能的办法,比方:

// 非原始值
import {reactive} from 'vue'
// 响应式状态
const state = reactive({count: 1})

// 原始值
import {ref} from 'vue'

// 响应式状态
const count = ref(0)
console.log(count.value)

此外还新增了 computedwatch 等等能够间接应用的办法,这些办法个别在应用组合式 api 的状况下应用。

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

这个曾经有十分多的文章具体的介绍它了,能够在掘金上搜寻或间接去官网上看,此处不赘述。

19.ref 的变动

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

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

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

<div ref="div">
    <ul>
        <li v-for="item in list" :ref="setLiList"></li>
    </ul>
    <MyComponent ref="component"></MyComponent>
</div>
export default {data() {
        return {liList: []
        }
    }
    mounted() {console.log(this.$refs.div, this.$refs.component)
        console.log(this.liList)
    },
    methods: {setLiList(el) {this.liList.push(el)
        }
    }
}

20.Vue-Router 变动

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

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

// 2.x
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [// ...]
const router = new VueRouter({
    // ... 一些选项配置
    routes
})
const app = new Vue({router}).$mount('#app')
// 3.x
import Vue from 'vue'
import VueRouter from 'vue-router@4'
const routes = [// ...]
const router = VueRouter.createRouter({
    // ... 一些选项配置
    routes
})
const app = Vue.createApp({})
app.use(router)
app.mount('#app')

除了创立路由的形式有变动外,其余也有很多细节变动,以及如何在组合式 api 中应用,笔者没看完,请自行浏览 vue-router 文档。

21.Vuex 变动

除路由外,官网的状态治理库 vuex 也配套降级了新版本,装置:npm install vuex@next --save

同样以一个非常简略的例子看一下初始化的变动:

// 2.x
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
    state: {count: 0},
    mutations: {},
    actions: {},
    // ...
})
new Vue({store})
// 3.x
import {createApp} from 'vue'
import {createStore} from 'vuex'
const store = createStore({state() {
        return {count:0}
    },
    mutations: {},
    actions: {},
    // ...
})
const app = createApp({})
app.use(store)

vuex的 api 根本没有大的变动,更多的能够去理解一下如何在组合式 api 中应用。

22. 其余变动一览

  • $attrs里也蕴含 classstyle
  • 移除了$children,如需拜访子组件请应用ref
  • 移除了 Vue 实例的 $on$emit$once 办法,之前常见的应用形式当初须要本人实现或者应用其余事件库:

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

    这一常见操作齐全被干掉了,因为当初要减少全局性能的话须要通过利用实例的 globalProperties 属性:

    app.config.globalProperties.$bus = new OtherEvent()
  • 反对多个根节点:

    <template>
        <div></div>
        <Header></Header>
    </template>
  • 一些 2.x 的全局 api 都改成应用导出的形式进行应用,比方:import {nextTick} from 'vue',这样能够利于构建工具去掉无用代码
  • 应用 template 组件进行循环操作时,key属性能够须要间接设置在 template 标签上:

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

以上大部分内容在 vue 的官网降级指南中也提到了,有趣味的也能够间接去看官网文档:https://v3.vuejs.org/guide/migration/introduction.html,以及中文版:https://v3.cn.vuejs.org/guide/migration/introduction.html,如果有任何谬误的话欢送指出。

退出移动版