前言
Vue3.0的步伐越來(lái)越近了,是時(shí)候了解起來(lái)了,雖然嘴上還喊學(xué)不動(dòng)了,但是,身體還得誠(chéng)實(shí)起來(lái),接著學(xué)。。。
通過(guò)各種博客資料,還有前段時(shí)間尤雨溪大佬的直播Vue3相對(duì)Vue2的比較大的變化有以下幾種:
- 使用 TypeScript
- 放棄 class 采用 function-based API
- option API => Composition API
- 重構(gòu) complier
- 重構(gòu) virtual DOM
- 新的響應(yīng)式機(jī)制
使用ts的話就是拋棄了谷歌的flow選擇擁抱微軟的ts。從這個(gè)情況也看得出來(lái),不會(huì)ts的該學(xué)起來(lái)了,比如我。。。
放棄class采用function-based API據(jù)說(shuō)也是為了更好支持ts,為了更靈活的邏輯復(fù)用能力,代碼更容易壓縮等...
option Api到Composition Api應(yīng)該是對(duì)我們?nèi)?xiě)代碼影響最大的一部分,稍后可以看代碼體驗(yàn)一下。
重構(gòu)compiler與virtual DOM使Vue變得更快,也是Vue越來(lái)越優(yōu)秀的原因。
新的響應(yīng)式機(jī)制采用了ES6的ProxyApi,拋棄了之前的Object.defineProperty()比較直觀的解決的是Vue2中這兩點(diǎn)問(wèn)題:
關(guān)于對(duì)象:Vue 無(wú)法檢測(cè) property 的添加或移除。由于 Vue 會(huì)在初始化實(shí)例時(shí)對(duì) property 執(zhí)行 getter/setter 轉(zhuǎn)化,所以 property 必須在
data對(duì)象上存在才能讓 Vue 將它轉(zhuǎn)換為響應(yīng)式的。-
關(guān)于數(shù)組:Vue 不能檢測(cè)以下數(shù)組的變動(dòng):
- 當(dāng)你利用索引直接設(shè)置一個(gè)數(shù)組項(xiàng)時(shí),例如:
vm.items[indexOfItem] = newValue - 當(dāng)你修改數(shù)組的長(zhǎng)度時(shí),例如:
vm.items.length = newLength
- 當(dāng)你利用索引直接設(shè)置一個(gè)數(shù)組項(xiàng)時(shí),例如:
在官網(wǎng)深入響應(yīng)式原理一章有較詳細(xì)闡述,針對(duì)以上兩種情況解決方法,官網(wǎng)也有給出答案,那就是使用set方法。
而Proxy可以完美的解決該問(wèn)題,當(dāng)然好處應(yīng)該不止這些,剩下的慢慢探究吧,Proxy也有缺點(diǎn),那就是兼容性問(wèn)題,有一些瀏覽器不支持,而且無(wú)完全polyfill,瀏覽器支持程度可以查看https://caniuse.com/#search=Proxy
簡(jiǎn)單了解Proxy
Vue核心就是響應(yīng)式數(shù)據(jù),Vue3.0中的響應(yīng)式采用了Proxy那就簡(jiǎn)單看看Proxy是怎么個(gè)樣子的呢。
Proxy 對(duì)象用于定義基本操作的自定義行為(如屬性查找、賦值、枚舉、函數(shù)調(diào)用等)。。。
from MDN,學(xué)習(xí)一個(gè)新的Api官方文檔還是要讀一下哈,雖然讀不懂這么深?yuàn)W的描述。。。
語(yǔ)法:
const p = new Proxy(target, handler)
參數(shù)target表示要使用Proxy包裝的對(duì)象(可以是任何類型的對(duì)象,包括原生數(shù)組,函數(shù),甚至另一個(gè)代理)
參數(shù)handler是一個(gè)通常以函數(shù)作為屬性的對(duì)象,各屬性中的函數(shù)分別定義了在執(zhí)行各種操作時(shí)代理 p 的行為
看看代碼吧:
let obj = {
a: 1,
b: 2
}
const proxy = new Proxy(obj, {
get: function(target, prop, receiver) {
return prop in target ? target[prop] : 0
},
set: function(target, prop, value, receiver) {
target[prop] = 666
}
})
console.log(proxy.a) // 1
console.log(proxy.c) // 0
proxy.a = 10
console.log(proxy.a) // 666
obj.b = 10
console.log(proxy.b) // 不是666 而是10
以上代碼中obj是我們要代理的目標(biāo)對(duì)象,get,set方法是參數(shù)handler的兩個(gè)屬性,具體如下:
handler.get()接收三個(gè)參數(shù),第一個(gè)參數(shù)target為代理的目標(biāo)對(duì)象,第二個(gè)參數(shù)prop是代理的目標(biāo)對(duì)象的屬性,第三個(gè)參數(shù)是Proxy或者繼承Proxy的對(duì)象,通常是proxy本身。
handler.set()接收四個(gè)參數(shù),其中三個(gè)參數(shù)都與get方法相同,唯獨(dú)多出來(lái)一個(gè)value表示新的屬性值。
上述代碼表示當(dāng)訪問(wèn)proxy的屬性時(shí),進(jìn)行攔截判斷,該屬性是否是目標(biāo)對(duì)象的屬性,如果是那么就將其值返回出來(lái),否則就返回0。
當(dāng)對(duì)proxy上的屬性進(jìn)行重寫(xiě)時(shí),將重寫(xiě)的該屬性賦值為666。
注意:此時(shí)對(duì)數(shù)據(jù)的劫持,只是劫持了代理對(duì)象proxy,而跟原對(duì)象obj沒(méi)有任何關(guān)系,對(duì)obj進(jìn)行操作,也不會(huì)監(jiān)聽(tīng)到。
用proxy實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的數(shù)據(jù)響應(yīng):
<body>
<h2 id="app"></h2>
<input id="input" type="text" />
</body>
let app = document.getElementById('app')
let input = document.getElementById('input')
let obj = { // 源數(shù)據(jù)
text:'hello world'
}
let proxy = new Proxy(obj, {
set: function(target, prop, value){ // input事件觸發(fā)進(jìn)行劫持,觸發(fā)update方法
target[prop] = value
update(value)
}
})
function update(value){ // update方法用于同步dom更新
app.innerHTML = value
input.value = value
}
input.addEventListener('input',function(e){ // 監(jiān)聽(tīng)input數(shù)據(jù)變化,并修改proxy的值
proxy.text = e.target.value
})
proxy.text = obj.text // 初始化源數(shù)據(jù)
使用Vue CLI體驗(yàn)Vue3.0
第一步,安裝vue-cli
npm install -g @vue/cli
安裝完成后查看是否已安裝成功
vue -V
@vue/cli 4.4.4
如果cli已安裝需要注意其版本應(yīng)該高于cli4.x。
第二步,初始化vue項(xiàng)目
vue create vue-next-test
輸入命令后,出現(xiàn)命令行交互,跟之前一樣,主要是在初始時(shí)勾選上vue-router,vuex,避免在升級(jí)vue3的過(guò)程中手寫(xiě)初始化代碼,會(huì)自動(dòng)生成初始化代碼。
注意:vue3.0項(xiàng)目目前不能直接創(chuàng)建,需從vue2.x升級(jí)。

第三步,升級(jí)成Vue3.0項(xiàng)目
以上只是創(chuàng)建了Vue2.x的項(xiàng)目,需要手動(dòng)升級(jí)成Vue3.0的項(xiàng)目
進(jìn)入vue-next-test文件夾
cd vue-next-test輸以下指令:
vue add vue-next
執(zhí)行上述指令會(huì)自動(dòng)安裝vue-cli-plugin-vue-next插件,該插件會(huì)自動(dòng)完成以下操作
- 安裝vue3.0beta版依賴
- 配置webpack去在vue3中編譯.vue文件
- 自動(dòng)遷移全局api(創(chuàng)建新模板)
- 升級(jí)安裝Vue Router4.0和Vuex 4.0,如果默認(rèn)為未安裝,則不升級(jí)。
- 自動(dòng)修改Vue Router 和Vuex模板代碼
升級(jí)完成之后就可以看代碼啦!
第四步,查看Vue3.0的部分新的東西
- Vuex對(duì)比
3.0版本Vuex
import Vuex from 'vuex'
export default Vuex.createStore({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
2.x版Vuex
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
Vue2.x版本采用構(gòu)造函數(shù)構(gòu)建Vue Router實(shí)例,而Vue3.0使用createStore方法來(lái)構(gòu)建Vue實(shí)例,Vuex語(yǔ)法和Api基本沒(méi)有發(fā)生變化。和之前一樣,該怎么樣寫(xiě)state,mutations等還是怎么寫(xiě),該怎么調(diào)還怎么調(diào)。
- Vue Router對(duì)比
3.0版本
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
},
{
path: '/test',
name: 'test',
component: () => import('../views/Test.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
2.x版本
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
},
{
path: '/test',
name: 'test',
component: () => import('../views/Test.vue')
}
]
const router = new VueRouter({
mode:"history",
routes
})
export default router
同樣的Vue Router也是之前采用構(gòu)造函數(shù)形式,3.0采用createRouter方法去創(chuàng)建Vue Router實(shí)例,配置方法都一樣。在路由模式配置上,之前是配置mode option,3.0則是采用vue-router中的createWebHistory方法去創(chuàng)建history屬性,我默認(rèn)選擇的是history模式用的是createWebHistory方法創(chuàng)建history屬性,如果要修改為hash模式則需要使用createWebHashHistory方法來(lái)創(chuàng)建。
總結(jié):總的來(lái)說(shuō),構(gòu)建Vue Router和Vuex的方式變了,但是它們的配置方式都和之前保持一致,可以無(wú)縫銜接使用。
-
Composition API
在3.0代碼基礎(chǔ)上繼續(xù)往下看,創(chuàng)建一個(gè)新的組件<Test/>,在<Test/>組件中認(rèn)識(shí)一下Composition API
之前2.x版本是采用了Options API的模式,可以理解為選項(xiàng)式的組件代碼編寫(xiě),Vue官方規(guī)定好的寫(xiě)法,響應(yīng)式數(shù)據(jù),methods,computed,components以及生命周期這些都是規(guī)定好的,需要在哪里寫(xiě),你就得在哪里寫(xiě)。
<script>
export default {
data:() => {
return {}
},
methods:{
},
computed:{
},
component:{
},
mounted(){
}
}
</script>
Vue3.0采用Composition API的模式,可以理解成組合API,怎么個(gè)組合法呢?就類似于,在組件中實(shí)現(xiàn)的這些東西,響應(yīng)式數(shù)據(jù),生命周期,計(jì)算屬性等,都可以在Vue中獲取對(duì)應(yīng)方法,然后在一個(gè)方法中組合起來(lái)統(tǒng)一對(duì)外輸出。
Composition API提供了以下一些函數(shù),
- ref
- reactive
- toRefs
- computed
- watch
- getCurrentInstance
- 生命周期hooks
- ...
在體驗(yàn)Composition API之前需要認(rèn)識(shí)一個(gè)函數(shù)叫做setup(),這個(gè)函數(shù)的主要功能是Composition API的入口,它在生命周期beforeCreate生命周期執(zhí)行之前被調(diào)用,接收props對(duì)象作為第一個(gè)參數(shù),接收來(lái)的props對(duì)象,可以通過(guò)watch監(jiān)視其變化。接受context對(duì)象作為第二個(gè)參數(shù),這個(gè)對(duì)象包含attrs,slots,emit三個(gè)屬性。多說(shuō)無(wú)益,直接看代碼吧。
import { ref, reactive } from 'vue'
export default {
setup(props, context){
const count = ref(0) // 定義響應(yīng)式數(shù)據(jù)count
const num = ref(1) // 定義響應(yīng)式數(shù)據(jù)num
const objData = {
name: 'erha',
age: '1',
skill: '拆家'
}
const obj = reactive(objData) // 定義響應(yīng)式數(shù)據(jù)obj
return {
count,
num,
obj
}
},
name:'test'
}
在Vue3.0中創(chuàng)建響應(yīng)式數(shù)據(jù)需要引用ref,reactive這兩個(gè)方法,ref一般用來(lái)創(chuàng)建基本數(shù)據(jù)類型的響應(yīng)式數(shù)據(jù),reactive一般用來(lái)創(chuàng)建引用數(shù)據(jù)類型的響應(yīng)式數(shù)據(jù)。
在模板中使用,跟之前沒(méi)有區(qū)別,需要注意的是,ref屬于將基本類型數(shù)據(jù)包裝成應(yīng)用類型,在模板中正常使用。在方法中訪問(wèn)的時(shí)候需要帶上.value才能訪問(wèn)到。
以下代碼我簡(jiǎn)寫(xiě)了,比如有一個(gè)按鈕點(diǎn)擊會(huì)觸發(fā)一個(gè)方法,該方法是讓count自增,那么應(yīng)該這樣寫(xiě):
setup(props, context){
const count = ref(0)
const addCount = () => {
count.value ++
}
return {
count,
addCount
}
}
為什么要這么寫(xiě)呢?是因?yàn)镻roxy的原因,Proxy要進(jìn)行數(shù)據(jù)劫持的時(shí)候需要接收一個(gè)對(duì)象,所以ref就對(duì)基本數(shù)據(jù)類型的數(shù)據(jù)進(jìn)行了包裝,使其可以進(jìn)行響應(yīng)式。在方法中需要使用count.value去操作,而在模板中進(jìn)行了處理,所以可以直接使用count進(jìn)行渲染。
由于Proxy的機(jī)制原因,如果將reactive中的響應(yīng)式數(shù)據(jù)進(jìn)行解構(gòu),那么原先的響應(yīng)式數(shù)據(jù)就變成不可響應(yīng)的了。
import { reactive } from 'vue'
const data =reactive({
name:'lisa',
age:18
})
let { name , age} = data
data.age = 20 // 響應(yīng)式
age = 30 // 非響應(yīng)式
為什么將可觀察對(duì)象中的屬性解構(gòu)出來(lái)后,變成不再可觀察了呢?因?yàn)橥ㄟ^(guò)reactive方法創(chuàng)建的可觀察對(duì)象,內(nèi)部的屬性本身并不是可觀察的,而是通過(guò)Proxy代理實(shí)現(xiàn)的讀寫(xiě)觀察,如果將這些屬性解構(gòu),這些屬性就不再通過(guò)原對(duì)象的代理來(lái)訪問(wèn)了,就無(wú)法再進(jìn)行觀察。
Composition API提供了一種方法來(lái)解決此機(jī)制帶來(lái)的問(wèn)題,那就是toRefs,它可以將reactive創(chuàng)建的可觀察對(duì)象,轉(zhuǎn)換成可觀察的ref對(duì)象
import {reactive, toRefs} from "vue"
const data =reactive({
name:'lisa',
age:18
})
let { name , age} = toRefs(data)
data.age = 20 // 響應(yīng)式
age = 30 // 響應(yīng)式
在模板中使用reactive生成的可觀察對(duì)象的時(shí)候是這樣的:
<template>
<div>{{obj.name}}</div>
</template>
<script>
import { reactive } from "vue"
export default{
setup(){
const data = {
name :"lisa"
}
const obj = reactive(data)
return {
obj
}
}
}
</script>
當(dāng)使用了toRefs的時(shí)候在模板中只需要使用name即可
<template>
<div>{{name}}</div>
</template>
<script>
import { reactive, toRefs } from "vue"
export default{
setup(){
const data = {
name :"lisa"
}
const obj = reactive(data)
return {
...toRefs(obj)
}
}
}
</script>
Composition API提供的computed方法就相當(dāng)于2.x版本中的計(jì)算屬性。使用如下:
import { ref, computed } from "vue"
const count = ref(0)
const doubleCount = computed(()=>{
return count.value*2
})
Composition API提供的watch方法相當(dāng)于就是2.x的觀察屬性。使用如下:
import { ref, watch } from "vue"
const count = ref(0)
const num = ref(1)
watch(() => { return count.value }, (newcount) => {
console.log('count變啦', newcount)
})
watch方法接收兩個(gè)參數(shù),第一個(gè)參數(shù)是一個(gè)函數(shù),第二個(gè)參數(shù)也是個(gè)函數(shù),第一個(gè)參數(shù)函數(shù)返回值表示要監(jiān)聽(tīng)哪個(gè)數(shù)據(jù),第二個(gè)參數(shù)函數(shù),表示監(jiān)聽(tīng)成功后的邏輯,該函數(shù)的第一個(gè)參數(shù)就是監(jiān)聽(tīng)到目標(biāo)數(shù)據(jù)變化后的值。
同時(shí)watch可以監(jiān)聽(tīng)多個(gè)數(shù)據(jù)。
watch(
[() => count.value, () => num.value],
([count, num], [oldCount, oldNum]) => { // watch 同時(shí)觀察count和num兩個(gè)值
console.log(`count:${count},num:${num} oldCount:${oldCount},oldNum:${oldNum}`)
})
在Vue2.x版本中頻繁出現(xiàn)的this,在Vue3.0中也消失了,取而代之的是Composition API提供的getCurrentInstance方法,用來(lái)獲取當(dāng)前組件實(shí)例,然后通過(guò)ctx獲取當(dāng)前上下文。
import {getCurrentInstance} from "vue"
export default{
setup(){
const {ctx} = getCurrentInstance()
console.log(ctx)
}
}
大概是這么個(gè)東西

可以和Vue2.x中this輸出對(duì)比一下。還是有不小的變動(dòng),但常用api都沒(méi)有發(fā)生變化。比如切換路由
const pushRoute = () => { // 編程導(dǎo)航
ctx.$router.push({
path: '/about'
})
}
整體的Options API,到Composition API,大致就是之前很多掛載在Vue原型上的東西,現(xiàn)在都獨(dú)立成一個(gè)方法然后去引用使用。之前組件中的this容易把人繞迷糊,如果采用Composition API就會(huì)好很多。之前Vue組件中強(qiáng)制data寫(xiě)在哪里,methods寫(xiě)在哪里,computed寫(xiě)在哪里,而在Vue3.0中這種規(guī)定被打破,開(kāi)發(fā)者可以比較自由的組織自己的代碼,兩者都有自己的好處與弊端。詳見(jiàn)可以參考文章https://juejin.im/post/5eb17a0fe51d454dd60cfe0f
最后看一下Vue3.0中的生命周期,生命周期也是有所改動(dòng),鉤子函數(shù)名稱均發(fā)生變化。beforeCreate,created生命周期在setup方法中自動(dòng)執(zhí)行,其余生命周期鉤子函數(shù)都是從Vue中引入使用(注意在setup方法中使用)
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onRenderTracked,
onRenderTriggered
} from 'vue'
export default {
setup (props, context) {
// console.log(props.msg, context)
const a = ref(0)
const setA = () => {
return a.value++
}
// 相當(dāng)于 beforeMount
onBeforeMount(() => {
console.log('onBeforeMount')
})
// 相當(dāng)于 mounted
onMounted(() => {
console.log('onMounted')
})
// 相當(dāng)于 beforeUpdate
onBeforeUpdate(() => {
console.log('onBeforeUpdate')
})
// 相當(dāng)于 updated
onUpdated(() => {
console.log('onUpdated')
})
// 相當(dāng)于 beforeDestroy
onBeforeUnmount(() => {
console.log('onBeforeUnmount')
})
// 相當(dāng)于 destroyed
onUnmounted(() => {
console.log('onUnmounted')
})
onErrorCaptured(() => { // 錯(cuò)誤監(jiān)控 參考文章 https://zhuanlan.zhihu.com/p/37404624
console.log('onErrorCaptured')
})
onRenderTracked(() => { // 已渲染
console.log('onRenderTracked')
})
onRenderTriggered(() => { // 當(dāng)組件更新時(shí)會(huì)首先觸發(fā)此生命周期鉤子 onRenderTriggered->onRenderTracked->onBeforeUpdate->onUpdated
console.log('onRenderTriggered')
})
return {
a,
setA
}
},
name: 'HelloWorld',
props: {
msg: String
}
}
onRenderTracked生命周期鉤子函數(shù)表示組件已渲染。組件首次渲染經(jīng)歷過(guò)程為onRenderTracked->onBeforeMount->onMounted
onErrorCaptured(err,vm,info)生命周期鉤子表示捕獲子孫組件中的發(fā)生錯(cuò)誤時(shí)的異常。err:錯(cuò)誤對(duì)象 vm:發(fā)生錯(cuò)誤的vuez組件實(shí)例 info:Vue特定錯(cuò)誤信息,比如發(fā)生錯(cuò)誤的生命周期
onRenderTriggered組件更新時(shí)會(huì)觸發(fā)此鉤子函數(shù)。觸發(fā)生命周期鉤子函數(shù)過(guò)程為onRenderTriggered->onRenderTracked->onBeforeUpdate->onUpdated
在Vue3.0中由于外界聲音反響比較大的原因,尤大以及團(tuán)隊(duì)考慮在3.0版本中可以持續(xù)使用2.x的東西,比如可以同時(shí)寫(xiě)mounted和onMounted兩個(gè)生命周期,但是不建議這樣做,如果使用Vue3.0那么就踏踏實(shí)實(shí)用3.0的東西去寫(xiě)。如果使用2.x版本的話,可以引用一些方法等,按照需要一點(diǎn)點(diǎn)向3.0慢慢過(guò)渡??傊魏我粋€(gè)框架都是需要更新的,更新肯定會(huì)有變化,那么就慢慢學(xué)吧。
我的練習(xí)源碼在github上里面有我寫(xiě)的一些注釋,有興趣的也可以看一看https://github.com/Mstian/Vue3.0-test
文中如有錯(cuò)誤,還望不吝指出,謝謝。
參考文章:
vue 3.0 初體驗(yàn) (項(xiàng)目搭建)
簡(jiǎn)明扼要聊聊 Vue3.0 的 Composition API 是啥東東!
VUE 3.0 學(xué)習(xí)探索入門(mén)系列 - Vue3.x 生命周期 和 Composition API 核心語(yǔ)法理解(6)
聊聊vue3.0新特性:compositon api 用法和注意事項(xiàng)
Vue源碼系列(二):錯(cuò)誤處理
偶然發(fā)現(xiàn)一些比較不錯(cuò)的資料:
https://www.yuque.com/vueconf/2019