前言
如果源码解释不对,麻烦过客留下宝贵的留言,感激不尽。
源码文件入口vue-next\packages\reactivity\src\reactive.ts
文章版本v1.0(后续会文章优化迭代)
第一次执行
源码分析
平时使用reactive时,调用的就是reactive.ts文件里的reactive()函数:
setup(props) {
const obj = reactive({a: 1});
}
来看看这个reactive函数源码:
// 先看看reactive函数会用到的东西
export const enum ReactiveFlags { // 这里定义了几个枚举值,用于下面Target接口的key
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
RAW = '__v_raw'
}
export interface Target {
[ReactiveFlags.SKIP]?: boolean
[ReactiveFlags.IS_REACTIVE]?: boolean
[ReactiveFlags.IS_READONLY]?: boolean
[ReactiveFlags.RAW]?: any
}
// Target相当于
export interface Target {
'__v_skip'?: boolean
'__v_isReactive'?: boolean // 是否已经被处理成响应式
'__v_isReadonly'?: boolean // 是否只读
'__v_raw'?: any // 是否是原生对象(未被处理过的)
}
了解后开始看reactive:
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T> // T extends object 泛型约束为对象,也就是传入的变量编辑器要认定为对象,返回的UnwrapNestedRefs<T> 先不解释
// (也不知道这块函数的定义是不是下个函数的重载写法)
// 开始进入reactive函数
export function reactive(target: object) {
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) { // 如果有值传进函数,并且是只读的那就直接返回
return target
}
// 返回一个创建响应式对象的函数,接下来看看这个函数是什么
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
createReactiveObject
:
function createReactiveObject(
target: Target, // 传进来的变量
isReadonly: boolean, // 是否只读
baseHandlers: ProxyHandler<any>, // 后面再分析
collectionHandlers: ProxyHandler<any>, // 后面再分析
proxyMap: WeakMap<Target, any> // 后面再分析
) {
if (!isObject(target)) { // 判断是否为一个引用类型,不是就返回(__DEV__为true的话就再多出个报错信息)
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.IS_REACTIVE])
) {
return target
}
const existingProxy = proxyMap.get(target) // proxyMap存放了已经被处理的对象集,所以这里看看是不是之前已经响应式处理过放在这里了
if (existingProxy) {
return existingProxy // 存过就直接返回
}
// only a whitelist of value types can be observed.
const targetType = getTargetType(target) // 鉴别target的类型(做了类型的归类),处理过程就略过了,只要知道的是返回0就是无效的,不可拓展的对象,是'Object'和'Array'类型,2是'Map'、'Set'、'WeakMap'、'WeakSet'类型
if (targetType === TargetType.INVALID) { // 如果为0,直接返回
return target
}
const proxy = new Proxy( // 通过Proxy将target进行响应式处理
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers // 后面再分析collectionHandlers与baseHandlers
)
proxyMap.set(target, proxy) // 并将处理结果存在proxyMap里
return proxy // 返回处理结果
}
实践分析
当我们第一次执行reactive函数时,传进来的对象并没有经过响应式处理,也没有ReactiveFlags上枚举的哪些属性,所以进入这个函数基本都是一路下来到new Proxy的执行,然后把结果保存在proxyMap中后返回处理结果。
可以自己亲自在const obj = reactive({a: 1});
上打个断点,一步一步的调试去查看源码是怎么走的。
createReactiveObject函数的入参
提示:在接着讲之前,需要知道proxy的使用方法。
前面提到在reactive调用createReactiveObject函数的时候,还有三个参数没讲到mutableHandlers、mutableCollectionHandlers、reactiveMap。
在createReactiveObject函数中的new Proxy部分就用到了mutableHandlers、mutableCollectionHandlers。
function createReactiveObject(
target: Target, // 传进来的变量
isReadonly: boolean, // 是否只读
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
//....
const proxy = new Proxy( // 通过Proxy将target进行响应式处理
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers // 如果targetType是2('Map'、'Set'、'WeakMap'、'WeakSet'类型),那么就以collectionHandlers作为proxy的第二参数,否则'Object'和'Array'类型就以baseHandlers作为第二参数
)
//...
}
因为我们常用的对象类型就是’Object’和’Array’类型,那么就看看baseHandlers,也就是reactive函数传入的mutableHandlers
mutableHandlers
源码:
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
这里我们就只看get、set和deleteProperty就行了:
get
get 方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
const get = /*#__PURE__*/ createGetter() // 返回一个proxy的get方法
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
if (key === ReactiveFlags.IS_REACTIVE) { // 获取__v_isReactive上的值
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) { // 获取__v_isReadonly上的值
return isReadonly
} else if ( // 获取原生未被处理的对象
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
const targetIsArray = isArray(target) // 判断target是否是一个数组
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) { // 对象是一个数组,并且key是符合vue改写后的数组api的(arrayInstrumentations应该是vue3把数组的几个api方法加工后的数组类型)
return Reflect.get(arrayInstrumentations, key, receiver) // 那就返回一个用于数组的get方法替代现在进入的这个get方法
}
const res = Reflect.get(target, key, receiver) // 获取key的值
// 接下来是对key的值做处理
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { // 不需要跟踪的key
return res
}
if (!isReadonly) { // 非只读的
track(target, TrackOpTypes.GET, key) // 这个作用是,当值发生改变,通知用到的dom重新渲染
}
if (shallow) { // 是浅响应式的,直接返回
return res
}
if (isRef(res)) { // 如果key是一个ref(说明支持key为一个ref处理过的对象)
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res // 如果不是一个数组或不是一个有下标作为key的变量,就返回.value
}
if (isObject(res)) { // key是一个对象,那么如果设置只读,就经过只读处理,没有就经过响应式处理(应该就是通过改写proxy的get实现遍历属性响应式处理)
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
set
再来看看set:
set 方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
const set = /*#__PURE__*/ createSetter() // 返回一个set方法
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key] // 拿到旧的值
if (!shallow) { // 如果不是浅响应式对象
// 进行原始化处理
value = toRaw(value)
oldValue = toRaw(oldValue)
if (!isArray(target) && isRef(oldValue) && !isRef(value)) { // 当外界xxx.a = 1改写了值,且旧值是通过ref进行的响应式处理
oldValue.value = value // 把.vulue更新一下
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
const hadKey = // 看看是不是新添加的key
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver) // 这里拿到的是个布尔值(不知道为什么createSetter方法要返回一个布尔值)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) { // 这种场景很少出现,翻译过来就是:如果目标在原始原型链中,则不触发
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value) // 新添加了key就跟踪到使用的地方
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue) // 修改了也跟踪
}
}
return result
}
}
deleteProperty
这就是删除
function deleteProperty(target: object, key: string | symbol): boolean {
const hadKey = hasOwn(target, key) // 是否有这key
const oldValue = (target as any)[key] // 拿到旧值
const result = Reflect.deleteProperty(target, key)
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue) // 删除后把用到的地方全部调用一遍
}
return result
}
动手实现
有篇文章已经总结的很好了Vue 3 深入响应式原理 - 聊一聊响应式构建的那些经历