Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 debugging reference for reactivity issues, computed errors, watcher bugs, async failures, and SSR hydration problems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/perf-computed-object-stability.md
1---2title: Return Stable Object References from Computed Properties3impact: MEDIUM4impactDescription: Computed properties returning new objects trigger effects even when values haven't meaningfully changed5type: efficiency6tags: [vue3, computed, performance, reactivity, vue3.4]7---89# Return Stable Object References from Computed Properties1011**Impact: MEDIUM** - In Vue 3.4+, computed properties only trigger effects when their value changes. However, if a computed returns a new object each time, Vue cannot detect that the values inside are the same. This causes unnecessary effect re-runs.1213For primitive values, Vue 3.4+ handles this automatically. For objects, manually compare and return the previous value when nothing meaningful has changed.1415## Task Checklist1617- [ ] For computed properties returning primitives, Vue 3.4+ handles stability automatically18- [ ] For computed properties returning objects, compare with previous value and return old reference if unchanged19- [ ] Always perform the full computation before comparing (to track dependencies correctly)20- [ ] Consider if you really need to return an object, or if primitives would suffice2122**Incorrect:**23```vue24<script setup>25import { ref, computed, watchEffect } from 'vue'2627const count = ref(0)2829// BAD: Returns new object every time, always triggers effects30const stats = computed(() => {31return {32isEven: count.value % 2 === 0,33doubleValue: count.value * 234}35})3637watchEffect(() => {38console.log('Stats changed:', stats.value)39// Logs on EVERY count change, even when isEven hasn't changed40// count: 0 -> 2 -> 4: isEven is always true, but effect runs each time41})42</script>43```4445**Correct:**46```vue47<script setup>48import { ref, computed, watchEffect } from 'vue'4950const count = ref(0)5152// GOOD (Vue 3.4+): Primitive computed - automatic stability53const isEven = computed(() => count.value % 2 === 0)5455watchEffect(() => {56console.log('isEven:', isEven.value)57// Only logs when isEven actually changes (0, 2, 4 won't re-trigger)58})5960// GOOD (Vue 3.4+): Manual comparison for object returns61const stats = computed((oldValue) => {62// Step 1: Always compute the new value first (to track dependencies)63const newValue = {64isEven: count.value % 2 === 0,65category: count.value < 10 ? 'small' : 'large'66}6768// Step 2: Compare with previous value69if (oldValue &&70oldValue.isEven === newValue.isEven &&71oldValue.category === newValue.category) {72return oldValue // Return old reference - no effect triggers73}7475return newValue76})7778watchEffect(() => {79console.log('Stats changed:', stats.value)80// Now only logs when isEven or category actually changes81})82</script>83```8485## Primitive vs Object Computed Behavior (Vue 3.4+)8687```javascript88import { ref, computed, watchEffect } from 'vue'8990const count = ref(0)9192// PRIMITIVE: Vue automatically detects value hasn't changed93const isEven = computed(() => count.value % 2 === 0)9495watchEffect(() => console.log(isEven.value)) // true9697count.value = 2 // isEven still true - NO log98count.value = 4 // isEven still true - NO log99count.value = 3 // isEven now false - logs: false100101// OBJECT: New reference every time (without manual comparison)102const obj = computed(() => ({ isEven: count.value % 2 === 0 }))103104watchEffect(() => console.log(obj.value)) // { isEven: true }105106count.value = 2 // Logs again! New object reference107count.value = 4 // Logs again! New object reference108```109110## Advanced: Deep Object Comparison111112```javascript113import { ref, computed } from 'vue'114import { isEqual } from 'lodash-es' // For deep comparison115116const filters = ref({ category: 'all', sortBy: 'date', page: 1 })117118// For complex objects, use deep comparison119const activeFilters = computed((oldValue) => {120const newValue = {121...filters.value,122hasFilters: filters.value.category !== 'all' || filters.value.sortBy !== 'date'123}124125// Deep compare for complex objects126if (oldValue && isEqual(oldValue, newValue)) {127return oldValue128}129130return newValue131})132```133134## Important: Always Compute Before Comparing135136```javascript137// BAD: Early return prevents dependency tracking138const optimized = computed((oldValue) => {139if (oldValue && someCondition) {140return oldValue // Dependencies not tracked!141}142return computeExpensiveValue()143})144145// GOOD: Compute first, then compare146const optimized = computed((oldValue) => {147const newValue = computeExpensiveValue() // Always track dependencies148if (oldValue && newValue === oldValue) {149return oldValue150}151return newValue152})153```154155## Reference156- [Vue.js Performance - Computed Stability](https://vuejs.org/guide/best-practices/performance.html#computed-stability)157- [Vue.js Computed Properties](https://vuejs.org/guide/essentials/computed.html)158