Vue3.0 源码通读笔记(壹) -- Reactive.ts

  Vue3.0 的预发布源码已经在前不久上线了,3.0版本中用typeScript重写同时也增加了很多新的特性。
  本文首先从Vue3.0 采用Proxy代理的文件出发,进行源码的通读和思考。

Reactive.ts

  "reactive"翻译出来是(反应的;电抗的;反动的),这里我们不用深究其具体的含义,可以把其当做对象的一种状态来看待,是为了改变对象,或者是类似装饰器模式的封装对象一样的来看待。
  打开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语法(别名机制)来看,我们可以发现,熟悉的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不是对象的处理过程 测试环境打印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() 申明此函数返回的是undefined 在js高程中也有这样写到,主要是防止出现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函数
 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的有三种数据类型的变量,和一些函数/方法


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什么数据类型中是有has的方法的呢?显然是复杂数据类型的,数组又被排除,显然我们应该考虑Map和Set,同时Map原型上是没有add方法的,这样执行add的数据类型也被我们猜测的八九不离十了。那么接下来我们就去验证下我们的猜想。
  找到初始化数据的位置

// 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的区别

  MDN上获取下WeakMap的信息,我们可以看到: "该WeakMap对象是键/值对的集合,其中键被弱引用。键必须是对象,并且值可以是任意值",其的不同之处是key键的弱引用,但是什么是弱引用呢?从Java来言,弱引用描述不必须的对象,对象在只被弱引用的时候,在GC的时候是会被回收的。弱引用的对象要想避免被GC的途径也是需要有其他的强引用引用对象。这样我们效仿一下,Map中为什么要出现弱引用的键呢?同样我们从Java来考虑,Java数据类型的WeakHahMap比较类似,其是在对应的某一条k-value的value没有引用的时候,在GC的时候会把整个K-value进行回收,同理可以看出,js同样是使用这样的机制,WeakMap在内存的管理方面是优于Map的。但是问题来了,那Map类型的为啥不能这样的回收机制呢,Map类型在Js中的表现形式其实可以看做是一个Object,对象中的属性和属性值是被对象自身引用的,所以Map是实现不了类似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
  )
}

首先上述中的第一行代码,就是一个别名申明,同时发现了<T>的泛型使用,代码经过对条件类型判断Ref的判断来进行返回的,Ref是什么呢?所以我们暂时停留,根据import { UnwrapRef, Ref } from './ref'前往Ref的老家去看一看

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

跳转过来,Ref就瞬间出现,原来是一个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']

跳转过去,发现这是一个类型别名声明,乍一看感觉这是什么奇怪的东西,仔细看一看有{}[] 这不正是一个对象[属性名]的结构。主要分为对对象的计算以及对属性名的运算。首先先看属性名的运算过程,这是一个嵌套的三目运算,此时首先我们要明白多个嵌套三目运算的规则同样是自左向右计算的。先计算?左侧的,之后一路向右边计算下去,返回的值作为判断结果再一层层的返回到根三目运算
由此我们可以解释上述的属性名运算代码块,如下图展示的


属性名运算

这里我们基本可以看出,实际上复杂的运算是为了确定传入的类型。同时实际上也只有四种确定的结果,即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是有一定联系的。从上述四行来言,其整体返回值为T,UnwrapRef<V>,Array<UnwrapRef<V>>, 以及{ [K in keyof T]: UnwrapRef<T[K]> }解耦的对象。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343