Chapter 78: Vue v-memo Directive
V-memo directive
Introduced in Vue 3.2 (October 2021) and still underappreciated in 2026, v-memo is the Vue equivalent of React’s React.memo + useMemo combined, but in a much more surgical, template-level way.
It is not a general-purpose tool — it is a very targeted scalpel for solving one specific kind of performance problem:
“This part of my template is extremely expensive to re-render, but it almost never actually needs to change — even when the parent component re-renders 50 times per second.”
1. The Problem v-memo Solves (Real-World Pain)
Imagine you have a component like this:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<template> <div> <!-- This chart re-renders every time count changes --> <HeavyChart :data="chartData" :options="chartOptions" /> <!-- This input causes the whole component to re-render 50× per second --> <input v-model="liveSearch" placeholder="Live search..." /> <p>Search: {{ liveSearch }}</p> <button @click="count++">Count: {{ count }}</button> </div> </template> |
Every keystroke in the input → whole component re-renders → <HeavyChart> gets destroyed and re-created → very expensive (maybe 100–300 ms lag, jank, memory spikes).
Without optimization → users feel lag even though the chart data didn’t change at all.
With v-memo → you can tell Vue:
“Only re-render this <HeavyChart> if the value I care about actually changed.”
2. Syntax & Core Rule (Memorize This)
|
0 1 2 3 4 5 6 7 8 9 10 |
<HeavyChart v-memo="[chartData, chartOptions]" :data="chartData" :options="chartOptions" /> |
The rule:
- v-memo takes an array of dependencies (just like watch or computed)
- Vue skips re-rendering the element (and its entire subtree) unless at least one item in the array changes (using Object.is comparison)
- If any dependency changes → normal re-render happens
If the array is empty (v-memo=”[]”), the element is never updated again after first render (like v-once but stronger).
3. Real, Practical Example – Live Search + Static Chart
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
<template> <div class="dashboard"> <h2>Live Search + Static Chart</h2> <!-- Very expensive chart – we don't want to re-render it on every keystroke --> <div v-memo="[chartData, theme]"> <HeavyChart :data="chartData" :theme="theme" :width="600" :height="400" /> </div> <!-- This input changes very frequently --> <input v-model.trim="searchQuery" placeholder="Live filter products..." class="search-input" /> <ul> <li v-for="product in filteredProducts" :key="product.id"> {{ product.name }} </li> </ul> <p>Keystrokes counter (proves parent re-renders): {{ keystrokeCount }}</p> </div> </template> <script setup lang="ts"> import { ref, computed, watchEffect } from 'vue' // Fake heavy chart data (imagine this is 10 000 points) const chartData = ref(Array.from({ length: 10000 }, (_, i) => ({ x: i, y: Math.sin(i / 100) * 100 + Math.random() * 20 }))) const theme = ref<'light' | 'dark'>('light') // Live search – changes constantly const searchQuery = ref('') const keystrokeCount = ref(0) watchEffect(() => { // Every keystroke increases counter (proves parent re-renders) searchQuery.value keystrokeCount.value++ }) // Fake product list const products = ref([ { id: 1, name: 'Vue Course' }, { id: 2, name: 'TypeScript Pro' }, { id: 3, name: 'Tailwind Kit' }, // ... ]) const filteredProducts = computed(() => { if (!searchQuery.value.trim()) return products.value const q = searchQuery.value.toLowerCase() return products.value.filter(p => p.name.toLowerCase().includes(q)) }) </script> <style scoped> .dashboard { max-width: 900px; margin: 3rem auto; padding: 2rem; } .search-input { width: 100%; padding: 0.9rem; font-size: 1.1rem; margin: 1.5rem 0; } </style> |
What happens without v-memo
- Every keystroke → whole component re-renders → <HeavyChart> gets destroyed & re-created
- Chart library re-initializes, re-draws 10 000 points → lag, jank, high CPU
With v-memo=”[chartData, theme]”
- chartData and theme almost never change → chart is skipped on re-renders
- Only the search input + list re-render → very fast, no jank
- Chart stays smooth and responsive even during rapid typing
4. Key Rules & Gotchas (2026 Must-Know)
| Rule / Gotcha | Correct Behavior / Best Practice |
|---|---|
| v-memo only works on one element | It memoizes that element + its entire subtree |
| Dependencies array is compared with Object.is | Primitive values → fine Objects/arrays → only reference change matters (not deep) |
| Empty array v-memo=”[]” | Element is rendered once and never updated again (stronger than v-once) |
| Dependencies change → full re-render | No way to “partially” re-render — it’s all or nothing |
| Works with components? | Yes — memoizes the component instance (prevents re-mount) |
| Works with <Transition>? | Yes — but be careful: memoized element skips enter/leave hooks |
| TypeScript? | No special typing — just array of any values |
5. When to Use v-memo (Realistic Checklist)
Use v-memo when at least 3 of these are true:
- The element/component is very expensive to render (large chart, complex SVG, 1000+ items virtual-scroller child, heavy canvas)
- Its props/data rarely change compared to how often parent re-renders
- You already tried v-once but need occasional updates
- You see performance issues in Vue Devtools → “render time > 50 ms” on parent
- You want to keep expensive component mounted (no unmount/remount cost)
Do NOT use v-memo when:
- The component is cheap/lightweight
- Props change almost as often as parent re-renders
- You’re trying to “optimize early” without measuring first
Pro Tips from Real Projects (Hyderabad 2026)
- Measure first — use Vue Devtools Performance tab → only memoize hot paths
- Prefer memoizing components (<HeavyChart v-memo=”[data]”>) over wrapping <div>
- Use empty arrayv-memo=”[]” for truly static parts (like static SVG icons, copyright footer)
- Combine with <KeepAlive> for cached tabs that contain expensive memoized charts
- Test on low-end mobile — memoization wins are much more visible there
Your mini practice task:
- Create a fake “HeavyChart” component (just a big <div> with “Rendering 10 000 points…” text)
- Add rapid-changing input (live search)
- Wrap chart in v-memo=”[staticData]” → type fast → see no lag
- Remove v-memo → type fast → feel the lag (or watch Devtools render time)
Any part confusing? Want full examples for:
- v-memo + <Transition> + list item?
- v-memo on custom component vs native element?
- Real Chart.js / ApexCharts with v-memo?
- v-memo vs v-once vs <KeepAlive> comparison?
Just tell me — we’ll optimize the next expensive component together 🚀
