Ref 类型定义
Ref 接口定义了 ref 函数返回的类型签名,value 属性保存着 ref 的原始值,[RefSymbol] 是内部定义的唯一符号用于类型区分,_shallow 标识标志这个这个 ref 是否为一个浅层 ref。
declare const RefSymbol: unique symbol
export interface Ref<T = any> {
value: T
/**
* Type differentiator only.
* We need this to be in public d.ts but don't want it to show up in IDE
* autocomplete, so we use a private Symbol instead.
*/
[RefSymbol]: true
/**
* @internal
*/
_shallow?: boolean
}
UnwrapRef
UnwrapRef 是用于解套 Ref 时的类型声明,通过 infer 推断出包裹类型再利用 UnwrapRefSimple 进一步解套。UnwrapRefSimple 定义的规则如下:
Function类型、集合类型、基本类型、Ref类型、RefUnwrapBailTypes直接返回,因为不涉及嵌套解套;- 数组和对象,有
ref转换的时候会对对象进行reactive所以要进行深层的解套。
export type UnwrapRef<T> = T extends Ref<infer V>
? UnwrapRefSimple<V>
: UnwrapRefSimple<T>
type UnwrapRefSimple<T> = T extends
| Function // a function
| CollectionTypes // Map|Set|WeakSet|WeakMap
| BaseTypes // string | boolean | number
| Ref // a Ref
| RefUnwrapBailTypes[keyof RefUnwrapBailTypes]
? T
: T extends Array<any>
? { [K in keyof T]: UnwrapRefSimple<T[K]> }
: T extends object
? UnwrappedObject<T>
: T
由于对对象结构时 in keyof 会导致 symbol 作为签名的属性丢失,导致一个对象失去 Object 类型的一些基本约束,所以要手动访问并且添加(这些属性签名本来就是硬编码到 Object 类型的):
type UnwrappedObject<T> = { [P in keyof T]: UnwrapRef<T[P]> } & SymbolExtract<T>
// Extract all known symbols from an object
// when unwrapping Object the symbols are not `in keyof`, this should cover all the
// known symbols
type SymbolExtract<T> =
(T extends { [Symbol.asyncIterator]: infer V }? { [Symbol.asyncIterator]: V } : {}) &
(T extends { [Symbol.hasInstance]: infer V } ? { [Symbol.hasInstance]: V } : {}) &
(T extends { [Symbol.isConcatSpreadable]: infer V } ? { [Symbol.isConcatSpreadable]: V } : {}) &
(T extends { [Symbol.iterator]: infer V } ? { [Symbol.iterator]: V } : {}) &
(T extends { [Symbol.match]: infer V } ? { [Symbol.match]: V } : {}) &
(T extends { [Symbol.matchAll]: infer V } ? { [Symbol.matchAll]: V } : {}) &
(T extends { [Symbol.replace]: infer V } ? { [Symbol.replace]: V } : {}) &
(T extends { [Symbol.search]: infer V } ? { [Symbol.search]: V } : {}) &
(T extends { [Symbol.species]: infer V } ? { [Symbol.species]: V } : {}) &
(T extends { [Symbol.split]: infer V } ? { [Symbol.split]: V } : {}) &
(T extends { [Symbol.toPrimitive]: infer V } ? { [Symbol.toPrimitive]: V }: {}) &
(T extends { [Symbol.toStringTag]: infer V } ? { [Symbol.toStringTag]: V } : {}) &
(T extends { [Symbol.unscopables]: infer V } ? { [Symbol.unscopables]: V } : {})
ToRef
主要是给包含 Ref 的 Union 类型使用的,我们希望 union 类型不产生类型分发:
export type ToRef<T> = [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
export type ToRefs<T = any> = {
// #2687: somehow using ToRef<T[K]> here turns the resulting type into
// a union of multiple Ref<*> types instead of a single Ref<* | *> type.
[K in keyof T]: T[K] extends Ref ? T[K] : Ref<UnwrapRef<T[K]>>
}
ref
来看看 ref 函数的实现,前三行都是函数重载,除了不传参数的时候会直接返回一个 Ref<T|undefined>,否则 ref 返回的都是 Ref<UnwrapRef<T>>先解包再封包的 Ref 类型,其中 ToRef 组织了条件分发,上面有单独说明:
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) {
return createRef(value)
}
createRef
ref 函数内部调用了 createRef,通过一个 rawValue 构造 ref 类型,如果rawValue已经是 ref 类型那直接返回,不然则通过 RefImpl 构造一个 Ref 类型:
function createRef(rawValue: unknown, shallow = false) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
RefTmpl
RefImpl 就是实际的 Ref 类型构造器,包含两个私有属性:
_value:存储传入的rawValue;__v_isRef:Ref标识符,用于判断;
然后是构造函数,接收两个参数 _rawvalue 就是原始值,_shallow 定义是否为浅层 ref,如果不是浅层 ref 会在传入的参数为 object 的时候将其转化为 reactive:
class RefImpl<T> {
private _value: T
public readonly __v_isRef = true
constructor(private _rawValue: T, public readonly _shallow = false) {
this._value = _shallow ? _rawValue : convert(_rawValue)
}
// ...
const convert = <T extends unknown>(val: T): T =>
isObject(val) ? reactive(val) : val
接下来就是 ref 响应式的主体实现了,回顾一下 reactive 构造响应式对象,是通过 proxy 给对象的所有属性添加get&set拦截器,并且在拦截器里做 track|trigger 跟踪 effect。
而 Ref 对象只有一个响应式属性就是 value,所以 RefImpl 里直接对这属性添加 get&set 绑定查询设置函数:
get:调用track函数收集副作用,本身作为target传入(this);set:先通过hasChanged(其实就是===额外加了NaN的判断逻辑) 判断rawValue是否有变,有变化就更新_value的值,更新规则和constructor一致。最后还会trigger触发所有副作用函数。
class RefImpl<T> {
// ...
get value() {
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newVal) {
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}
}
// compare whether a value has changed, accounting for NaN.
export const hasChanged = (value: any, oldValue: any): boolean =>
value !== oldValue && (value === value || oldValue === oldValue)
在 ref.spec 里分析对 ref 的测试用例,之前之所以对传入的对象进行 convert 就是为让嵌套的属性可以响应:
it('should make nested properties reactive', () => {
const a = ref({
count: 1
})
let dummy
effect(() => {
dummy = a.value.count
})
expect(dummy).toBe(1)
a.value.count = 2
expect(dummy).toBe(2)
})
customRef
customRef 用于自定义一个 ref,可以显式地控制依赖追踪和触发响应,接受一个工厂函数,两个参数分别是用于追踪的 track 与用于触发响应的 trigger,并返回一个一个带有 get 和 set 属性的对象
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
return new CustomRefImpl(factory) as any
}
export type CustomRefFactory<T> = (
track: () => void,
trigger: () => void
) => {
get: () => T
set: (value: T) => void
}
customRef通过CustomRefImpl,没啥好说的就把 track 和 trigger 注入进去了:
class CustomRefImpl<T> {
private readonly _get: ReturnType<CustomRefFactory<T>>['get']
private readonly _set: ReturnType<CustomRefFactory<T>>['set']
public readonly __v_isRef = true
constructor(factory: CustomRefFactory<T>) {
const { get, set } = factory(
() => track(this, TrackOpTypes.GET, 'value'),
() => trigger(this, TriggerOpTypes.SET, 'value')
)
this._get = get
this._set = set
}
get value() {
return this._get()
}
set value(newVal) {
this._set(newVal)
}
}
shallowRef
shallowRef 阻止其将内部的元素转换为 reactive :
export function shallowRef<T extends object>(
value: T
): T extends Ref ? T : Ref<T>
export function shallowRef<T>(value: T): Ref<T>
export function shallowRef<T = any>(): Ref<T | undefined>
export function shallowRef(value?: unknown) {
return createRef(value, true)
}
isRef
isRef 判断参数是否为 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)
}
toRef
toRef 可以用来为一个对象的属性创建一个 ref。这个 ref 可以被传递并且能够保持响应性:
const state = reactive({
foo: 1,
bar: 2,
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
函数定义如下,内部调用了 ObjectRefImpl:
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K
): ToRef<T[K]> {
return isRef(object[key])
? object[key]
: (new ObjectRefImpl(object, key) as any)
}
ObjectRefImpl 实现的方式其实很简单,这个结构缓存传入对象的引用和键,然后给自己的 value 做个代理到源对象,不管是修改这个 ref还是源对象,修改的都是相同的引用。
如果原对象是个 reactive 对象,set/get会触发其内部的代理所以可以保持响应性。
class ObjectRefImpl<T extends object, K extends keyof T> {
public readonly __v_isRef = true
constructor(private readonly _object: T, private readonly _key: K) {}
get value() {
return this._object[this._key]
}
set value(newVal) {
this._object[this._key] = newVal
}
}
toRefs
把一个响应式对象/普通对像转换成值为 ref 的普通对象,该普通对象的每个 property 都是一个 ref ,和响应式对象 property 一一对应。
const state = reactive({
foo: 1,
bar: 2,
})
const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型如下:
{
foo: Ref<number>,
bar: Ref<number>
}
先构造新对象或数组,内部调用了 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
}
unref
一个很简单的语法糖:
export function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
return isRef(ref) ? (ref.value as any) : ref
}