Vue3丨進一步了解這 20 個響應式 API,寫碼如有神
(關(guān)注前端精,讓你前端越來越精 ~ )
前面說的話
在 Vue2 中,個人覺得對于數(shù)據(jù)的操作比較 “黑盒” 。
而 Vue3 把響應式系統(tǒng)更顯式地暴露出來,使得我們對數(shù)據(jù)的操作有了更多的靈活性。
所以,對于 Vue3 的幾個響應式的 API ,我們需要更加的理解掌握,才能在實戰(zhàn)中運用自如。
先了解,什么是響應式 ?
Vue3 官網(wǎng)有舉過一個例子
var?val1?=?2
var?val2?=?3
var?sum?=?val1?+?val2
我們希望 val1 或 val2 的值改變的時候,sum 也會響應的做出正確的改變。
大白話
我依賴了你,你變了。你就通知我讓我知道,好讓我做點 “操作” 。
從 Vue3 的源碼來講
讓我們記住三個關(guān)鍵的英語單詞,它們的順序也是完成一個響應式的順序。
effect > track > trigger > effect
淺淺的解釋一下:在組件渲染過程中,假設當前正在走一個 “effect”(副作用),這個 effect 會在過程中把它接觸到的值(也就是說會觸發(fā)到值的 get 方法),從而對值進行 track(追蹤)。當值發(fā)生改變,就會進行 trigger(觸發(fā)),執(zhí)行 effect 來完成一個響應!
用代碼來解釋
在 Vue 中,有三種 effect ,且說是視圖渲染effect、計算屬性effect、偵聽器effect
<template>
??<div>count:{{count}}div>
??<div>computedCount:{{computedCount}}div>
??<button?@click="handleAdd">addbutton>
template>
//?...
setup()?{
??const?count?=?ref(1);
??const?computedCount?=?computed(()?=>?{
????return?count.value?+?1;
??});
??watch(count,?(val,?oldVal)?=>?{
????console.log('val?:>>?',?val);
??});
??const?handleAdd?=?()?=>?{
????count.value++;
??};
??return?{
????count,
????computedCount,
????handleAdd
??};
}
//?...
上面這段代碼,對于依賴值的追蹤之后會被存放于這樣的一個集合中,如圖:

注:以上的最內(nèi)層集合數(shù)組里的 reactiveEffect 方法分別是 偵聽器effect、視圖渲染effect、計算屬性effect。
當執(zhí)行 handleAdd 動作時,就會觸發(fā) count.value 的 set 方法,進行 trigger 響應式調(diào)用集合相關(guān)的 3 個 effect ,然后分別去更新視圖,更新 computedCount 的值,調(diào)用 watch 偵聽器的回調(diào)方法進行輸出。
不太理解沒關(guān)系,腦袋瓜先有個大體的結(jié)構(gòu)即可 ~
簡單的介紹了響應式是什么之后,讓我們來進入本文的主題,進一步了解 Vue3 的響應式 API ~
Vue3 內(nèi)置的 20 個響應式 API
1. reactive
先看 Proxy
在了解 reactive 之前,我們先來了解一波實現(xiàn) reactive API 的關(guān)鍵類 > ES6 的 Proxy ,它還有一個好基友 Reflect。這里我們先看一個簡單的例子:
const?targetObj?=?{
??id:?1,
??name:?'front-refined',
??childObj:?{
????hobby:?'coding'
??}
};
const?proxyObj?=?new?Proxy(targetObj,?{
??get(target,?key,?receiver)?{
????console.log(`get key:${key}`);
????return?Reflect.get(...arguments);
??},
??set(target,?key,?value,?receiver)?{
????console.log(`set key:${key},value:${value}`);
????return?Reflect.set(...arguments);
??}
});
我們來分析兩件事:
在瀏覽器打印一下代理之后的對象

[[Handler]]:處理器,目前攔截了 get、set [[Target]]:代理的目標對象[[IsRevoked]]:代理是否撤銷
第一次接觸 [[IsRevoked]] 的時候,有點好奇它的作用。也好奇的小伙伴看下這段代碼:
//?用?Proxy?的靜態(tài)方法?revocable?代理一個對象
const?targetObj?=?{?id:?1,?name:?'front-refined'?};
const?{?proxy,?revoke?}?=?Proxy.revocable(targetObj,?{});
revoke();
console.log('proxy-after?:>>?',?proxy);
proxy.id?=?2;
輸出如圖:

報錯:因為代理已經(jīng)被撤回了,所以不能對 id 進行 set 動作
對上面的代碼在控制臺打印看看輸出了啥?
proxyObj.name
// get key:name
proxyObj.name="hello~"
// set key:name,value:hello~
proxyObj.childObj.hobby
// get key:childObj
proxyObj.childObj.hobby="play"
// get key:childObj
我們可以看到對于 hobby 的 get/set 輸出只到了 childObj 。如果是這樣的話,不就攔截不了 hobby 的 get/set 了,那怎么進行追蹤,觸發(fā)更新?讓我們帶著疑問繼續(xù)往下看。
reactive 源碼(深層對象的代理)
我們可以看到不管對 hobby 進行 get 或 set,都會先去 get childObj ?// get key:childObj,那么我們就可以在 get 訪問器里做點操作,這里拿 reactive 相關(guān)源碼舉個例子(我知道看源碼復雜,所以我已經(jīng)精簡了,并且加上了注釋。這段代碼可以直接 copy 運行哦~):
//?工具方法:判斷是否是一個對象(注:typeof 數(shù)組?也等于?'object'
const?isObject?=?val?=>?val?!==?null?&&?typeof?val?===?'object';
//?工具方法:值是否改變,改變才觸發(fā)更新
const?hasChanged?=?(value,?oldValue)?=>
??value?!==?oldValue?&&?(value?===?value?||?oldValue?===?oldValue);
//?工具方法:判斷當前的 key 是否是已經(jīng)存在的
const?hasOwn?=?(val,?key)?=>?hasOwnProperty.call(val,?key);
//?閉包:生成一個 get 方法
function?createGetter()?{
??return?function?get(target,?key,?receiver)?{
????const?res?=?Reflect.get(target,?key,?receiver);
????console.log(`getting key:${key}`);
????//?track(target,?'get'?/*?GET?*/,?key);
????//?深層代理對象的關(guān)鍵?。?!判斷這個屬性是否是一個對象,是的話繼續(xù)代理動作,使對象內(nèi)部的值可追蹤
????if?(isObject(res))?{
??????return?reactive(res);
????}
????return?res;
??};
}
//?閉包:生成一個 set 方法
function?createSetter()?{
??return?function?set(target,?key,?value,?receiver)?{
????const?oldValue?=?target[key];
????const?hadKey?=?hasOwn(target,?key);
????const?result?=?Reflect.set(target,?key,?value,?receiver);
????//?判斷當前?key?是否已經(jīng)存在,不存在的話表示為新增的?key?,后續(xù)?Vue?“標記”新的值使它其成為響應式
????if?(!hadKey)?{
??????console.log(`add key:${key},value:${value}`);
??????//?trigger(target,?'add'?/*?ADD?*/,?key,?value);
????}?else?if?(hasChanged(value,?oldValue))?{
??????console.log(`set key:${key},value:${value}`);
??????//?trigger(target,?'set'?/*?SET?*/,?key,?value,?oldValue);
????}
????return?result;
??};
}
const?get?=?createGetter();
const?set?=?createSetter();
//?基礎的處理器對象
const?mutableHandlers?=?{
??get,
??set
??//?deleteProperty
};
//?暴露出去的方法,reactive
function?reactive(target)?{
??return?createReactiveObject(target,?mutableHandlers);
}
//?創(chuàng)建一個響應式對象
function?createReactiveObject(target,?baseHandlers)?{
??const?proxy?=?new?Proxy(target,?baseHandlers);
??return?proxy;
}
const?proxyObj?=?reactive({
??id:?1,
??name:?'front-refined',
??childObj:?{
????hobby:?'coding'
??}
});
proxyObj.childObj.hobby
// get key:childObj
// get key:hobby
proxyObj.childObj.hobby="play"
// get key:childObj
// set key:hobby,value:play
可以看見經(jīng)過 Vue 的“洗禮”之后,我們就可以攔截到 hobby 的 get/set 了。
不需要 Vue.set() 了
在 Vue3 我們已經(jīng)不需要用 Vue.set 方法來動態(tài)添加一個響應式 property,因為背后的實現(xiàn)機制已經(jīng)不同:
在 Vue2,使用了 Object.defineProperty 只能預先對某些屬性進行攔截,粒度較小。
在 Vue3,使用的 Proxy,攔截的是整個對象。
簡單用代碼解釋如:
//?Object.defineProperty
const?obj1?=?{};
Object.defineProperty(obj1,?'a',?{
??get()?{
????console.log('get1');
??},
??set()?{
????console.log('set1');
??}
});
obj1.b?=?2;
上面的代碼無任何輸出!
//?Proxy
const?obj2?=?{};
const?proxyObj2?=?new?Proxy(obj2,?{
??get()?{
????console.log('get2');
??},
??set()?{
????console.log('set2');
??}
});
proxyObj2.b?=?2;
//?set2
觸發(fā)了 set 訪問器。
2. shallowReactive
第一次看見這個 shallow 的字眼,我就聯(lián)想到了 React 中經(jīng)典的淺比較,這個「淺」的概念是一致的,讓我們來看下:
const?shallowReactiveObj?=?shallowReactive({
??id:?1,
??name:?'front-refiend',
??childObj:?{?hobby:?'coding'?}
});
//?改變?id?是響應式的
shallowReactiveObj.id?=?2;
//?改變嵌套對象的屬性是非響應式的,但是本身的值是有被改變的
shallowReactiveObj.childObj.hobby?=?'play';
我們看看在源碼中是怎么控制的,讓我們對上面的 reactive 精簡過的源碼加點東西(這里簡單用 // +++ 注釋來表示新增的代碼塊):
//?...
//?+++?新增了?shallow?入?yún)?br>//?閉包:生成一個 get 方法
function?createGetter(shallow?=?false)?{
??return?function?get(target,?key,?receiver)?{
????const?res?=?Reflect.get(target,?key,?receiver);
??? console.log(`get key:${key}`);
????//?track(target,?'get'?/*?GET?*/,?key);
????//?+++
????//?shallow=true,就直接?return?結(jié)果,所以不會深層追蹤
????if?(shallow)?{
??????return?res;
????}
????//?深層代理對象的關(guān)鍵?。?!判斷這個屬性是否是一個對象,是的話繼續(xù)代理動作,使對象內(nèi)部的值可追蹤
????if?(isObject(res))?{
??????return?reactive(res);
????}
????return?res;
??};
}
//?+++
const?shallowGet?=?createGetter(true);
//?+++
//?淺處理器對象,合并覆蓋基礎的處理器對象
const?shallowReactiveHandlers?=?Object.assign({},?mutableHandlers,?{
??get:?shallowGet
});
//?+++
//?暴露出去的方法,shallowReactive
function?shallowReactive(target)?{
??return?createReactiveObject(target,?shallowReactiveHandlers);
}
//?...
3. readonly
官網(wǎng):獲取一個對象 (響應式或純對象) 或 ref 并返回原始 proxy 的只讀 proxy。只讀 proxy 是深層的:訪問的任何嵌套 property 也是只讀的。
舉例:
const?proxyObj?=?reactive({
??childObj:?{
????hobby:?'coding'
??}
});
const?readonlyObj?=?readonly(proxyObj);
//?如果被拷貝對象?proxyObj?做了修改,打印?readonlyObj.childObj.hobby?也會看到有變更
proxyObj.childObj.hobby?=?'play';
console.log('readonlyObj.childObj.hobby?:>>?',?readonlyObj.childObj.hobby);
//?readonlyObj.childObj.hobby?:>>??play
//?只讀對象被改變,警告
readonlyObj.childObj.hobby?=?'play';
//????Set?operation?on?key?"hobby"?failed:?target?is?readonly.
在這個例子中,readonlyObj 與 proxyObj 共享所有,除了不能被改變。它的所有屬性也都是響應式的,讓我們再看下源碼,我們依然是對上面 reactive 精簡過的源碼加點東西:
//?+++?新增了?isReadonly?參數(shù)
//?閉包:生成一個 get 方法
function?createGetter(shallow?=?false,?isReadonly?=?false)?{
??return?function?get(target,?key,?receiver)?{
????const?res?=?Reflect.get(target,?key,?receiver);
??? console.log(`get key:${key}`);
????//?+++
????//?當前是只讀的情況,自己不會被改變,所以就沒必要進行追蹤變化
????if?(!isReadonly)?{
??????//?track(target,?"get"?/*?GET?*/,?key);
????}
????//?shallow=true,就直接?return?結(jié)果,所以不會深層追蹤
????if?(shallow)?{
??????return?res;
????}
????//?深層代理對象的關(guān)鍵?。?!判斷這個屬性是否是一個對象,是的話繼續(xù)代理動作,使對象內(nèi)部的值可追蹤
????if?(isObject(res))?{
??????//?+++
??????//?如果是只讀,也要同步進行深層代理
??????return?isReadonly???readonly(res)?:?reactive(res);
????}
????return?res;
??};
}
//?+++
const?readonlyGet?=?createGetter(false,?true);
//?+++
//?只讀處理器對象
const?readonlyHandlers?=?{
??get:?readonlyGet,
??//?只讀,不允許?set?,所以這里警告
??set(target,?key)?{
????{
??????console.warn(
????????`Set?operation?on?key?"${String(
??????????key
????????)}"?failed:?target?is?readonly.`,
????????target
??????);
????}
????return?true;
??}
};
//?+++
//?暴露出去的方法,readonly
function?readonly(target)?{
??return?createReactiveObject(target,?readonlyHandlers);
}
如上,新增了一個 isReadonly 參數(shù),用來標記是否進行深層代理。
上面的 readonly 例子就類似是“代理一個代理”,即:proxy(proxy(原始對象)),如圖:
我們平常接觸最多的子組件接收父組件傳遞的 props。它就是用 readonly 創(chuàng)建的,所以保持了只讀。要修改的話只能通過 emit 提交至父組件,從而保證了 Vue 傳統(tǒng)的單向數(shù)據(jù)流。
4. shallowReadonly
顧名思義,就是這個代理對象 shallow=true & readonly=true,那這樣會發(fā)生什么呢?
舉個例子:
const?shallowReadonlyObj?=?shallowReadonly({
??id:?1,
??name:?'front-refiend',
??childObj:?{?hobby:?'coding'?}
});
shallowReadonlyObj.id?=?2;
//????Set?operation?on?key?"id"?failed:?target?is?readonly.?
//?對象本身的屬性不能被修改
shallowReadonlyObj.childObj.hobby?=?'runnnig';
//?嵌套對象的屬性可以被修改,但是是非響應式的!
我們看看在源碼中是怎么控制的,讓我們繼續(xù)對上面的 reactive 精簡過的源碼加點東西:
//?...
//?+++
//?shallow=true?&?readonly=true
const?shallowReadonlyGet?=?createGetter(true,?true);
//?+++
//?淺只讀處理器對象,合并覆蓋?readonlyHandlers?處理器對象
const?shallowReadonlyHandlers?=?Object.assign({},?readonlyHandlers,?{
??get:?shallowReadonlyGet
});
//?+++
//?暴露出去的方法,shallowReadonly
function?shallowReadonly(target)?{
??return?createReactiveObject(target,?shallowReadonlyHandlers);
}
//?...
5. ref
個人覺得,ref 方法更加提升我們?nèi)ダ斫?js 中的引用類型。簡單的來講就是把一個簡單類型包裝成一個對象,使它可以被追蹤(響應式)。
ref 返回的是一個包含 .value 屬性的對象。
例子:
const?refNum?=?ref(1);
refNum.value++;
讓我們來扒一扒背后的實現(xiàn)原理(精簡了 ref 相關(guān)源碼):
//?工具方法:值是否改變,改變才觸發(fā)更新
const?hasChanged?=?(value,?oldValue)?=>
??value?!==?oldValue?&&?(value?===?value?||?oldValue?===?oldValue);
//?工具方法:判斷是否是一個對象(注:typeof 數(shù)組?也等于?'object'
const?isObject?=?val?=>?val?!==?null?&&?typeof?val?===?'object';
//?工具方法:判斷傳入的值是否是一個對象,是的話就用 reactive 來代理
const?convert?=?val?=>?(isObject(val)???reactive(val)?:?val);
function?toRaw(observed)?{
??return?(observed?&&?toRaw(observed['__v_raw'?/*?RAW?*/]))?||?observed;
}
//?ref?實現(xiàn)類
class?RefImpl?{
??constructor(_rawValue,?_shallow?=?false)?{
????this._rawValue?=?_rawValue;
????this._shallow?=?_shallow;
????this.__v_isRef?=?true;
????this._value?=?_shallow???_rawValue?:?convert(_rawValue);
??}
??get?value()?{
????//?track(toRaw(this),?'get'?/*?GET?*/,?'value');
????return?this._value;
??}
??set?value(newVal)?{
????if?(hasChanged(toRaw(newVal),?this._rawValue))?{
??????this._rawValue?=?newVal;
??????this._value?=?this._shallow???newVal?:?convert(newVal);
??????//?trigger(toRaw(this),?'set'?/*?SET?*/,?'value',?newVal);
????}
??}
}
//?創(chuàng)建一個?ref
function?createRef(rawValue,?shallow?=?false)?{
??return?new?RefImpl(rawValue,?shallow);
}
//?暴露出去的方法,ref
function?ref(value)?{
??return?createRef(value);
}
//?暴露出去的方法,shallowRef
function?shallowRef(value)?{
??return?createRef(value,?true);
}
核心類 RefImpl ,我們可以看到在類中使用了經(jīng)典的 get/set 存取器,來進行追蹤和觸發(fā)。convert 方法讓我們知道了 ref 不僅僅用來包裝一個值類型,也可以是一個對象/數(shù)組,然后把對象/數(shù)組再交給 reactive 進行代理。直接看個例子:
const?refArr?=?ref([1,?2,?3]);
const?refObj?=?ref({?id:?1,?name:?'front-refined'?});
//?操作它們
refArr.value.push(1);
refObj.value.id?=?2;
6. unref
展開一個 ref:判斷參數(shù)為 ref ,則返回 .value ,否則返回參數(shù)本身。
源碼:
function?isRef(r)?{
??return?Boolean(r?&&?r.__v_isRef?===?true);
}
function?unref(ref)?{
??return?isRef(ref)???ref.value?:?ref;
}
為了方便開發(fā),Vue 處理了在 template 中用到的 ref 將會被自動展開,也就是不用寫 .value 了,背后的實現(xiàn),讓我們一起來看一下:
這里用「模擬」的方式來闡述,核心邏輯沒有改變~
//?模擬:在 setup 內(nèi)定義一個 ref
const?num?=?ref(1);
//?模擬:在 setup 返回,提供 template 使用
function?setup()?{
??return?{?num?};
}
//?模擬:接收了 setup 返回的對象
const?setupReturnObj?=?setup();
//?定義處理器對象,get?訪問器里的?unref?是關(guān)鍵
const?shallowUnwrapHandlers?=?{
??get:?(target,?key,?receiver)?=>
????unref(Reflect.get(target,?key,?receiver)),
??set:?(target,?key,?value,?receiver)?=>?{
????const?oldValue?=?target[key];
????if?(isRef(oldValue)?&&?!isRef(value))?{
??????oldValue.value?=?value;
??????return?true;
????}?else?{
??????return?Reflect.set(target,?key,?value,?receiver);
????}
??}
};
//?模擬:返回組件實例上下文
const?ctx?=?new?Proxy(setupReturnObj,?shallowUnwrapHandlers);
//?模擬:template 最終被編譯成 render 函數(shù)
/*?
??
????
????num:{{num}}
??
??*/
function?render(ctx)?{
??with?(ctx)?{
????//?模擬:在template中,進行賦值動作?"onUpdate:modelValue":?$event =>?(num =?$event)
????//?num?=?666;
????//?模擬:在template中,進行讀取動作?{{num}}
????console.log('num?:>>?',?num);
??}
}
render(ctx);
//?模擬:在 setup 內(nèi)部進行賦值動作
num.value?+=?1;
//?模擬:num 改變 trigger 視圖渲染effect,更新視圖
render(ctx);
7. shallowRef
ref 的介紹已經(jīng)包含了 shallowRef 方法的實現(xiàn):this._value = _shallow ? _rawValue : convert(_rawValue);
如果傳入的 shallow 值為 true 那么直接返回傳入的原始值,也就是說,不會再去深層代理對象了,讓我們來看兩個場景:
傳入的是一個對象
const?shallowRefObj?=?shallowRef({
??id:?1,
??name:?'front-refiend',
});
上面的對象加工之后,我們可以簡單的理解成:
const?shallowRefObj?=?{
??value:?{
????id:?1,
????name:?'front-refiend'
??}
};
既然是 shallow(淺層)那就止于 value ,不再進行深層代理。也就是說,對于嵌套對象的屬性不會進行追蹤,但是我們修改 shallowRefObj 本身的 value 屬性還是響應式的,如:shallowRefObj.value = 'hello~';
傳入的是一個簡單類型
const?shallowRefNum?=?shallowRef(1);
當傳入的值是一個簡單類型時候,結(jié)合這兩句代碼:const convert = val => (isObject(val) ? reactive(val) : val); ,this._value = _shallow ? _rawValue : convert(_rawValue);
我們就可以知道 shallowRef 和 ref 對于入?yún)⑹且粋€簡單類型時,其最終效果是一致的。
8. triggerRef
個人覺得這個 API 理解起來較為抽象,小伙伴們一起仔細琢磨琢磨~
triggerRef 是和 shallowRef 配合使用的,例子:
const?shallowRefObj?=?shallowRef({
??name:?'front-refined'
});
//?這里不會觸發(fā)副作用,因為是這個?ref?是淺層的
shallowRefObj.value.name?=?'hello~';
//?手動執(zhí)行與 shallowRef 關(guān)聯(lián)的任何副作用,這樣子就能觸發(fā)了。
triggerRef(shallowRefObj);
看下背后的實現(xiàn)原理:
在開篇我們有講到的 effect 這個概念,假設當前正在走 視圖渲染effect 。
template 綁定的了值,如:
<template>?{{shallowRefObj.name}}?template>
當執(zhí)行 “render” 時,就會讀取到了 shallowRefObj.value.name ,由于當前的 ref 是淺層的,只能追蹤到 value 的變化,所以在 value 的 get 方法進行 track 如:track(toRaw(this), "get" /* GET */, 'value');
track 方法源碼精簡:
//?targetMap?是一個大集合
//?activeEffect?表示當前正在走的?effect?,假設當前是?視圖渲染effect
function?track(target,?type,?key)?{
??let?depsMap?=?targetMap.get(target);
??if?(!depsMap)?{
????targetMap.set(target,?(depsMap?=?new?Map()));
??}
??let?dep?=?depsMap.get(key);
??if?(!dep)?{
????depsMap.set(key,?(dep?=?new?Set()));
??}
??if?(!dep.has(activeEffect))?{
????dep.add(activeEffect);
??}
}
打印 targetMap

也就是說,如果 ?shallowRefObj.value 有改變就可以 trigger 視圖渲染effect 來更新視圖,或著我們也可以手動 trigger 它。
但是,我們目前改變的是 shallowRefObj.value.name = 'hello~';,所以我們要 “騙” trigger 方法。手動 trigger,只要我們的入?yún)α?,就會響應式更新視圖了,看一下 triggerRef 與 trigger 的源碼:
function?triggerRef(ref)?{
??trigger(toRaw(ref),?'set'?/*?SET?*/,?'value',?ref.value);
}
//?trigger?響應式觸發(fā)
function?trigger(target,?type,?key,?newValue,?oldValue,?oldTarget)?{
??const?depsMap?=?targetMap.get(target);
??if?(!depsMap)?{
????//?沒有被追蹤,直接?return
????return;
??}
??//?拿到了?視圖渲染effect?就可以進行排隊更新?effect?了
??const?run?=?depsMap.get(key);
??/*?開始執(zhí)行?effect,這里做了很多事...?*/
??run();?
}
我們用 target 和 key 拿到了 視圖渲染的effect。至此,就可以完成一個手動更新了~
9. customRef
自定義的 ref 。這個 API 就更顯式的讓我們了解 track 與 trigger,看個例子:
<template>
??<div>name:{{name}}div>
??<input?v-model="name"?/>
template>
//?...
setup()?{
??let?value?=?'front-refined';
??//?參數(shù)是一個工廠函數(shù)
??const?name?=?customRef((track,?trigger)?=>?{
????return?{
??????get()?{
????????//?收集依賴它的?effect
????????track();
????????return?value;
??????},
??????set(newValue)?{
????????value?=?newValue;
????????//?觸發(fā)更新依賴它的所有?effect
????????trigger();
??????}
????};
??});
??return?{
????name
??};
}
讓我們看下源碼實現(xiàn):
//?自定義ref?實現(xiàn)類
class?CustomRefImpl?{
??constructor(factory)?{
????this.__v_isRef?=?true;
????const?{?get,?set?}?=?factory(
??????()?=>?track(this,?'get'?/*?GET?*/,?'value'),
??????()?=>?trigger(this,?'set'?/*?SET?*/,?'value')
????);
????this._get?=?get;
????this._set?=?set;
??}
??get?value()?{
????return?this._get();
??}
??set?value(newVal)?{
????this._set(newVal);
??}
}
function?customRef(factory)?{
??return?new?CustomRefImpl(factory);
}
結(jié)合我們上面有提過的 ref 源碼相關(guān),我們可以看到 customRef 只是把 ref 內(nèi)部的實現(xiàn),更顯式的暴露出來,讓我們更靈活的控制。比如可以延遲 trigger ,如:
//?...
set(newValue)?{
??clearTimeout(timer);
??timer?=?setTimeout(()?=>?{
????value?=?newValue;
????//?觸發(fā)更新依賴它的所有?effect
????trigger();
??},?2000);
}
//?...
10. toRef
可以用來為響應式對象上的 property 新創(chuàng)建一個 ref ,從而保持對其源 property 的響應式連接。舉個例子:
假設我們傳遞給一個組合式函數(shù)一個響應式數(shù)據(jù),在組合式函數(shù)內(nèi)部就可以響應式的修改它:
//?1.?傳遞整個響應式對象
function?useHello(state)?{
??state.name?=?'hello~';
}
//?2.?傳遞一個具體的?ref
function?useHello2(name)?{
??name.value?=?'hello~';
}
export?default?{
??setup()?{
????const?state?=?reactive({
??????id:?1,
??????name:?'front-refiend'
????});
????//?1.?直接傳遞整個響應式對象
????useHello(state);
????//?2.?傳遞一個新創(chuàng)建的?ref
????useHello2(toRef(state,?'name'));
??}
};
讓我們看下源碼實現(xiàn):
//?ObjectRef?實現(xiàn)類
class?ObjectRefImpl?{
??constructor(_object,?_key)?{
????this._object?=?_object;
????this._key?=?_key;
????this.__v_isRef?=?true;
??}
??get?value()?{
????return?this._object[this._key];
??}
??set?value(newVal)?{
????this._object[this._key]?=?newVal;
??}
}
//?暴露出去的方法
function?toRef(object,?key)?{
??return?new?ObjectRefImpl(object,?key);
}
即使 name 屬性不存在,toRef 也會返回一個可用的 ref,如:我們在上面那個例子指定了一個對象沒有的屬性:
useHello2(toRef(state,?'other'));
這個動作就相當于往對象新增了一個屬性 other,且會響應式。
11. toRefs
toRefs 底層就是 toRef。
將響應式對象轉(zhuǎn)換為普通對象,其中結(jié)果對象的每個 property 都是指向原始對象相應 property 的 ref,保持對其源 property 的響應式連接。
toRefs 的出現(xiàn)其實也是為了開發(fā)上的便利。讓我們直接來看看它的幾個使用場景:
解構(gòu) props
export?default?{
??props:?{
????id:?Number,
????name:?String
??},
??setup(props,?ctx)?{
????const?{?id,?name?}?=?toRefs(props);
????watch(id,?()?=>?{
??????console.log('id?change');
????});
????
????//?沒有使用?toRefs?的話,需要通過這種方式監(jiān)聽
????watch(
??????()?=>?props.id,
??????()?=>?{
????????console.log('id?change');
??????}
????);
??}
};
這樣子我們就能保證能監(jiān)聽到 id 的變化(沒有使用 toRefs 的解構(gòu)是不行的),因為通過 toRefs 方法之后,id 其實就是一個 ref 對象。
setup return 時轉(zhuǎn)換
<template>
??<div>id:{{id}}div>
??<div>name:{{name}}div>
template>
//?...
setup()?{
??const?state?=?reactive({
????id:?1,
????name:?'front-refiend'
??});
??return?{
????...toRefs(state)
??};
}
這樣的寫法我們就更加方便的在模板上直接寫對應的值,而不需要 ?{{state.id}} , {{state.name}}
讓我們看下源碼:
function?toRefs(object)?{
??const?ret?=?{};
??for?(const?key?in?object)?{
????ret[key]?=?toRef(object,?key);
??}
??return?ret;
}
12. compouted
開頭有講過,compouted 是一個 “計算屬性effect” 。它依賴響應式基礎數(shù)據(jù),當數(shù)據(jù)變化時候會觸發(fā)它的更新。computed 主要的靚點就是緩存了,可以緩存性能開銷比較大的計算。它返回一個 ref 對象。
讓我們一起來看一個 computed 閉環(huán)的精簡源碼(主要是了解思路,雖然精簡了,但代碼還是有一丟丟多,不夠看完你肯定有收獲。直接 copy 可以運行哦~):
<body>
??<fieldset>
????<legend>包含get/set方法的?computedlegend>
????<button?onclick="handleChangeFirsttName()">changeFirsttNamebutton>
????<button?onclick="handleChangeLastName()">changeLastNamebutton>
????<button?onclick="handleSetFullName()">setFullNamebutton>
??fieldset>
??<fieldset>
????<legend>只讀?computedlegend>
????<button?onclick="handleAddCount1()">handleAddCount1button>
????<button?onclick="handleSetCount()">handleSetCountbutton>
??fieldset>
??<script>
????//?大集合,存放依賴相關(guān)
????const?targetMap?=?new?WeakMap();
????//?當前正在走的?effect
????let?activeEffect;
????//?精簡:創(chuàng)建一個 effect
????const?createReactiveEffect?=?(fn,?options)?=>?{
??????const?effect?=?function?reactiveEffect()?{
????????try?{
??????????activeEffect?=?effect;
??????????return?fn();
????????}?finally?{
??????????//?當前的?effect?走完之后(相關(guān)的依賴收集完畢之后),就退出
??????????activeEffect?=?undefined;
????????}
??????};
??????effect.options?=?options;
??????//?該副作用的依賴集合
??????effect.deps?=?[];
??????return?effect;
????};
????//#region 精簡:ref 方法
????//?工具方法:值是否改變,改變才觸發(fā)更新
????const?hasChanged?=?(value,?oldValue)?=>
??????value?!==?oldValue?&&?(value?===?value?||?oldValue?===?oldValue);
????//?ref?實現(xiàn)類
????class?RefImpl?{
??????constructor(_rawValue)?{
????????this._rawValue?=?_rawValue;
????????this.__v_isRef?=?true;
????????this._value?=?_rawValue;
??????}
??????get?value()?{
????????track(this,?'get',?'value');
????????return?this._value;
??????}
??????set?value(newVal)?{
????????if?(hasChanged(newVal,?this._rawValue))?{
??????????this._rawValue?=?newVal;
??????????this._value?=?newVal;
??????????trigger(this,?'set',?'value',?newVal);
????????}
??????}
????}
????//?創(chuàng)建一個?ref
????function?createRef(rawValue)?{
??????return?new?RefImpl(rawValue);
????}
????//?暴露出去的方法,ref
????function?ref(value)?{
??????return?createRef(value);
????}
????//#endregion
????//#region 精簡:track、trigger
????const?track?=?(target,?type,?key)?=>?{
??????if?(activeEffect?===?undefined)?{
????????return;
??????}
??????let?depsMap?=?targetMap.get(target);
??????if?(!depsMap)?{
????????targetMap.set(target,?(depsMap?=?new?Map()));
??????}
??????let?dep?=?depsMap.get(key);
??????if?(!dep)?{
????????depsMap.set(key,?(dep?=?new?Set()));
??????}
??????if?(!dep.has(activeEffect))?{
????????dep.add(activeEffect);
????????//?存儲該副作用相關(guān)依賴集合
????????activeEffect.deps.push(dep);
??????}
????};
????const?trigger?=?(target,?type,?key,?newValue)?=>?{
??????const?depsMap?=?targetMap.get(target);
??????if?(!depsMap)?{
????????//?沒有被追蹤,直接?return
????????return;
??????}
??????const?effects?=?depsMap.get(key);
??????const?run?=?effect?=>?{
????????if?(effect.options.scheduler)?{
??????????//?調(diào)度執(zhí)行
??????????effect.options.scheduler();
????????}
??????};
??????effects.forEach(run);
????};
????//#endregion
????//#region 精簡:computed 方法
????const?isFunction?=?val?=>?typeof?val?===?'function';
????//?暴露出去的方法
????function?computed(getterOrOptions)?{
??????let?getter;
??????let?setter;
??????if?(isFunction(getterOrOptions))?{
????????getter?=?getterOrOptions;
????????setter?=?()?=>?{
??????????//?提示,當前的?computed?如果是只讀的,也就是說沒有在調(diào)用的時候傳入?set?方法
??????????console.warn('Write?operation?failed:?computed?value?is?readonly');
????????};
??????}?else?{
????????getter?=?getterOrOptions.get;
????????setter?=?getterOrOptions.set;
??????}
??????return?new?ComputedRefImpl(getter,?setter);
????}
????//?computed?核心方法
????class?ComputedRefImpl?{
??????constructor(getter,?_setter)?{
????????this._setter?=?_setter;
????????this._dirty?=?true;
????????this.effect?=?createReactiveEffect(getter,?{
??????????scheduler:?()?=>?{
????????????//?依賴的數(shù)據(jù)改變了,標記為臟值,等?get?value?時進行計算獲取
????????????if?(!this._dirty)?{
??????????????this._dirty?=?true;
????????????}
??????????}
????????});
??????}
??????get?value()?{
????????//?臟值需要計算?_dirty=true?代表需要計算
????????if?(this._dirty)?{
??????????console.log('臟值,需要計算...');
??????????this._value?=?this.effect();
??????????//?標記臟值為?false,進行緩存值(下次獲取時,不需要計算)
??????????this._dirty?=?false;
????????}
????????return?this._value;
??????}
??????set?value(newValue)?{
????????this._setter(newValue);
??????}
????}
????//#endregion
????//#region?例子
????//?1.?創(chuàng)建一個只讀?computed
????const?count1?=?ref(0);
????const?count?=?computed(()?=>?{
??????return?count1.value?*?10;
????});
????const?handleAddCount1?=?()?=>?{
??????count1.value++;
??????console.log('count.value?:>>?',?count.value);
????};
????const?handleSetCount?=?()?=>?{
??????count.value?=?1000;
????};
????//?2.?創(chuàng)建一個包含?get/set?方法的?computed
????//?獲取的?computed?數(shù)據(jù)
????const?consoleFullName?=?()?=>
??????console.log('fullName.value?:>>?',?fullName.value);
????const?firsttName?=?ref('san');
????const?lastName?=?ref('zhang');
????const?fullName?=?computed({
??????get:?()?=>?firsttName.value?+?'.'?+?lastName.value,
??????set:?val?=>?{
????????lastName.value?+=?val;
??????}
????});
????//?改變依賴的值觸發(fā)?computed?更新
????const?handleChangeFirsttName?=?()?=>?{
??????firsttName.value?=?'si';
??????consoleFullName();
????};
????//?改變依賴的值觸發(fā)?computed?更新
????const?handleChangeLastName?=?()?=>?{
??????lastName.value?=?'li';
??????consoleFullName();
????};
????//?觸發(fā)?fullName?set,如果?computed?為只讀就警告
????const?handleSetFullName?=?()?=>?{
??????fullName.value?=?'?happy?niu?year~';
??????consoleFullName();
????};
????//?必須要有讀取行為,才會進行依賴收集。當依賴改變時候,才會響應式更新!
????consoleFullName();
????//#endregion
??script>
body>
computed 的閉環(huán)流程是這樣子的:
computed 創(chuàng)建的 ref 對象初次被調(diào)用 get(讀 computed 的 value),會進行依賴收集,當依賴改變時,調(diào)度執(zhí)行觸發(fā) dirty = true,標記臟值,需要計算。下一次再去調(diào)用 computed 的 get 時候,就需要重新計算獲取新值,如此反復。
13. watch
關(guān)于 watch ,這里直接先上一段稍長的源碼例子(代碼挺長,但是都是精簡過的,而且有注釋分塊。小伙伴們耐心看,copy 可以直接運行哦~)
<body>
??<button?onclick="handleChangeCount()">點我觸發(fā)watchbutton>
??<button?onclick="handleChangeCount2()">點我觸發(fā)watchEffectbutton>
??<script>
????//?大集合,存放依賴相關(guān)
????const?targetMap?=?new?WeakMap();
????//?當前正在走的?effect
????let?activeEffect;
????//?精簡:創(chuàng)建一個 effect
????const?createReactiveEffect?=?(fn,?options)?=>?{
??????const?effect?=?function?reactiveEffect()?{
????????try?{
??????????activeEffect?=?effect;
??????????return?fn();
????????}?finally?{
??????????//?當前的?effect?走完之后(相關(guān)的依賴收集完畢之后),就退出
??????????activeEffect?=?undefined;
????????}
??????};
??????effect.options?=?options;
??????//?該副作用的依賴集合
??????effect.deps?=?[];
??????return?effect;
????};
????//#region 精簡:ref 方法
????//?工具方法:判斷是否是一個 ref 對象
????const?isRef?=?r?=>?{
??????return?Boolean(r?&&?r.__v_isRef?===?true);
????};
????//?工具方法:值是否改變,改變才觸發(fā)更新
????const?hasChanged?=?(value,?oldValue)?=>
??????value?!==?oldValue?&&?(value?===?value?||?oldValue?===?oldValue);
????//?工具方法:判斷是否是一個方法
????const?isFunction?=?val?=>?typeof?val?===?'function';
????//?ref?實現(xiàn)類
????class?RefImpl?{
??????constructor(_rawValue)?{
????????this._rawValue?=?_rawValue;
????????this.__v_isRef?=?true;
????????this._value?=?_rawValue;
??????}
??????get?value()?{
????????track(this,?'get',?'value');
????????return?this._value;
??????}
??????set?value(newVal)?{
????????if?(hasChanged(newVal,?this._rawValue))?{
??????????this._rawValue?=?newVal;
??????????this._value?=?newVal;
??????????trigger(this,?'set',?'value',?newVal);
????????}
??????}
????}
????//?創(chuàng)建一個?ref
????function?createRef(rawValue)?{
??????return?new?RefImpl(rawValue);
????}
????//?暴露出去的方法,ref
????function?ref(value)?{
??????return?createRef(value);
????}
????//#endregion
????//#region 精簡:track、trigger
????const?track?=?(target,?type,?key)?=>?{
??????if?(activeEffect?===?undefined)?{
????????return;
??????}
??????let?depsMap?=?targetMap.get(target);
??????if?(!depsMap)?{
????????targetMap.set(target,?(depsMap?=?new?Map()));
??????}
??????let?dep?=?depsMap.get(key);
??????if?(!dep)?{
????????depsMap.set(key,?(dep?=?new?Set()));
??????}
??????if?(!dep.has(activeEffect))?{
????????dep.add(activeEffect);
????????//?存儲該副作用相關(guān)依賴集合
????????activeEffect.deps.push(dep);
??????}
????};
????const?trigger?=?(target,?type,?key,?newValue)?=>?{
??????const?depsMap?=?targetMap.get(target);
??????if?(!depsMap)?{
????????//?沒有被追蹤,直接?return
????????return;
??????}
??????const?effects?=?depsMap.get(key);
??????const?run?=?effect?=>?{
????????if?(effect.options.scheduler)?{
??????????//?調(diào)度執(zhí)行
??????????effect.options.scheduler();
????????}
??????};
??????effects.forEach(run);
????};
????//#endregion
????//#region?停止監(jiān)聽相關(guān)
????//?停止偵聽,如果有?onStop?方法一并調(diào)用,onStop?也就是?onInvalidate?回調(diào)方法
????function?stop(effect)?{
??????cleanup(effect);
??????if?(effect.options.onStop)?{
????????effect.options.onStop();
??????}
????}
????//?清空改?effect?收集的依賴相關(guān),這樣子改變了就不再繼續(xù)觸發(fā)了,也就是“停止偵聽”
????function?cleanup(effect)?{
??????const?{?deps?}?=?effect;
??????if?(deps.length)?{
????????for?(let?i?=?0;?i???????????deps[i].delete(effect);
????????}
????????deps.length?=?0;
??????}
????}
????//#endregion
????//#region?暴露出去的?watchEffect?方法
????function?watchEffect(effect,?options)?{
??????return?doWatch(effect,?null,?options);
????}
????//#endregion
????//#region?暴露出去的?watch?方法
????function?watch(source,?cb,?options)?{
??????return?doWatch(source,?cb,?options);
????}
????function?doWatch(source,?cb,?{?immediate,?deep?}?=?{})?{
??????let?getter;
??????//?判斷是否?ref?對象
??????if?(isRef(source))?{
????????getter?=?()?=>?source.value;
??????}
??????//?判斷是一個?reactive?對象,默認遞歸追蹤?deep=true
??????else?if?(/*isReactive(source)*/?0)?{
????????//?省略...
????????//?getter??=?()?=>?source;
????????//?deep?=?true;
??????}
??????//?判斷是一個數(shù)組,也就是?Vue3?新的特性,watch?可以以數(shù)組的方式偵聽
??????else?if?(/*isArray(source)*/?0)?{
????????//?省略...
??????}
??????//?判斷是否是一個方法,這樣子的入?yún)?/span>
??????else?if?(isFunction(source))?{
????????debugger;
????????//?這里是類似這樣子的入?yún)ⅲ?)?=>?proxyObj.id
????????if?(cb)?{
??????????//?省略...
????????}?else?{
??????????//?cb?為?null,表示當前為?watchEffect
??????????getter?=?()?=>?{
????????????if?(cleanup)?{
??????????????cleanup();
????????????}
????????????return?source(onInvalidate);
??????????};
????????}
??????}
??????//?判斷是否?deep?就會遞歸追蹤
??????if?(/*cb?&&?deep*/?0)?{
????????//?const?baseGetter?=?getter;
????????//?getter?=?()?=>?traverse(baseGetter());
??????}
??????//?清理?effect
??????let?cleanup;
??????const?onInvalidate?=?fn?=>?{
????????cleanup?=?runner.options.onStop?=?()?=>?{
??????????fn();
????????};
??????};
??????let?oldValue?=?undefined;
??????const?job?=?()?=>?{
????????if?(cb)?{
??????????//?獲取改變改變后的新值
??????????const?newValue?=?runner();
??????????if?(hasChanged(newValue,?oldValue))?{
????????????if?(cleanup)?{
??????????????cleanup();
????????????}
????????????//?觸發(fā)回調(diào)
????????????cb(newValue,?oldValue,?onInvalidate);
????????????//?把新值賦值給舊值
????????????oldValue?=?newValue;
??????????}
????????}?else?{
??????????//?watchEffect
??????????runner();
????????}
??????};
??????//?調(diào)度
??????let?scheduler;
??????//?default:?'pre'
??????scheduler?=?()?=>?{
????????job();
??????};
??????//?創(chuàng)建一個?effect,調(diào)用?runner?其實就是在進行依賴收集
??????const?runner?=?createReactiveEffect(getter,?{
????????scheduler
??????});
??????//?初始化?run
??????if?(cb)?{
????????if?(immediate)?{
??????????job();
????????}?else?{
??????????oldValue?=?runner();
????????}
??????}?else?{
????????//?watchEffect?默認立即執(zhí)行
????????runner();
??????}
??????//?返回一個方法,調(diào)用即停止偵聽
??????return?()?=>?{
????????stop(runner);
??????};
????}
????//#endregion
????//#region?例子
????//?1.?watch?例子
????const?count?=?ref(0);
????const?myStop?=?watch(
??????count,
??????(val,?oldVal,?onInvalidate)?=>?{
????????onInvalidate(()?=>?{
??????????console.log('watch-clear...');
????????});
????????console.log('watch-val?:>>?',?val);
????????console.log('watch-oldVal?:>>?',?oldVal);
??????},
??????{?immediate:?true?}
????);
????//?改變依賴的值觸發(fā)?觸發(fā)偵聽器回調(diào)
????const?handleChangeCount?=?()?=>?{
??????count.value++;
????};
????//?停止偵聽
????//?myStop();
????//?2.?watchEffect?例子
????const?count2?=?ref(0);
????watchEffect(()?=>?{
??????console.log('watchEffect-count2.value?:>>?',?count2.value);
????});
????//?改變依賴的值觸發(fā)?觸發(fā)偵聽器回調(diào)
????const?handleChangeCount2?=?()?=>?{
??????count2.value++;
????};
????//#endregion
??script>
body>
以上的代碼簡單的實現(xiàn)了 watch 監(jiān)聽 ref 對象的例子,那么我們該如何去正確的使用 watch 呢?讓我們一起結(jié)合源碼一起看兩點:
關(guān)于偵聽源的寫法,官網(wǎng)有描述,可以是返回值的 getter 函數(shù),也可以直接是 ref,也就是:
const?state?=?reactive({?id:?1?});
//?使用
()?=>?state.id
//?或
const?count?=?ref(0);
//?使用?count
count
//?看完源碼,我們也可以這樣子寫~
()?=>?count.value
結(jié)合源碼,我們發(fā)現(xiàn)也可以直接偵聽一個 reactive 對象,而且默認會進進行深度監(jiān)聽(deep=true),會對對象進行遞歸遍歷追蹤。但是偵聽一個數(shù)組的話,只有當數(shù)組被替換時才會觸發(fā)回調(diào)。如果你需要在數(shù)組改變時觸發(fā)回調(diào),必須指定?deep?選項。當沒有指定 deep = true:
const?arr?=?ref([1,?2,?3]);
//?只有這種方式才會生效
arr.value?=?[4,?5,?6];
//?其他的無法觸發(fā)回調(diào)
arr.value[0]?=?111;
arr.value.push(4);
個人建議盡量避免深度偵聽,因為這可能會影響性能,大部分場景我們都可以使用偵聽一個 getter 的方式,比如需要偵聽數(shù)組的變化 () => arr.value.length。如果你想要同時監(jiān)聽一個對象多個值的變化,Vue3 提供了數(shù)組的操作:
watch(
??[()?=>?state.id,?()?=>?state.name],
??([id,?name],?[oldId,?oldName])?=>?{
????/*?...?*/
??}
);
watch 返回值也就是一個停止偵聽的方法,它與 onInvalidate 本質(zhì)是不同的,當我們調(diào)用了停止偵聽,底層是做了移除當前清空該 effect 收集的依賴集合,這樣子依賴數(shù)據(jù)改變了就不再繼續(xù)觸發(fā)了,也就是“停止偵聽”。而 onInvalidate,個人認為,它就是提供了一個在回調(diào)之前的操作,具體的例子,可以參考之前寫過的一篇文章《Vue3丨從 5 個維度來講 Vue3》 變化 詳情看 watchEffect vs watch 內(nèi)容。
14. watchEffect
和 watch 共享底層代碼,在 watch 分析中我們已經(jīng)有體現(xiàn)了,小伙伴們可以往上再看看,這里不再贅述~
看了那么多有些許復雜的源碼之后,讓我們來輕松一下,來看下 Vue3 一些響應式 API 的小工具。小伙伴應該都有看到一些源碼中帶有 `__v_` 前綴的屬性,其實這些屬性是用來做一些判斷的標識,讓我們一起來看看:
15. isReadonly
檢查對象是否是由 readonly 創(chuàng)建的只讀 proxy。
function?isReadonly(value)?{
????return?!!(value?&&?value["__v_isReadonly"?/*?IS_READONLY?*/]);
}
//?readonly
const?originalObj?=?reactive({?id:?1?});
const?copyObj?=?readonly(originalObj);
isReadonly(copyObj);?//?true
//?只讀?computed?
const?firsttName?=?ref('san');
const?lastName?=?ref('zhang');
const?fullName?=?computed(
??()?=>?firsttName.value?+?'?'?+?lastName.value
);
isReadonly(fullName);?//?true
其實在創(chuàng)建一個 get 訪問器的時候,利用閉包就已經(jīng)記錄了,然后通過對應的 key 去獲取,如:
function?createGetter(isReadonly?=?false,?shallow?=?false)?{
??return?function?get(target,?key,?receiver)?{
????//?...
????if?(key?===?'__v_isReadonly')?{
??????return?isReadonly;
????}
????//?...
??};
}
16. isReactive
檢查對象是否是 reactive 創(chuàng)建的響應式 proxy。
function?isReactive(value)?{
????if?(isReadonly(value))?{
????????return?isReactive(value["__v_raw"?/*?RAW?*/]);
????}
????return?!!(value?&&?value["__v_isReactive"?/*?IS_REACTIVE?*/]);
}
createGetter 方法判斷相關(guān):
//?...
if?(key?===?'__v_isReactive'?/*?IS_REACTIVE?*/)?{
??return?!isReadonly;
}?else?if?(key?===?'__v_isReadonly'?/*?IS_READONLY?*/)?{
??return?isReadonly;
}
//?...?
17. isProxy
檢查對象是否是由 reactive 或 readonly 創(chuàng)建的 proxy。
function?isProxy(value)?{
????return?isReactive(value)?||?isReadonly(value);
}
18. toRaw
toRaw 可以用來打印原始對象,有時候我們在調(diào)試查看控制臺的時候,就比較方便。
function?toRaw(observed)?{
????return?((observed?&&?toRaw(observed["__v_raw"?/*?RAW?*/]))?||?observed);
}
toRaw 對于轉(zhuǎn)換 ref 對象,仍然保留包裝過的對象,例子:
const?obj?=?reactive({?id:?1,?name:?'front-refiend'?});
console.log(toRaw(obj));
//?{id:?1,?name:?"front-refiend"}
const?count?=?ref(0);
console.log(toRaw(count));
//?{__v_isRef:?true,?_rawValue:?0,?_shallow:?false,?_value:?0,?value:?0}
createGetter 方法判斷相關(guān):
//?...
if?(
??key?===?'__v_raw'?/*?RAW?*/?&&
??receiver?===?reactiveMap.get(target)
)?{
??return?target;
}
//?...
我們可以在 createGetter 時就會把對象用 {key:原始對象,value:proxy 代理對象} 這樣子的形式存放于 reactiveMap ,然后根據(jù)鍵來取值。
19. markRaw
標記一個對象,使其永遠不會轉(zhuǎn)換為 proxy。返回對象本身。
const?def?=?(obj,?key,?value)?=>?{
????Object.defineProperty(obj,?key,?{
????????configurable:?true,
????????enumerable:?false,
????????value
????});
};
function?markRaw(value)?{
????//?標記跳過對該對象的代理
????def(value,?"__v_skip"?/*?SKIP?*/,?true);
????return?value;
}
createReactiveObject 方法相關(guān):
function?createReactiveObject(target)?{
??//...
??//?判斷對象中是否含有?__v_skip?屬性是的話,直接返回對象本身
??if?(target['__v_skip'])?{
????return?target;
??}
??const?proxy?=?new?Proxy(target);
??//?...
??return?proxy;
}
20. isRef
判斷是否是 ref 對象。__v_isRef 標識就是我們在創(chuàng)建 ref 的時候在 RefImpl實現(xiàn)類里賦值的 this.__v_isRef = true;
function?isRef(r)?{
????return?Boolean(r?&&?r.__v_isRef?===?true);
}
總結(jié)
以上的 20 個API,在我們項目實戰(zhàn)中,有些也許幾乎沒有用到。因為有部分API,是 Vue3 整個框架設計有使用到的。對于我們的業(yè)務場景來說,目前使用頻次較高的應該是 reactive,ref,computed,watch,toRefs...
理解所有響應式 API 對于我們在編碼會更加有自信,不會有那么多的疑惑。也幫助我們更加理解框架的底層,如:proxy 怎么用的?Vue3 怎么追蹤一個簡單類型的?怎樣去編碼才能讓我們系統(tǒng)更優(yōu)。這才是本文分析這幾個 API 的初衷。
怎么樣,你了解這 20 個響應式 API 了嗎?
?? 前端精,求關(guān)注~
2021年,公眾號關(guān)注「前端精」(front-refined),我們一起學 Vue3,用 Vue3,深入 Vue3 。
最后,祝小伙伴們新年快樂,開開心心過春節(jié)~
感謝您的點贊和在看?????
