6. 原始值的响应式方案 
6.1 引入 ref 的概念 
由于 Proxy 的代理目标必须是非原始值,所以需要使用一个非原始值去“包裹”原始值
js
const wrapper = {
  value: "vue",
};
// 可以使用 Proxy 代理 wrapper,间接实现对原始值的拦截
const name = reactive(wrapper);
name.value; // vue
name.value = "vue3";封装一个函数
js
// 封装一个 ref 函数
function ref(val) {
  // 在 ref 函数内部创建包裹对象
  const wrapper = {
    value: val,
  };
  // 将包裹对象变成响应式数据
  return reactive(wrapper);
}但是为了让它有自动脱 ref的能力,我们有必要区分一个数据到底是不是 ref
js
function ref(val) {
  const wrapper = {
    value: val,
  };
  // 使用 Object.defineProperty 在 wrapper 对象上定义一个不可枚举的属性 __v_isRef,并且值为 true
  Object.defineProperty(wrapper, "__v_isRef", {
    value: true,
  });
  return reactive(wrapper);
}6.2 响应丢失问题 
通过解构对象的方式会使模板数据丢失响应式
js
export default {
  setup() {
    // 响应式数据
    const obj = reactive({ foo: 1, bar: 2 });
    // 将数据暴露到模板中
    return {
      ...obj,
    };
    // 相当于
    // return {
    //   foo: 1,
    //   bar: 2,
    // };
  },
};如何解决?
js
// obj 是响应式数据
const obj = reactive({ foo: 1, bar: 2 });
// newObj 对象下具有与 obj 对象同名的属性,并且每个属性值都是一个对象,
// 该对象具有一个访问器属性 value,当读取 value 的值时,其实读取的是 obj 对象下相应的属性值
const newObj = {
  foo: {
    get value() {
      return obj.foo;
    },
  },
  bar: {
    get value() {
      return obj.bar;
    },
  },
};
effect(() => {
  // 在副作用函数内通过新的对象 newObj 读取 foo 属性值
  console.log(newObj.foo.value);
});
// 这时能够触发响应了
obj.foo = 100;将 foo 和 bar 方法封装成一个toRef方法
js
function toRef(obj, key) {
  const wrapper = {
    get value() {
      return obj[key];
    },
  };
  return wrapper;
}
// 使用
return {
  foo: toRef(obj, "foo"),
  bar: toRef(obj, "bar"),
};继续优化,批量解构
js
function toRefs(obj) {
  const ret = {};
  // 使用 for...in 循环遍历对象
  for (const key in obj) {
    // 逐个调用 toRef 完成转换
    ret[key] = toRef(obj, key);
  }
  return ret;
}
// 使用
return {
  ...toRefs(obj),
};完整 toRef 方法
js
function toRef(obj, key) {
  const wrapper = {
    get value() {
      return obj[key];
    },
    // 允许设置值
    set value(val) {
      obj[key] = val;
    },
  };
  // 将通过 toRef 或 toRefs 转换后得到的结果视为真正的 ref 数据
  Object.defineProperty(wrapper, "__v_isRef", {
    value: true,
  });
  return wrapper;
}6.3 自动脱 ref 
js
const obj = reactive({ foo: 1, bar: 2 });
obj.foo; // 1
obj.bar; // 2
const newObj = { ...toRefs(obj) };
// 必须使用 value 访问值
newObj.foo.value; // 1
newObj.bar.value; // 2使用 Proxy 为 newObj 创建一个代理对象,通过代理来实现脱 ref
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;
    },
  });
}
// 调用 proxyRefs 函数创建代理
const newObj = proxyRefs({ ...toRefs(obj) });实际上,我们在编写 Vue.js 组件时,组件中的 setup 函数所返回的数据会传递给 proxyRefs 函数进行处理:
js
const MyComponent = {
  setup() {
    const count = ref(0);
    // 返回的这个对象会传递给 proxyRefs
    return { count };
  },
};为 proxyRefs 添加设置值的能力
js
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) {
      // 通过 target 读取真实值
      const value = target[key];
      // 如果值是 Ref,则设置其对应的 value 属性值
      if (value.__v_isRef) {
        value.value = newValue;
        return true;
      }
      return Reflect.set(target, key, newValue, receiver);
    },
  });
}