loading

Vue - 深层次响应式与浅层响应式

shallowReactive

reactive 函数的返回了一个 Proxy 代理对象,代理对象与源对象不同(内存地址不同),下面的代码中,Vue 给 obj 对象添加 set 和 get 拦截,把 tracktrigger 两个函数加入进来可以让页面进行实时刷新(内在原理我不知)。

file:[reactive 伪代码]
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)
    }
  })
}

以上代码来自于官方文档的伪代码。那么,什么是深层次响应式呢?在理解“深层次”之前需要理解一个简单的例子:

file:[src/App.vue]
const originalObj = {
  foo: {
    bar: { value: 1 }
  },
  foobar: 100
};

const proxyObj = new Proxy(originalObj, {
  get(target, key) {
    console.log("getter >>> ", target);

    return target[key];
  },
  set(target, key, value) {
    console.log("setter >>> ", target);
    target[key] = value;
    return true;
  }
});

// 修改 foo.bar.value
proxyObj.foo.bar.value = 20;

// 打印
console.log(proxyObj.foo.bar.value);

上面举的例子中,对象层级有两岑,第一层是 foo,第二层是 bar,bar 下面的一个属性 value 是数字类型(基本类型,不属于对象)。

设置 bar 触发一次 get,打印 bar 触发一次 get

从控制台中没有看到 set 函数的触发,当遇到第二层对象的值修改或访问时都触发了 get 函数。如果修改第一层对象的值呢?

file:[src/App.vue]
// 修改 foobar
proxyObj.foobar = 20;

// 打印
console.log(proxyObj.foobar);

设置时触发 set,获取时获取 get

从控制台中看到了 set 和 get 函数的触发,这说明了,代理对象对于第一层对象有追踪(拦截)功能,对于第二层对象或更深层次的对象是没有的。将上面给的官方文档给的 reactive 函数实现伪代码可知,对于深层次的对象,set 和 get 不起作用,也就是说tracktrigger 函数就用不上,这两个函数用不上,界面就没办法重新渲染。

为了让深层次对象也有 tracktrigger 函数,就递归地创建深层次对象的代理对象。这就是浅层和深层响应式数据的区别,也就是说,你的对象越深、越多,它要递归的次数和消耗的性能就越多,但是这种消耗根据文档的意思来说,不是特别大。因此,要创建不递归的响应式对象,请使用 shallowReactiveshallowRef 来创建。

失去响应式

代理对象会遇到失去响应式的情况,所以由 reactive 函数创建的响应式数据时,一定要注意不要改变根对象。所谓根对象就是第一层对象,也就是 const state = reactive({}) 中的 state 这个对象(代理对象)不要被重新复制。所以,一般使用 const 关键字变量。

file:[src/App.vue]
let state = shallowReactive({
  foo: {
    bar: 1
  },
  foobar: 2
});

function changeState() {
  state = {
    foo: {
      bar: 1000
    },
    foobar: 2000
  };
}

如下图所示,state.foobar 在替换顶级对象之前,一直是有响应式的,但是被替换了之后,就失去了响应式。

替换顶级对象,其浅层响应式也失去了

shallowRef

shallowRefref 创建的响应式数据被替换依旧保留响应式。

file:[src/App.vue]
let state = shallowRef({
  foo: {
    bar: 1
  },
  foobar: 2
});

function changeState() {
  // 自加不会触发页面更新
  state.value.foo.bar++;
  state.value.foobar++;

  // 替换 state.value 的根对象页面才会更新
  const replace = {
    foo: {
      bar: state.value.foo.bar
    },
    foobar: state.value.foobar
  };

  state.value = replace;
}

如下图所示,先是点击下面两个按钮,视图并没有更新。根对象被替换了之后,视图就会因此而更新。

changeState 替换顶级对象,依旧保持响应式

更推荐的做法是通过 triggerRef 来通知视图更新。

file:[src/App.vue]
function triggerUpdate() {
  state.value.foo.bar++;
  state.value.foobar++;

  triggerRef(state);
}

tiggerRef 强制更新浅层代码

shallowRef 和 shallowReactive 的区别

下面是官方文档的伪代码:

file:[ref 伪代码]
function ref(value) {
  const refObject = {
    get value() {
      track(refObject, 'value')
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(refObject, 'value')
    }
  }
  return refObject
}

ref 函数创建的响应式数据其实也是返回了一个对象,不过这个对象不是 Proxy 对象,也拥有 get 和 set 的函数,通过 .value 访问创建的数据本身。ref 对于对象类型(Map、Object 等)交给了 reactive 进行创建。

shallowRef 使用场景

如果你希望一些响应式数据不立马更新页面,可以通过 shallowRef 来做到,在需要更新的时候使用 triggeRef 来更新页面。

如上图所示,这是一个表格,当我展开表格编辑界面时,这些输入框等组件绑定的是表格中的值,当输入内容时,会立马通知视图更新,界面会发生重新渲染,导致我打开的界面被关闭,这就是因为表格的数据是由 ref 创建的,所以这个时候就可以使用 shallowRef 来创建响应式数据。虽然浅层响应式数据也可能会让界面的值发生变化,但至少它不会让我打开的界面关闭。

posted @ 2023-03-19 20:39  Himmelbleu  阅读(126)  评论(0编辑  收藏  举报