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)
    }
  })
}

结果:

相关推荐
阿阳微客19 分钟前
Steam 搬砖项目深度拆解:从抵触到真香的转型之路
前端·笔记·学习·游戏
德育处主任Pro1 小时前
『React』Fragment的用法及简写形式
前端·javascript·react.js
CodeBlossom1 小时前
javaweb -html -CSS
前端·javascript·html
打小就很皮...2 小时前
HBuilder 发行Android(apk包)全流程指南
前端·javascript·微信小程序
集成显卡3 小时前
PlayWright | 初识微软出品的 WEB 应用自动化测试框架
前端·chrome·测试工具·microsoft·自动化·edge浏览器
前端小趴菜053 小时前
React - 组件通信
前端·react.js·前端框架
Amy_cx4 小时前
在表单输入框按回车页面刷新的问题
前端·elementui
dancing9994 小时前
cocos3.X的oops框架oops-plugin-excel-to-json改进兼容多表单导出功能
前端·javascript·typescript·游戏程序
后海 0_o5 小时前
2025前端微服务 - 无界 的实战应用
前端·微服务·架构
Scabbards_5 小时前
CPT304-2425-S2-Software Engineering II
前端