阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 响应式性能优化(markRaw等)

响应式性能优化(markRaw等)

作者:陈川 阅读数:11252人阅读 分类: Vue.js

响应式性能优化(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
  }
})

特殊场景处理

某些场景需要特别注意响应式行为:

  1. Vuex/Pinia状态
// store中
state: () => ({
  cachedData: markRaw(heavyData)
})
  1. 模板直接使用
<!-- 不会自动解包markRaw -->
<div v-for="item in heavyData.items" :key="item.id">
  {{ item.name }}
</div>
  1. 动态取消标记
const rawData = markRaw({...})
// 取消标记(不推荐)
const reactiveData = reactive({...rawData})

性能对比实测

通过10,000个对象的列表测试:

方案 内存占用 初始化时间 更新耗时
全响应式 12.5MB 320ms 45ms
markRaw 4.2MB 80ms -

其他优化API

  1. shallowRef
const heavyArray = shallowRef([...])
// 需要替换整个数组才会触发更新
heavyArray.value = [...heavyArray.value, newItem]
  1. 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
}

常见误区

  1. 过度优化
// 不必要的小对象优化
const overOptimized = markRaw({ count: 0 })
  1. 错误的结构设计
// 更好的方式
reactive({
  pagination: { current: 1 },
  rawData: markRaw([...])
})

// 而非
markRaw({
  pagination: { current: 1 }, // 失去了响应式
  items: [...]
})

浏览器内存分析

Chrome DevTools内存快照对比:

  1. 全响应式对象:
  • 多个ReactiveEffect实例
  • 大量Proxy包装器
  • 依赖关系的Map存储
  1. markRaw对象:
  • 原始对象结构
  • 无额外内存开销
  • 无依赖追踪结构

与Object.freeze区别

Object.freezemarkRaw有本质差异:

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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌