We use cookies to enhance your experience on the site
CodeWorlds

Performance and Composable Optimization

At NOVA LAB station every watt of energy counts - likewise in Vue applications, every unnecessary render and computation is a waste of performance. Let's explore composable optimization techniques.

shallowRef - Shallow Reactivity

ref()
creates deep reactivity - any change inside an object triggers an update.
shallowRef()
reacts only to a change of the entire value, not internal properties.

1import { shallowRef, triggerRef } from 'vue'
2
3// Large data list - shallowRef is more efficient
4const sensorReadings = shallowRef([])
5
6// This will NOT trigger an update (shallow reactivity)
7sensorReadings.value.push({ id: 1, temp: 22 })
8
9// This will trigger an update - assigning a new array
10sensorReadings.value = [...sensorReadings.value, { id: 1, temp: 22 }]
11
12// Alternatively: manually trigger an update
13sensorReadings.value.push({ id: 2, temp: 23 })
14triggerRef(sensorReadings) // Force update
1<script setup>
2import { shallowRef, triggerRef, computed } from 'vue'
3
4// Efficient composable for large datasets
5function useLargeDataset() {
6  const items = shallowRef([])
7  const count = computed(() => items.value.length)
8
9  function addItem(item) {
10    // Create a new array - triggers reactivity
11    items.value = [...items.value, item]
12  }
13
14  function addMany(newItems) {
15    // One update instead of N
16    items.value = [...items.value, ...newItems]
17  }
18
19  function removeItem(id) {
20    items.value = items.value.filter(item => item.id !== id)
21  }
22
23  function updateItem(id, updates) {
24    items.value = items.value.map(item =>
25      item.id === id ? { ...item, ...updates } : item
26    )
27  }
28
29  return { items, count, addItem, addMany, removeItem, updateItem }
30}
31</script>

When to use

shallowRef
:

  • Large arrays (100+ elements)
  • Objects with many nested levels
  • Data that changes rarely or entirely

Computed Caching

Computed properties are automatically cached - they recalculate only when their dependencies change. This is a key optimization.

1import { ref, computed } from 'vue'
2
3function useFilteredExperiments() {
4  const experiments = ref([])
5  const searchQuery = ref('')
6  const category = ref('all')
7
8  // GOOD: computed is cached
9  // Recalculates ONLY when experiments, searchQuery or category changes
10  const filtered = computed(() => {
11    console.log('Recalculating filtered experiments') // logs rarely
12    let result = experiments.value
13
14    if (searchQuery.value) {
15      const q = searchQuery.value.toLowerCase()
16      result = result.filter(e =>
17        e.name.toLowerCase().includes(q)
18      )
19    }
20
21    if (category.value !== 'all') {
22      result = result.filter(e => e.category === category.value)
23    }
24
25    return result
26  })
27
28  // BAD: function recalculates on every render
29  function getFiltered() {
30    console.log('Recalculating every time!') // logs frequently
31    return experiments.value.filter(...)
32  }
33
34  // GOOD: chaining computed in a pipeline
35  const stats = computed(() => ({
36    total: filtered.value.length,
37    active: filtered.value.filter(e => e.status === 'active').length,
38    completed: filtered.value.filter(e => e.status === 'completed').length
39  }))
40
41  return { experiments, searchQuery, category, filtered, stats }
42}

watchEffect vs watch - Performance

watch
and
watchEffect
differ not only in API but also in performance.

1import { ref, watch, watchEffect } from 'vue'
2
3const temperature = ref(22)
4const pressure = ref(101)
5const humidity = ref(45)
6
7// watchEffect - tracks ALL reactive values used
8// Fires on change of temperature, pressure OR humidity
9watchEffect(() => {
10  console.log('watchEffect:', temperature.value)
11  // Even if you use pressure only in a condition,
12  // watchEffect tracks it ALWAYS
13  if (temperature.value > 30) {
14    console.log('Pressure:', pressure.value)
15  }
16})
17
18// watch - tracks ONLY the specified source
19// Fires ONLY on temperature change
20watch(temperature, (newTemp) => {
21  console.log('watch:', newTemp)
22  // You can read other refs without creating dependencies
23  if (newTemp > 30) {
24    console.log('Pressure:', pressure.value)
25  }
26})

Rule: use

watch
when you need to react to specific changes. Use
watchEffect
when you want automatic tracking of multiple dependencies.

Memory Leaks in Composables

One of the most common problems - uncleaned resources cause memory leaks.

1import { ref, onMounted, onUnmounted, watch } from 'vue'
2
3// BAD - memory leak!
4function useLeakyTimer() {
5  const seconds = ref(0)
6
7  onMounted(() => {
8    setInterval(() => { // Never cleaned up!
9      seconds.value++
10    }, 1000)
11  })
12
13  return { seconds }
14}
15
16// GOOD - proper cleanup
17function useSafeTimer() {
18  const seconds = ref(0)
19  const isRunning = ref(false)
20  let intervalId = null
21
22  function start() {
23    if (isRunning.value) return
24    isRunning.value = true
25    intervalId = setInterval(() => {
26      seconds.value++
27    }, 1000)
28  }
29
30  function stop() {
31    if (!isRunning.value) return
32    isRunning.value = false
33    clearInterval(intervalId)
34    intervalId = null
35  }
36
37  // Always clean up in onUnmounted
38  onUnmounted(() => {
39    if (intervalId) {
40      clearInterval(intervalId)
41    }
42  })
43
44  return { seconds, isRunning, start, stop }
45}

Composable Performance Checklist

| Technique | When to use | |----------|-------------| |

shallowRef
| Large arrays/objects, infrequent updates | |
computed
instead of functions | Value depends on reactive state | |
watch
instead of
watchEffect
| Specific source to observe | | Cleanup in
onUnmounted
| Timers, event listeners, WebSocket | | Batch updates | Multiple changes at once instead of one by one | |
triggerRef
| Mutating shallowRef without creating a copy |

1// Batch updates - one update instead of three
2import { ref } from 'vue'
3
4function useBatchUpdate() {
5  const data = ref({ temp: 0, pressure: 0, humidity: 0 })
6
7  // BAD: 3 separate updates
8  function updateBad(temp, pressure, humidity) {
9    data.value.temp = temp       // render
10    data.value.pressure = pressure // render
11    data.value.humidity = humidity // render
12  }
13
14  // GOOD: 1 update (new object)
15  function updateGood(temp, pressure, humidity) {
16    data.value = { temp, pressure, humidity } // 1 render
17  }
18
19  return { data, updateGood }
20}
Go to CodeWorlds