Vue 3 的响应式系统由两大核心部分组成:reactive 和 ref。如果说 reactive 是处理对象的响应式,那么 ref 就是处理基本数据类型的响应式。本文将深入分析 Vue 3 中 ref.ts 文件的实现原理,通过丰富的示例帮助你全面掌握 Vue 3 Ref 系统。
目录
- Vue 3 Ref 系统完全指南:深入理解 ref.ts
1. 概述
ref.ts 是 Vue 3 响应式系统的核心模块之一,专门用于处理基本数据类型的响应式。它通过创建一个包含 value 属性的对象来包装基本数据类型,使它们具备响应式能力。
Ref 系统提供了多种 API 来满足不同场景的需求:
ref: 创建深度响应式 refshallowRef: 创建浅层响应式 refisRef: 检查值是否为 ref 对象unref: 解包 ref 对象toRef: 将对象属性转换为 reftoRefs: 将响应式对象转换为 ref 对象集合customRef: 创建自定义 reftriggerRef: 手动触发浅层 ref 的更新
2. 核心 API 详解
2.1 ref
ref 是最基本的 Ref API,用于创建一个响应式的 ref 对象。
export function ref<T>(
value: T,
): [T] extends [Ref] ? IfAny<T, Ref<T>, T> : Ref<UnwrapRef<T>, UnwrapRef<T> | T>
export function ref<T = any>(): Ref<T | undefined>
特点:
使用示例:
import { ref, watchEffect } from 'vue'
// 基本类型
const count = ref(0)
console.log(count.value) // 0
// 修改值
count.value++
console.log(count.value) // 1
// 对象类型
const state = ref({
name: 'Vue',
version: 3
})
// 响应式访问
watchEffect(() => {
console.log(state.value.name) // Vue
})
// 修改对象属性
state.value.name = 'Vue 3'
// 会触发 watchEffect 重新执行
2.2 shallowRef
shallowRef 创建一个浅层响应式的 ref 对象。
export function shallowRef<T>(
value: T,
): Ref extends T
? T extends Ref
? IfAny<T, ShallowRef<T>, T>
: ShallowRef<T>
: ShallowRef<T>
特点:
- 只有对 .value 的修改是响应式的
- 不会对值内部属性的修改做出响应
- 适用于大型数据结构,避免不必要的性能开销
使用示例:
import { shallowRef, watchEffect, triggerRef } from 'vue'
const state = shallowRef({
count: 1,
list: [{ id: 1, name: 'Item 1' }]
})
// 响应式监听
watchEffect(() => {
console.log(state.value.count)
})
// 修改 .value 是响应式的
state.value = { count: 2, list: [] }
// 会触发 watchEffect 重新执行
// 修改内部属性不是响应式的
state.value.count = 3
// 不会触发 watchEffect 重新执行
// 需要手动触发更新
triggerRef(state)
// 现在会触发 watchEffect 重新执行
2.3 isRef
检查一个值是否为 ref 对象。
export function isRef<T>(r: Ref<T> | unknown): r is Ref<T>
export function isRef(r: any): r is Ref {
return r ? r[ReactiveFlags.IS_REF] === true : false
}
使用示例:
import { ref, isRef } from 'vue'
const count = ref(0)
const notRef = 1
console.log(isRef(count)) // true
console.log(isRef(notRef)) // false
2.4 unref
如果参数是 ref,则返回其内部值,否则直接返回参数本身。
export function unref<T>(ref: MaybeRef<T> | ComputedRef<T>): T {
return isRef(ref) ? ref.value : ref
}
使用示例:
import { ref, unref } from 'vue'
const count = ref(1)
const normalValue = 2
console.log(unref(count)) // 1
console.log(unref(normalValue)) // 2
// 等价于
console.log(isRef(count) ? count.value : count) // 1
2.5 toRef
将值标准化为 ref,或将响应式对象的属性转换为 ref。
export function toRef<T>(
value: T,
): T extends () => infer R
? Readonly<Ref<R>>
: T extends Ref
? T
: Ref<UnwrapRef<T>>
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
): ToRef<T[K]>
使用示例:
import { reactive, toRef } from 'vue'
const state = reactive({
foo: 1,
bar: 2
})
// 将属性转换为 ref
const fooRef = toRef(state, 'foo')
// 修改 ref 会影响原始对象
fooRef.value++
console.log(state.foo) // 2
// 修改原始对象也会影响 ref
state.foo++
console.log(fooRef.value) // 3
2.6 toRefs
将响应式对象转换为普通对象,其中每个属性都是指向原始对象相应属性的 ref。
export function toRefs<T extends object>(object: T): ToRefs<T>
使用示例:
import { reactive, toRefs } from 'vue'
const state = reactive({
foo: 1,
bar: 2
})
// 转换为 refs
const { foo, bar } = toRefs(state)
// 现在可以解构并保持响应式
console.log(foo.value) // 1
console.log(bar.value) // 2
// 修改 ref 会影响原始对象
foo.value++
console.log(state.foo) // 2
2.7 toValue
标准化值/ref/getter 为值。
export function toValue<T>(source: MaybeRefOrGetter<T>): T {
return isFunction(source) ? source() : unref(source)
}
使用示例:
import { ref, toValue } from 'vue'
const count = ref(1)
const getCount = () => 2
const normalValue = 3
console.log(toValue(count)) // 1
console.log(toValue(getCount)) // 2
console.log(toValue(normalValue)) // 3
2.8 customRef
创建一个自定义的 ref,可以显式控制依赖追踪和更新触发。
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T>
使用示例:
import { customRef } from 'vue'
// 创建一个防抖 ref
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track() // 通知 Vue 追踪依赖
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger() // 通知 Vue 触发更新
}, delay)
}
}
})
}
const debouncedValue = useDebouncedRef('hello')
2.9 triggerRef
手动触发浅层 ref 的依赖更新。
export function triggerRef(ref: Ref): void
使用示例:
import { shallowRef, watchEffect, triggerRef } from 'vue'
const shallow = shallowRef({
greet: 'Hello, world'
})
watchEffect(() => {
console.log(shallow.value.greet)
})
// 输出: Hello, world
// 修改内部属性不会触发更新
shallow.value.greet = 'Hello, universe'
// 手动触发更新
triggerRef(shallow)
// 输出: Hello, universe
2.10 proxyRefs
创建一个代理对象,该对象会自动解包属性中的 ref。
export function proxyRefs<T extends object>(
objectWithRefs: T,
): ShallowUnwrapRef<T>
使用示例:
import { ref, proxyRefs } from 'vue'
const state = {
foo: ref(1),
bar: ref(2)
}
const stateProxy = proxyRefs(state)
// 可以直接访问 ref 的值
console.log(stateProxy.foo) // 1
console.log(stateProxy.bar) // 2
// 修改值也会更新原始 ref
stateProxy.foo = 3
console.log(state.foo.value) // 3
3. 工具类型解析
3.1 Ref 接口
export interface Ref<T = any, S = T> {
get value(): T
set value(_: S)
[RefSymbol]: true
}
Ref 接口定义了 ref 对象的基本结构,包含 value 的 getter 和 setter,以及用于类型识别的 RefSymbol。
3.2 MaybeRef 和 MaybeRefOrGetter
export type MaybeRef<T = any> =
| T
| Ref<T>
| ShallowRef<T>
| WritableComputedRef<T>
export type MaybeRefOrGetter<T = any> = MaybeRef<T> | ComputedRef<T> | (() => T)
这些类型用于表示一个值可能是普通值、ref、浅层 ref 或计算属性,增强了类型安全性。
3.3 UnwrapRef 和相关类型
export type UnwrapRef<T> =
T extends ShallowRef<infer V, unknown>
? V
: T extends Ref<infer V, unknown>
? UnwrapRefSimple<V>
: UnwrapRefSimple<T>
这些类型用于解包 ref 类型,获取其内部值的类型,支持递归解包复杂类型。
4. 内部实现机制
4.1 RefImpl 类
Ref 的核心实现是 RefImpl 类:
class RefImpl<T = any> {
_value: T
private _rawValue: T
dep: Dep = new Dep()
public readonly [ReactiveFlags.IS_REF] = true
public readonly [ReactiveFlags.IS_SHALLOW]: boolean = false
constructor(value: T, isShallow: boolean) {
this._rawValue = isShallow ? value : toRaw(value)
this._value = isShallow ? value : toReactive(value)
this[ReactiveFlags.IS_SHALLOW] = isShallow
}
get value() {
if (__DEV__) {
this.dep.track({
target: this,
type: TrackOpTypes.GET,
key: 'value',
})
} else {
this.dep.track()
}
return this._value
}
set value(newValue) {
const oldValue = this._rawValue
const useDirectValue =
this[ReactiveFlags.IS_SHALLOW] ||
isShallow(newValue) ||
isReadonly(newValue)
newValue = useDirectValue ? newValue : toRaw(newValue)
if (hasChanged(newValue, oldValue)) {
this._rawValue = newValue
this._value = useDirectValue ? newValue : toReactive(newValue)
if (__DEV__) {
this.dep.trigger({
target: this,
type: TriggerOpTypes.SET,
key: 'value',
newValue,
oldValue,
})
} else {
this.dep.trigger()
}
}
}
}
4.2 依赖追踪机制
Ref 通过 Dep 类实现依赖追踪:
- get value() 中调用
this.dep.track()追踪依赖 - set value() 中调用
this.dep.trigger()触发更新
5. TypeScript 高级特性应用
5.1 泛型与条件类型
export function ref<T>(
value: T,
): [T] extends [Ref] ? IfAny<T, Ref<T>, T> : Ref<UnwrapRef<T>, UnwrapRef<T> | T>
使用条件类型根据传入值的类型决定返回类型。
5.2 映射类型
export type ToRefs<T = any> = {
[K in keyof T]: ToRef<T[K]>
}
使用映射类型将对象的所有属性转换为 ref。
5.3 infer 关键字
T extends () => infer R
? Readonly<Ref<R>>
: T extends Ref
? T
: Ref<UnwrapRef<T>>
使用 infer 关键字从函数类型中推断返回值类型。
5.4 联合类型与交叉类型
export type MaybeRef<T = any> =
| T
| Ref<T>
| ShallowRef<T>
| WritableComputedRef<T>
使用联合类型表示多种可能的类型。
5.5 unique symbol
declare const RefSymbol: unique symbol
使用 unique symbol 创建全局唯一的标识符。
5.6 函数重载
export function toRef<T>(
value: T,
): T extends () => infer R
? Readonly<Ref<R>>
: T extends Ref
? T
: Ref<UnwrapRef<T>>
export function toRef<T extends object, K extends keyof T>(
object: T,
key: K,
): ToRef<T[K]>
通过函数重载提供多种调用方式。
6. 实际应用示例
6.1 基础使用
import { ref, watchEffect } from 'vue'
// 创建响应式数据
const count = ref(0)
const message = ref('Hello Vue!')
// 响应式计算
const doubled = ref(0)
watchEffect(() => {
doubled.value = count.value * 2
})
// 修改数据
count.value++
console.log(doubled.value) // 2
6.2 组合式函数中使用 Ref
import { ref, watch } from 'vue'
// 自定义组合式函数
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
return { count, increment, decrement, reset }
}
// 使用组合式函数
const { count, increment, decrement, reset } = useCounter(10)
increment()
console.log(count.value) // 11
6.3 表单处理
import { ref, reactive } from 'vue'
export function useForm(initialData) {
const formData = reactive(initialData)
const errors = ref({})
const isSubmitting = ref(false)
const validate = () => {
// 表单验证逻辑
const newErrors = {}
// ...验证逻辑
errors.value = newErrors
return Object.keys(newErrors).length === 0
}
const submit = async (submitFn) => {
if (!validate()) return
isSubmitting.value = true
try {
await submitFn(formData)
} finally {
isSubmitting.value = false
}
}
return {
formData,
errors,
isSubmitting,
submit,
validate
}
}
// 使用表单处理
const { formData, errors, isSubmitting, submit } = useForm({
name: '',
email: ''
})
6.4 自定义 Ref
import { customRef } from 'vue'
// 创建一个支持本地存储的 ref
function useLocalStorage(key, defaultValue) {
const storedValue = localStorage.getItem(key)
const initial = storedValue ? JSON.parse(storedValue) : defaultValue
return customRef((track, trigger) => {
return {
get() {
track()
return initial
},
set(value) {
initial = value
localStorage.setItem(key, JSON.stringify(value))
trigger()
}
}
})
}
// 使用本地存储 ref
const userName = useLocalStorage('userName', 'Anonymous')
console.log(userName.value) // Anonymous
userName.value = 'John'
// 值会自动保存到 localStorage
7. 性能优化策略
- 合理使用 shallowRef: 对于大型数据结构,使用 shallowRef 避免不必要的响应式开销
- 批量更新: 对于需要频繁修改的数据,考虑批量更新以减少触发次数
- 避免不必要的 toRefs: 只在需要解构并保持响应式时使用 toRefs
- 使用 triggerRef: 在修改 shallowRef 内部属性后手动触发更新
8. 常见问题与解决方案
8.1 直接解构失去响应式
// 错误做法
const state = reactive({ count: 0 })
const { count } = state // 失去响应式
// 正确做法
const state = reactive({ count: 0 })
const { count } = toRefs(state) // 保持响应式
8.2 修改 shallowRef 内部属性不更新
const state = shallowRef({ count: 0 })
// 错误:不会触发更新
state.value.count++
// 正确:手动触发更新
state.value.count++
triggerRef(state)
8.3 在模板中访问 ref 值
<template>
<!-- 错误:在模板中不需要 .value -->
<div>{{ count.value }}</div>
<!-- 正确:模板中自动解包 -->
<div>{{ count }}</div>
</template>
9. 总结
Vue 3 的 Ref 系统通过巧妙地结合 getter/setter、依赖追踪和 TypeScript 类型系统,实现了强大而灵活的响应式能力。它不仅提供了多种 API 满足不同场景需求,还通过各种优化策略保证了良好的性能。