Vue设计与实现:原始值的响应式方案

什么是原始值

原始值指的是 Boolean、Number、 BigInt、String、Symbol、undefined 和 null 等类型的值

引入 ref 的概念

Proxy第一个参数需要是对象,所以对原始值不起效果,需要用一个对象来包裹

解决思路:

  1. 封装一个 ref 函数用wrapper对象的value属性指向传入val
  2. 使用 Object.defineProperty 为包裹对象 wrapper 定义 了一个不可枚举且不可写的属性 __v_isRef,它的值为 true,代表 这个对象是一个 ref,而非普通对象。这样我们就可以通过检查 __v_isRef 属性来判断一个数据是否是 ref 了
  3. 将wrapper传入reactive转换成响应对象
js 复制代码
// 封装一个 ref 函数
function ref(val) {
  // 在 ref 函数内部创建包裹对象
  const wrapper = {
    value: val
  }
  // 使用 Object.defineProperty 在 wrapper 对象上定义一个不可枚举的属性 __v_isRef,并且值为 true
  Object.defineProperty(wrapper, '__v_isRef', {
    value: true
  })
  // 将包裹对象变成响应式数据
  return reactive(wrapper)
}

// 创建原始值的响应式数据
const refVal = ref(1)
effect(() => {
  // 在副作用函数内通过 value 属性读取原始值
  console.log(refVal.value)
})
// 修改值能够触发副作用函数重新执行
refVal.value = 2

结果:

响应丢失问题

在effect读取的是新对象不是响应对象所以不会跟effect关联

js 复制代码
// obj 是响应式数据
const obj = reactive({ foo: 1, bar: 2 })

// 将响应式数据展开到一个新的对象 newObj
const newObj = {
  ...obj
}

effect(() => {
  // 在副作用函数内通过新的对象 newObj 读取 foo 属性值
  console.log(newObj.foo)
})

// 很显然,此时修改 obj.foo 并不会触发响应
obj.foo = 100

解决思路:

  1. 当读取value的值时,返回obj对象下相应的属性值 ,使newObj有obj一样的属性,当在副作用函数 内读取 newObj.foo 时,等同于间接读取了 obj.foo 的值,设置setter使赋值newObj的属性能直接修改obj的属性
js 复制代码
function toRef(obj, key) {
  const wrapper = {
    get value(){
      return obj[key]
    },
    // 允许设置值
    set value(val) {
      obj[key] = val
    }
  }
  // 定义 __v_isRef 属性
  Object.defineProperty(wrapper, '__v_isRef', {
    value: true
  })
  return wrapper
}

function toRefs(obj) {
  const ret = {}
  for (const key in obj) {
    ret[key] = toRef(obj, key)
  }
  return ret
}

// obj 是响应式数据
const obj = reactive({ foo: 1, bar: 2 })

// 将响应式数据展开到一个新的对象 newObj
const newObj = { ...toRefs(obj) }

effect(() => {
  // 在副作用函数内通过新的对象 newObj 读取 foo 属性值
  console.log(newObj.foo.value)
})

obj.foo = 100

结果:

自动脱 ref

如果读取的属性是一个 ref,则直接将该 ref 对应 的 value 属性值返回,减少访问难度

解决思路:

  1. newObj.foo是ref有__v_isRef属性,可以给ref外面再包一层,当有__v_isRef属性就通过value.value直接返回,否则直接返回value
  2. 当设置新值时判断是否有__v_isRef,如果有就说明是ref就赋值给value.value
js 复制代码
function proxyRefs(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      const value = Reflect.get(target, key, receiver)
      // 自动脱 ref 实现:如果读取的值是 ref,则返回它的 value 属性值
      return value.__v_isRef ? value.value : value
    },
    set(target, key, newValue, receiver) {
      // 通过 target 读取真实值
      const value = target[key]
      // 如果值是 Ref,则设置其对应的 value 属性值
      if (value.__v_isRef) {
        value.value = newValue
        return true
      }
      return Reflect.set(target, key, newValue, receiver)
    }
  })
}

结果:

相关推荐
Highcharts.js9 分钟前
AI向量知识谱系图表创建示例代码|Highcharts网络图表(networkgraph)搭建案例
开发语言·前端·javascript·网络·信息可视化·编辑器·highcharts
zhangxingchao26 分钟前
AI应用开发五:RAG高级技术与调优
前端·人工智能·后端
KaMeidebaby37 分钟前
卡梅德生物技术快报|单 B 细胞抗体技术:全犬源单抗制备流程、关键参数与性能验证
前端·数据库·其他·百度·新浪微博
hazel1 小时前
网络与工程化
前端
KnowWhere1 小时前
Vue3 调试技巧:一键挂载组件实例到 Window(仅开发环境生效)
vue.js
甜味弥漫1 小时前
一篇文章搞懂CSS中的定位布局
前端
A南方故人1 小时前
vue3常用指令以及注册
前端·javascript·vue.js
helloweilei1 小时前
Vue 3 中 <script setup>顶层 await与 <Suspense>的结合使用
vue.js
AeahKa1 小时前
ztree 依赖问题解决记录
前端·webpack
子兮曰2 小时前
AI Coding 为什么全选了 TUI?从 Claude Code 到 Codex CLI,终端架构的底层逻辑
前端·后端·ai编程