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
}
复制代码