《Vue.js设计与实现》笔记 第6章:原始值的响应式方案
Vue.js设计与实现 第6章:原始值的响应式方案
本章导读
前面章节实现了:
const obj = reactive({
foo: 1
})
但对于:
let foo = 1
却无法实现:
reactive(foo)
因为:
Proxy只能代理对象
不能代理原始值
因此 Vue3 引入:
ref
用于实现原始值响应式。
一、为什么原始值无法响应式
Object可以代理
const obj = {
foo: 1
}
reactive(obj)
因为:
obj是引用类型
Proxy可以拦截:
obj.foo
obj.foo = 2
Primitive无法代理
例如:
let foo = 1
执行:
reactive(foo)
实际上:
foo不是对象
Proxy无法工作。
JavaScript基本数据类型
String
Number
Boolean
Null
Undefined
Symbol
BigInt
统称:
Primitive Value
原始值
二、ref的设计思想
核心思路
把原始值包装成对象。
例如:
let foo = 1
变成:
const wrapper = {
value: 1
}
然后:
reactive(wrapper)
即可。
最简单实现
function ref(val) {
const wrapper = {
value: val
}
return reactive(wrapper)
}
使用方式
const count = ref(0)
count.value++
为什么使用value
因为:
包装对象必须有统一入口
Vue约定:
.value
访问真实值。
三、ref本质
实际结构
const count = ref(1)
本质类似:
const count = reactive({
value: 1
})
工作流程
count.value
│
▼
Proxy.get
│
▼
track()
count.value++
│
▼
Proxy.set
│
▼
trigger()
四、解决响应丢失问题
问题
const obj = reactive({
foo: 1,
bar: 2
})
解构:
const { foo } = obj
修改:
obj.foo++
不会影响:
foo
原因
解构得到的是值副本
失去响应式联系。
五、toRef
作用
为对象属性创建Ref。
示例
const obj = reactive({
foo: 1
})
const fooRef =
toRef(obj,'foo')
使用
fooRef.value++
等价于:
obj.foo++
实现思想
function toRef(
obj,
key
){
return {
get value(){
return obj[key]
},
set value(val){
obj[key] = val
}
}
}
特点
不保存值
只做代理转发
六、为什么需要toRef
问题
const {
foo
} = reactiveObj
响应丢失。
解决
const foo =
toRef(
reactiveObj,
'foo'
)
读取:
foo.value
实际上:
reactiveObj.foo
保持响应式。
七、toRefs
问题
对象很多属性:
const obj = reactive({
foo:1,
bar:2,
baz:3
})
手动:
toRef(obj,'foo')
toRef(obj,'bar')
toRef(obj,'baz')
很麻烦。
Vue提供
toRefs()
使用
const refs =
toRefs(obj)
结果:
{
foo: Ref,
bar: Ref,
baz: Ref
}
实现思想
function toRefs(obj){
const ret = {}
for(const key in obj){
ret[key] =
toRef(obj,key)
}
return ret
}
八、配合展开运算符
最常见写法
const obj = reactive({
foo:1,
bar:2
})
return {
...toRefs(obj)
}
作用
保持解构后响应式
九、proxyRefs
问题
使用Ref比较麻烦
例如:
foo.value
bar.value
需要频繁写:
.value
Vue解决方案
proxyRefs()
使用
const state = proxyRefs({
foo: ref(1)
})
读取
state.foo
自动变成:
state.foo.value
赋值
state.foo = 2
自动变成:
state.foo.value = 2
十、自动脱Ref
get拦截
get(target,key){
const value =
Reflect.get(
target,
key
)
return value.__v_isRef
? value.value
: value
}
set拦截
set(target,key,newVal){
const value =
target[key]
if(value.__v_isRef){
value.value = newVal
return true
}
return Reflect.set(
target,
key,
newVal
)
}
十一、Ref标识
为什么需要标识
需要判断:
是不是Ref
Vue内部:
__v_isRef
创建Ref时:
const wrapper = {
value: val
}
Object.defineProperty(
wrapper,
'__v_isRef',
{
value:true
}
)
判断
if(value.__v_isRef)
即可识别。
十二、为什么Ref如此重要
Vue3组合式API大量依赖Ref。
例如:
const count = ref(0)
const name = ref('')
计算属性:
const total =
computed(...)
返回:
Ref
很多Vue API:
computed()
customRef()
最终都建立在:
Ref
机制之上。
十三、Setup中的响应式解构
错误写法
setup(){
const state = reactive({
foo:1
})
const {
foo
} = state
}
结果:
失去响应式
正确写法
setup(){
const state = reactive({
foo:1
})
return {
...toRefs(state)
}
}
Vue官方推荐
return {
...toRefs(state)
}
十四、Reactive与Ref区别
| 对比项 | reactive | ref |
|---|---|---|
| 数据类型 | 对象 | 任意类型 |
| 返回值 | Proxy对象 | Ref对象 |
| 访问方式 | obj.foo | ref.value |
| 底层实现 | Proxy | 包装对象+Proxy |
| 支持原始值 | ❌ | ✅ |
十五、什么时候使用Ref
推荐
原始值:
const count = ref(0)
const loading = ref(false)
const name = ref('')
推荐
单个状态:
const visible =
ref(true)
十六、什么时候使用Reactive
推荐
复杂对象:
const state = reactive({
user:{},
list:[],
loading:false
})
推荐
业务状态集合:
const form =
reactive({
username:'',
password:''
})
第六章核心知识图谱
Primitive Reactivity
ref()
│
├── value
├── __v_isRef
└── reactive包装
toRef()
│
└── 属性代理
toRefs()
│
└── 批量转换
proxyRefs()
│
├── 自动解包
└── 自动赋值
应用场景
├── setup()
├── computed()
├── customRef()
└── Composition API
高频面试题
为什么需要ref?
因为:
Proxy无法代理原始值
ref本质是什么?
reactive({
value: xxx
})
toRef作用是什么?
为对象属性创建响应式引用。
toRefs作用是什么?
批量创建Ref。
为什么解构Reactive会失去响应式?
因为:
得到值副本
不是代理访问
proxyRefs作用是什么?
自动:
脱Ref
无需频繁写:
.value
reactive和ref如何选择?
对象 → reactive
原始值 → ref
本章总结
第6章解决了:
原始值无法被Proxy代理
的问题。
核心方案:
ref
↓
包装对象
↓
reactive
↓
响应式
同时引入:
- ref
- toRef
- toRefs
- proxyRefs
解决:
解构丢失响应式
.value繁琐
等问题。
最终形成 Vue3 完整响应式体系:
Object响应式
↓
Reactive
Primitive响应式
↓
Ref
共同构成:
Vue3 Reactivity System

浙公网安备 33010602011771号