持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第12天,点击查看活动详情
前言
我们知道 Vue3 的响应式原理采用的是 Proxy 来代理对象,通过劫持方法来实现响应式,而 reactive 的作用就是返回对象的响应式副本。
使用
对传入的对象进行响应式转换,这个转换是深层的,它影响这个对象所有嵌套的属性。
const obj = reactive({ count: 1 })
源码
源码的位置在,/packages/reactivity/src/reactive.ts
// packages/reactivity/src/reactive.ts
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
return createReac tiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
首先会判断这个对象是否是只读的,如果是会直接方法这个对象。只读是通过对象中是否有 ReactiveFlags.IS_READONLY 来判断的,ReactiveFlags 枚举非常常用,我们可以看下它的值。
export const enum ReactiveFlags {
SKIP = '__v_skip', // 是否跳过响应式 返回原始对象
IS_REACTIVE = '__v_isReactive', // 标记一个响应式对象
IS_READONLY = '__v_isReadonly',// 标记一个只读对象
IS_SHALLOW = '__v_isShallow',
RAW = '__v_raw'// 标记获取原始值
}
createReactiveObject
createReactiveObject 是 reactive 的核心方法,最终返回这个代理对象。
/**
*
* @param target 目标数据
* @param isReadonly 是否只读
* @param baseHandlers 生成代理对象的 handler 参数。当 target 类型是 Array 或 Object 时使用该 handler。
* @param collectionHandlers 当 target 类型是 Map、Set、WeakMap、WeakSet 时使用该 handler。
* @param proxyMap 存储生成代理对象后的 Map 对象。
*/
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
// 如果目标不是对象,直接返回原始值
if (!isObject(target)) {
return target
}
// 如果目标已经是一个代理,直接返回
// 除非对一个响应式对象执行 readonly
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 目标已经存在对应的代理对象
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 只有白名单里的类型才能被创建响应式对象
// Vue3 仅会对 Array、Object、Map、Set、WeakMap、WeakSet 生成代理,其他对象会被标记为 INVALID,
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
先来看下它的5个参数。
- target 目标数据
- isReadonly 是否只读
- baseHandlers 生成代理对象的 handler 参数。当 target 类型是 Array 或 Object 时使用该 handler。
- collectionHandlers 当 target 类型是 Map、Set、WeakMap、WeakSet 时使用该 handler。
- proxyMap 存储生成代理对象后的 Map 对象。
- 在方法中首先判断目标数据是否为对象,不是则直接返回原始值
- 判断目标是否已经是一个代理,如果是直接返回
- 目标已经存在对应的代理对象,直接返回
- 只有白名单里的类型才能被创建响应式对象
- 通过 Proxy 创建代理,并传入 Handlers。
- 最后返回这个 proxy
get handler
在 handler 中定义了劫持这个对象的操作方法,我们来看下具体实现。
baseHandlers 中处理 Array、Object 的数据类型,也是我们绝大部分时间使用 Vue3 时使用的类型。
const get = /*#__PURE__*/ createGetter()
get 通过 createGetter 进行创建。
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// 如果 get 访问的 key 是 '__v_isReactive',返回 createGetter 的 isReadonly 参数取反结果
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
// 如果 get 访问的 key 是 '__v_isReadonly',返回 createGetter 的 isReadonly 参数
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
return shallow
} else if (
// 如果 get 访问的 key 是 '__v_raw',并且 receiver 与原始标识相等,则返回原始值
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
const targetIsArray = isArray(target)
// arrayInstrumentations是一个对象,对象内保存了若干个被特殊处理的数组方法,并以键值对的形式存储。
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 如果是 shallow 浅层响应式,直接返回 get 结果
if (shallow) {
return res
}
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
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
}
}
- 首先会通过 ReactiveFlags 的各种类型进行判断返回相应的值。
- 通过映射
const res = Reflect.get(target, key, receiver)获取到对应key的值。 - 使用 track 方法就行依赖收集,这个与副作用函数 effect 有关,我们后面再详细展开讲
set
set 的劫持也差不多。通过 createSetter 进行创建。
在 set 方法只,也是通过映射来设置值 const result = Reflect.set(target, key, value, receiver)。
然后调用 trigger 方法来派发更新