vue3中的"ref家族"成员剖析
前言
本文主要整理关于在
vue3
中所提及到的所有与ref
相关的API,通过对比每个API的作用以及使用场景,新增对vue3
中相关的API的认知,主要借助于vue3官方API的阅读!
“ref”成员一览
参考官方所整理的关于不同场景下的“ref”,对应整理 👇 的相关属性
响应式核心:ref()
ref
接收一个内部值,返回一个响应式的,可更改的ref
对象,此对象只有一个指向其内部值的属性.value
关于该函数的签名以及对象的类型定义如下:
1 | // packages/activity/src/ref.ts |
🌟 上述我们发现关于ref()
方法定义了三个重载,目的是兼容多种不同的调用方式,可以根据传入的参数来确定调用哪个函数签名,上述三种重载分别描述如下:
- 接收一个参数
value
,并返回一个Ref
类型的实例,一般适用于初始化一个具有初始值的响应式变量; - 不接受任何参数,返回一个
Ref
类型的实例,一般适用于初始化一个没有初始值的响应式变量; - 最终具体函数实现,根据传入的参数来调用不同的重载,从而实现了对不同的调用方式的兼容性。
👊 注意我们上述对这个响应结果进行加粗处理,需要关注下这里就算是传递的基本数据类型的value,其响应结果都是一个实例对象,这里我们可以发现当调用ref()
的时候,最终返回的都是一个new RefImpl<T>()
实例对象,该对象将传递进入来的value作为其_value属性来存储,并针对value提供了对应的getter
以及setter
函数,当我们针对ref()
的响应结果的value属性进行访问时,都将会触发到相应的“订阅通知操作”(通过上述的trackRefValue()
以及triggerRefValue()!
响应式工具:isRef()、unref()、toRef()、toRefs()
isRef()
检查某个值是否为ref对象,此方法的定义如下:
1 | export function isRef<T>(r: Ref<T> | unknown): r is Ref<T> |
🌟 上述这里通过判断一个r对象是否拥有__v_isRef属性,从而来判断这个r对象是否为一个ref实例对象,具体可上 👆 的关于class RefImpl
的定义!
unref()
如果参数是ref对象,则返回其内部值value,否则返回参数本身,此方法的定义如下:
1 | export function unref<T>(ref: MaybeRef<T> | ComputedRef<T>): T{ |
🌟 通过借助于isRef()
方法,用来将一个”类似于Ref”的对象给返回其value属性!
toRef()
可以将值、refs或者getters规范化为refs,也可以针对一个ref对象中的内部值value的属性来创建一个对应的ref对象(这里我愿称之为value子属性ref对象),这样子就可以让这个子属性ref对象与父ref对象中的value保持同步,一旦有一方发生改变,另外一方也相应地发生改变,关于此方法的定义(3个不同的重载方法)如下:
1 | export function toRef<T>(value: T): T extends () => infer R ? Readonly<Ref<R>> ? T extends Ref ? T : Ref<UnwrapRef<T>> |
⭐ 该泛型函数接收一个参数value,根据参数的类型来确定返回值的类型,如果value是一个函数类型,则返回一个只读的Ref类型的实例;如果value是一个Ref类型的实例,则直接返回这个ref实例;否则直接将这个value包装起来的Ref对象实例!
1 | //注意这里下方的返回值ToRef(),这个是vue3中所提供的一个类型工具函数,代表这个类型是一个Ref类型 |
⭐ 该泛型函数接收两个参数object与key,其中object是一个对象,key是对象T的属性名,返回一个ToRef<T[K]>
类型的值,表示对象object的属性key的响应式引用,主要将对象的某个属性给ref相应化,并保持与源对象的属性的数据同步!
1 | export function toRef<T extends object, K extends keyof T>( |
⭐ 该泛型函数接收两个参数object和key以及一个可选参数defaultValue,与上面第二个类似,但补充了一个逻辑:如果属性值为undefined
,则使用defaultValue
来代替!
👊 下面是最终的该函数的实现
1 | export function toRef( |
关于这个方法API的具体使用以及需要注意的地方,详见官方toref
toRefs()
将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的ref对象,每个单独的属性ref对象都是使用
toRef()
来创建的,该函数的定义如下
1 | export type ToRefs<T = any> = { |
⚠ 注意这里我们通过toRefs()
方法所创建出来的对象是一个普通的对象,其属性成员才是ref对象!
响应式进阶:shallowRef()、triggerRef()、customRef()
shallowRef(): 对value的直接改变才触发更新
ref()
的浅层响应作用形式。通常情况下,使用ref()
函数所创建的响应式饮用会对其值进行深层响应式转换,即时值是一个对象,其内部的属性也会被转换为响应式的, 👉 这意味着当对象的属性发生变化时,视图会自动更新。但是有时,我们又不想它去自动更新,因此可以采用shallowRef
,它创建的响应式引用时浅层的,即只对其值进行浅层响应式转换,也就是说对.value
的访问时响应式的, 🤌 这通常对于大型数据结构的性能优化或者时外部状态管理系统集成非常有用!
以下是关于该API的简单运用:
1 | const state = shallowRef({ count: 1 }) |
关于该方法的定义如下:
1 | export function shallowRef<T>( |
🌟 从上面我可以看到该方法与ref()
使用相差无异,就只是其底层在new RefImpl()
的时候,传递了属性shallow=true
triggerRef()
强制触发依赖于一个浅层ref: 对value的直接改变才触发更新)的副作用,一般在对浅引用的内部值进行深度变更后使用,一般情况下,vue3中响应式引用的更新是由其自身的响应式系统自动管理的,当引用的值发生变化时,视图自动更新, ⚠ 但有时候,我们可能需要手动触发更新,这时就可以使用
triggerRef
函数
1 | const shallow = shallowRef({ |
与vue2中的
$forceUpdate()
方法的异同:
相同点:都是用于手动强制更新组件或者响应式数据的方法!
不同点:
triggerRef()
: 主要用于手动触发一个响应式引用的更新,即使引用的值没有发生变化,特别对于浅层ref的依赖更新非常有用;$forceUpdate()
: vue2中组件实例的一个方法,用于强制更新组件的视图,会强制触发组件的重新渲染,无论数据是否发生变化
关于该方法的定义如下:
1 | export function triggerRef(ref: Ref){ |
🌟 上述关于triggerRef()
方法主要是通过调用triggerRefValue()
方法来实现强制视图的更新的,主要有三个参数:
ref
: 要触发更新的响应式引用;dirtyLevel
: 更新的脏状态级别,即表示数据发生变化的程度;newValue
: 新的值,用于更新响应式引用的值,如果不传入, 则表示不修改当前值,仅触发视图更新
customRef()
创建一个自定义的ref,显示声明对其依赖追踪和更新出发的控制方式,使用
customRef
可以创建一个具有自定义getter
与setter
行为的响应式引用,通常情况下,我们使用ref
函数来创建响应式引用(创建的new RefImpl()
实例),它将自动创建getter
与setter
,并将其绑定到内部的响应式数据上,但是有时候,我们如果想要更加灵活地控制getter
与setter
的行为,这时就可以使用customRef
了customRef
通过接收一个函数作为参数,这个函数接收一个track
和一个trigger
函数作为参数,并在函数内部返回一个对象,这个对象包含了自定义的getter
与setter
,关于该方法的定义如下:
1 | export type CustomRefFactory<T> = { |
🌟 customRef()
方法的执行过程,就是创建一个CustomRefImpl
实例的过程,上述CustomRefFactory
类型是一个包含接收track
以及trigger
方法,然后返回带有get
以及set
方法的对象这里的track
与trigger
方法无需我们去实现,因为在CustomRefImpl
的构造函数中,它会自动地将这两个参数分别指向vue3中的响应式核心实现方法中,然后将对value的get
与set
方法暴露出来,指向传入的自定义实现,借此来实现整个自定义响应式的目的!下面是对应的使用例子:
1 | import { customRef } from 'vue' |
⭐ 这里通过自定义一composition API来创建一个自定义的响应式依赖实现,可以在value发生改变时延迟200毫秒更新视图,当然,这里也可以追加其他的逻辑的实现!!!
特殊attributes: ref
用于注册模版引用,一般接收一个字符串或者一个函数,用于注册元素或者子组件的引用,选项式API使用方式则存储在
this.$refs
中,组合式API时,引用则存储在与名字匹配的ref中,如下所示:
1 | <script> |
如果将ref用于普通的DOM元素,引用将是元素本身,如果用于子组件的话,那么引用将是子组件的实例
TypeScript工具类型: MaybeRef、MaybeRefOrGetter
1 | export type MaybeRef<T = any> = T | Ref<T> |
🌟 两种类型定义,MaybeRef
代表是可能普通类型,也可能是ref类型,而MaybeRefOrGetter
则代表还可能是getter函数类型