Vue3源码解析(二)ref的响应性

41 阅读2分钟

上篇文章我们了解了reactive的响应性,知道了 reactive 函数的一些局限性,因为reactive的不足,所以vue3又为我们提供了ref函数构建响应性,那么今天我们一起来探究一下ref的响应性吧。

一、ref函数构建复杂数据类型的响应性

创建packages/reactivity/src/ref.ts 模块:

通过阅读源码可知,ref函数是返回了一个RefImpl类型的实例,首先我们来实现一下这个实例:

import { createDep, Dep } from "./dep"
import { activeEffect, trackEffects } from "./effect"
import { toReactive } from "./reactive"

export interface Ref<T = any> {
    value: T
}

export function ref (value?: unknown) {
    return createRef(value, false)
}

export function createRef(rawValue: unknown, shallow: boolean) {
    if(isRef(rawValue)) {
        return rawValue
    }
    return new RefImpl(rawValue, shallow)
}

class RefImpl<T> {
    private _value: T

    public dep?: Dep = undefined

    public readonly __v_isRef = true
    constructor (value: T, public readonly __v_isShallow: boolean) {
        // 如果 __v_isShallow 为 true,则 value 不会被转化为 reactive 数据,即如果当前 value 为复杂数据类型,则会失去响应性。对应官方文档 shallowRef :https://cn.vuejs.org/api/reactivity-advanced.html#shallowref
        this._value = __v_isShallow ? value : toReactive(value)
    }
    get value() {
        trackRefValue(this)
        return this._value
    }

    set value(newVal) {
    }
}

export function trackRefValue (ref) {
    if(activeEffect) {
        trackEffects(ref.dep || (ref.dep = createDep()))
    }
}

export function isRef(r: any): r is Ref {
    return !!(r && (r as any).__v_isRef === true)
}

ref函数构建复杂数据类型的响应性,本质上还是通过reactive函数实现的 在reactive.ts中:

export const toReactive = <T extends unknown>(value: T) : T => isObject(value) ? reactive(value as object) : value

二、ref函数构建简单数据类型的响应性

对于构建简单数据类型的响应性,在set value中对原始值和新值进行对比,从而判断是否触发对应的依赖:

set value(newVal) {
        if(hasChanged(newVal, this._rawValue)) {
            this._rawValue = newVal
            this._value = toReactive(newVal)
            triggerRefValue(this)
        }
    }
export function triggerRefValue(ref) {
    if(ref.dep) {
        triggerEffects(ref.dep)
    }
}

在这样的代码,我们需要知道的最重要的一点是:简单数据类型,不具备数据监听的概念,即本身并不是响应性的。

只是因为 vue 通过了 set value()的语法,把 函数调用变成了属性调用的形式,让我们通过主动调用该函数,来完成了一个“类似于”响应性的结果。

三、总结

ref函数本质上是生成了一个 RefImpl 类型的实例对象,通过 get 和 set 标记处理了 value函数

因为 ref 需要处理简单数据类型的响应性,但是对于简单数据类型而言,它无法通过proxy 建立代理, 所以 vue 通过 get value()和 set value () 定义了两个属性函数,通过 主动 触发这两个函数(属性调用)的形式来进行 依赖收集 和 触发依赖所以我们必须通过.value 来保证响应性。