Ref类型
// 生成一个唯一key,开发环境下增加描述符 'refSymbol'
declare const RefSymbol: unique symbol
export interface Ref<T = any> {
/**
* 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
// 之前这个类型是UnwrapNestedRefs<T>,这样会造成Ref类型套Ref类型
// 像改成现在这种,最外层最多只有一层Ref,避免需要写多次value
// export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T>
// 之前迭代的代码
value: T
}
// ToRefs方法主要将对象第一层的value都变成Ref类型
export type ToRefs<T = any> = { [K in keyof T]: Ref<T[K]> }
// 判断是否是Ref数据的方法
// is是给一个提示,函数用来判断r是否是Ref
// 如果使用isRef<string>(ref('')),则进入第一个function函数
// 如果使用isRef(ref('')),则进入第二个function函数
// 返回值不同,第一个是r is Ref<T>,第二个是r is Ref
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref {
return r ? r.__v_isRef === true : false
}
Ref类型主要有两个字段,1.RefSymbol字段,唯一标识属性,用来判断是否为Ref类型;2.value字段为T类型(泛型),举个例子:Ref,则value值为string类型。isRef声明了两个函数,通过不同方式调用使用不同的函数,只是公用函数逻辑,返回值的提示不同。
UnwrapRef
// 解构T类型,如果是Ref<V>类型进行则是结构Ref,变成UnwrapRefSimple<V>
// 其余类型直接UnwrapRefSimple<T>
// 从以上可了解到主要是将Ref类型解套,去除掉Ref
export type UnwrapRef<T> = T extends Ref<infer V>
? UnwrapRefSimple<V>
: UnwrapRefSimple<T>
type BaseTypes = string | number | boolean
// CollectionTypes指Map、Set、WeakMap、WeakSet类型
// 如果是Funcion、Map、Set、WeakMap、WeakSet、Ref、string、number、boolean
// 则直接保持原类型,数组与对象继续递归解构
type UnwrapRefSimple<T> = T extends
| Function
| CollectionTypes
| BaseTypes
| Ref
| RefUnwrapBailTypes[keyof RefUnwrapBailTypes]
? T
: T extends Array<any>
? { [K in keyof T]: UnwrapRefSimple<T[K]> }
: T extends object ? UnwrappedObject<T> : T
// Extract all known symbols from an object
// 这地方用到了交叉类型,使对象类型覆盖更全
type UnwrappedObject<T> = { [P in keyof T]: UnwrapRef<T[P]> } & SymbolExtract<T>
UnwrapRef主要用来解构Ref类型,如果是UnwrapRef<Ref> ==> UnwrapRefSimple ===> string,最终结果将Ref类型进行了解构。UnwrapRefSimple主要是递归进行解构数据解构类型,普通类型直接返回,数组与对象则递归遍历其中所有项进行获取类型。上述有个问题,针对Array是没有进行处理的,因此数组中调用ref数据必须一层层调用,之前版本有过处理,这版本没有,有待调查。
SymbolExtract
// 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 }
: {})
部分Object的key可能不能通过in keyof获取,这里是用来涵盖这部分key对应value的类型。其中&为交叉类型作用,上述作用就是object中的类型可能为{ [P in keyof T]: UnwrapRef<T[P]> }、{}、{ [Symbol.unscopables]: V }的合并类型,拥有上述三种所有类型的属性。
ShallowUnwrapRef
export type ShallowUnwrapRef<T> = {
[K in keyof T]: T[K] extends Ref<infer V> ? V : T[K]
}
上述方法同样是为了解构,将包含Ref类型对象与数组进行一层解构。
Ref函数
// 下面四个ref函数主要针对调用方式不同而进行不同的调用,
// 但是都会触发createRef(value)
// 针对四个函数的作用是针对不同情况进行对返回值不同类型处理
// 如果使用T为对象,则返回T或Ref<UnwrapRef>
export function ref<T extends object>(
value: T
): T extends Ref ? T : Ref<UnwrapRef<T>>
// 调用加了类型,因为不满足上述条件,则一定不是对象,一定不是Ref类型
// 直接返回类型为Ref<UnwrapRef<T>>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
// 如果加了类型但是没有传参,返回类型为Ref<T | undefined>
export function ref<T = any>(): Ref<T | undefined>
// 如果以上都不满足,兜底函数,不限制入参与返回值
export function ref(value?: unknown) {
return createRef(value)
}
// shallowRef差不多,不过调用createRef的第二个参数为true
export function shallowRef<T>(value: T): T extends Ref ? T : Ref<T>
export function shallowRef<T = any>(): Ref<T | undefined>
export function shallowRef(value?: unknown) {
return createRef(value, true)
}
function createRef(rawValue: unknown, shallow = false) {
// 如果是Ref类型则直接返回
if (isRef(rawValue)) {
return rawValue
}
// 如果是shallow则简单处理,直接返回
// 否则调用convert进行使用reactive变成响应式数据
// shallow只对value本身做代理,而ref是对整个数据使用reactive递归做代理
let value = shallow ? rawValue : convert(rawValue)
const r = {
__v_isRef: true,
// 这种方式的定义必须通过r.value值进行获取与赋值
get value() {
// 获取依赖,effect模块介绍,其实很简单就是effect(watch)与dep,与vue2中的队列方式差不多,只是个映射关系
track(r, TrackOpTypes.GET, 'value')
return value
},
set value(newVal) {
// toRaw就是将响应式对象变成原始对象
// 每次reactive返回的对象都是一个新的对象
if (hasChanged(toRaw(newVal), rawValue)) {
rawValue = newVal
value = shallow ? newVal : convert(newVal)
// 触发依赖,effect模块介绍
trigger(r, TriggerOpTypes.SET, 'value', newVal)
}
}
}
return r
}
// 判断value与oldValue是否有过更改
export const hasChanged = (value: any, oldValue: any): boolean =>
value !== oldValue && (value === value || oldValue === oldValue)
// 如果是对象用reactive进行处理,否则直接返回val,由此可看出,reactive主要负责处理复杂数据类型的响应式,ref针对简单数据类型的响应式
// reactive由reactive部分进行讲解
const convert = <T extends unknown>(val: T): T =>
isObject(val) ? reactive(val) : val
上述核心是createRef函数,主要是对value值进行代理,通过对get与set进行收集依赖与触发依赖。
shallowRef主要是对value本身做代理与依赖收集触发,而ref是通过reactive将value变成响应式数据结构。
Ref与shallowRef总结
shallowRef的出现与我前几天思考的一个问题正好关联,我在想,如果前端使用select或后端使用picker实现下拉框操作时,下拉框中的所有项都实现了数据监听,假设有几十个下拉选项,会对几十个下拉选项都做监控,非常影响性能,shallowRef的出现正好可以解决此类,问题,因为他只对value本身做了数据代理,正好可以解决range针对性能造成的问题。
总结
Ref模块主要是针对vue中的类型进行一些定义,主要难点都是typescript的知识点,其余没有什么难点,其中最核心的就是针对Ref嵌套类型的解构,避免深层次调用Ref类型还需要使用value属性进行获取值。ref函数也比较简单,直接通过reactive变成响应式数据,shallowRef主要针对简单响应式数据,依据不同的场景选择不同的函数调用方式,ref函数的总结是我想到一个点,如果有更多欢迎分享。