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

结果:

相关推荐
编程零零七2 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
(⊙o⊙)~哦4 小时前
JavaScript substring() 方法
前端
无心使然云中漫步4 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者5 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_5 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
罗政5 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
麒麟而非淇淋6 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120536 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢6 小时前
【Vue】VueRouter路由
前端·javascript·vue.js
随笔写7 小时前
vue使用关于speak-tss插件的详细介绍
前端·javascript·vue.js