前言
Vue3.0的步伐越來越近了,是時(shí)候了解起來了获黔,雖然嘴上還喊學(xué)不動(dòng)了蚀苛,但是,身體還得誠實(shí)起來玷氏,接著學(xué)堵未。。盏触。
通過各種博客資料渗蟹,還有前段時(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è)情況也看得出來赞辩,不會(huì)ts的該學(xué)起來了雌芽,比如我。辨嗽。世落。
放棄class采用function-based API據(jù)說也是為了更好支持ts,為了更靈活的邏輯復(fù)用能力糟需,代碼更容易壓縮等...
option Api到Composition Api應(yīng)該是對(duì)我們?nèi)懘a影響最大的一部分岛心,稍后可以看代碼體驗(yàn)一下。
重構(gòu)compiler與virtual DOM使Vue變得更快篮灼,也是Vue越來越優(yōu)秀的原因忘古。
新的響應(yīng)式機(jī)制采用了ES6的Proxy
Api,拋棄了之前的Object.defineProperty()
比較直觀的解決的是Vue2中這兩點(diǎn)問題:
關(guān)于對(duì)象:Vue 無法檢測 property 的添加或移除诅诱。由于 Vue 會(huì)在初始化實(shí)例時(shí)對(duì) property 執(zhí)行 getter/setter 轉(zhuǎn)化髓堪,所以 property 必須在
data
對(duì)象上存在才能讓 Vue 將它轉(zhuǎn)換為響應(yīng)式的。-
關(guān)于數(shù)組:Vue 不能檢測以下數(shù)組的變動(dòng):
- 當(dāng)你利用索引直接設(shè)置一個(gè)數(shù)組項(xiàng)時(shí),例如:
vm.items[indexOfItem] = newValue
- 當(dāng)你修改數(shù)組的長度時(shí)干旁,例如:
vm.items.length = newLength
- 當(dāng)你利用索引直接設(shè)置一個(gè)數(shù)組項(xiàng)時(shí),例如:
在官網(wǎng)深入響應(yīng)式原理一章有較詳細(xì)闡述驶沼,針對(duì)以上兩種情況解決方法,官網(wǎng)也有給出答案争群,那就是使用set
方法回怜。
而Proxy
可以完美的解決該問題,當(dāng)然好處應(yīng)該不止這些换薄,剩下的慢慢探究吧玉雾,Proxy也有缺點(diǎn),那就是兼容性問題轻要,有一些瀏覽器不支持复旬,而且無完全polyfill,瀏覽器支持程度可以查看https://caniuse.com/#search=Proxy
簡單了解Proxy
Vue核心就是響應(yīng)式數(shù)據(jù)冲泥,Vue3.0中的響應(yīng)式采用了Proxy
那就簡單看看Proxy
是怎么個(gè)樣子的呢驹碍。
Proxy 對(duì)象用于定義基本操作的自定義行為(如屬性查找、賦值凡恍、枚舉志秃、函數(shù)調(diào)用等)。嚼酝。洽损。
from MDN,學(xué)習(xí)一個(gè)新的Api官方文檔還是要讀一下哈革半,雖然讀不懂這么深?yuàn)W的描述碑定。。又官。
語法:
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ú)多出來一個(gè)value
表示新的屬性值。
上述代碼表示當(dāng)訪問proxy
的屬性時(shí)矮湘,進(jìn)行攔截判斷斟冕,該屬性是否是目標(biāo)對(duì)象的屬性,如果是那么就將其值返回出來缅阳,否則就返回0磕蛇。
當(dāng)對(duì)proxy
上的屬性進(jìn)行重寫時(shí),將重寫的該屬性賦值為666十办。
注意:此時(shí)對(duì)數(shù)據(jù)的劫持秀撇,只是劫持了代理對(duì)象proxy
,而跟原對(duì)象obj
沒有任何關(guān)系橘洞,對(duì)obj
進(jìn)行操作捌袜,也不會(huì)監(jiān)聽到说搅。
用proxy
實(shí)現(xiàn)一個(gè)簡易版的數(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)聽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的過程中手寫初始化代碼,會(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方法來構(gòu)建Vue實(shí)例赴穗,Vuex語法和Api基本沒有發(fā)生變化父阻。和之前一樣愈涩,該怎么樣寫state,mutations等還是怎么寫,該怎么調(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
方法來創(chuàng)建。
總結(jié):總的來說妓羊,構(gòu)建Vue Router和Vuex的方式變了胯究,但是它們的配置方式都和之前保持一致,可以無縫銜接使用躁绸。
-
Composition API
在3.0代碼基礎(chǔ)上繼續(xù)往下看裕循,創(chuàng)建一個(gè)新的組件<Test/>
,在<Test/>
組件中認(rèn)識(shí)一下Composition API
之前2.x版本是采用了Options API的模式净刮,可以理解為選項(xiàng)式的組件代碼編寫剥哑,Vue官方規(guī)定好的寫法,響應(yīng)式數(shù)據(jù)淹父,methods株婴,computed,components以及生命周期這些都是規(guī)定好的暑认,需要在哪里寫困介,你就得在哪里寫。
<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è)方法中組合起來統(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ù)褥伴,接收來的props對(duì)象谅将,可以通過watch監(jiān)視其變化。接受context對(duì)象作為第二個(gè)參數(shù)重慢,這個(gè)對(duì)象包含attrs饥臂,slots,emit三個(gè)屬性似踱。多說無益隅熙,直接看代碼吧。
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一般用來創(chuàng)建基本數(shù)據(jù)類型的響應(yīng)式數(shù)據(jù),reactive一般用來創(chuàng)建引用數(shù)據(jù)類型的響應(yīng)式數(shù)據(jù)轧简。
在模板中使用驰坊,跟之前沒有區(qū)別,需要注意的是哮独,ref屬于將基本類型數(shù)據(jù)包裝成應(yīng)用類型拳芙,在模板中正常使用。在方法中訪問的時(shí)候需要帶上.value才能訪問到借嗽。
以下代碼我簡寫了态鳖,比如有一個(gè)按鈕點(diǎn)擊會(huì)觸發(fā)一個(gè)方法转培,該方法是讓count自增恶导,那么應(yīng)該這樣寫:
setup(props, context){
const count = ref(0)
const addCount = () => {
count.value ++
}
return {
count,
addCount
}
}
為什么要這么寫呢?是因?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)出來后牵署,變成不再可觀察了呢学少?因?yàn)橥ㄟ^reactive方法創(chuàng)建的可觀察對(duì)象磅摹,內(nèi)部的屬性本身并不是可觀察的站宗,而是通過Proxy代理實(shí)現(xiàn)的讀寫觀察闸准,如果將這些屬性解構(gòu),這些屬性就不再通過原對(duì)象的代理來訪問了梢灭,就無法再進(jìn)行觀察夷家。
Composition API提供了一種方法來解決此機(jī)制帶來的問題蒸其,那就是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)聽哪個(gè)數(shù)據(jù)湿蛔,第二個(gè)參數(shù)函數(shù)膀曾,表示監(jiān)聽成功后的邏輯,該函數(shù)的第一個(gè)參數(shù)就是監(jiān)聽到目標(biāo)數(shù)據(jù)變化后的值阳啥。
同時(shí)watch可以監(jiān)聽多個(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
方法斩狱,用來獲取當(dāng)前組件實(shí)例,然后通過ctx獲取當(dāng)前上下文扎瓶。
import {getCurrentInstance} from "vue"
export default{
setup(){
const {ctx} = getCurrentInstance()
console.log(ctx)
}
}
大概是這么個(gè)東西
可以和Vue2.x中this輸出對(duì)比一下所踊。還是有不小的變動(dòng),但常用api都沒有發(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寫在哪里遏考,methods寫在哪里,computed寫在哪里蓝谨,而在Vue3.0中這種規(guī)定被打破灌具,開發(fā)者可以比較自由的組織自己的代碼,兩者都有自己的好處與弊端譬巫。詳見可以參考文章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)歷過程為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ù)過程為onRenderTriggered->onRenderTracked->onBeforeUpdate->onUpdated
在Vue3.0中由于外界聲音反響比較大的原因,尤大以及團(tuán)隊(duì)考慮在3.0版本中可以持續(xù)使用2.x的東西宵睦,比如可以同時(shí)寫mounted和onMounted兩個(gè)生命周期记罚,但是不建議這樣做,如果使用Vue3.0那么就踏踏實(shí)實(shí)用3.0的東西去寫壳嚎。如果使用2.x版本的話桐智,可以引用一些方法等,按照需要一點(diǎn)點(diǎn)向3.0慢慢過渡烟馅∷低ィ總之,任何一個(gè)框架都是需要更新的郑趁,更新肯定會(huì)有變化刊驴,那么就慢慢學(xué)吧。
我的練習(xí)源碼在github上里面有我寫的一些注釋寡润,有興趣的也可以看一看https://github.com/Mstian/Vue3.0-test
文中如有錯(cuò)誤捆憎,還望不吝指出,謝謝梭纹。
參考文章:
vue 3.0 初體驗(yàn) (項(xiàng)目搭建)
簡明扼要聊聊 Vue3.0 的 Composition API 是啥東東!
VUE 3.0 學(xué)習(xí)探索入門系列 - Vue3.x 生命周期 和 Composition API 核心語法理解(6)
聊聊vue3.0新特性:compositon api 用法和注意事項(xiàng)
Vue源碼系列(二):錯(cuò)誤處理
偶然發(fā)現(xiàn)一些比較不錯(cuò)的資料:
https://www.yuque.com/vueconf/2019