目录
- 【真香系列】Vue-Next 源码第一章:阅读源码的准备工作
- 【真香系列】Vue-Next 源码第二章:初始化流程上
- 【真香系列】Vue-Next 源码第三章:初始化流程下
- 【真香系列】Vue-Next 源码第四章:更新
- 【真香系列】Vue-Next 源码第五章:reative & effect
- 【真香系列】Vue-Next 源码第六章:新特性原理
- 【真香系列】Vue-Next 源码第七章:实战组件
Demo
回顾下我们的案例,我们使用 reactive 创建了一个 state,之后该 state 的变化都会触发响应式的更新渲染。
const state = reactive({ message: 'World' })
reactive
调用 createReactiveObject 方法并返回创建的 state 代理对象。
// packages/reactivity/src/reactive.ts
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
// 如果试图观测一个只读的代理对象,返回只读版本
// Vue3 中有一个 readonly api 可以创建只读的代理对象,如果操作只读代理会触发一个 warning
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
}
createReactiveObject
先看下参数:
target是{ message: 'World' }对象isReadonly用来区分是否为只读,Vue3中可以使用readonly创建只读的代理对象。baseHandlers针对Object/Array的proxy相关属性get/set/has/ownKeys/deletePropertycollectionHandlers针对集合类型Set/Map/WeakMap/WeakSet的proxy相关属性
packages/reactivity/src/baseHandlers.ts
packages/reactivity/src/collectionHandlers.ts
然后经过一系列如是否可创建、是否已经是 Proxy 等判断后,创建 target 对应 Proxy。
// packages/reactivity/src/reactive.ts
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
// 必须是对象才可以被创建 reactive
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
// 如果目标已经是一个 Proxy 对象则返回
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
// 在 readonlyMap/reactiveMap 中寻找与 target 对应的 Proxy,如果已经存则返回这个 Proxy 对象
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only a whitelist of value types can be observed.
// 进一步判断目标上是否存在 __v_skip 属性或者目标是一个不可扩展的对象(如 Object.freeze 会把对象标记为不可扩展)
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 创建 Proxy 对象,Object/Array 使用 baseHandlers,集合使用 collectionHandlers
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 放入 proxyMap 缓存避免重复创建
proxyMap.set(target, proxy)
return proxy
}
ReactiveFlags
markRaw 是 Vue3 提供的一个方法,可以让一个对象不可被转换为 Proxy 对象,调用这个方法会在对象上添加一个 __v_skip 标识属性。__v_isReactive 和 __v_isReadonly 用来区分响应或只读。__v_raw 原始 target。
// packages/reactivity/src/reactive.ts
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
RAW = '__v_raw'
}
TargetType
根据 TargetType 会使用对应的 Handlers。
// packages/reactivity/src/reactive.ts
const enum TargetType {
INVALID = 0,
COMMON = 1, // Object/Array
COLLECTION = 2 // Map/Set/WeakMap/WeakSet
}
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}
shallowReactive & shallowReadonly
Vue3 可以创建四种类型的 Proxy 对象,分别为:
reactiveshallowReactive只对根属性做响应处理readonlyshallowReadonly只对根属性做只读处理
带 shallow 前缀的代表响应的浅处理,每种类型都有对应的 Proxy Handler。
baseHandlers & collectionHandlers
baseHandlers 包括 mutableHandlers、 readonlyHandlers、 shallowReactiveHandlers 和 shallowReadonlyHandlers
// packages/reactivity/src/baseHandlers.ts
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
这里我们详细看 mutableHandlers,主要介绍 proxy 的两个属性:
get
// packages/reactivity/src/baseHandlers.ts
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (
key === ReactiveFlags.RAW &&
receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
) {
return target
}
const targetIsArray = isArray(target)
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
const keyIsSymbol = isSymbol(key)
if (
keyIsSymbol
? builtInSymbols.has(key as symbol)
: key === `__proto__` || key === `__v_isRef`
) {
return res
}
// 非只读进行 track
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// shallowReactiveHandlers 用
if (shallow) {
return res
}
// 如果是 res 是一个 ref,判断是否为数组,非数组进行 unwrapped
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
// 如果 res 是对象,则进行 readonly/reactive
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
当对 state 做 get 操作时,先判断 state 是否为数组且调用方法 key 是否为 includes/indexOf/lastIndexOf,如果是则使用 arrayInstrumentations 中对应的方法。arrayInstrumentations 包装了数组的各个方法。
// packages/reactivity/src/baseHandlers.ts
;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
const method = Array.prototype[key] as any
arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
const arr = toRaw(this) // 原始数组
// 因为 includes/indexOf/lastIndexOf 同样会触发数组每一项的 getter
// 数组的每一项都要进行 track 依赖收集,因为后续数组操作的都是原始对象而非 proxy,不能自动收集
for (let i = 0, l = this.length; i < l; i++) {
track(arr, TrackOpTypes.GET, i + '')
}
// we run the method using the original args first (which may be reactive)
// 先用初始值执行一次(初始值可能是 reactive)
const res = method.apply(arr, args)
if (res === -1 || res === false) {
// if that didn't work, run it again using raw values.
// 如果不成功,初始值转成原始值执行
// 之所以会执行两次是因为,如果原始数组 raw 里包含一个 reactive obj, 原始数组在转成 reactive arr, 那么 arr 其实是不包含 obj 的,是转换之前的 raw 包含 obj
return method.apply(arr, args.map(toRaw))
} else {
return res
}
}
})
如果操作的不是数组,则 const res = Reflect.get(target, key, receiver) 获取值,如果不是只读,则进行 track 依赖收集。
set
// packages/reactivity/src/baseHandlers.ts
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
const oldValue = (target as any)[key]
if (!shallow) {
value = toRaw(value)
// 如果 oldValue 是一个 ref,要改变的是 unwrapper 的 value
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
// 判断 target 是否有操作属性 key,决定是添加还是 set 属性
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
// 如果 target 不等于 receiver 证明 setter 操作的属性是在原型链上,那么不进行 trigger
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
第四篇文章里介绍了更新流程,当时介绍的入口方法是 trigger,其实就是从这里触发的。
reactive 总结
proxy大致分为四种,每种都有对应的handlershandlers重点在于track/trigger操作,分别代表依赖收集和派发更新。Vue3对于数组方法进行了hack
接下来我们重点介绍 effect。