​Vue3的响应式实现


Vue3的响应式是通过proxy实现的,在源码的/packages/reactivity目录下。

整个响应式系统的流程如下:

1、通过state = reactive(target) 来定义响应式数据(代理get、set、deleteProperty、has、ownKeys等操作)

2、通过 effect 声明依赖响应式数据的函数cb ( 例如视图渲染函数render函数),并执行cb函数,执行过程中,会触发响应式数据 getter

3、在响应式数据 getter中进行 track依赖收集:存储响应式数据与更新函数 cb 的映射关系,存储于targetMap

4、当变更响应式数据时,触发trigger,根据targetMap找到关联的cb并执行

通过源码来看下这几个关键函数的实现:

reactive

/packages/reactivity/reactive:

function reactive(target: object) {
// 如果尝试观察只读代理,则返回只读版本
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
)
{
// 如果不是对象,直接返回即可
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// 代理的目标本身就是代理的proxy,直接返回自身
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 代理的目标已经被代理过了,直接返回代理对象
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 只能代理可以代理的白名单类型对象.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 判断代理的对象类型,来根据不同的类型做不同的代理处理
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 保存在proxyMap,防止目标对象被重复代理
proxyMap.set(target, proxy)
return proxy
}

通过reactive调用createReactiveObject生成响应式对象,对传入的target有做不同情况的处理,proxy的handler用传入的baseHandlers,这里默认传入的是mutableHandlers,这个方法从reactivity/baseHandlers导入:

mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
const get = /*#__PURE__*/ createGetter()
const set = /*#__PURE__*/ createSetter()
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {

...

// 对数组做特殊的读取值处理
const targetIsArray = isArray(target)

if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}

const res = Reflect.get(target, key, receiver)

// track 依赖收集
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}

...

// 如果读取的值是对象,递归调用reactive,使之成为响应式对象
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}

return res
}
}
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean
{
let oldValue = (target as any)[key]

...

// 判断是新增还是删除属性
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
if (target === toRaw(receiver)) {
if (!hadKey) {
// trigger更新函数
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}

mutableHandlers对get、set、deleteProperty等属性操作做了处理,这边只分析get 和set。在get的时候会进行track依赖收集,如果get的属性值是对象还会进行递归响应式处理,set则会trigger进行更新。

track

function track(target: object, type: TrackOpTypes, key: unknown) {
if (!shouldTrack || activeEffect === undefined) {
return
}
// 获取target对应依赖表
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 获取key对应的响应函数集合
let dep = depsMap.get(key)
if (!dep) {
// 动态创建依赖关系
depsMap.set(key, (dep = new Set()))
}
// activeEffect临时变量,getter触发依赖收集的回调函数,可能是render或者effect生成的副作用函数
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}

track依赖收集的时候,先判断targetMap是否存在访问的这个对象,targetMap是一个weakMap的结构,格式为{target:{ key: [fn1,fn2]}},target为weakMap的key,value是一个map类型,key为访问到的target的属性,值为这个属性对应的回调函数集合。最后面有一个activeEffect的判断,这个判断依赖收集的副作用函数,这个副作用函数可能是ffect临时生成,也有可能是在render渲染函数临时生成的副作用函数。

trigger

function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
)
{
// 获取触发更新的target对应的属性映射集合
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}

const effects = new Set<ReactiveEffect>()
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}

if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
add(depsMap.get(key))
}

// also run for iteration key on ADD | DELETE | Map.SET
// 根据触发的操作类型做不同的回调函数处理
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}

const run = (effect: ReactiveEffect) => {
if (__DEV__ && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
// 执行所有的回调函数集合
effects.forEach(run)
}

trigger触发更新,根据targetsMap找到target对应的属性依赖集合,再根据key找到回调函数集合,然后还要根据操作类型做处理后,执行所有的回调函数集合。

effect

// effect栈,保存所有的effect副作用函数
const effectStack: ReactiveEffect[] = []
function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T>
{
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T>
{
const effect = function reactiveEffect(): unknown {
if (!effect.active) {
return options.scheduler ? undefined : fn()
}
// effectStack是否存在当前执行的副作用函数
if (!effectStack.includes(effect)) {
cleanup(effect)
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}

effectStack栈结构的数组,effect的时候,将副作用函数放入effectStack中,再将activeEffect临时赋值为当前执行的effect函数,用于track的时候将effect函数放入响应式数据的key的回调函数集合,effect执行完再将activeEffect赋值回原来effectStack的末位函数。


原文始发于微信公众号(消失的程序员):​Vue3的响应式实现

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/250637.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!