<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Vue3丨進一步了解這 20 個響應式 API,寫碼如有神

          共 31489字,需瀏覽 63分鐘

           ·

          2021-02-08 15:49

          (關(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);
          ??}
          });

          我們來分析兩件事:

          1. 在瀏覽器打印一下代理之后的對象

          [[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 動作

          1. 對上面的代碼在控制臺打印看看輸出了啥?
          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ù)
          /*?
          ??
          ??*/

          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 那么直接返回傳入的原始值,也就是說,不會再去深層代理對象了,讓我們來看兩個場景:

          1. 傳入的是一個對象
          const?shallowRefObj?=?shallowRef({
          ??id:?1,
          ??name:?'front-refiend',
          });

          上面的對象加工之后,我們可以簡單的理解成:

          const?shallowRefObj?=?{
          ??value:?{
          ????id:?1,
          ????name:?'front-refiend'
          ??}
          };

          既然是 shallow(淺層)那就止于 value ,不再進行深層代理。也就是說,對于嵌套對象的屬性不會進行追蹤,但是我們修改 shallowRefObj 本身的 value 屬性還是響應式的,如:shallowRefObj.value = 'hello~';

          1. 傳入的是一個簡單類型
          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ā)上的便利。讓我們直接來看看它的幾個使用場景:

          1. 解構(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 對象。

          1. 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,watchtoRefs...
          理解所有響應式 API 對于我們在編碼會更加有自信,不會有那么多的疑惑。也幫助我們更加理解框架的底層,如:proxy 怎么用的?Vue3 怎么追蹤一個簡單類型的?怎樣去編碼才能讓我們系統(tǒng)更優(yōu)。這才是本文分析這幾個 API 的初衷。
          怎么樣,你了解這 20 個響應式 API 了嗎?

          ?? 前端精,求關(guān)注~

          2021年,公眾號關(guān)注「前端精」(front-refined),我們一起學 Vue3,用 Vue3,深入 Vue3 。
          最后,祝小伙伴們新年快樂,開開心心過春節(jié)~

          感謝您的點贊和在看?????


          瀏覽 70
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  爱骚逼自拍偷拍精品视频 | 先锋资源av | 波多野结衣熟练中出 | 最新欧美va在线观看 | 影音先锋麻豆电影 |