源码中的TS类型记录
类型谓词is的使用(TS类型收窄方式)
- TS类型收窄方式
类型谓词是"用户自定义类型保护"中唯一且核心的语法形式
IfAny
typescript
swift
export type UnwrapRef<T> = IfAny<T, T,
T extends Ref<infer V>
? UnwrapRefSimple<V>
: UnwrapRefSimple<T>
>
// 如果没有 IfAny 保护:
// UnwrapRef<any> 会进行递归解包,可能导致深层嵌套的类型计算
// 有了 IfAny,直接返回 any,更高效且符合直觉
为了更全面地理解 IfAny 的行为,将其与TypeScript中其他特殊类型进行对比测试:
| 测试类型 | 1 & T |
0 extends 1 & T |
IfAny<T, 'Y', 'N'> |
说明 |
|---|---|---|---|---|
any |
any |
true |
'Y' |
目标检测类型 |
unknown |
1 |
false |
'N' |
与 any 不同,unknown 更安全 |
never |
never |
false |
'N' |
空类型 |
1 |
never |
false |
'N' |
字面量类型 |
string |
never |
false |
'N' |
普通类型 |
any[] |
any |
true |
'Y' |
数组包含 any 元素,整个类型被视为 any |
{ x: any } |
any |
true |
'Y' |
对象包含 any 属性,整个类型被视为 any |
重要发现 :IfAny 不仅检测纯粹的 any,也检测包含 any 的复合类型 (如 any[]、{x: any})。这是因为 any 的"传染性"在交叉类型中会传播。
any & T 的结果总是 any ,无论 T 是什么类型。这是 any 类型的特殊"传染性"。
为什么[T] extends [Ref]不写成T extends Ref
- 核心区别:是否触发"分布式条件类型"
- 这是 TypeScript 条件类型的一个高级特性 。当
extends左侧是裸类型参数时,会触发"分布式条件类型",否则不会。
typescript
typescript
// 情况1:裸类型参数 - 触发分布式
type Test1<T> = T extends Ref ? 'Yes' : 'No'
// 当 T 是联合类型时,会分别检查每个成员
// 情况2:包裹类型参数 - 不触发分布式
type Test2<T> = [T] extends [Ref] ? 'Yes' : 'No'
// 将 T 视为一个整体检查
Ref
一张图解释vue3中的响应式逻辑

从图中可以看出,Ref<T> 是最通用 的响应式容器,既能处理基础类型,也能处理对象类型。而 Reactive<T> 专门处理对象,ComputedRef<T> 是特殊的只读 Ref<T>。
实际使用中的类型特性
1. 自动解包(模板中)
vue
xml
<template>
<!-- 模板中自动解包,不需要 .value -->
<div>{{ count }}</div> <!-- 显示 0,而不是 {value: 0} -->
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0) // Ref<number>
</script>
2. 响应式类型守卫
typescript
typescript
import { ref, isRef, unref } from 'vue'
function processValue(input: number | Ref<number>) {
// 类型守卫:判断是否为 Ref
if (isRef(input)) {
// 此处 TypeScript 知道 input 是 Ref<number>
return input.value * 2
}
// 此处 TypeScript 知道 input 是 number
return input * 2
// 或者使用 unref 自动解包
const value = unref(input) // number
}
3. 在响应式对象中的自动解包【重要】
typescript
javascript
import { reactive, ref } from 'vue'
const count = ref(0)
const obj = reactive({
count, // 自动解包为 number
normal: 1
})
console.log(obj.count) // 0 (number,不是 Ref<number>)
console.log(obj.normal) // 1
- 源码分析
ts
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
/**
* @internal
*/
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()
}
}
}
}
- 总结:ref的对于基本类型的响应式处理就是包装成了一个具有value属性的对象,在访问和值变化的时候进行依赖收集和触发更新;即.value 只是普通对象属性 + getter/setter
- 这种设计非常巧妙:
- 具有统一性
csharp
// ref 可以包装任何类型,API 统一
const num = ref(0) // Ref<number>
const obj = ref({ x: 1 }) // Ref<{x: number}>(内部用 reactive 包装)
const arr = ref([1, 2, 3]) // Ref<number[]>
// 都是 .value 访问,心智负担小
- 可预测性
csharp
const count = ref(0)
// 明确的触发点:只有 .value 赋值会触发更新
count.value = 1 // ✅ 触发
count.value++ // ✅ 触发
// 没有意外的触发
const obj = ref({ x: 1 })
obj.value.x = 2 // ❌ 不触发(除非是 reactive 包装的深层 ref)
- 类型安全
csharp
// TypeScript 完美支持
const count = ref(0) // 推断为 Ref<number>
count.value = "hello" // ❌ 类型错误:不能将 string 赋值给 number
// 泛型支持
const maybe = ref<number | null>(null) // Ref<number | null>
maybe.value = 42 // ✅
maybe.value = null // ✅
maybe.value = "text" // ❌
- 从Vue3 Ref的这段源码可以学习到类的分层级的访问控制策略

- 为什么这样分层设计?
-
_rawValue最严格:- 存储原始值 用于
hasChanged()比较 - 如果被外部修改,响应式系统会完全失效
- 必须用
private绝对保护
- 存储原始值 用于
-
_value较宽松:- 存储响应式值 (可能是
reactive()代理) - 外部访问可能看到代理对象,但不破坏核心逻辑
- 开发工具可能需要检查这个值
- 存储响应式值 (可能是
-
dep完全隐藏:- 是实现机制 ,不是数据模型
- 用户永远不需要直接操作依赖关系
- 通过不导出类来彻底隐藏