阿里云主机折上折
  • 微信号
Current Site:Index > Responsive performance optimization (markRaw, etc.)

Responsive performance optimization (markRaw, etc.)

Author:Chuan Chen 阅读数:11253人阅读 分类: Vue.js

Responsive Performance Optimization (markRaw, etc.)

Vue.js's reactivity system is one of its core features, but excessive use of reactivity can lead to performance issues. In certain scenarios, data does not need reactive tracking, and APIs like markRaw can be used for optimization.

Overhead of the Reactivity System

Vue uses Proxy/defineProperty to implement reactivity, where each reactive object creates a corresponding dependency collector. When objects have deep hierarchies or contain large amounts of data, significant performance overhead can occur:

const data = reactive({
  items: Array(10000).fill().map((_, i) => ({ 
    id: i,
    nested: { value: Math.random() }
  }))
})

In this example, Vue creates reactive proxies for each array element and its nested objects, resulting in approximately 20,000 proxy objects. If this data is only used for display and does not require reactive updates, it causes unnecessary performance loss.

Basic Usage of markRaw

markRaw marks an object so it will never be converted into a reactive proxy:

import { reactive, markRaw } from 'vue'

const staticData = markRaw({
  largeArray: Array(10000).fill(0)
})

const state = reactive({
  // largeArray will not be recursively converted
  config: staticData
})

Marked objects and their nested properties will skip reactive conversion. This is particularly useful in the following scenarios:

  • Large static datasets
  • Third-party library instances
  • Configuration objects that do not need to change

Combining with shallowReactive

shallowReactive only creates shallow reactivity, and combining it with markRaw allows precise control over reactivity levels:

const state = shallowReactive({
  // First-level properties are reactive
  pagination: {
    current: 1,
    total: 100
  },
  
  // Internal data is marked as raw
  heavyData: markRaw({
    items: [...],
    meta: {...}
  })
})

// Reactive
state.pagination.current++ 

// Non-reactive (will not trigger updates)
state.heavyData.items.push(newItem)

Application in Components

Component props are converted to reactivity by default. For large static props, optimization is possible:

// Parent component
const heavyList = markRaw([...])

<Child :data="heavyList" />

// Child component
defineProps({
  data: {
    type: Array,
    required: true
  }
})

Special Scenario Handling

Certain scenarios require special attention to reactive behavior:

  1. Vuex/Pinia State:
// In store
state: () => ({
  cachedData: markRaw(heavyData)
})
  1. Direct Use in Templates:
<!-- markRaw is not automatically unwrapped -->
<div v-for="item in heavyData.items" :key="item.id">
  {{ item.name }}
</div>
  1. Dynamic Unmarking:
const rawData = markRaw({...})
// Unmarking (not recommended)
const reactiveData = reactive({...rawData})

Performance Comparison Tests

Testing with a list of 10,000 objects:

Solution Memory Usage Initialization Time Update Time
Fully Reactive 12.5MB 320ms 45ms
markRaw 4.2MB 80ms -

Other Optimization APIs

  1. shallowRef:
const heavyArray = shallowRef([...])
// Requires replacing the entire array to trigger updates
heavyArray.value = [...heavyArray.value, newItem]
  1. customRef for Lazy Reactivity:
function lazyRef(value) {
  return customRef((track, trigger) => ({
    get() {
      track()
      return value
    },
    set(newVal) {
      value = markRaw(newVal)
      trigger()
    }
  }))
}

In-Depth Reactivity Principles

Understanding the implementation principles helps in correctly using optimization techniques:

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)
      // Returns raw value directly for markRaw objects
      return isRaw(res) ? res : reactive(res)
    }
    // ...other traps
  })
  
  return proxy
}

Common Misconceptions

  1. Over-Optimization:
// Unnecessary optimization for small objects
const overOptimized = markRaw({ count: 0 })
  1. Incorrect Structural Design:
// Better approach
reactive({
  pagination: { current: 1 },
  rawData: markRaw([...])
})

// Instead of
markRaw({
  pagination: { current: 1 }, // Loses reactivity
  items: [...]
})

Browser Memory Analysis

Chrome DevTools memory snapshot comparison:

  1. Fully Reactive Objects:
  • Multiple ReactiveEffect instances
  • Numerous Proxy wrappers
  • Map storage for dependencies
  1. markRaw Objects:
  • Original object structure
  • No additional memory overhead
  • No dependency tracking structures

Difference from Object.freeze

Object.freeze and markRaw are fundamentally different:

const frozen = Object.freeze({ prop: 'value' })
frozen.prop = 'new' // Throws error in strict mode

const raw = markRaw({ prop: 'value' })
raw.prop = 'new' // Allows modification but does not trigger reactivity

Practices in Composition API

Typical applications in the setup function:

export default {
  setup() {
    const heavyData = markRaw(importLargeDataSet())
    
    const state = reactive({
      filters: {},
      // Does not convert heavyData
      dataSet: heavyData
    })
    
    return { state }
  }
}

Server-Side Rendering Scenarios

In SSR, reactive objects are serialized on the server. markRaw data reduces payload size:

// Server-side
const ssrData = markRaw({
  // Will not be converted to reactive
  initialData: fetchData()
})

// Skips conversion during client hydration
const app = createSSRApp(App, { ssrData })

TypeScript Integration

Adding type support for markedRaw objects:

import type { MarkedRaw } from 'vue'

interface HeavyData {
  items: Array<{...}>
}

const rawData: MarkedRaw<HeavyData> = markRaw({
  items: [...]
})

Interaction with watch

Special behavior when observing markRaw objects:

const rawObj = markRaw({ count: 0 })

watch(
  () => rawObj.count,
  (newVal) => {
    // Will not trigger because rawObj is not reactive
  }
)

watch(
  () => ({ ...rawObj }),
  (newVal) => {
    // Deep detection can be triggered by copying the object
  },
  { deep: true }
)

Performance Monitoring Strategy

Implementing an automatic marking strategy for large data:

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.