Vue3 中的 shallowRef 技巧
前言
使用 Vue3
至今为止算是熟悉,但不能说很熟练了,比如针对 shallowRef
, shallowReactive
这类就不算特别熟悉,但当初没有仔细看文档的回旋镖总会砸到自己头上的。
最近开发一个数据大量可视化的项目,引用大量外部类库文件,在更新数据的时候发现网页性能大幅度下降,使用性能定位工具看了下是深度响应式问题导致的:大量使用 ref
定义外部类库函数,导致系统一直索引内部深度响应式。
shallowRef
因为之前开发几乎没有遇到大数据性能问题,直接梭哈 ref
,这次算是遇到坑了,其实解决方法也简单就是将 ref
换成 shallowRef
就行了,所以说 shallowRef
是有什么特性:ref() 的浅层作用形式
,说人话就是只有替换最顶层的值才能响应式:
import { shallowRef } from 'vue'
const state = shallowRef({ count: 0 })
// 直接修改嵌套属性不会触发更新
state.value.count = 1 // ❌ 不会触发视图更新
state.value = { count: 1 } // ✅ 这样才能触发响应
所以说这个使用场景在:
- 用在外部类库声明上,比如
ECharts.js
组件或者说富文本组件等等。 - 还有就是一些大量数据声明:比如后端给个数据,需要频繁变化,但不想关心内部结构,只需要在意是否有变化。
- 父组件管理子组件一些数据维护开支等。
进阶学习
思考后感觉没这么简单,实践后发现一些奇淫技巧,以后开发会用到的,就笔记下来了:
手动更新
如果现在有个场景一个数据使用 shallowRef
声明了很多,但发现替换顶层数据太麻烦了,只想更新其中一个对象属性,这时候可以把 triggerRef
拉出来使用了:
import { shallowRef, triggerRef } from 'vue';
const state = shallowRef({ count: 0 });
// 直接修改嵌套属性不会触发更新
state.value.count = 1; // ❌ 不会触发视图更新
// 手动强制触发更新
triggerRef(state); // ✅ 强制触发响应
这一般可以用在数据频繁修改或者大型数据修改的时候,可以避免性能追踪开销,只要手动修改就行。
再比如在动画实时变化的场景下:
import { shallowRef, onMounted } from 'vue';
const data = shallowRef({ x: 0, y: 0 });
onMounted(() => {
requestAnimationFrame(() => {
data.value.x++; // 高频修改属性
if (data.value.x === 10000) triggerRef(data); // 手动触发更新(按需调用)
});
});
混合响应式数据
之前遇到过一种数据,大概有五六层深度的对象数据,需要变动三层某个属性数据,这时候利用 shallowRef
和 reactive
完成:
import { shallowRef, reactive } from 'vue';
const refState = shallowRef({
config: reactive({ enabled: true }), // 部分属性保持响应式
metadata: { version: '1.0' } // 非响应式部分
});
refState.value.config.enabled = false; // ✅ 触发响应
refState.value.metadata.version = '2.0'; // ❌ 不触发响应
customRef 的一些技巧
正常来说能用到 customRef
场景很少,官方文档是不建议使用这个,除非需要对变量的细腻度追踪,Demo
代码建议去文档查看,这边就展示一些常用的代码技巧:
- 追踪一个变量的变化历史,比如编辑器的用户操作场景:
function historyRef(initialValue) {
let history = [initialValue];
return customRef((track, trigger) => ({
// 读取动作
get() {
track();
return history[history.length - 1];
},
// 记录动作
set(newValue) {
history.push(newValue);
trigger();
},
// 撤回动作
undo() {
if (history.length > 1) {
history.pop();
trigger();
}
}
}));
}
- 数据验证拦截
function validatedRef(initialValue, validator) {
return customRef((track, trigger) => ({
get() {
track();
return initialValue;
},
set(newValue) {
if (validator(newValue)) {
initialValue = newValue;
trigger();
} else {
console.warn('Invalid value!');
}
}
}));
}
// 使用
const age = validatedRef(18, (v) => v >= 0);
- 非响应库集成
import { customRef } from 'vue';
import { NonReactiveStore } from 'some-library';
const store = new NonReactiveStore();
const storeRef = customRef((track, trigger) => ({
get() {
track();
return store.getState();
},
set(newValue) {
store.setState(newValue);
trigger();
}
}));
// 监听外部库变化
store.onChange(() => {
trigger(storeRef); // 外部变化时手动触发更新
});
- 缓存计算
function cachedRef(initialValue, computeFn) {
let cache = null;
return customRef((track, trigger) => ({
get() {
track();
if (cache === null) {
cache = computeFn(initialValue);
}
return cache;
},
set(newValue) {
initialValue = newValue;
cache = null; // 清空缓存
trigger();
}
}));
}
// 使用
const expensiveData = cachedRef(0, (n) => heavyCalculation(n));