reactive API的定義為傳入一個物件返回一個基於原物件的響應式代理,即返回一個proxy,相當於Vue2.x中的Vue.Observer
。
優點
在Vue2.x中資料的響應式處理是基於Object.defindProperty()
的,但他只會偵聽物件的屬性,不會偵聽物件,在新增物件屬性的時候需要:
Vue.$set(object, 'name', 'lock li')
reactive
API則是基於ES2015 proxy
實現了對物件的響應式處理,在vue3.0中往物件中新增屬性,並且這個屬性也會具有響應式的效果:
object.name = 'lock li'
注意點
① 使用reactive
API時,setup
中返回的reactive
需要通過物件的形式:
export default definComponent({
name: 'example',
setup(props) {
const obj = reactive({foo: 'bar'})
return {
obj
}
}
})
defineComponent
definComponent主要是用來幫助Vue在TS下正確推斷出setup()元件的引數型別
export function defineComponent(options: unknown) {
return isFunction(options) ? { setup: options } : options
}
② 或者藉助toRefs
API包裹一下匯出,(使用toRefs
包裹匯出的我們可以使用展開運運算元或解構接收):
export default defineComponent({
setup() {
let obj = { foo: 'bar' }
obj = toRefs(obj)
return {
...obj
}
}
})
二、原始碼實現
function reactive(target) {
// if trying to observe a readonly proxy, return the readonly version.
if (readonlyToRaw.has(target)) {
return target;
}
// target is explicitly marked as readonly by user
if (readonlyValues.has(target)) {
return readonly(target);
}
if (isRef(target)) {
return target;
}
return createReactiveObject(target, rawToReactive, reactiveToRaw, mutableHandlers, mutableCollectionHandlers);
}
可以看到先有3個判斷邏輯,對readonlyToRaw
、readonlyValues
、isRef
分別進行了判斷,先不看這些判斷邏輯,通常我們對reactive()
傳入一個物件,可以直接命中createReactiveObject()
。
createReactiveObject()
函數如下:
function createReactiveObject(target, toProxy, toRaw, baseHandlers, collectionHandlers) {
if (!isObject(target)) {
if ((process.env.NODE_ENV !== 'production')) {
console.warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
// target already has corresponding Proxy
let observed = toProxy.get(target);
if (observed !== void 0) {
return observed;
}
// target is already a Proxy
if (toRaw.has(target)) {
return target;
}
// only a whitelist of value types can be observed.
if (!canObserve(target)) {
return target;
}
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers;
observed = new Proxy(target, handlers);
toProxy.set(target, observed);
toRaw.set(observed, target);
return observed;
}
createReactiveObject()
傳入了4個引數,分別是:
target
:我們定義reactive
時傳入的物件
toProxy
:一個空的WeakSet
toRaw
:判斷傳入的reactive
是否已經被代理
baseHandlers
:一個已經被定義好的的具有get
和set
的物件,它看起來會是這樣子:
const baseHandlers = {
get(target, key, value, receiver) {},
set(target, key, value, receiver) {},
deleteProxy(target, key) {},
has(target, key) {},
ownKey(target) {}
}
collectionHandlers
是一個只包含get
的物件。詳細看createReactiveObject
函數,一些分支邏輯先不用管,我們直接看最後的邏輯:
const handlers = collectionTypes.has(target.constructor)
? collectionHandlers
: baseHandlers;
observed = new Proxy(target, handlers);
toProxy.set(target, observed);
toRaw.set(observed, target);
return observed;
首先判斷collectionTypes
中是否包含我們傳入的target
的建構函式(構造器),而collectionTypes
是一個Set
集合主要包含:Set
Map
WeakSet
WeakMap
等4種集合的建構函式。
如果collectionTypes
中包含我們傳入的建構函式,則將handlers
賦值為僅包含get
屬性的collectionHanders
否則 賦值為baseHandlers
。
兩者的區別在於collectionHandlers
僅含有get
屬性,是用來留給不需要派發更新的變數使用的,例如我們常用的props
屬性
然後將target
handlers
作為兩個引數傳入,使用替換了vue 2.x中的Object.defineProperty
的Proxy
代理函數,並範例化
接下來兩步也非常重要,將target
和observed
作為鍵值對賦值到toProxy
,用於下次檢測傳入的target
是否已經被代理,並返回被代理的observed
物件:
// target already has corresponding Proxy
let observed = toProxy.get(target);
if (observed !== void 0) {
return observed;
}
將observed
和target
作為鍵值對賦值到toRaw
,用於下次檢測傳入的target
是否是一個代理物件,並返回代這個target
:
// target is already a Proxy
if (toRaw.has(target)) {
return target;
}
二、toRefs
把一個響應式物件轉換成普通物件,該普通物件的每個 property 都是一個 ref ,和響應式物件 property 一一對應。
使用toRefs
其實是為了方便我們使用解構和展開運運算元,具體程式碼:
function toRefs(object) {
if ((process.env.NODE_ENV !== 'production') && !isReactive(object)) {
console.warn(`toRefs() expects a reactive object but received a plain one.`);
}
const ret = {};
for (const key in object) {
ret[key] = toProxyRef(object, key);
}
return ret;
}
function toProxyRef(object, key) {
return {
_isRef: true,
get value() {
return object[key];
},
set value(newVal) {
object[key] = newVal;
}
};
}
可以看到toRefs
是在原有Proxy
的基礎上,返回了一個所有屬性帶有get
、set
的物件,這樣就解決了Proxy
物件遇到解構和展開運運算元之後,失去響應性的問題。