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

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

Reactive.ts

??"reactive"翻譯出來是(反應的扮叨;電抗的;反動的)疑苔,這里我們不用深究其具體的含義甫匹,可以把其當做對象的一種狀態(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語法(別名機制)來看恍箭,我們可以發(fā)現(xiàn),熟悉的Es6瞧省,import/export的語法扯夭,顯然這個文件有導入也有導出鳍贾,可以說是一個中間文件,是為了提供某種方法給其他文件交洗,那么首先我們應當關心的是骑科,這個文件究竟提供了什么功能,即都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
 // 被用戶顯式設置成只讀 轉化成只讀版本返回
 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對象已經有對應的代理
 let observed = toProxy.get(target)
 // void 0 (void() 運算符 不管后面是什么 都一致返回undefined void function() 申明此函數(shù)返回的是undefined 在js高程中也有這樣寫到咆爽,主要是防止出現(xiàn)undefined = xx被重寫的風險)
 // 已經有代理了顯然直接不用處理了 直接返回其代理對象
 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對象的構造器是否屬于collectionTypes類型的 Set, Map, WeakMap, WeakSet 是四種類型返回采集器 不是的話應該是需要處理器進行處理 返回處理器
 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ù)據類型的變量置森,和一些函數(shù)/方法


image.png

自然讀代碼我們也需要尋尋漸進斗埂,從易開始

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ù)據類型中是有has的方法的呢?顯然是復雜數(shù)據類型的建瘫,數(shù)組又被排除县好,顯然我們應該考慮Map和Set,同時Map原型上是沒有add方法的暖混,這樣執(zhí)行add的數(shù)據類型也被我們猜測的八九不離十了。那么接下來我們就去驗證下我們的猜想翁授。
??找到初始化數(shù)據的位置

// 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,雖然實際中用的是weakMap和weakSet收擦,但是為什么要用Weak版本的Map和Set呢贮配?

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

??MDN上獲取下WeakMap的信息,我們可以看到: "該WeakMap對象是鍵/值對的集合塞赂,其中鍵被弱引用泪勒。鍵必須是對象,并且值可以是任意值"宴猾,其的不同之處是key鍵的弱引用圆存,但是什么是弱引用呢?從Java來言仇哆,弱引用描述不必須的對象沦辙,對象在只被弱引用的時候,在GC的時候是會被回收的讹剔。弱引用的對象要想避免被GC的途徑也是需要有其他的強引用引用對象油讯。這樣我們效仿一下详民,Map中為什么要出現(xiàn)弱引用的鍵呢?同樣我們從Java來考慮陌兑,Java數(shù)據類型的WeakHahMap比較類似沈跨,其是在對應的某一條k-value的value沒有引用的時候,在GC的時候會把整個K-value進行回收兔综,同理可以看出饿凛,js同樣是使用這樣的機制,WeakMap在內存的管理方面是優(yōu)于Map的邻奠。但是問題來了笤喳,那Map類型的為啥不能這樣的回收機制呢,Map類型在Js中的表現(xiàn)形式其實可以看做是一個Object碌宴,對象中的屬性和屬性值是被對象自身引用的杀狡,所以Map是實現(xiàn)不了類似WeakMap的回收機制的。同理Set也是這樣的道理贰镣,就不再說明呜象。
??OK,回到我們的方法isReactive是判斷reactiveToRaw或者readonlyToRaw集合中是否存在Key值碑隆,isReadonly是判斷readonlyToRaw集合的是否存在某個Key值恭陡,toRaw是從reactiveToRaw或者readonlyToRaw集合中獲取某個Key值對應的Value,markReadonly和markNonReactive分別是對readonlyValues以及nonReactiveValues添加元素的封裝上煤。

reactive

??接下來分析reactive方法

// type別名機制 根據條件類型判斷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
  // 被用戶顯式設置成只讀 轉化成只讀版本返回
  if (readonlyValues.has(target)) {
    return readonly(target)
  }
  return createReactiveObject(
    target,
    rawToReactive,
    reactiveToRaw,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

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

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

跳轉過來呐矾,Ref就瞬間出現(xiàn),原來是一個interface類型懦砂,其中有_isRef屬性被初始化為true蜒犯,這是表示是一個Ref類型的標識,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']

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


屬性名運算

這里我們基本可以看出趾疚,實際上復雜的運算是為了確定傳入的類型缨历。同時實際上也只有四種確定的結果,即cRef, ref, array, object糙麦。正好對應上述對象字面量中的四個屬性值辛孵,學到一手,字面量對象后面直接跟上屬性名取值的操作赡磅。
下面看對象中的運算

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屬性值的運算是條件類型判斷ComputedRef<infer V> 魄缚,是的話會返回UnwrapRef<V>,這里暫且不深入去了解ComputedRef類型了焚廊,字面上來看其可能與computed是有一定聯(lián)系的冶匹。從上述四行來言,其整體返回值為T咆瘟,UnwrapRef<V>嚼隘,Array<UnwrapRef<V>>, 以及{ [K in keyof T]: UnwrapRef<T[K]> }解耦的對象袒餐。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末飞蛹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子灸眼,更是在濱河造成了極大的恐慌桩皿,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幢炸,死亡現(xiàn)場離奇詭異,居然都是意外死亡拒贱,警方通過查閱死者的電腦和手機宛徊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逻澳,“玉大人闸天,你說我怎么就攤上這事⌒弊觯” “怎么了苞氮?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瓤逼。 經常有香客問我笼吟,道長库物,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任贷帮,我火速辦了婚禮戚揭,結果婚禮上,老公的妹妹穿的比我還像新娘撵枢。我一直安慰自己民晒,他們只是感情好,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布锄禽。 她就那樣靜靜地躺著潜必,像睡著了一般。 火紅的嫁衣襯著肌膚如雪沃但。 梳的紋絲不亂的頭發(fā)上磁滚,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機與錄音绽慈,去河邊找鬼恨旱。 笑死,一個胖子當著我的面吹牛坝疼,可吹牛的內容都是我干的搜贤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼钝凶,長吁一口氣:“原來是場噩夢啊……” “哼仪芒!你這毒婦竟也來了?” 一聲冷哼從身側響起耕陷,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤掂名,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哟沫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饺蔑,經...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年嗜诀,在試婚紗的時候發(fā)現(xiàn)自己被綠了猾警。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡隆敢,死狀恐怖发皿,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情拂蝎,我是刑警寧澤穴墅,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響玄货,放射性物質發(fā)生泄漏皇钞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一誉结、第九天 我趴在偏房一處隱蔽的房頂上張望鹅士。 院中可真熱鬧,春花似錦惩坑、人聲如沸掉盅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趾痘。三九已至,卻和暖如春蔓钟,著一層夾襖步出監(jiān)牢的瞬間永票,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工滥沫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留侣集,地道東北人。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓兰绣,卻偏偏與公主長得像世分,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缀辩,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355