深入理解Vue3響應式原理
目錄
響應式原理
利用ES6中Proxy作為攔截器,在get時收集依賴,在set時觸發依賴,來實現響應式。
手寫實現
1、實現Reactive
基于原理,我們可以先寫一下測試用例
//reactive.spec.ts describe("effect", () => { it("happy path", () => { const original = { foo: 1 }; //原始數據 const observed = reactive(original); //響應式數據 expect(observed).not.toBe(original); expect(observed.foo).toBe(1); //正常獲取數據 expect(isReactive(observed)).toBe(true); expect(isReactive(original)).toBe(false); expect(isProxy(observed)).toBe(true); }); });
首先實現數據的攔截處理,通過ES6的Proxy,實現獲取和賦值操作。
//reactive.ts //對new Proxy()進行包裝 export function reactive(raw) { return createActiveObject(raw, mutableHandlers); } function createActiveObject(raw: any, baseHandlers) { //直接返回一個Proxy對象,實現響應式 return new Proxy(raw, baseHandlers); }
//baseHandler.ts //抽離出一個handler對象 export const mutableHandlers = { get:createGetter(), set:createSetter(), }; function createGetter(isReadOnly: Boolean = false, shallow: Boolean = false) { return function get(target, key) { const res = Reflect.get(target, key); // 看看res是否是一個object if (isObject(res)) { //如果是,則進行嵌套處理,使得返回的對象中的 對象 也具備響應式 return isReadOnly ? readonly(res) : reactive(res); } if (!isReadOnly) { //如果不是readonly類型,則收集依賴 track(target, key); } return res; }; } function createSetter() { return function set(target, key, value) { const res = Reflect.set(target, key, value); //觸發依賴 trigger(target, key); return res; }; }
從上述代碼中,我們可以??注意到track(target, key) 和trigger(target, key) 這兩個函數,分別是對依賴的收集和觸發。
依賴:我們可以把依賴認為是把用戶對數據的操控(用戶函數,副作用函數)包裝成一個東西,我們在get的時候將依賴一個一個收集起來,set的時候全部觸發,即可實現響應式效果。
2、實現依賴的收集和觸發
//effect.ts //全局變量 let activeEffect: ReactiveEffect; //當前的依賴 let shouldTrack: Boolean; //是否收集依賴 const targetMap = new WeakMap(); //依賴樹
targetMap結構:
targetMap: {
每一個target(depsMap):{
每一個key(depSet):[
每一個依賴
]
}
}
WeakMap和Map的區別
1、WeakMap只接受對象作為key,如果設置其他類型的數據作為key,會報錯。
2、WeakMap的key所引用的對象都是弱引用,只要對象的其他引用被刪除,垃圾回收機制就會釋放該對象占用的內存,從而避免內存泄漏。
3、由于WeakMap的成員隨時可能被垃圾回收機制回收,成員的數量不穩定,所以沒有size屬性。
4、沒有clear()方法
5、不能遍歷
首先我們定義一個依賴類,稱為ReactiveEffect,對用戶函數進行包裝,賦予一些屬性和方法。參考:前端手寫面試題詳細解答
//effect.ts //響應式依賴 — ReactiveEffect類 class ReactiveEffect { private _fn: any; //用戶函數, active = true; //表示當前依賴是否激活,如果清除過則為false deps: any[] = []; //包含該依賴的deps onStop?: () => void; //停止該依賴的回調函數 public scheduler: Function; //調度函數 //構造函數 constructor(fn, scheduler?) { this._fn = fn; this.scheduler = scheduler; } //執行副作用函數 run() { //用戶函數,可以報錯,需要用try包裹 try { //如果當前依賴不是激活狀態,不進行依賴收集,直接返回 if (!this.active) { return this._fn(); } //開啟依賴收集 shouldTrack = true; activeEffect = this; //調用時會觸發依賴收集 const result = this._fn(); //關閉依賴收集 shouldTrack = false; //返回結果 return result; } finally { //todo } } }
effect影響函數
創建一個用戶函數作用函數,稱為effect,這個函數的功能為基于ReactiveEffect類創建一個依賴,觸發用戶函數(的時候,觸發依賴收集),返回用戶函數。
//創建一個依賴 export function effect(fn, option: any = {}) { //為當前的依賴創建響應式實例 const _effect = new ReactiveEffect(fn, option.scheduler); Object.assign(_effect, option); //最開始調用一次,其中會觸發依賴收集 _effect.run() -> _fn() -> get() -> track() _effect.run(); const runner: any = _effect.run.bind(_effect); //在runner上掛載依賴,方便在其他地方通過runner訪問到該依賴 runner.effect = _effect; return runner; }
bind():在原函數的基礎上創建一個新函數,使新函數的this指向傳入的第一個參數,其他參數作為新函數的參數
用戶觸發依賴收集時,將依賴添加到targetMap中。
收集/添加依賴
//把依賴添加到targetMap對應target的key中,在重新set時在trigger中重新觸發 export function track(target: Object, key) { //如果不是track的狀態,直接返回 if (!isTracking()) return; // target -> key -> dep //獲取對應target,獲取不到則創建一個,并加進targetMap中 let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } //獲取對應key,獲取不到則創建一個,并加進target中 let depSet = depsMap.get(key); if (!depSet) { depsMap.set(key, (depSet = new Set())); } //如果depSet中已經存在該依賴,直接返回 if (depSet.has(activeEffect)) return; //添加依賴 trackEffects(depSet); } export function trackEffects(dep) { //往target中添加依賴 dep.add(activeEffect); //添加到當前依賴的deps數組中 activeEffect.deps.push(dep); }
觸發依賴
//一次性觸發對應target中key的所有依賴 export function trigger(target, key) { let depsMap = targetMap.get(target); let depSet = depsMap.get(key); //觸發依賴 triggerEffects(depSet); } export function triggerEffects(dep) { for (const effect of dep) { if (effect.scheduler) { effect.scheduler(); } else { effect.run(); } } }
3、移除/停止依賴
我們在ReactiveEffect這個類中,增加一個stop方法,來暫停依賴收集和清除已經存在的依賴
//響應式依賴 — 類 class ReactiveEffect { private _fn: any; //用戶函數, active = true; //表示當前依賴是否激活,如果清除過則為false deps: any[] = []; //包含該依賴的deps onStop?: () => void; //停止該依賴的回調函數 public scheduler: Function; //調度函數 //... stop() { if (this.active) { cleanupEffect(this); //執行回調 if (this.onStop) { this.onStop(); } //清除激活狀態 this.active = false; } } } //清除該依賴掛載的deps每一項中的該依賴 function cleanupEffect(effect) { effect.deps.forEach((dep: any) => { dep.delete(effect); }); effect.deps.length = 0; } //移除一個依賴 export function stop(runner) { runner.effect.stop(); }
衍生類型
1、實現readonly
readonly相比于reactive,實現上相對比較簡單,它是一個只讀類型,不會涉及set操作,更不需要收集/觸發依賴。
export function readonly(raw) { return createActiveObject(raw, readonlyHandlers); } export const readonlyHandlers = { get: readonlyGet, set: (key, target) => { console.warn(`key:${key} set 失敗,因為target是一個readonly對象`, target); return true; }, }; const readonlyGet = createGetter(true); function createGetter(isReadOnly: Boolean = false, shallow: Boolean = false) { return function get(target, key) { if (key === ReactiveFlags.IS_REACTIVE) { return !isReadOnly; } else if (key === ReactiveFlags.IS_READONLY) { return isReadOnly; } //... // 看看res是否是一個object if (isObject(res)) { return isReadOnly ? readonly(res) : reactive(res); } ? if (!isReadOnly) { //收集依賴 track(target, key); } return res; }; }
2、實現shallowReadonly
我們先看一下shallow的含義
shallow:不深的, 淺的,不深的, 不嚴肅的, 膚淺的,淺薄的。
那么shallowReadonly,指的是只對最外層進行限制,而內部的仍然是一個普通的、正常的值。
//shallowReadonly.ts export function shallowReadonly(raw) { return createActiveObject(raw, shallowReadonlyHandlers); } export const shallowReadonlyHandlers = extend({}, readonlyHandlers, { get: shallowReadonlyGet, }); const shallowReadonlyGet = createGetter(true, true); function createGetter(isReadOnly: Boolean = false, shallow: Boolean = false) { return function get(target, key) { //.. const res = Reflect.get(target, key); //是否shallow,是的話很直接返回 if (shallow) { return res; } if (isObject(res)) { //... } }; }
3、實現ref
ref相對reactive而言,實際上他不存在嵌套關系,就是一個value。
//ref.ts export function ref(value: any) { return new RefImpl(value); }
我們來實現一下RefImpl類,原理其實跟reactive類似,只是一些細節處不同。
//ref.ts class RefImpl { private _value: any; //轉化后的值 public dep; //依賴容器 private _rawValue: any; //原始值, public _v_isRef = true; //判斷ref類型 constructor(value) { this._rawValue = value; //記錄原始值 this._value = convert(value); //存儲轉化后的值 this.dep = new Set(); //創建依賴容器 } get value() { trackRefValue(this); //收集依賴 return this._value; } set value(newValue) { //新老值不同,才觸發更改 if (hasChanged(newValue, this._rawValue)) { // 一定先修改value,再觸發依賴 this._rawValue = newValue; this._value = convert(newValue); triggerEffects(this.dep); } } }
//ref.ts //對value進行轉換(value可能是object) export function convert(value: any) { return isObject(value) ? reactive(value) : value; } export function trackRefValue(ref: RefImpl) { if (isTracking()) { trackEffects(ref.dep); } } //effect.ts export function isTracking(): Boolean { //是否開啟收集依賴 & 是否有依賴 return shouldTrack && activeEffect !== undefined; } export function trackEffects(dep) { dep.add(activeEffect); activeEffect.deps.push(dep); } export function triggerEffects(dep) { for (const effect of dep) { if (effect.scheduler) { effect.scheduler(); } else { effect.run(); } } }
實現proxyRefs
//實現對ref對象進行代理 //如user = { // age:ref(10), // ... //} export function proxyRefs(ObjectWithRefs) { return new Proxy(ObjectWithRefs, { get(target, key) { // 如果是ref 返回.value //如果不是 返回value return unRef(Reflect.get(target, key)); }, set(target, key, value) { if (isRef(target[key]) && !isRef(value)) { target[key].value = value; return true; //? } else { return Reflect.set(target, key, value); } }, }); }
4、實現computed
computed的實現也很巧妙,利用調度器機制和一個私有變量_value,實現緩存和惰性求值。
通過注解(一)(二)(三)可理解其實現流程
//computed import { ReactiveEffect } from "./effect"; ? class computedRefImpl { private _dirty: boolean = true; private _effect: ReactiveEffect; private _value: any; constructor(getter) { //創建時,會創建一個響應式實例,并且掛載 this._effect = new ReactiveEffect(getter, () => { //(三) //當監聽的值發生改變時,會觸發set,此時觸發當前依賴 //因為存在調度器,不會立刻執行用戶fn(實現了lazy),而是將_dirty更改為true //在下一次用戶get時,會調用run方法,重新拿到最新的值返回 if (!this._dirty) { this._dirty = true; } }); } get value() { //(一) //默認_dirty是true //那么在第一次get的時候,會觸發響應式實例的run方法,觸發依賴收集 //同時拿到用戶fn的值,存儲起來,然后返回出去 if (this._dirty) { this._dirty = false; this._value = this._effect.run(); } //(二) //當監聽的值沒有改變時,_dirty一直為false //所以,第二次get時,因為_dirty為false,那么直接返回存儲起來的_value return this._value; } } export function computed(getter) { //創建一個computed實例 return new computedRefImpl(getter); }
工具類
//是否是reactive響應式類型 export function isReactive(target) { return !!target[ReactiveFlags.IS_REACTIVE]; } //是否是readonly響應式類型 export function isReadOnly(target) { return !!target[ReactiveFlags.IS_READONLY]; } //是否是響應式對象 export function isProxy(target) { return isReactive(target) || isReadOnly(target); } //是否是對象 export function isObject(target) { return typeof target === "object" && target !== null; } //是否是ref export function isRef(ref: any) { return !!ref._v_isRef; } //解構ref export function unRef(ref: any) { return isRef(ref) ? ref.value : ref; } //是否改變 export const hasChanged = (val, newVal) => { return !Object.is(val, newVal); };
判斷響應式類型的依據是,在get的時候,檢查傳進來的key是否等于某枚舉值來做為判斷依據,在get中加入
//reactive.ts export const enum ReactiveFlags { IS_REACTIVE = "__v_isReactive", IS_READONLY = "__v_isReadOnly", } //baseHandler.ts function createGetter(isReadOnly: Boolean = false, shallow: Boolean = false) { return function get(target, key) { //... if (key === ReactiveFlags.IS_REACTIVE) { return !isReadOnly; } else if (key === ReactiveFlags.IS_READONLY) { return isReadOnly; } //... }; }
原文地址:https://blog.csdn.net/helloworld1024fd/article/details/127582869