原始值的响应式方案-vue3

103 阅读2分钟

原始值响应式方案

在Javascript中原始值是按照值传递的,而非引用传递,这意味着一个函数接收一个原始值作为参数,形参和实参,是两个独立的值。以及proxy只能代理对象无法对原始值进行代理,因此想要对原始值进行代理,变成响应式数据就得给原始值包裹一层对象。就是vue3中实现的ref。

自定义简易ref

const wrapper = {
    value: 'vue'
}
const name = reactive(wrapper)

name.vue = 1 // 触发响应式

封装ref

const { reactive, effect } = require("@vue/reactivity")

function ref(value) {
  const wrapper = {
    value
  }
  // 标记为ref 不可枚举
  Object.defineProperty(wrapper, '__v_isRef', {
    value: true
  })
  return reactive(wrapper)
}
// 实现
const refVal = ref(1)
effect(() => {
  // 收集依赖
  console.log(refVal.value, 'refVal')
})
// 修改值触发响应式重新执行
refVal.value = 2

响应式丢失

ref除了能解决原始值的响应式方案以外,还能用来解决响应式丢失问题。

const obj = reactive({ a:1, b: 2})
const {a, b} = obj // 对象的解构以及...运算符都使对象失去响应式
const obj1 = {...obj}
// 等价于
const obj1 = {
    a: obj.a,
    b: obj.b
}

// 可以使用toRef
function toRef(obj, key) {
  const wrapper = {
    get value() {
      return obj[key]
    },
    set value(val) {
      obj[key] = val
    }
  }
  return wrapper
}

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

// toRef 使得解构的数据重新与源对象建立联系 从而解决了响应式丢失的问题

自动脱ref

在vue开发项目中,我们在script中使用ref都是有value的,而在模板中使用自动给我们去掉了.value。大致的实现原理就是在元素抛出去的时候,调用一个函数,来解决这个问题。

// 给ref重新包一个代理,如果值是ref 则在读取obj.a的时候 直接返回obj.a.value
// 这样就实现了自动脱ref, 
function proxyRefs(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      const value = Reflect.get(target, key, receiver);
      return value.__v_isRef ? value.value : value;
    },
    set(target, key, newVal, receiver) {
      const value = target[key]
      if (value.__v_isRef) {
        // 如果是ref, 在给对象赋值的时候,自动赋值给ref.value
        value.value = newVal
        return true
      }
      return Reflect.set(target, key, newVal, receiver)
    }
  });
}

const name = ref(1)
const name1 = reactive({name}) // 自动脱ref

在vue的使用场景中,ref转到reactiv的时候也会自动脱ref。