vue3(V3.0.0)源码解读---reactive

239 阅读9分钟

reactive

reactive返回一个对象的响应式代理 源码位置: packages -> reactivity -> src -> reactive.ts

export function reactive(target: object) {
    // if trying to observe a readonly proxy, return the readonly version. 
    if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
        return target
    } 
    return createReactiveObject(
        target,
        false,
        mutableHandlers,
        mutableCollectionHandlers
    )
}

判断对象传入的是不是reactive对象,如果是就直接retrn出去,如果不是就创建ReactiveObject

  • mutableHandlers 用于处理普通对象(即非集合类型的对象) 源码位置:reactivity -> src -> baseHandlers.ts
  • mutableCollectionHandlers 专门用于处理集合类型(如数组、Map、Set 等)的处理器, 源码位置:reactivity -> src -> collectionHandlers

mutableHandlers 普通对象拦截处理

const get = /*#__PURE__*/ createGetter()
export const mutableHandlers: ProxyHandler<object> = { 
    get, 
    set, 
    deleteProperty, 
    has, 
    ownKeys 
}

get拦截操作解读

createGetter 返回一个get拦截函数

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 
        } 
        if (!isReadonly) { 
            track(target, TrackOpTypes.GET, key) 
        } 
        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.IS_REACTIVE: 返回对象是否为响应式(非只读)。
  • ReactiveFlags.IS_READONLY: 返回对象是否为只读。
  • ReactiveFlags.RAW: 返回原始目标对象(未代理)。 首先会判断读取的属性是不是这三种类型,如果是就返回对应的处理。

二: 数组特殊处理

接下来处理,判断当前的target对象是不是数组,

arrayInstrumentations
const arrayInstrumentations: Record<string, Function> = {} 
// instrument identity-sensitive Array methods to account for possible reactive 
// values 
;(['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) 
        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) 
        const res = method.apply(arr, args) 
        if (res === -1 || res === false) { 
            // if that didn't work, run it again using raw values. 
            return method.apply(arr, args.map(toRaw)) 
        } else { 
            return res 
        } 
    } 
})

// instrument length-altering mutation methods to avoid length being tracked 
// which leads to infinite loops in some cases (#2137) 
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => { 
    const method = Array.prototype[key] as any 
    arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) { 
        pauseTracking() 
        const res = method.apply(this, args) 
        enableTracking() 
        return res 
    } 
})


// 用于暂时暂停和恢复依赖追踪,代码位置: packages -> reactivity -> src ->  effect.ts
let shouldTrack = true 
const trackStack: boolean[] = [] 
export function pauseTracking() { 
    trackStack.push(shouldTrack) 
    shouldTrack = false 
} 
export function enableTracking() { 
    trackStack.push(shouldTrack) 
    shouldTrack = true 
}

通过为数组的特定方法创建增强版本,Vue3的响应式系统能够更精确地追踪数据的变化,并避免潜在的无限循环问题。这些仪器化方法是Vue响应式系统的核心部分,它们使得Vue能够高效地处理数据绑定和视图更新。

身份敏感的数组方法
  • track函数是Vue响应式系统的一部分,用于追踪依赖关系。在这里,它遍历数组的每个元素,并追踪对这些元素的访问(使用元素的索引作为键)。
  • 首先尝试使用原始参数调用原始数组方法。如果方法返回-1false(表示未找到),则再次调用该方法,但这次使用toRaw处理过的参数(确保参数是原始值而不是响应式对象)。这是因为,在某些情况下,响应式包装器可能会干扰查找操作的结果。
改变数组长度的突变方法
  • pauseTrackingenableTracking是Vue响应式系统的函数,用于暂时暂停和恢复依赖追踪。这是必要的,因为直接修改数组长度可能会导致无限循环的依赖追踪(例如,当数组长度变化触发观察者更新,而这些更新又进一步修改数组长度时)。
  • 仪器化方法在执行原始数组方法之前调用pauseTracking,然后在方法执行完成后调用enableTracking。这确保了数组长度的变化不会导致不必要的依赖追踪。

三:访问属性

 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 
} 
  • 通过Reflect.get获取值
  • 对一些特殊符号键(内置符号或__proto____v_isRef)直接返回结果,不进行额外处理。

四:收集依赖副作用 & 浅响应式处理

if (!isReadonly) { 
    track(target, TrackOpTypes.GET, key) 
} 
if (shallow) { 
    return res 
}
  • 如果不是只读对象,则调用track函数进行依赖收集。 -如果设置了shallow,则直接返回属性值,不进行深度响应式转换。

五: ref解包

// 源码位置: packages -> shared -> src ->  index.ts
export const isIntegerKey = (key: unknown) => 
    isString(key) && 
    key !== 'NaN' && 
    key[0] !== '-' && 
    '' + parseInt(key, 10) === key
    
if (isRef(res)) { 
    // ref unwrapping - does not apply for Array + integer key. 
    const shouldUnwrap = !targetIsArray || !isIntegerKey(key) 
    return shouldUnwrap ? res.value : res 
}
  • 如果属性值是ref对象(Vue的响应式引用),则根据条件解包返回其value属性。对于数组和整数键,不解包以保持数组元素的行为一致性。

六:深度响应式转换

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)
}
  • 如果属性值是对象,则根据isReadonly参数,使用readonlyreactive函数进行深度响应式转换。

set拦截操作解读

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)
            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
        }
        
        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(target, TriggerOpTypes.ADD, key, value)
            } else if (hasChanged(value, oldValue)) {
                trigger(target, TriggerOpTypes.SET, key, value, oldValue)
            }
        }
        return result
    }
}

先获取保存旧值,然后非浅模式处理

非浅模式处理

value = toRaw(value)
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
    oldValue.value = value
    return true
}

首先尝试将新值转换为原始值(如果它是一个响应式引用,则获取其内部值)。如果目标不是数组,旧值是一个响应式引用,而新值不是响应式引用,那么直接将新值赋给旧值的.value属性,并返回true

  • 在浅模式下,不执行上述转换,对象按原样设置,不考虑其是否响应式。

检查键是否存在

const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)

判断目标对象是否已经拥有这个键。对于数组,如果键是一个整数索引,并且索引小于数组长度,则认为键存在;对于对象,使用hasOwn函数检查键是否直接存在于对象上。

使用Reflect.set设置值 触发依赖更新

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(target, TriggerOpTypes.ADD, key, value)
    } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
    }
}

如果目标对象与接收者的原始对象相同(确保不是通过原型链继承的某个对象),则根据键是否存在以及值是否改变,触发相应的依赖更新。如果键不存在,则触发添加操作(ADD);如果键存在且值已改变,则触发设置操作(SET)。

最后,返回Reflect.set的结果,这通常是true,表示设置操作成功。

deleteProperty 删除操作拦截

function deleteProperty(target: object, key: string | symbol): boolean { 
    const hadKey = hasOwn(target, 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 
}

首先检查key是否存在,,然后获取key对应的值,再通过Reflect.deleteProperty删除属性。最后如果删除成功且key存在就触发依赖更新

has 函数

function has(target: object, key: string | symbol): boolean { 
    const result = Reflect.has(target, key) 
    if (!isSymbol(key) || !builtInSymbols.has(key)) { 
        track(target, TrackOpTypes.HAS, key) 
    } 
    return result 
}

has 函数用于检查对象 target 上是否存在键 key

条件性依赖收集

  • if (!isSymbol(key) || !builtInSymbols.has(key)):这个条件检查 key 是否不是符号(Symbol),或者即使它是符号,也不是内置符号集合 builtInSymbols 中的一员。
  • 如果条件为真,则调用 track(target, TrackOpTypes.HAS, key)track 函数是Vue 3响应式系统的一部分,用于收集依赖关系。在这里,它表示有一个依赖正在检查 target 对象上是否存在 key 属性。
  • 注意:对于某些内置符号(如 Symbol.iterator),Vue可能选择不收集依赖,因为它们通常用于标准的迭代行为,而不是用于响应式数据的直接访问。

ownKeys函数

function ownKeys(target: object): (string | number | symbol)[] { 
    track(target, TrackOpTypes.ITERATE, ITERATE_KEY) 
    return Reflect.ownKeys(target)
}

ownKeys 函数用于获取对象 target 的所有键,包括可枚举和不可枚举属性以及符号键。

依赖收集

  • track(target, TrackOpTypes.ITERATE, ITERATE_KEY):在迭代对象之前,调用 track 函数来收集依赖关系。这里,TrackOpTypes.ITERATE 表示操作类型是迭代,而 ITERATE_KEY 是一个特殊的键或标识符,用于表示这个依赖是来自于对对象键的迭代。

获取所有键Reflect.ownKeys(target) 返回一个数组,包含 target 对象的所有键,包括可枚举和不可枚举属性以及符号键。

mutableCollectionHandlers 处理集合类型( Map、Set 等)。

export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = { 
    get: createInstrumentationGetter(false, false) 
}

function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) { 
    const instrumentations = shallow ? shallowInstrumentations  
    :  isReadonly  ? readonlyInstrumentations  : mutableInstrumentations 
    
    return ( 
        target: CollectionTypes, 
        key: string | symbol, 
        receiver: CollectionTypes 
    ) => {

        if (key === ReactiveFlags.IS_REACTIVE) { 
            return !isReadonly 
        } else if (key === ReactiveFlags.IS_READONLY) { 
            return isReadonly 
        } else if (key === ReactiveFlags.RAW) { 
            return target 
        } 

        return Reflect.get( 
            hasOwn(instrumentations, key) && key in target 
            ? instrumentations 
            : target, 
            key, 
            receiver 
            ) 
    } 
}
 

// 源码位置: packages -> reactivity -> src ->  index.ts
export const hasOwn = ( 
        val: object, 
        key: string | symbol 
    ): key is keyof typeof val => hasOwnProperty.call(val, key)
  • isReadonly: 一个布尔值,指示创建的响应式对象是否为只读
  • shallow: 一个布尔值,指示是否创建浅响应式对象
  • shallowInstrumentations: 处理浅响应式
  • readonlyInstrumentations: 处理只读响应式的逻辑
  • mutableInstrumentations: 处理可变响应式的逻辑
  • hasOwn 该函数用于检查一个对象(val)是否自身(而非通过其原型链)拥有一个特定的属性(key

createReactiveObject

function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
    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
    if (
        target[ReactiveFlags.RAW] &&
        !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
    ) {
        return target
    }

    // target already has corresponding Proxy
    const proxyMap = isReadonly ? readonlyMap : reactiveMap
    const existingProxy = proxyMap.get(target)
    if (existingProxy) {
        return existingProxy
    }
    // only a whitelist of value types can be observed.
    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
}
  1. 首先判断传入target是不是对象类型,如果不是直接return出去,在开发环境会打印提示log
  2. 然后判断传入target是不是响应式,
  3. 根据isReadonly判断获取响应代理集合
  4. 如果传入target已经在存在代理,就直接return出已经存在的代理
  5. 如果target的类型事白名单的直接return出去
  6. 创建Proxy代理,并将创建的代理存入响应代理集合中
  7. 最后return出去Proxy代理