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

结果:

相关推荐
一个处女座的程序猿O(∩_∩)O4 分钟前
完成第一个 Vue3.2 项目后,这是我的技术总结
前端·vue.js
mubeibeinv5 分钟前
项目搭建+图片(添加+图片)
java·服务器·前端
逆旅行天涯11 分钟前
【Threejs】从零开始(六)--GUI调试开发3D效果
前端·javascript·3d
m0_7482552632 分钟前
easyExcel导出大数据量EXCEL文件,前端实现进度条或者遮罩层
前端·excel
web147862107231 小时前
C# .Net Web 路由相关配置
前端·c#·.net
m0_748247801 小时前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
飞的肖1 小时前
前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端
前端·spring boot·架构
青灯文案11 小时前
前端 HTTP 请求由 Nginx 反向代理和 API 网关到后端服务的流程
前端·nginx·http
m0_748254881 小时前
DataX3.0+DataX-Web部署分布式可视化ETL系统
前端·分布式·etl
ZJ_.2 小时前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps