《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
↓
响应式

同时引入:

  1. ref
  2. toRef
  3. toRefs
  4. proxyRefs

解决:

解构丢失响应式
.value繁琐

等问题。

最终形成 Vue3 完整响应式体系:

Object响应式

Reactive

Primitive响应式

Ref

共同构成:

Vue3 Reactivity System
posted @ 2025-03-25 15:42  Li_pk  阅读(5)  评论(0)    收藏  举报