Vue3.0 源碼通讀筆記(壹) -- Reactive.ts

  Vue3.0 的預(yù)發(fā)布源碼已經(jīng)在前不久上線了,3.0版本中用typeScript重寫同時也增加了很多新的特性。
  本文首先從Vue3.0 采用Proxy代理的文件出發(fā),進(jìn)行源碼的通讀和思考。

Reactive.ts

??"reactive"翻譯出來是(反應(yīng)的;電抗的;反動的),這里我們不用深究其具體的含義,可以把其當(dāng)做對象的一種狀態(tài)來看待,是為了改變對象,或者是類似裝飾器模式的封裝對象一樣的來看待。
??打開reactive.ts,首先映入眼旁的是:

import { isObject, toRawType } from '@vue/shared'
import { mutableHandlers, readonlyHandlers } from './baseHandlers'
import {
 mutableCollectionHandlers,
 readonlyCollectionHandlers
} from './collectionHandlers'
import { ReactiveEffect } from './effect'
import { UnwrapRef, Ref } from './ref'
import { makeMap } from '@vue/shared'

??嘖嘖,拋去ts的type語法(別名機(jī)制)來看,我們可以發(fā)現(xiàn),熟悉的Es6,import/export的語法,顯然這個文件有導(dǎo)入也有導(dǎo)出,可以說是一個中間文件,是為了提供某種方法給其他文件,那么首先我們應(yīng)當(dāng)關(guān)心的是,這個文件究竟提供了什么功能,即都export了什么,我們先下翻一下大致了解下

// 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.
export type Dep = Set<ReactiveEffect>
export type KeyToDepMap = Map<any, Dep>
export const targetMap = new WeakMap<any, KeyToDepMap>()
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
 // if trying to observe a readonly proxy, return the readonly version.
 // 對只讀代理的訂閱返回的是只讀版本
 if (readonlyToRaw.has(target)) {
   return target
 }
 // target is explicitly marked as readonly by user
 // 被用戶顯式設(shè)置成只讀 轉(zhuǎn)化成只讀版本返回
 if (readonlyValues.has(target)) {
   return readonly(target)
 }
 return createReactiveObject(
   target,
   rawToReactive,
   reactiveToRaw,
   mutableHandlers,
   mutableCollectionHandlers
 )
}

/**
* 只讀版本的createReactiveObject
* @param target 
*/
export function readonly<T extends object>(
 target: T
): Readonly<UnwrapNestedRefs<T>> {
 // value is a mutable(可變的) observable(可觀察量), retrieve its original(回退) and return
 // a readonly version.
 if (reactiveToRaw.has(target)) {
   target = reactiveToRaw.get(target)
 }
 return createReactiveObject(
   target,
   // rawToReactive
   rawToReadonly,
   // reactiveToRaw
   readonlyToRaw,
   // mutableHandlers
   readonlyHandlers,
   // mutableCollectionHandlers
   readonlyCollectionHandlers
 )
}

/**
* 
* @param target 
* @param toProxy 對象遷移保存 已代理
* @param toRaw 對象遷移保存 已加工
* @param baseHandlers 處理器
* @param collectionHandlers 采集器
*/
function createReactiveObject(
 target: unknown,
 toProxy: WeakMap<any, any>,
 toRaw: WeakMap<any, any>,
 baseHandlers: ProxyHandler<any>,
 collectionHandlers: ProxyHandler<any>
) {
 // target不是對象的處理過程 測試環(huán)境打印warning
 if (!isObject(target)) {
   if (__DEV__) {
     console.warn(`value cannot be made reactive: ${String(target)}`)
   }
   return target
 }
 // target already has corresponding Proxy
 // target對象已經(jīng)有對應(yīng)的代理
 let observed = toProxy.get(target)
 // void 0 (void() 運(yùn)算符 不管后面是什么 都一致返回undefined void function() 申明此函數(shù)返回的是undefined 在js高程中也有這樣寫到,主要是防止出現(xiàn)undefined = xx被重寫的風(fēng)險(xiǎn))
 // 已經(jīng)有代理了顯然直接不用處理了 直接返回其代理對象
 if (observed !== void 0) {
   return observed
 }
 // target is already a Proxy
 // target對象是一個代理 直接返回target
 if (toRaw.has(target)) {
   return target
 }
 // only a whitelist of value types can be observed.
 // 只有白名單中的value才可以被訂閱處理 用的是上述的canObserve函數(shù)
 if (!canObserve(target)) {
   return target
 }
 // 判斷target對象的構(gòu)造器是否屬于collectionTypes類型的 Set, Map, WeakMap, WeakSet 是四種類型返回采集器 不是的話應(yīng)該是需要處理器進(jìn)行處理 返回處理器
 const handlers = collectionTypes.has(target.constructor)
   ? collectionHandlers
   : baseHandlers
 // 新建代理
 observed = new Proxy(target, handlers)
 // 存在toProxy map中
 toProxy.set(target, observed)
 // 正反映射
 toRaw.set(observed, target)
 // targetMap中沒有target屬性 就新建target屬性 初始化為map
 if (!targetMap.has(target)) {
   targetMap.set(target, new Map())
 }
 return observed
}

export function isReactive(value: unknown): boolean {
 return reactiveToRaw.has(value) || readonlyToRaw.has(value)
}

export function isReadonly(value: unknown): boolean {
 return readonlyToRaw.has(value)
}

export function toRaw<T>(observed: T): T {
 return reactiveToRaw.get(observed) || readonlyToRaw.get(observed) || observed
}

export function markReadonly<T>(value: T): T {
 readonlyValues.add(value)
 return value
}

export function markNonReactive<T>(value: T): T {
 nonReactiveValues.add(value)
 return value
}

顯然我們看出,export的有三種數(shù)據(jù)類型的變量,和一些函數(shù)/方法


image.png

自然讀代碼我們也需要尋尋漸進(jìn),從易開始

isReactive/isReadonly/toRaw/markReadonly/markNonReactive

// 封裝對工作域集合的方法
export function isReactive(value: unknown): boolean {
  return reactiveToRaw.has(value) || readonlyToRaw.has(value)
}

export function isReadonly(value: unknown): boolean {
  return readonlyToRaw.has(value)
}

export function toRaw<T>(observed: T): T {
  return reactiveToRaw.get(observed) || readonlyToRaw.get(observed) || observed
}

export function markReadonly<T>(value: T): T {
  readonlyValues.add(value)
  return value
}

export function markNonReactive<T>(value: T): T {
  nonReactiveValues.add(value)
  return value
}
  

??這五個方法為什么放在一起看呢?從上圖中我們可以看出,其中多的是has,get,add這種方法,從has中我們分析出來,js什么數(shù)據(jù)類型中是有has的方法的呢?顯然是復(fù)雜數(shù)據(jù)類型的,數(shù)組又被排除,顯然我們應(yīng)該考慮Map和Set,同時Map原型上是沒有add方法的,這樣執(zhí)行add的數(shù)據(jù)類型也被我們猜測的八九不離十了。那么接下來我們就去驗(yàn)證下我們的猜想。
??找到初始化數(shù)據(jù)的位置

// WeakMaps that store {raw <-> observed} pairs.
const rawToReactive = new WeakMap<any, any>()
const reactiveToRaw = new WeakMap<any, any>()
const rawToReadonly = new WeakMap<any, any>()
const readonlyToRaw = new WeakMap<any, any>()

// WeakSets for values that are marked readonly or non-reactive during
// observable creation.
const readonlyValues = new WeakSet<any>()
const nonReactiveValues = new WeakSet<any>()

不錯,就是Map和Set,雖然實(shí)際中用的是weakMap和weakSet,但是為什么要用Weak版本的Map和Set呢?

??Weak版本和Map的區(qū)別

??MDN上獲取下WeakMap的信息,我們可以看到: "該WeakMap對象是鍵/值對的集合,其中鍵被弱引用。鍵必須是對象,并且值可以是任意值",其的不同之處是key鍵的弱引用,但是什么是弱引用呢?從Java來言,弱引用描述不必須的對象,對象在只被弱引用的時候,在GC的時候是會被回收的。弱引用的對象要想避免被GC的途徑也是需要有其他的強(qiáng)引用引用對象。這樣我們效仿一下,Map中為什么要出現(xiàn)弱引用的鍵呢?同樣我們從Java來考慮,Java數(shù)據(jù)類型的WeakHahMap比較類似,其是在對應(yīng)的某一條k-value的value沒有引用的時候,在GC的時候會把整個K-value進(jìn)行回收,同理可以看出,js同樣是使用這樣的機(jī)制,WeakMap在內(nèi)存的管理方面是優(yōu)于Map的。但是問題來了,那Map類型的為啥不能這樣的回收機(jī)制呢,Map類型在Js中的表現(xiàn)形式其實(shí)可以看做是一個Object,對象中的屬性和屬性值是被對象自身引用的,所以Map是實(shí)現(xiàn)不了類似WeakMap的回收機(jī)制的。同理Set也是這樣的道理,就不再說明。
??OK,回到我們的方法isReactive是判斷reactiveToRaw或者readonlyToRaw集合中是否存在Key值,isReadonly是判斷readonlyToRaw集合的是否存在某個Key值,toRaw是從reactiveToRaw或者readonlyToRaw集合中獲取某個Key值對應(yīng)的Value,markReadonly和markNonReactive分別是對readonlyValues以及nonReactiveValues添加元素的封裝。

reactive

??接下來分析reactive方法

// type別名機(jī)制 根據(jù)條件類型判斷Ref來判斷返回
type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  // 對只讀代理的訂閱返回的是只讀版本
  if (readonlyToRaw.has(target)) {
    return target
  }
  // target is explicitly marked as readonly by user
  // 被用戶顯式設(shè)置成只讀 轉(zhuǎn)化成只讀版本返回
  if (readonlyValues.has(target)) {
    return readonly(target)
  }
  return createReactiveObject(
    target,
    rawToReactive,
    reactiveToRaw,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

首先上述中的第一行代碼,就是一個別名申明,同時發(fā)現(xiàn)了<T>的泛型使用,代碼經(jīng)過對條件類型判斷Ref的判斷來進(jìn)行返回的,Ref是什么呢?所以我們暫時停留,根據(jù)import { UnwrapRef, Ref } from './ref'前往Ref的老家去看一看

export interface Ref<T = any> {
  _isRef: true
  value: UnwrapRef<T>
}

跳轉(zhuǎn)過來,Ref就瞬間出現(xiàn),原來是一個interface類型,其中有_isRef屬性被初始化為true,這是表示是一個Ref類型的標(biāo)識,value的值就比較奇怪了,UnwrapRef是什么呢?

// Recursively unwraps nested value bindings.
export type UnwrapRef<T> = {
  cRef: T extends ComputedRef<infer V> ? UnwrapRef<V> : T
  ref: T extends Ref<infer V> ? UnwrapRef<V> : T
  array: T extends Array<infer V> ? Array<UnwrapRef<V>> : T
  object: { [K in keyof T]: UnwrapRef<T[K]> }
}[T extends ComputedRef<any>
  ? 'cRef'
  : T extends Ref
    ? 'ref'
    : T extends Array<any>
      ? 'array'
      : T extends Function | CollectionTypes
        ? 'ref' // bail out on types that shouldn't be unwrapped
        : T extends object ? 'object' : 'ref']

跳轉(zhuǎn)過去,發(fā)現(xiàn)這是一個類型別名聲明,乍一看感覺這是什么奇怪的東西,仔細(xì)看一看有{}[] 這不正是一個對象[屬性名]的結(jié)構(gòu)。主要分為對對象的計(jì)算以及對屬性名的運(yùn)算。首先先看屬性名的運(yùn)算過程,這是一個嵌套的三目運(yùn)算,此時首先我們要明白多個嵌套三目運(yùn)算的規(guī)則同樣是自左向右計(jì)算的。先計(jì)算?左側(cè)的,之后一路向右邊計(jì)算下去,返回的值作為判斷結(jié)果再一層層的返回到根三目運(yùn)算
由此我們可以解釋上述的屬性名運(yùn)算代碼塊,如下圖展示的


屬性名運(yùn)算

這里我們基本可以看出,實(shí)際上復(fù)雜的運(yùn)算是為了確定傳入的類型。同時實(shí)際上也只有四種確定的結(jié)果,即cRef, ref, array, object。正好對應(yīng)上述對象字面量中的四個屬性值,學(xué)到一手,字面量對象后面直接跟上屬性名取值的操作。
下面看對象中的運(yùn)算

cRef: T extends ComputedRef<infer V> ? UnwrapRef<V> : T
ref: T extends Ref<infer V> ? UnwrapRef<V> : T
array: T extends Array<infer V> ? Array<UnwrapRef<V>> : T
object: { [K in keyof T]: UnwrapRef<T[K]> }

cRef屬性值的運(yùn)算是條件類型判斷ComputedRef<infer V> ,是的話會返回UnwrapRef<V>,這里暫且不深入去了解ComputedRef類型了,字面上來看其可能與computed是有一定聯(lián)系的。從上述四行來言,其整體返回值為T,UnwrapRef<V>,Array<UnwrapRef<V>>, 以及{ [K in keyof T]: UnwrapRef<T[K]> }解耦的對象。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容