前提
响应式API reactive 是放在了 reactivity 文件夹里面,这里面还同样放置了ref,
ref 要另起一篇来讲
reactive
reactive.ts
总体来说,做了两件事,
- 把对象转化为响应式对象
- 收集依赖,更新依赖
createReactiveObject 函数
- reactive 核心函数
- createReactiveObject 函数核心就是Proxy
- 目的是可以监听到用户的get 和 set的动作
- 使用缓存做了优化
- 参数proxyMap 其实就是reactiveMap, 用来做缓存的
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
/* 如果不是对象的话,直接返回,因为基础类型是要用ref函数处理的 */
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target 已经是一个proxy对象了,直接返回
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
/*
先获取,是否缓存过 target
为了解决: 一个对象多次使用reactive转化,都是返回同一个响应式对象
*/
const existingProxy = proxyMap.get(target)
if (existingProxy) {
/* 如果之前缓存过,那么直接返回 */
return existingProxy
}
/*
获取目标类型,用于判断下面的逻辑
*/
const targetType = getTargetType(target)
/* 如果是冻结对象,也就是说不可扩展的对象,就直接返回target */
if (targetType === TargetType.INVALID) {
return target
}
/*
这里是这个函数的关键代码
通过proxy代理对象
collectionHandlers 这个指的是 Map, Set, WeakMap, WeakSet 这四种类型的 handlers
baseHandlers: Object, Array
*/
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
/*
代理完之后,把proxy给缓存起来
*/
proxyMap.set(target, proxy)
// 最后,返回proxy对象
return proxy
}
mutableHandlers
/*
这是最基础的 handlers
处理对象和数组
*/
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
createGetter
createGetter 生成get 函数
/*
createGetter 接受两个参数
1,是否只读,2, 浅代理
*/
function createGetter(isReadonly = false, shallow = false) {
/* */
return function get(target: Target, key: string | symbol, receiver: object) {
/* 下面的判断都是返回特定的get */
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)
) {
/* toRaw() 函数, 执行到这里了 */
return target
}
/* 判断是不是数组 */
const targetIsArray = isArray(target)
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
/*
数组的话,到这里结束了
在这个里面 arrayInstrumentations, 重点看一下这个,处理了数组的一些方法
数组其实需要单独开一篇讲解的,可以参考字节的大佬的文章,https://juejin.cn/post/6844904056356339720
*/
return Reflect.get(arrayInstrumentations, key, receiver)
}
/*
获取get返回值
*/
const res = Reflect.get(target, key, receiver)
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
/*
问题:为什么是 readonly 的时候不做依赖收集呢
readonly 的话,是不可以被 set 的, 那不可以被 set 就意味着不会触发 trigger
所有就没有收集依赖的必要了
只读的数据回改变,也就不需要收集依赖
*/
if (!isReadonly) {
/*
在触发 get 的时候进行依赖收集
*/
track(target, TrackOpTypes.GET, key)
}
/* shallow 的话,只劫持一层 */
if (shallow) {
/* 直接return */
return res
}
/* 如果是ref 类型 */
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)) {
/*
如果是对象的话
再执行reactive代理,每一层都代理
如果是只读的话,就直接返回 readonly后的
注意: 这里是延迟代理
这里注意,只会代理第一层的数据,只有在读取数据触发get 才会把嵌套的对象转化成响应式对象
Vue2里面,是直接递归把所有的数据全部转化成响应式对象
*/
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
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)) {
/*
不是数组,并且,旧值是ref, 新值不是ref,执行下面的操作
*/
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
/* 判断要设置的key 存不存在 */
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// receiver : Proxy或者继承Proxy的对象
/*
if 用于判断不是继承自proxy的对象,也就是说,如果是原型链中的东西,就不要触发
*/
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
}
}