响应式性能优化(markRaw等)
响应式性能优化(markRaw等)
Vue.js的响应式系统是其核心特性之一,但过度使用响应式可能导致性能问题。某些场景下数据不需要响应式追踪,这时可以通过markRaw等API进行优化。
响应式系统的开销
Vue使用Proxy/defineProperty实现响应式,每个响应式对象都会创建对应的依赖收集器。当对象层级较深或包含大量数据时,会产生显著性能开销:
const data = reactive({
items: Array(10000).fill().map((_, i) => ({
id: i,
nested: { value: Math.random() }
}))
})
这个例子中,Vue会为每个数组元素及其嵌套对象创建响应式代理,总共产生约20000个代理对象。如果这些数据仅用于展示而不需要响应式更新,就会造成不必要的性能损耗。
markRaw基础用法
markRaw
标记对象使其永远不会被转为响应式代理:
import { reactive, markRaw } from 'vue'
const staticData = markRaw({
largeArray: Array(10000).fill(0)
})
const state = reactive({
// 不会递归转换largeArray
config: staticData
})
被标记的对象及其嵌套属性都会跳过响应式转换。这在以下场景特别有用:
- 大型静态数据集
- 第三方库实例
- 不需要变化的配置对象
与shallowReactive配合使用
shallowReactive
只创建浅层响应式,结合markRaw
可精确控制响应式层级:
const state = shallowReactive({
// 第一层属性是响应式的
pagination: {
current: 1,
total: 100
},
// 内部数据被标记为原始值
heavyData: markRaw({
items: [...],
meta: {...}
})
})
// 响应式
state.pagination.current++
// 非响应式(不会触发更新)
state.heavyData.items.push(newItem)
在组件中的应用
组件props默认会进行响应式转换,对于大型静态props可优化:
// 父组件
const heavyList = markRaw([...])
<Child :data="heavyList" />
// 子组件
defineProps({
data: {
type: Array,
required: true
}
})
特殊场景处理
某些场景需要特别注意响应式行为:
- Vuex/Pinia状态:
// store中
state: () => ({
cachedData: markRaw(heavyData)
})
- 模板直接使用:
<!-- 不会自动解包markRaw -->
<div v-for="item in heavyData.items" :key="item.id">
{{ item.name }}
</div>
- 动态取消标记:
const rawData = markRaw({...})
// 取消标记(不推荐)
const reactiveData = reactive({...rawData})
性能对比实测
通过10,000个对象的列表测试:
方案 | 内存占用 | 初始化时间 | 更新耗时 |
---|---|---|---|
全响应式 | 12.5MB | 320ms | 45ms |
markRaw | 4.2MB | 80ms | - |
其他优化API
- shallowRef:
const heavyArray = shallowRef([...])
// 需要替换整个数组才会触发更新
heavyArray.value = [...heavyArray.value, newItem]
- customRef实现惰性响应:
function lazyRef(value) {
return customRef((track, trigger) => ({
get() {
track()
return value
},
set(newVal) {
value = markRaw(newVal)
trigger()
}
}))
}
响应式原理深度
了解实现原理有助于正确使用优化手段:
function reactive(obj) {
if (isRaw(obj)) return obj
const proxy = new Proxy(obj, {
get(target, key) {
track(target, key)
const res = Reflect.get(target, key)
// 遇到markRaw对象直接返回原始值
return isRaw(res) ? res : reactive(res)
}
// ...其他trap
})
return proxy
}
常见误区
- 过度优化:
// 不必要的小对象优化
const overOptimized = markRaw({ count: 0 })
- 错误的结构设计:
// 更好的方式
reactive({
pagination: { current: 1 },
rawData: markRaw([...])
})
// 而非
markRaw({
pagination: { current: 1 }, // 失去了响应式
items: [...]
})
浏览器内存分析
Chrome DevTools内存快照对比:
- 全响应式对象:
- 多个
ReactiveEffect
实例 - 大量
Proxy
包装器 - 依赖关系的
Map
存储
- markRaw对象:
- 原始对象结构
- 无额外内存开销
- 无依赖追踪结构
与Object.freeze区别
Object.freeze
与markRaw
有本质差异:
const frozen = Object.freeze({ prop: 'value' })
frozen.prop = 'new' // 严格模式报错
const raw = markRaw({ prop: 'value' })
raw.prop = 'new' // 允许修改,但不触发响应
组合式API中的实践
在setup函数中的典型应用:
export default {
setup() {
const heavyData = markRaw(importLargeDataSet())
const state = reactive({
filters: {},
// 不转换heavyData
dataSet: heavyData
})
return { state }
}
}
服务端渲染场景
SSR中响应式对象会在服务端被序列化,markRaw数据能减少payload大小:
// 服务端
const ssrData = markRaw({
// 不会被转为响应式
initialData: fetchData()
})
// 客户端hydrate时跳过转换
const app = createSSRApp(App, { ssrData })
TypeScript集成
为markedRaw对象添加类型支持:
import type { MarkedRaw } from 'vue'
interface HeavyData {
items: Array<{...}>
}
const rawData: MarkedRaw<HeavyData> = markRaw({
items: [...]
})
与watch的交互
观察markRaw对象时的特殊行为:
const rawObj = markRaw({ count: 0 })
watch(
() => rawObj.count,
(newVal) => {
// 不会触发,因为rawObj不是响应式的
}
)
watch(
() => ({ ...rawObj }),
(newVal) => {
// 通过拷贝对象可触发深度检测
},
{ deep: true }
)
性能监控策略
实现自动标记大型数据的策略:
const autoMarkRaw = (obj, threshold = 1000) => {
if (Array.isArray(obj) && obj.length > threshold) {
return markRaw(obj.map(item => autoMarkRaw(item, threshold)))
}
if (isPlainObject(obj) && Object.keys(obj).length > threshold/10) {
return markRaw(
Object.fromEntries(
Object.entries(obj).map(([k, v]) => [k, autoMarkRaw(v, threshold)])
)
}
return obj
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
下一篇:自定义响应式effect