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ù)/方法
自然讀代碼我們也需要尋尋漸進斗埂,從易開始
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]> }解耦的對象袒餐。