shallowReactive
reactive
函数的返回了一个 Proxy 代理对象,代理对象与源对象不同(内存地址不同),下面的代码中,Vue 给 obj 对象添加 set 和 get 拦截,把 track
和 trigger
两个函数加入进来可以让页面进行实时刷新(内在原理我不知)。
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 是数字类型(基本类型,不属于对象)。
从控制台中没有看到 set 函数的触发,当遇到第二层对象的值修改或访问时都触发了 get 函数。如果修改第一层对象的值呢?
file:[src/App.vue]
// 修改 foobar
proxyObj.foobar = 20;
// 打印
console.log(proxyObj.foobar);
从控制台中看到了 set 和 get 函数的触发,这说明了,代理对象对于第一层对象有追踪(拦截)功能,对于第二层对象或更深层次的对象是没有的。将上面给的官方文档给的 reactive
函数实现伪代码可知,对于深层次的对象,set 和 get 不起作用,也就是说track
和 trigger
函数就用不上,这两个函数用不上,界面就没办法重新渲染。
为了让深层次对象也有 track
和 trigger
函数,就递归地创建深层次对象的代理对象。这就是浅层和深层响应式数据的区别,也就是说,你的对象越深、越多,它要递归的次数和消耗的性能就越多,但是这种消耗根据文档的意思来说,不是特别大。因此,要创建不递归的响应式对象,请使用 shallowReactive
或 shallowRef
来创建。
失去响应式
代理对象会遇到失去响应式的情况,所以由 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
shallowRef
和 ref
创建的响应式数据被替换依旧保留响应式。
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;
}
如下图所示,先是点击下面两个按钮,视图并没有更新。根对象被替换了之后,视图就会因此而更新。
更推荐的做法是通过 triggerRef
来通知视图更新。
file:[src/App.vue]
function triggerUpdate() {
state.value.foo.bar++;
state.value.foobar++;
triggerRef(state);
}
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
来创建响应式数据。虽然浅层响应式数据也可能会让界面的值发生变化,但至少它不会让我打开的界面关闭。