本文主要分以下幾個主題討論最新的Composition API:
- reactive API
- ref API
- watch API變化
- computed API變化
- 生命周期鉤子變化
- TypeScript和JSX支持
Composition API 可謂是修復(fù)了 Function API 諸多問題而提供的最新“修正案”,下面來看比起之前的vue-function-api,究竟修改了些什么呢?
state更名為reactive
在vue-function-api中,通過state創(chuàng)建響應(yīng)式對象,這個state創(chuàng)建的響應(yīng)式對象并不是包裝對象,不需要使用.value來取值。但問題在于:state通常會被用作描述 Vue 組件狀態(tài)對象的變量名,容易對開發(fā)者造成誤導(dǎo),Vue官方團隊認為將state API 更名為reactive更為優(yōu)雅,reactive等價于 Vue 2.x 的Vue.observable(),用于獲取一個對象的響應(yīng)性代理對象:
const obj = reactive({ count: 0 });
復(fù)制代碼
value更名為ref,并提供isRef和toRefs
在vue-function-api中,通過value函數(shù)創(chuàng)建一個包裝對象,它包含一個響應(yīng)式屬性value。在 Vue 官方團隊充分采用社區(qū)意見后,將這個API更改為ref。ref用創(chuàng)建一個包裝對象,只具備一個響應(yīng)式屬性value,如果將對象指定為ref的值,該對象將被reactive方法深度遍歷。要知道, Composition API 之所以被提出和使用,就是為了讓我們更加方便地進行組件復(fù)用,將狀態(tài)經(jīng)過函數(shù)式地傳遞過程中,由于JavaScript函數(shù)傳參是值傳遞的,而基本類型不具備引用,為了保證屬性的響應(yīng)式,將使用ref來創(chuàng)建包裝對象進行傳遞。
const count = ref(0);
console.log(count.value); // 0
count.value++;
console.log(count.value); // 1
復(fù)制代碼
提供isRef,用于檢查一個對象是否是ref對象:
const unwrapped = isRef(foo) ? foo.value : foo;
復(fù)制代碼
如果讀者你看到這里,可能就會比較疑惑了,究竟什么時候該使用ref,什么時候該使用reactive呢?其使用場景其實與我們所習(xí)慣的編碼風(fēng)格密切相關(guān),通過下面的例子,我們能更好理解使用ref和reactive的區(qū)別:
// 風(fēng)格一:通過基本類型變量來聲明狀態(tài)
let x = 0;
let y = 0;
function updatePosition(e) {
x = e.pageX;
y = e.pageY;
}
// --- compared to ---
// 風(fēng)格二:通過單一對象來聲明狀態(tài)
const pos = {
x: 0,
y: 0,
};
function updatePosition(e) {
pos.x = e.pageX;
pos.y = e.pageY;
}
復(fù)制代碼
如果開發(fā)者習(xí)慣風(fēng)格一的寫法,通常通過ref將基本類型的變量轉(zhuǎn)化為響應(yīng)式包裝對象來使其具備響應(yīng)式,而如果是風(fēng)格二的話,只需要創(chuàng)建reactive對象。
然而,思考下面的場景:
function useMousePosition() {
const pos = reactive({
x: 0,
y: 0,
});
// ...
return pos;
}
// consuming component
export default {
setup() {
// 對象解構(gòu)將會導(dǎo)致響應(yīng)式會被丟失
const { x, y } = useMousePosition();
return {
x,
y,
};
// 拓展運算符將導(dǎo)致響應(yīng)式丟失
return {
...useMousePosition()
}
// 只有這樣才能保證響應(yīng)式不被丟失
// 通過pos.x的pos.y來取值才會保留x,y的響應(yīng)式
return {
pos: useMousePosition()
}
}
};
復(fù)制代碼
通過上述的例子,要知道,我們沒有辦法通過編碼風(fēng)格的限制來保證通過組合函數(shù)返回的響應(yīng)式狀態(tài)不被丟失,Vue官方團隊建議在組合函數(shù)中都通過返回ref對象來規(guī)避這一類問題,toRef便是做這一件事情的最好方式:
function useMousePosition() {
const pos = reactive({
x: 0,
y: 0
});
// ...
return toRefs(pos);
}
// x 和 y 現(xiàn)在具備了響應(yīng)式
const { x, y } = useMousePosition();
復(fù)制代碼
toRefs將reactive對象轉(zhuǎn)換為普通對象,其中結(jié)果對象上的每個屬性都是指向原始對象中相應(yīng)屬性的ref引用對象,這在組合函數(shù)返回響應(yīng)式狀態(tài)時非常有用,這樣保證了開發(fā)者使用對象解構(gòu)或拓展運算符不會丟失原有響應(yīng)式對象的響應(yīng)。
watch可作用于單一函數(shù)
比起上一篇文章中介紹的watch API 的傳參方式,最新的@vue/composition-api修正案中,watch的傳遞方式可以收斂為單一函數(shù),Vue 3.x 將會在其依賴的響應(yīng)式狀態(tài)改變是執(zhí)行watch的回調(diào)函數(shù):
const count = ref(0);
watch(() => console.log(count.value)); // 打印0
setTimeout(() => {
count.value++; // 打印1
}, 100);
復(fù)制代碼
computed可傳入get和set,用于定義可更改的計算屬性
基本示例如下所示,與 Vue 2.x 類似的,可以定義可更改的計算屬性。
const count = ref(1);
const plusOne = computed({
get: () => count.value + 1,
set: val => { count.value = val - 1 }
});
plusOne.value = 1;
console.log(count.value); // 0
復(fù)制代碼
生命周期鉤子
比起vue-function-api,@vue/composition-api刪除了onBeforeCreate和onCreated。因為setup總是在創(chuàng)建組件實例時調(diào)用,即onBeforeCreate之后和onCreated之前調(diào)用,因此onBeforeCreate和onCreated將可以使用setup進行代替。
使用TypeScript和JSX
setup現(xiàn)在支持返回一個渲染函數(shù),這個函數(shù)返回一個JSX,JSX可以直接使用聲明在setup作用域的響應(yīng)式狀態(tài):
export default {
setup() {
const count = ref(0);
return () => (<div>{count.value}</div>);
},
};
復(fù)制代碼
注:如果使用
TypeScript,同時希望使用需要在JSX命名空間內(nèi)聲明以下interface:
// file: shim-tsx.d.ts
import Vue, { VNode } from 'vue';
import { ComponentRenderProxy } from '@vue/composition-api';
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode { }
// tslint:disable no-empty-interface
interface ElementClass extends ComponentRenderProxy { }
interface ElementAttributesProperty {
$props: any; // specify the property name to use
}
interface IntrinsicElements {
[elem: string]: any;
}
}
}
復(fù)制代碼
此外,為了更好地配合 TypeScript 進行類型推斷,Vue Composition API 推薦使用createComponent來定義一個組件,以便于Vue進行類型推導(dǎo):
import { createComponent } from 'vue';
export default createComponent({
props: {
foo: String,
},
setup(props) {
console.log(props.foo);
},
});
復(fù)制代碼
小結(jié)
本文是筆者上一篇文章Vue 3.0 前瞻,體驗 Vue Function API的續(xù)篇,主要描述 Vue Composition API 對比 之前的草案 Vue Function API 的變化,可以看到Vue 官方針對社區(qū)建議修改了 Vue Function API 草案的諸多問題。下一篇文章中,筆者帶來 Vue Composition API 的響應(yīng)式對象原理解讀,在解讀學(xué)習(xí)過程中,加深對 Vue Composition API 的理解。