天天看點

位元組二面:說一下Vue3中你知道的Composition API

作者:尚矽谷教育

大家都知道,現在Vue3的各個版本已經陸續釋出了,并且有很多的團隊已經着手各個庫的開發與Vue2向Vue3的更新,我們當然也不能落後,是以趕緊将跟着本文一起學習新的API吧

一、setup

setup的了解:

Vue3.0的一個配置項,值為一個函數,setup是所有Composition API(組合API)的入口“表演舞台”,元件中所用到的:資料、方法等等,均要配置在setup中。

setup函數有兩種傳回值:

若傳回一個對象,則對象中的屬性、方法, 在模闆中均可以直接使用

若傳回一個渲染函數:則可以自定義渲染内容。

setup使用注意點:

盡量不要與Vue2.x配置混用

setup不能是一個async函數,因為傳回值不再是return的對象, 而是promise, 模闆看不到return對象中的屬性。(後期也可以傳回一個Promise執行個體,但需要Suspense和異步元件的配合)

vue2可以擷取vue3中的屬性和方法,vue3無法擷取vue2中的屬性和方法。

位元組二面:說一下Vue3中你知道的Composition API

setup函數的使用:

setup 函數是在 created 時候(執行個體被初始化前)執行的。裡邊傳回資料或者方法,插值函數可以檢測到。

因為是執行個體初始化前執行,是以 setup 裡不能使用 this 通路到執行個體。

<template>

<div>

<h3>姓名:{{ name }}</h3>

<h3>年齡:{{ age }}</h3>

<h3>技能:{{ skill }}</h3>

</div>

</template>

<script>

export default {

setup() {

let name = "張三";

let age = 18;

let skill = "犯罪";

return {

name,

age,

skill,

};

},

};

</script>

<style scoped></style>

setup 函數裡有兩個參數,第一個是 props,指元件從父元件那兒接收到的參數,第二個是 context,暴露了 attrs、slot、emit 等實用方法。

二、ref和reactive

上面看着很合理,但是 name 和 age 都是非響應式的,即資料改變并不會觸發渲染。

如何解決問題呢?現在需要響應式的引用:通過 proxy 對資料進行封裝。當資料變化時,觸發模闆等内容的更新。

後面的例子都是通過一秒後修改 name 的值來檢視是否有響應式。如果頁面資料沒有改變,說明沒有響應式。

ref:響應式封裝基本類型資料

使用ref函數,如ref(‘張三’),對基本類型的資料進行封裝,變成proxy({value:‘張三’})這樣一個響應式的資料。

<template>

<div>

<h3>姓名:{{ name }}</h3>

<h3>年齡:{{ age }}</h3>

<h3>技能:{{ skill }}</h3>

</div>

</template>

<script>

import { ref } from "vue";

export default {

setup() {

let name = ref("張三");

let age = ref(18);

let skill = ref("犯罪");

setTimeout(() => {

name.value = "李四",

age.value = 19,

skill.value = "飛刀"

}, 1000);

return {

name,

age,

skill,

};

},

};

</script>

<style scoped>

</style>

改成響應式引用後資料變化,頁面也會發生變化

備注:

接收的資料可以是:基本類型、也可以是對象類型

  • 基本類型:響應式依然是靠Object.defineProperty()的get與set完成的
  • 對象類型:内部借用了Vue3.0中的一個新函數-reactive函數

(ref處理基本類型用的是get與set處理對象資料用的是es6中的Proxy)

reactive:響應式封裝非基本類型資料

使用reactive函數,将非基本資料類型轉為響應式,一般是對象或者數組。

原理:reactive({name:‘張三’})轉為proxy({name:‘張三’})

<template>

<div>

<h3>姓名:{{ obj.name }}</h3>

<h3>年齡:{{ obj.age }}</h3>

<h3>技能:{{ obj.skill }}</h3>

</div>

</template>

<script>

import { reactive } from "vue";

export default {

setup() {

let obj = reactive({

name: '張三',

age: 18,

skill: '犯罪'

})

setTimeout(() => {

obj.name = '李四',

obj.age = 19,

obj.skill = '飛刀'

})

return {

obj

};

},

};

</script>

<style scoped>

</style>

toRefs

使用過對象解構的同學會覺得插值函數裡的寫法好麻煩啊,是以會用對象解構。但是這裡需要注意的一點,如果直接使用結構後的值,這些值不是響應式的。

解構會将對象或數組解構成普通的資料,丢失了響應。是以解構之前需要用 toRefs 包裹一下,使解構後所有值變成響應式的。

原理:toRefs({ name: "張三", age: 18,skill:"犯罪" }) 轉化成 { name: proxy({ value: "張三" }), age: proxy({ value: 18 }),skill:proxy({value:"犯罪"}) },将内部的所有資料變成響應式的。現在使用解構後就不會丢失響應式了。

<template>

<div>

<h3>姓名:{{ name }}</h3>

<h3>年齡:{{ age }}</h3>

<h3>技能:{{ skill }}</h3>

</div>

</template>

<script>

import { reactive, toRefs } from "vue";

export default {

setup() {

let obj = reactive({

name: "張三",

age: 18,

skill: "犯罪",

});

setTimeout(() => {

obj.name = "李四",

obj.age = 19,

obj.skill = "飛刀";

});

let { name, age, skill } = toRefs(obj);

return {

name,

age,

skill,

};

},

};

</script>

<style scoped>

</style>

reactive對比ref

從定義資料角度對比:

ref用來定義:基本類型資料。

reactive用來定義:對象(或數組)類型資料。

備注:ref也可以用來定義對象(或數組)類型資料,它内部會自動通過reactive轉為代理對象。

從原理角度對比:

ref定義一個基本資料類型是通過Object.defineProperty()的get()與set()來實作響應式(資料劫持)。

ref如果定義的是一個引用資料類型,其底層本質還是reactive,會将我們傳入的值自動轉變為:ref(1) ==> reactive({value:1})

reactive通過使用Proxy來實作響應式(資料劫持),并通過Reflect操作源對象内部的資料。

從使用角度對比:

ref定義的資料:操作資料需要.value,讀取資料時模闆中直接讀取不需要.value。

reactive定義的資料:操作數與讀取資料:均不需要.value。

三、computed和watch

computed函數

與Vue2.X的computed的配置功能一緻

寫法:

<template>

<h1>一個人的資訊</h1>

姓:<input type="text" v-model="person.firstName">

<br>

名:<input type="text" v-model="person.lastName">

<!-- 隻讀 -->

<br>

<!-- <span>全名:{{person.fullName}}</span> -->

全名:<input type="text" v-model="person.fullName">

</template>

<script>

import { reactive, computed } from 'vue'

export default {

name: 'Demo',

setup() {

//資料

let person = reactive({

firstName: '李',

lastName: '四'

})

//計算屬性---簡寫(不考慮計算屬性被修改的情況)

// person.fullName = computed(()=>{

// return person.firstName + '-' +person.lastName

// })

//計算屬性---完整版(考慮讀和寫)

person.fullName = computed({

get() {

return person.firstName + '-' + person.lastName

},

set(value) {

const nameArr = value.split('-')

person.firstName = nameArr[0]

person.lastName = nameArr[1]

}

})

//傳回一個對象

return {

person,

}

}

}

</script>

可以看到,在 Composition API 中,需要先引入 computed 函數,然後正常使用即可。

watch函數

與Vue2.X的配置功能一緻

注意:

監視reactive定義的響應式資料時:oldValue無法正确擷取、強制開啟了深度監視(deep配置失效)

監視reactive定義的響應式資料中的某個屬性(屬性為對象時)時:deep配置有效

<template>

<h2>目前求和為:{{ sum }}</h2>

<button @click="sum++">點我+1</button>

<hr>

<h2>資訊為:{{ msg }}</h2>

<button @click="msg += '!'">修改資訊</button>

<hr>

<h2>一個人的資訊:</h2>

<h2>姓名:{{ person.name }}</h2>

<h2>年齡:{{ person.age }}</h2>

<h3>工資:{{ person.job.j1.salary }}</h3>

<button @click="person.name += '~'">修改姓名</button>

<button @click="person.age++">修改年齡</button>

<button @click="person.job.j1.salary++">漲薪</button>

</template>

<script>

import { ref, reactive, watch } from 'vue'

export default {

name: 'Demo',

setup() {

//資料

let sum = ref(0)

let msg = ref("nihao")

let person = reactive({

name: '李四',

age: 39,

job: {

j1: {

salary: 20

}

}

})

//情況一:監視ref所定義的一個響應式資料

// watch(sum,(newValue,oldValue)=>{

// console.log('sum變了',newValue,oldValue)

// },{immediate:true})

//情況二:監視ref所定義的多個響應式資料

// watch([sum,msg],(newValue,oldValue)=>{

// console.log('sum或msg變了',newValue,oldValue)

// },{immediate:true})

//情況三:監視reactive所定義的一個響應式資料的全部屬性

//1.此處無法正确的擷取oldValue

//2.強制開啟了深度監視,且無法關閉(deep配置無效)

// watch(person,(newValue,oldValue)=>{

// console.log('person變了',newValue,oldValue)

// },{immediate:true,deep:false})

//情況四:監視reactive所定義的一個響應式資料的某個屬性

// watch(()=>person.age,(newValue,oldValue)=>{

// console.log('年齡變了',newValue,oldValue)

// },{immediate:true})

//情況四:監視reactive所定義的一個響應式資料的某些屬性

// watch([()=>person.age,()=>person.name],(newValue,oldValue)=>{

// console.log('年齡或姓名變了',newValue,oldValue)

// })

//特殊情況

//此處由于監視的是reactive定義的對象中的某個屬性,是以deep有效

watch(() => person.age, (newValue, oldValue) => {

console.log('年齡變了', newValue, oldValue)

}, { deep: true })

//傳回一個對象

return {

sum,

msg,

person

}

}

}

</script>

<style>

</style>

watchEffect

watchEffect 和 watch 的差別:

隻要寫了 watchEffect,回調就立即執行,沒有惰性

watchEffect 會自己感覺内部的代碼資料是否因為相關依賴發生了改變,如果發生了改變就會執行,是以不需要傳遞很多參數。watch 需要通過參數指定監聽哪個資料。

watchEffect 無法擷取之前的資料和目前的資料。watch 可以擷取之前和目前的資料

watchEffect有點像computed:

但computed注重的是計算出來的值(回調函數的傳回值),是以必須要寫傳回值。

而watchEffect更注重的是過程(回調函數的函數體),是以不用寫傳回值。

watchEffect(() => {

// 當 nameObj.name 發生改變,會觸發 watchEffect 的回調函數

console.log(nameObj.name)

})

暫停watchEffect

例如:5S後結束監聽

const stop = watchEffect(() => {

console.log(nameObj.name)

setTimeout(() => {

stop()

}, 5000)

})

四、生命周期函數

現在用 Composition API 來寫生命周期鈎子和Vue2的生命周期鈎子的對比:

選項式API 組合式API
beforeCreate setup
created setup
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
activated onActivated
deactivated onDeactivated

選項式 API 就是 vue2 的那種 API,因為 vue 元件是通過對象内的配置建立出來的。

因為 setup 函數執行的時間就在 beforeCreate 和 created 之間,是以 Composition API 沒有提供對應的鈎子。

export default {

setup() {

// mounted

onMounted(() => {

console.log('Component is mounted!')

})

}

}

總結

Vue3.X與Vue2.X的差別

1.重構響應式系統,使用Proxy替換Object.defineProperty。

2.使用Proxy優勢:可直接監聽數組類型的資料變化,監聽的目标為對象本身,不需要像Object.defineProperty一樣周遊每個屬性,有一定的性能提升,可攔截apply、ownKeys、has等13種方法,而Object.defineProperty不行,直接實作對象屬性的新增/删除。

3.新增Composition API,更好的邏輯複用和代碼組織。

4.重構 Virtual DOM:模闆編譯時的優化,将一些靜态節點編譯成常量,slot優化,将slot編譯為lazy函數,将slot的渲染的決定權交給子元件,模闆中内聯事件的提取并重用(原本每次渲染都重新生成内聯函數)。

5.代碼結構調整,更便于Tree shaking,使得體積更小。

6.使用Typescript替換Flow。

7.當然vue3.0,完全相容vue2.0,不喜歡新文法的夥伴可以保持使用2.0文法。

繼續閱讀