1 2 | Vue3.0 的预发布源码已经在前不久上线了,3.0版本中用typeScript重写同时也增加了很多新的特性。 本文首先从Vue3.0 采用Proxy代理的文件出发,进行源码的通读和思考。 |
Reactive.ts
??"reactive"翻译出来是(反应的;电抗的;反动的),这里我们不用深究其具体的含义,可以把其当做对象的一种状态来看待,是为了改变对象,或者是类似装饰器模式的封装对象一样的来看待。
??打开reactive.ts,首先映入眼旁的是:
1 2 3 4 5 6 7 8 9 | 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了什么,我们先下翻一下大致了解下
1 2 3 4 5 6 7 | // 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>() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // 封装对工作域集合的方法 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的数据类型也被我们猜测的八九不离十了。那么接下来我们就去验证下我们的猜想。
??找到初始化数据的位置
1 2 3 4 5 6 7 8 9 10 | // 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方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // 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 ) } |
首先上述中的第一行代码,就是一个别名申明,同时发现了
1 2 3 4 | export interface Ref<T = any> { _isRef: true value: UnwrapRef<T> } |
跳转过来,Ref就瞬间出现,原来是一个interface类型,其中有_isRef属性被初始化为true,这是表示是一个Ref类型的标识,value的值就比较奇怪了,UnwrapRef是什么呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // 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。正好对应上述对象字面量中的四个属性值,学到一手,字面量对象后面直接跟上属性名取值的操作。
下面看对象中的运算
1 2 3 4 | 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