vue3源碼閱讀-reactivity


看源碼時候做的筆記-----------

 1.ref是什么?

from:vue-next/packages/reactivity/src/ref.ts

示例:

import { ref, ref } from 'vue';
let a: Ref<String> = ref('hello world');
let b: Ref<Object> = ref({name: 'hello world'});
console.log(b)

生成的變量:

 首先變量a的類型是 Ref<String>,這個類型 Ref的定義如下,包含了 value,_shallow,[RefSymbol]。我們從上圖看,ref變量里面還有 __v_isRef、_rawValue、_value、_shallow

declare const RefSymbol: unique symbol

export interface Ref<T = any> {
  value: T
  /**
   * Type differentiator only.
   * We need this to be in public d.ts but don't want it to show up in IDE
   * autocomplete, so we use a private Symbol instead.
   */
  [RefSymbol]: true
  /**
   * @internal
   */
  _shallow?: boolean
}

再看看 ref 的實現:

(ts的函數重載:  https://jkchao.github.io/typescript-book-chinese/typings/functions.html#%E9%87%8D%E8%BD%BD

export function ref<T extends object>(value: T): ToRef<T> 
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
  return createRef(value)
}

function createRef(rawValue: unknown, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue
  }
 //
RefImpl 實例會初始化 __v_isRef、_rawValue、_value、_shallow
//  __v_isRef 用在isRef func中判斷,當前變量是不是ref
return new RefImpl(rawValue, shallow)
}
RefImpl在構造ref的時候 ,會判斷用戶傳入的_rawValue是不是object,不是object就直接賦值給_value, 是object就 convert(_rawValue) 
const convert = <T extends unknown>(val: T): T =>
  isObject(val) ? reactive(val) : val

可以看出來,最后將object類型的變量,構造為reactive的proxy。

 

 2.reactive是什么?

 from:vue-next/packages/reactivity/src/reactive.ts

響應性基礎API:https://www.vue3js.cn/docs/zh/api/basic-reactivity.html#reactive

2.1.reactiveMap 和 readonlyMap 為什么是 weakMap,WeakMap 和 Map有什么區別?

Map 是由 key 數組和 value 數組構成,遍歷時,先遍歷 key, 找到 index , 然后再從 value 數組取值。兩個很大的缺點:

  a) 賦值和搜索操作都是 O(n) 的時間復雜度( n 是鍵值對的個數),因為這兩個操作都需要遍歷全部整個數組來進行匹配。

  b) 是可能會導致內存泄漏,因為數組會一直引用着每個鍵和值。這種引用使得垃圾回收算法不能回收處理他們,即使沒有其他任何引用存在了。

WeakMap 對象是一組鍵/值對的集合,其中的鍵是弱引用的,因為是弱引用,所以key不可被枚舉,如果key 是可枚舉的話,其列表將會受垃圾回收機制的影響,從而得到不確定的結果。其鍵必須是對象,而值可以是任意的。

  a) WeakMap 持有的是每個鍵對象的“弱引用”,而弱引用的對象,垃圾回收機制不考慮對該對象的引用,這意味着在沒有其他引用存在時垃圾回收能正確進行。

  原生 WeakMap 的結構是特殊且有效的,其用於映射的 key 只有在其沒有被回收時才是有效的。

effec.ts 中,Dep是個set,KeyToDepMap 是個 Map,targetMap 也是個WeakMap。

2.2.Reflect 和 Object 有什么區別?

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/Comparing_Reflect_and_Object_methods

2.3 怎么定義reactive數據?

可以創建reactive、shallowReactive、readonly、shallowReadonly四種不同的reactive數據類型,具體解釋見:https://www.vue3js.cn/docs/zh/api/basic-reactivity.html#markraw

每種reactive類型對應不同的createHandler,分別是mutableHandlers、shallowReactiveHandlers、readonlyHandlers、shallowReadonlyHandlers,

 

 創建過程中,需要檢測teaget是否是readonly、是否已經是reactive、是否已經被標記為raw類型、是否是基礎類型不可擴展、是否已經存在於map中,並做相應處理。

未創建並且滿足創建條件的,將target轉換為proxy:

baseHandlers 和 collectionHandlers,負責給proxy傳入不同的劫持handler。

2.4 怎么創建reactive數據類型?

 那 collectionHandlers(適用於Map/WeakMap/Set/WeakSet類型的target)和  baseHandlers (適用於Object和Array類型的target)有什么區別?

2.4.1:baseHandlers:vue-next/packages/reactivity/src/baseHandlers.ts

其實baseHandler主要是為proxy提供了handler:

reactiveVar 
    = new Proxy(
        {},
        {
            get,
            set,
            has,
            deleteProperty,
            ownKeys
        }
    )
    = new Proxy({},handler)

 在baseHandlers中,createGetter 負責創建get/shallowGet/readonlyGet/shallowReadonlyGet,區分在key是symbol、readonly、shallow、ref等情況下key的返回值,根絕情況確認是否要track追蹤收集依賴。

createGetter(isReadonly = false, shallow = false) {
  // 處理 __v_isReactive、__v_isReadonly、__v_raw 情況
  // 處理 數組取值
  // 處理 key 是symbol的情況
  // track
  // 處理shallow和ref的情況
  // 處理 res(Reflect.get(target, key, receiver))是object的情況,遞歸處理nested varabiles
}

createSetter 負責創建 set/shallowSet,(readonly不需要set)

function set( target: object, key: string | symbol, value: unknown, receiver: object): boolean {
    // 處理 非shallow非數組且isRef的情況,直接賦值
    // 處理 數組復制的情況 Reflect.set(target, key, value, receiver), trigger
}
2.4.2:collectionHandlers:vue-next/packages/reactivity/src/collectionHandlers.ts

因為 collectionHandler 處理的對象類型的特殊性,key是object,所以 collectionHandlers 給proxy提供的handler類型很多。

總歸來說,就是將各種數據類型分為了三類,分別處理,生成proxy,將數據置為reactive

 最后,可以實踐一下,看看reactive的產物,有什么區別?

        const wm1 = new WeakMap();
        const o1 = {},
            o2 = function(){},
            o3 = window;
        wm1.set(o1, 37);
        wm1.set(o2, "azerty");

        const ws = new WeakSet();
        const foo = {};
        const bar = {};
        ws.add(foo);
        ws.add(bar);

        const testCOMMONobject = reactive({
            a: 1,
            b: 'test',
        });
        console.log(testCOMMONobject);
        const testCOMMONarr = reactive(['測試數組', '測試數組1', '測試數組2'])
        console.log(testCOMMONarr)
        const testCOLLECTIONwm = reactive(wm1);
        console.log(testCOLLECTIONwm)
        const testCOLLECTIONws = reactive(ws);
        console.log(testCOLLECTIONws)
        console.log(readonly(testCOMMONobject));
        console.log(markRaw(testCOMMONarr)) // __v_skip: true        

reactive的原理就是生成proxy劫持,在setter時候trigger,在trigger中收集目標並觸發effects,最終觸發patch。

3.trigger和track是怎么生效的?

import { track, trigger } from './effect';
 track(target, TrackOpTypes.GET, key); 
  trigger(target, TriggerOpTypes.SET, key, value, oldValue?);

from: vue-next/packages/reactivity/src/effect.ts

3.1.哪些操作operations會觸發track和trigger?

 當對一個對象進行get、has、iterate的時候,會觸發該對象的track,收集依賴到targetMap。對對象進行set、add、delete、clear時會觸發trigger,使用target中的deps觸發依賴追蹤。

3.2.怎么收集保存對象的依賴信息?

我們來看源碼,源碼先聲明了一個targetMap來保存對象的依賴信息:

(不得不說,vue3 也是一本 typescript 高級教程😂

// The main WeakMap that stores {target -> key -> dep} connections.
// Conceptually, it's easier to think of a dependency as a Dep class
// which maintains a Set of subscribers, but we simply store them as
// raw Sets to reduce memory overhead.
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()

export interface ReactiveEffect<T = any> {
 
// 限制這個接口的實現必須是一個返回值為T(泛型)的函數,https://forum.vuejs.org/t/vue3-0-ts-t/81276
 (): T
  _isEffect: true
  id: number
  active: boolean
  raw: () => T
  deps: Array<Dep>
  options: ReactiveEffectOptions
  allowRecurse: boolean
}

 3.3.track

track(obj, TrackOpTypes.GET,'a');

運行示例,就可以看到target的數據結構:

        const obj = {
            a: '1',
        }
        const targetMap = new WeakMap();
        const depsMap = new Map()
        targetMap.set(obj, depsMap);
        let dep = depsMap.get('a')
        if (!dep) {
            depsMap.set('a', (dep = new Set()))
        }
        let effect = {};
        effect.id = 2;
        effect.allowRecurse = true;
        effect._isEffect = true;
        effect.active = true;
        effect.raw = () => {};
        effect.deps = [];
        effect.options = {};
        dep.add(effect);
        effect.deps.push(dep);
        console.log(targetMap)    

我們通過一個實例:

const obj = {
    a: '1',
}
track(obj, TrackOpTypes.GET, 'a');

看看track的運行過程,看看track是如何收集obj.a的依賴的:

export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (!shouldTrack || activeEffect === undefined) {
    return
  }
  // 查詢 targetMap 中是否保存有 obj 的依賴,obj是否被追蹤
  let depsMap = targetMap.get(target)
  // 如果 depsMap 不存在,targetMap.set(obj, new Map()),添加obj的依賴追蹤
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  // 查詢targetMap的 key -> obj 中是否有 'a'的依賴追蹤,如果沒有添加
  let dep = depsMap.get(key)
  if (!dep) {
    // depsMap.set('a', new Set())
    depsMap.set(key, (dep = new Set()))
  }
  // 如果沒有 activeEffect,新增
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect)
    activeEffect.deps.push(dep)
    if (__DEV__ && activeEffect.options.onTrack) {
      activeEffect.options.onTrack({
        effect: activeEffect,
        target,
        type,
        key
      })
    }
  }
}

3.4 trigger

trigger的運行過程。其實是消費targetMap的依賴,找到依賴后,開始執行任務。

const obj = {
    a: '1',
}
trigger(obj, TriggerOpTypes.SET, 'a', '2', '1');

 

4.computed

也是通過effect實現依賴的收集和觸發。不同的是 ComputedRefImpl 加入了_dirty 和 _value。_dirty 用來確認當前計算是否結束,是否需要更新計算結果。_value 用來緩存計算結果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM