前言
这篇文章的内容是vue另一个常见重要的API ref 实现的思路是有一个容器存储的值,有两个方法,一个用于调用 trackEffect 收集依赖,一个用于调用 triggerEffect 触发依赖更新,还有很多衍生API,例如:toRef,toRefs等
源代码位置在:vue-next3.2/packages/reactivity/src/ref.ts
工具函数
// 如果用户使用ref包装对象,使其转换成reactive代理对象
const convert = <T extends unknown>(val: T): T =>
isObject(val) ? reactive(val) : val
// 判断是否是ref对象
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref {
return Boolean(r && r.__v_isRef === true)
}
// 转换成原始数据
export function toRaw<T>(observed: T): T {
const raw = observed && (observed as Target)[ReactiveFlags.RAW]
return raw ? toRaw(raw) : observed
}
基本了解
代码中有主要功能都是基于三个类进行实现的,分别是:RefImpl、CustomRefImpl、ObjectRefImpl
RefImpl:ref api的构造函数,
CustomRefImpl:自定义ref customRef的构造函数
ObjectRefImpl: toRef的构造函数 可以用来为源响应式对象上的某个 property 新创建一个 ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
源码解析
ref API
这个方法的作用是:接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property .value。
看看实现原理
export function ref<T extends object>(value: T): ToRef<T>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
// 逻辑封装在 createRef 中
return createRef(value)
}
function createRef(rawValue: unknown, shallow = false) {
if (isRef(rawValue)) {
return rawValue
}
// RefImpl 实例化一个 ref
return new RefImpl(rawValue, shallow)
}
class RefImpl<T> {
// 存储的响应式的值
private _value: T
// 存储的是原始数据
private _rawValue: T
// 依赖项
public dep?: Dep = undefined
// 标识为他是ref
public readonly __v_isRef = true
// 在初始化时,对 _value _rawValue进行赋值 但是这两个数据不能直接被外面获取和修改
// 有一对getter和setter来进行拦截
constructor(value: T, public readonly _shallow = false) {
this._rawValue = _shallow ? value : toRaw(value)
this._value = _shallow ? value : convert(value)
}
get value() {
// 获取值时、通过trackRefValue做一些处理之后,再去调用trackEffects进行依赖收集
trackRefValue(this)
return this._value
}
set value(newVal) {
// value可能是代理对象,一般不会存储代理对象而是原始数据
newVal = this._shallow ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
// 原始数据可能是对象,转换一下
this._value = this._shallow ? newVal : convert(newVal)
triggerRefValue(this, newVal)
}
}
}
从上面可以看出,ref的核心逻辑都在 RefImpl 这个类里面,其中有四个重要的属性,_value:取数据和修改数据都是在这个属性上面操作,_rawValue:存储的是原始数据,在_value被修改的时候,他也会跟着被修改,dep:存储的是副作用函数,一般触发依赖都是遍历这个集合,_v_isRef:这个属性被设置为true 标识这个对象是ref类型
toRef
这个API是ref的变体,ref是把一个值变成响应式的值,而这个是拿到对象上的一个属性的属性值变成响应式属性作为一个ref,下面看一下代码实现
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K
): ToRef<T[K]> {
// 是ref对象 返回 不是就实例化
return isRef(object[key])
? object[key]
: (new ObjectRefImpl(object, key) as any)
}
class ObjectRefImpl<T extends object, K extends keyof T> {
// __v_isRef 为true 作为ref对象的标识
public readonly __v_isRef = true
// 存储对象 存储key
constructor(private readonly _object: T, private readonly _key: K) {}
// 获取和修改都是在对象上操作 ref对象本身不会存储值
get value() {
return this._object[this._key]
}
set value(newVal) {
this._object[this._key] = newVal
}
}
这样所产生的ref对象,身上不会存储值,而是存储了一个目标对象,且都有一个标识__v_isRef 作为ref标识,但是,在获取值和修改值是去目标对象上操作,不会有收集依赖和触发依赖的操作,而toRefs 就是由这个API所衍生处理的,
toRefs
其核心就是遍历目标,然后拿到所有的key和value去调用toRef 返回值存储在一个相对于的结构对象上,最后再把对象返回,但前提是目标是一个代理对象,
export function toRefs<T extends object>(object: T): ToRefs<T> {
if (__DEV__ && !isProxy(object)) {
console.warn(`toRefs() expects a reactive object but received a plain one.`)
}
// 根据源数据结构生成一个对应的数据结构
const ret: any = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
// 存储转换后的数据
ret[key] = toRef(object, key)
}
return ret
}
自定义Ref
官方文档是这样说的:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 track 和 trigger 函数作为参数,并且应该返回一个带有 get 和 set 的对象。
class CustomRefImpl<T> {
public dep?: Dep = undefined
private readonly _get: ReturnType<CustomRefFactory<T>>['get']
private readonly _set: ReturnType<CustomRefFactory<T>>['set']
public readonly __v_isRef = true
constructor(factory: CustomRefFactory<T>) {
// factory 是用户传递进来的工厂函数
// get 和 set 是用户自定义 并将依赖收集和依赖触发函数传递过去
const { get, set } = factory(
() => trackRefValue(this),
() => triggerRefValue(this)
)
// 存储自定义 get 和 set
this._get = get
this._set = set
}
get value() {
return this._get()
}
set value(newVal) {
this._set(newVal)
}
}
// 自定义Ref
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
return new CustomRefImpl(factory) as any
}
具体原理是,内部接受一个函数,函数需要实现两个方法并且返回,而函数会接受到收集依赖和触发依赖的方法,注意的一共由两点:1.需要自己去手动调用收集和触发依赖的方法,2. 在进行更新值的时候,内部不会进行校验是否由改变值,这可能会触发一些不必要的调用,需要自己去校验
总结
我个人认为ref模块是整一个响应式模块里最简单的,相对其他模块,不会由太复杂的逻辑,很容易看明白,而ref本身可以视为一个容器,里面存着一系列属性和一对更新方法,且对于vue3的组合API,ref的使用可能会比reactive更多,在传递的过程中,ref 传递的是容器的地址, reactive 传递只是通过get返回的值,虽然可以通过toRefs转换成,那还不如开始就用ref 以上就是我对ref的实现原理的解析,欢迎各位大佬补充,谢谢大家