前言
使用 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));