第六章-原始值的响应式方案

79 阅读2分钟

1、引入ref的概念

非对象的变量无法使用proxy进行代理,需要进行包裹, 由此引出ref

function ref(val) {
  let wrapper = {
     value : val,
  }
  return reactive(wrapper)
}
// 测试示例
const flag = ref(true)
effect(() => {
   console.log(flag.value)
})
flag.value = false

// 测试结果
true
false

需要给ref一个标识, 方便后面脱ref

function ref(val) {
  let wrapper = {
     value : val,
  }
  Object.defineProperty(wrapper, "_v_isRef", {value: truef})
  return reactive(wrapper)
}

2、响应丢失问题

这是涉及到实际应用

<template>
   <p>{{ foo }} / {{ bar }}</p>
</template>
<script>
  export default {
    setup() {
     // 响应式数据
      const obj = reactive({ foo: 1, bar: 2 })
      // 1s 后修改响应式数据的值,不会触发重新渲染
      setTimeout(() => { obj.foo = 100 }, 1000)
      return {...obj}
    }
  }
</script>

上面的展开运算符会导致响应丢失, 返回了一个普通对象

该怎么建立联系, 如下面代码

const obj = reactive({foo: 1, bar: 2})
const newObj = {
  foo: {
    get value() {
      return obj.value;
    }
  },
  bar: {
     get value() {
       return obj.bar;
     }
  }
}

// 测试示例
effect(() => {
   console.log(newObj.bar.value);
})
obj.bar = 1;
// 测试结果
2
1

将上面的解构抽取并封装

// 单个
function toRef(obj, key) {
  const wrapper = {
    get value() {
      return obj[key]
    }
  }
  Object.defineProperty(wrapper, "_v_isRef", {value: true}); // 这里解构成了ref, 同样需要打上标记
  return wrapper
}
// 多个
function toRefs(obj) {
  const ret = {}
  for(const key in obj) {
    ret[key] = toRef(obj, key)
  }
  return ret
}

// 测试示例
const obj = reactive({foo: 1, bar: 2})
const newObj = { ...toRefs(obj)}
effect(() => {console.log(newObj.bar.value);})
obj.bar = 1;
// 测试结果
2
1

上面对于属性只有get方法, 没有set的方法

function toRef(obj, key) {
  const wrapper = {
    get value() {
      return obj[key]
    },
    set value(val){
      obj[key] = val;
    }
  }
  Object.defineProperty(wrapper, "_v_isRef", {value: true})
  return wrapper
}

3、自动脱ref

上面的toRefs会把响应式数据的第一层属性值转换成ref, 因此必须要通过value属性访问值, 增加了用户的心智负担

newObj.bar.value
newObj.foo.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, newValue, receiver) {
      let value = target[key];
      if(value._v_isRef) return value.value = newValue;
      Reflect.set(target, key, value, receiver)
    }
  })
}

const obj = reactive({foo: 1, bar: 2})
const newObj = proxyRefs({...toRefs(obj)})
effect(() => {console.log(newObj.bar);})
obj.bar = 1;

理解在setup的return返回的对象中, 底层包装了一层类似proxyRefs的方法, 实现了对象的解构,同时也不增加用户的心智负担