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

结果:

相关推荐
芭拉拉小魔仙10 小时前
企业级Vue项目的状态管理:从原理到实战架构
前端·vue.js·架构
恋猫de小郭10 小时前
丰田正在使用 Flutter 开发游戏引擎 Fluorite
android·前端·flutter
扶苏100211 小时前
Vue 3 响应式原理深度解析
前端·javascript·vue.js
NEXT0611 小时前
React 性能优化:图片懒加载
前端·react.js·面试
PineappleCoder11 小时前
别让字体拖了后腿:FOIT/FOUT 深度解析与字体加载优化全攻略
前端·性能优化
NEXT0612 小时前
后端跑路了怎么办?前端工程师用 Mock.js 自救实录
前端·后端·程序员
装不满的克莱因瓶13 小时前
Java7新特性:try-with-resources写法
java·前端·javascript·jdk·新特性·jdk7
SailingCoder14 小时前
【 从“打补丁“到“换思路“ 】一次企业级 AI Agent 的架构拐点
大数据·前端·人工智能·面试·架构·agent
~央千澈~14 小时前
抖音弹幕游戏开发之第12集:添加冷却时间机制·优雅草云桧·卓伊凡
java·服务器·前端
CappuccinoRose14 小时前
CSS 语法学习文档(十三)
前端·css·学习·postcss·模块化·预处理器