前言
学习 Vue3.0 源码必须对以下知识有所了解:
- proxy reflect iterator
- map weakmap set weakset symbol
这些知识可以看一下阮一峰老师的《ES6 入门教程》。
如果不会 ts,我觉得影响不大,了解一下泛型就可以了。因为我就没用过 TS,但是不影响看代码。
阅读源码,建议先过一遍该模块下的 API,了解一下有哪些功能。然后再看一遍相关的单元测试,单元测试一般会把所有的功能细节都测一边。对源码的功能有所了解后,再去阅读源码的细节,效果更好。
proxy 术语
const p = new Proxy(target, handler)
- handler,包含捕捉器(trap)的占位符对象,可译为处理器对象。
- target,被 Proxy 代理的对象。
友情提醒
在阅读源码的过程中,要时刻问自己三个问题:
- 这是什么?
- 为什么要这样?为什么不那样?
- 有没有更好的实现方式?
正所谓知其然,知其所以然。
阅读源码除了要了解一个库具有什么特性,还要了解它为什么要这样设计,并且要问自己能不能用更好的方式去实现它。
如果只是单纯的停留在“是什么”这个阶段,对你可能没有什么帮助。就像看流水账似的,看完就忘,你得去思考,才能理解得更加深刻。
正文
reactivity 模块是 Vue3.0 的响应式系统,它有以下几个文件:
baseHandlers.ts collectionHandlers.ts computed.ts effect.ts index.ts operations.ts reactive.ts ref.ts
接下来按重要程度顺序来讲解一下各个文件的 API 用法和实现。
reactive.ts 文件
在 Vue.2x 中,使用 Object.defineProperty()
对对象进行监听。而在 Vue3.0 中,改用 Proxy
进行监听。Proxy
比起 Object.defineProperty()
有如下优势:
- 可以监听属性的增删操作。
- 可以监听数组某个索引值的变化以及数组长度的变化。
reactive()
reactive()
的作用主要是将目标转化为响应式的 proxy 实例。例如:
const obj = { count: 0 } const proxy = reactive(obj)
如果是嵌套的对象,会继续递归将子对象转为响应式对象。
reactive()
是向用户暴露的 API,它真正执行的是 createReactiveObject()
函数:
// 根据 target 生成 proxy 实例 function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any> ) { if (!isObject(target)) { if (__DEV__) { console.warn(`value cannot be made reactive: ${String(target)}`) } return target } // target is already a Proxy, return it. // exception: calling readonly() on a reactive object if ( target[ReactiveFlags.raw] && !(isReadonly && target[ReactiveFlags.isReactive]) ) { return target } // target already has corresponding Proxy if ( hasOwn(target, isReadonly ? ReactiveFlags.readonly : ReactiveFlags.reactive) ) { return isReadonly ? target[ReactiveFlags.readonly] : target[ReactiveFlags.reactive] } // only a whitelist of value types can be observed. if (!canObserve(target)) { return target } const observed = new Proxy( target, // 根据是否 Set, Map, WeakMap, WeakSet 来决定 proxy 的 handler 参数 collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers ) // 在原始对象上定义一个属性(只读则为 "__v_readonly",否则为 "__v_reactive"),这个属性的值就是根据原始对象生成的 proxy 实例。 def( target, isReadonly ? ReactiveFlags.readonly : ReactiveFlags.reactive, observed ) return observed }
这个函数的处理逻辑如下:
- 如果 target 不是一个对象,返回 target。
- 如果 target 已经是 proxy 实例,返回 target。
- 如果 target 不是一个可观察的对象,返回 target。
- 生成 proxy 实例,并在原始对象 target 上添加一个属性(只读则为
__v_readonly
,否则为__v_reactive
),指向这个 proxy 实例,最后返回这个实例。添加这个属性就是为了在第 2 步做判断用的,防止对同一对象重复监听。
其中第 3、4 点需要单独拎出来讲一讲。
什么是可观察的对象
const canObserve = (value: Target): boolean => { return ( !value[ReactiveFlags.skip] && isObservableType(toRawType(value)) && !Object.isFrozen(value) ) }
canObserve()
函数就是用来判断 value 是否是可观察的对象,满足以下条件才是可观察的对象:
- ReactiveFlags.skip 的值不能为
__v_skip
,__v_skip
是用来定义这个对象是否可跳过,即不监听。 - target 的类型必须为下列值之一
Object,Array,Map,Set,WeakMap,WeakSet
才可被监听。 - 不能是冻结的对象。
传递给 proxy 的处理器对象是什么
根据上面的代码可以看出来,在生成 proxy 实例时,处理器对象是根据一个三元表达式产生的:
// collectionTypes 的值为 Set, Map, WeakMap, WeakSet
collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers
这个三元表达式非常简单,如果是普通的对象 Object
或 Array
,处理器对象就使用 baseHandlers;如果是 Set, Map, WeakMap, WeakSet
中的一个,就使用 collectionHandlers。
collectionHandlers 和 baseHandlers 是从 collectionHandlers.ts
和 baseHandlers.ts
处引入的,这里先放一放,接下来再讲。
有多少种 proxy 实例
createReactiveObject()
根据不同的参数,可以创建多种不同的 proxy 实例:
- 完全响应式的 proxy 实例,如果有嵌套对象,会递归调用
reactive()
。 - 只读的 proxy 实例。
- 浅层响应的 proxy 实例,即一个对象只有第一层的属性是响应式的。
- 只读的浅层响应的 proxy 实例。
浅层响应的 proxy 实例是什么?
之所以有浅层响应的 proxy 实例,是因为 proxy 只代理对象的第一层属性,更深层的属性是不会代理的。如果确实需要生成完全响应式的 proxy 实例,就得递归调用 reactive()
。不过这个过程是内部自动执行的,用户感知不到。
其他一些函数介绍
// 判断 value 是否是响应式的 export function isReactive(value: unknown): boolean { if (isReadonly(value)) { return isReactive((value as Target)[ReactiveFlags.raw]) } return !!(value && (value as Target)[ReactiveFlags.isReactive]) } // 判断 value 是否是只读的 export function isReadonly(value: unknown): boolean { return !!(value && (value as Target)[ReactiveFlags.isReadonly]) } // 判断 value 是否是 proxy 实例 export function isProxy(value: unknown): boolean { return isReactive(value) || isReadonly(value) } // 将响应式数据转为原始数据,如果不是响应数据,则返回源数据 export function toRaw<T>(observed: T): T { return ( (observed && toRaw((observed as Target)[ReactiveFlags.raw])) || observed ) } // 给 value 设置 skip 属性,跳过代理,让数据不可被代理 export function markRaw<T extends object>(value: T): T { def(value, ReactiveFlags.skip, true) return value }