阿里云主机折上折
  • 微信号
Current Site:Index > Best practices for responsive performance

Best practices for responsive performance

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

The reactive system is one of the core features of Vue.js, but improper usage can lead to performance issues. Properly leveraging the reactive mechanism and reducing unnecessary computations and rendering can significantly improve application smoothness.

Reactive Data Optimization

Avoid declaring unnecessary reactive properties in data. Vue recursively processes all properties in data for reactivity, and large objects or non-reactive data can increase initialization overhead.

// Not recommended
data() {
  return {
    largeArray: new Array(10000).fill({ value: 0 }), // Each element will become reactive
    config: Object.freeze({ // Frozen objects will still be recursively processed for reactivity
      apiUrl: 'https://api.example.com',
      timeout: 5000
    })
  }
}

// Recommended approach
data() {
  return {
    // Data that needs reactivity
    activeItem: null,
    // Non-reactive large data
    largeArray: markRaw(new Array(10000).fill({ value: 0 })),
    config: { // Completely avoid reactivity with Object.freeze + markRaw
      apiUrl: 'https://api.example.com',
      timeout: 5000
    }
  }
}

For complex data structures, use shallowRef or shallowReactive to avoid deep reactivity:

import { shallowReactive } from 'vue'

const state = shallowReactive({
  nested: {
    // Only the first level is reactive
    data: { /* Large data structure */ }
  }
})

Computed Property Caching Strategy

Computed properties cache results by default but recalculate when dependencies change. Avoid expensive operations in computed properties:

// Inefficient implementation
computed: {
  expensiveCalculation() {
    // Performs O(n^2) operation on every access
    return this.items.reduce((acc, item) => {
      return acc + this.otherArray.find(o => o.id === item.id).value
    }, 0)
  }
}

// Optimized solution
computed: {
  itemMap() {
    // First create a lookup table
    return this.otherArray.reduce((map, item) => {
      map[item.id] = item.value
      return map
    }, {})
  },
  expensiveCalculation() {
    // Reduces complexity to O(n)
    return this.items.reduce((acc, item) => {
      return acc + (this.itemMap[item.id] || 0)
    }, 0)
  }
}

For rarely changing but frequently accessed data, manually control caching:

import { computed } from 'vue'

let cache = null
let lastDeps = null

function cachedComputed(getter) {
  return computed(() => {
    const newDeps = getter()
    if (!shallowEqual(newDeps, lastDeps)) {
      cache = expensiveOperation(newDeps)
      lastDeps = newDeps
    }
    return cache
  })
}

List Rendering Performance

When rendering large lists with v-for, always provide a key and use stable identifiers. Avoid using array indices as keys:

<!-- Inefficient approach -->
<ul>
  <li v-for="(item, index) in items" :key="index">
    {{ item.text }}
  </li>
</ul>

<!-- Optimized solution -->
<ul>
  <li v-for="item in items" :key="item.id">
    {{ item.text }}
  </li>
</ul>

For very long lists (1000+ items), use virtual scrolling:

import { VirtualScroller } from 'vue-virtual-scroller'

export default {
  components: { VirtualScroller },
  template: `
    <VirtualScroller
      :items="largeList"
      :item-size="50"
      key-field="id"
    >
      <template #default="{ item }">
        <div class="item">{{ item.content }}</div>
      </template>
    </VirtualScroller>
  `
}

Watcher Optimization

Avoid deep watching large objects; explicitly specify the property paths to watch:

// Not recommended
watch: {
  bigObject: {
    handler() { /* Logic */ },
    deep: true // Performance killer
  }
}

// Recommended approach
watch: {
  'bigObject.importantProp'(newVal) {
    // Only watch specific properties
  }
}

For high-frequency events (e.g., scrolling, mouse movement), use debouncing or throttling:

import { throttle } from 'lodash-es'

export default {
  methods: {
    handleScroll: throttle(function() {
      // Logic
    }, 100)
  },
  created() {
    window.addEventListener('scroll', this.handleScroll)
  },
  beforeUnmount() {
    window.removeEventListener('scroll', this.handleScroll)
  }
}

Component Optimization Strategies

Properly splitting components can narrow update scopes. Use v-once to mark static content:

<template>
  <div>
    <header v-once>
      <!-- Header that never updates -->
      <h1>{{ title }}</h1>
    </header>
    <main>
      <!-- Dynamic content -->
    </main>
  </div>
</template>

For purely presentational components, use the functional option or the Composition API's setup function:

// Functional component
export default {
  functional: true,
  props: ['item'],
  render(ctx) {
    return h('div', ctx.item.text)
  }
}

// Composition API optimization
import { h } from 'vue'
export default {
  props: ['item'],
  setup(props) {
    return () => h('div', props.item.text)
  }
}

Reactive Update Batching

Vue batches synchronous reactive updates by default. For asynchronous operations, manually control update timing:

import { nextTick } from 'vue'

async function updateMultiple() {
  // These updates will be batched
  state.a = 1
  state.b = 2
  state.c = 3

  await nextTick()
  // DOM updates are complete
}

In extreme performance-sensitive scenarios, use flush: 'post' to ensure watchers execute after rendering:

watch(
  () => state.someValue,
  () => {
    // Execute after DOM updates
  },
  { flush: 'post' }
)

Memory Management

Clean up unused reactive data promptly, especially global state:

import { effectScope } from 'vue'

const scope = effectScope()

scope.run(() => {
  const state = reactive({ /* ... */ })
  // Related computed properties/watchers
})

// When the component unmounts
scope.stop() // Automatically cleans up all reactive effects

Avoid holding reactive references in closures long-term:

// Potential memory leak
let cachedState
export function useLeakyState() {
  cachedState = reactive({ data: null })
  return cachedState
}

// Safe usage
export function useSafeState() {
  const state = reactive({ data: null })
  onUnmounted(() => {
    // Cleanup
  })
  return state
}

Compile-Time Optimizations

Leverage Vue 3's compile-time optimizations. When static content in templates exceeds a certain ratio, Vue automatically hoists static nodes:

// Static content in templates will be hoisted
<template>
  <div>
    <h1>Static Title</h1>  <!-- Hoisted -->
    <p>{{ dynamicContent }}</p>
  </div>
</template>

Manually mark props that won't change:

<MyComponent
  :static-prop="42"
  v-bind="{ 
    'static-attr': 'value',
    [dynamicAttr]: dynamicValue 
  }"
/>

Performance Monitoring Tools

Use the Performance tab in Vue Devtools to analyze component rendering times. Programmatically measure specific operations:

import { performance } from 'perf_hooks'

function measure(fn) {
  const start = performance.now()
  const result = fn()
  console.log(`Time taken: ${performance.now() - start}ms`)
  return result
}

// Usage example
measure(() => {
  app.mount('#app')
})

For production environments, use window.performance.mark to record critical timings:

export function trackTiming(name) {
  if (process.env.NODE_ENV === 'production') {
    performance.mark(`${name}-start`)
    return () => {
      performance.mark(`${name}-end`)
      performance.measure(name, `${name}-start`, `${name}-end`)
    }
  }
  return () => {}
}

// Usage example
const endTracking = trackTiming('data-loading')
loadData().finally(endTracking)

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱: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 ☕.