Skip to content

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

原始值:Boolean、Number、String、undefined、null、BigInt、Symbol

ref

Proxy 只能代理对象,因此为了让原始值获得响应式,需要将其包裹

javascript
const wrapper = {
  value: 'vue3'
};
const name = reactive(wrapper);
name.value = 'vue';

为了方便为原始值创建响应式

javascript
function ref(val) {
  const wrapper = {
    value: val
  };
  // 定义一个不可枚举的变量,用来区分数据是否是构建出的原始值对象
  Object.defineProperty(wrapper, '__v_isRef', { value: true });
  return reactive(wrapper);
}

响应丢失的问题

当执行解构的时候会出现响应丢失。

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

解决:创建一个 toRef 的方法,将 obj 转换为

javascript

function toRef(obj, key) {
  const wrapper = {
    get value() {
      return obj[key];
    }
  };
  Object.defineProperty(wrapper, '__v_isRef', { value: true });
  return wrapper;
}

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


const obj = reactive({ foo: 1, bar: 2 });
toRefs(obj)
// 转换后的结构
{
  foo: {
    get value() {
      return obj.foo;
    }
  },
  bar: {
    get value() {
      return obj.bar;
    }
  }
};

自动脱 ref

toRefs 解决了响应丢失的问题,但也带来了新的问题。

javascript
const obj = reactive({ foo: 1, bar: 2 });
const newObj = { ...toRefs(obj) };
// 访问属性时必须要加 .value
newObj.foo.value; // 1

尤其在模版中

html
<!-- 用户希望的样式 -->
{{foo}}
<!-- 现在的样子 -->
{{foo.value}}

所谓脱 ref 是指:如果读取的属性是一个 ref,那么自动返回 .value

javascript
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) {
        value.value = newVal;
        return true;
      }
      return Reflect.set(target, key, newVal, receiver);
    }
  });
}

TIP

在 Vue.js 中

  1. setup 的返回值会传递给 proxyRefs 处理
  2. reactive 函数也有自动脱勾 ref 的能力
javascript
const count = ref(0);
const obj = reactive(count);
obj.count; // 0