vue3 reactive值为什么不推荐解构&&分析响应式结构

6,324 阅读3分钟

书接上文,我们继续探讨这个话题,我还是先把官网的截图甩在这里。

image.png

问题思考

  1. 解构的值是基本数据类型,会丢失响应式,那如果是引用数据类型,会保持响应式吗?
  2. 丢失响应式是 vue 内部帮我们做的吗?

问题探究

我们先写例子来构造下场景,比较简单。

<script setup>
import { reactive } from "vue";

const state = reactive({
  obj: {
    name: "zhangsan",
  },
});

const handleClick = () => {
  const { obj } = state;
  obj.name = "lisi";
};
</script>
<template>
  <button @click="handleClick">点击切换</button>
  <div>{{ state }}</div>
</template>

效果如下:

snapshot.gif

下面我们改造下代码:

const handleClick = () => {
  const { name } = state.obj;
  name = "lisi";
};

效果如下:

snapshot (1).gif

经过上面比对后发现,当解构出来的是一个引用对象类型时,它是响应式的,当时基本数据类型时,响应式会丢失。

源码分析

image.png

我们在最上面代码打上debug,然后我们进入到 reactive 方法里面,它接收一个 target,然后判断是否是只读的,最后返回的是 createReactiveObject 方法调用的返回值,我们进入到这个方法里面。

image.png

在这个方法里面,做了一些判断,首先判断是否是对象,如果不是,返回该值。

再判断标识 __v_raw 是否已经是一个被代理的对象了,如果是则返回。

接下来的判断 existingProxy 对已经Proxy的,则直接从 proxyMap 数据结构中取出这个Proxy对象,这是提升性能的一种方法。从代码中可以看出后面会对Proxy对象会进行 proxyMap.set(target, proxy) 操作,这里的 proxyMap 调用的是 WeakMap 的实例。

接着往下走,后面还对可以reactive的对象加了个白名单,只有 Object,Array,Map,Set,WeakMap,WeakSet 对象可以进行 reactive 操作。

image.png

最后进行了 Proxy,在初次进行reactive的时候只对第一层进行Proxy。我们看下reactive后的Proxy对象

image.png

此时的 targetType 值是 1,所以会走 baseHandlers,这个方法是 createReactiveObject 入参的第三个参数,也就是 mutableHandlers,这里面包含了 Proxy 的常用方法

image.png

createGetter 就是调用 get 方法后执行的函数,在 447 行我们发现,如果子项仍是对象, 会递归调用 reactive 进行深层次代理,到这里,我们发现,源码没有对丢失响应式做任何事情,也就是说它失去响应式不在于Vue而是在于Proxy对象本身。

image.png

简单实现

const obj = {
    count: 1
};
const proxy = new Proxy(obj, {
    get(target, key) {
        console.log("get");
        return target[key]
    },
    set(target, key, value) {
        console.log("set");
        target[key] = value
        return true;
    }
});
console.log(proxy.count);

当我们尝试读取值的时候会调用 get 方法,设置值的时候会调用 set 方法,如果 let { count } = proxy; count = 2 解构再赋值是不会触发 set 方法调用的。那如果是解构出来的是引用数据类型呢,现在我们改下上面代码:

const obj = {
  counter: {
    count: 1
  }
};
const reactive = (target) => {
  return new Proxy(obj, {
    get(target, key) {
      console.log("get");
      if (typeof target[key] === 'object') {
        return reactive(target[key]);
      }
      return target[key]
    },
    set(target, key, value) {
      console.log("set");
      target[key] = value
      return true;
    }
  });
}
let {
  counter
} = reactive(obj);
counter.count = 2

这个时候,get 方法和 set 方法都会被调用,这也证实了我们上面的结论,