In Vue 3, how does v-memo let a large v-for list skip update work, and what breaks when its dependency array is incomplete?
answer
- fixed-length array of values
- all equal means reuse
- skips vnode creation too
- same element as v-for
basics
~10 sv-memo takes a fixed-length array; if every value equals last render's, Vue reuses the cached subtree, skipping vnode creation and diffing. Leave out a value the subtree displays and that part silently stops updating.
solid answer
~40 s`v-memo` (Vue 3.2+) memoizes a template subtree against a fixed-length array of values. On each re-render Vue compares every entry with the previous render's; if none changed, it reuses the cached vnode, so even vnode creation is skipped for that subtree, not just the DOM patch. On a `v-for` list it goes on the same element as `v-for` - it does not work on an element nested inside the loop - and the item's `:key` is included implicitly. The contract is yours: every value the subtree renders must be in the array. Omit one and changes to it are never shown. Entries are compared by identity, so listing a mutable object such as `row` hides in-place mutations too. The docs frame it as a rare micro-optimization, mainly for lists over about 1,000 items.
code
vue · 26 lines<script setup lang="ts">
import { ref } from 'vue'
interface Order {
id: number
status: 'open' | 'shipped'
total: number
assignee: string
}
const orders = ref<Order[]>([])
const highlightedId = ref<number | null>(null)
</script>
<template>
<ul>
<li
v-for="order in orders"
:key="order.id"
v-memo="[order.status, order.total, order.assignee, order.id === highlightedId]"
:class="{ highlighted: order.id === highlightedId }"
>
#{{ order.id }} {{ order.status }} {{ order.total }} ({{ order.assignee }})
</li>
</ul>
</template>go deeper
Recall that v-memo takes an array of values and skips updating a subtree while those values are unchanged.
Explain the identity comparison, the reuse of the cached vnode, and the placement rule on the v-for element.
Treat the array as a contract: audit it for every rendered value, prefer primitives, and profile before and after adding it.
Limit v-memo to measured hot spots, since each use is a manual dependency list that can drift silently from the template.
## What v-memo is `v-memo` is a built-in directive, available since **Vue 3.2**, that memoizes part of a template. It expects a **fixed-length array** of dependency values: ```html <div v-memo="[valueA, valueB]">...</div> ``` When the component re-renders and both values are the same as last time, Vue skips **all** updates for that element and its children. The API reference stresses that even **VNode creation** is skipped, because the memoized copy of the subtree is reused. ## How the comparison works 1. On the first render, Vue renders the subtree and stores the vnode together with the memo array. 2. On the next render, before building the subtree, Vue compares the new array with the stored one: a different length counts as changed, and each entry is compared by identity with `Object.is` semantics. 3. If nothing differs, the cached vnode is returned as-is and the renderer does not diff it. 4. If anything differs, the subtree is rendered and diffed normally, and the new array is stored. ## Using it on a v-for list The common case is a long `v-for` list where a change touches few rows: - Put `v-memo` on **the same element** as `v-for`. The API docs warn that `v-memo` does **not** work inside `v-for`, on a nested element. - You do not need to list the item's key: Vue infers it from `:key`. - List every value the row renders: `v-memo="[order.status, order.total, order.assignee]"`. ## What breaks when the array is incomplete | Mistake | Symptom | |---|---| | a rendered field is missing from the array | that field shows its old value until something listed changes | | a mutable object listed, e.g. `v-memo="[row]"` | in-place mutations are invisible, since the reference is the same | | the array length varies between renders | any render where the length differs counts as changed | | `v-memo` placed on a child of the `v-for` element | the memo does not work as intended; the docs say it does not work inside `v-for` | The failure mode is **silent staleness**: no warning, just a row that stops reflecting its data. ## When it is worth it - The Vue docs call it a **micro-optimization for performance-critical scenarios** that should rarely be needed, typically large lists of more than about 1,000 items. - Try **stable props** first: passing a primitive such as `:selected="row.id === selectedId"` to row components already lets unaffected rows skip their update. `v-memo` additionally skips vnode creation in the parent, which matters when the rows are plain elements or very numerous. - It can also be put on a component to prevent updates where the child update check has been de-optimized, with the same responsibility for a correct array. - `v-memo="[]"` never changes, so it behaves like `v-once`. ## What exactly is skipped It helps to be precise about the cost model, because interviewers probe it: - **Skipped**: evaluating the row's template expressions, creating its vnodes, and diffing them against the previous vnodes - for every row whose memo array is unchanged. - **Still done**: evaluating the memo array itself for every row on every parent render, and iterating the `v-for` source. - **Not affected**: child components inside a memoized row keep their own render effects, so state they read themselves still updates them. That is why `v-memo` helps most when rows are made of many plain elements: the saving is proportional to the vnode work in each skipped row, while the overhead is one small array per row. ## Review checklist - Does the array list every reactive value the subtree displays, including class and style inputs? - Are entries primitives or references that change when the content changes? - Is it on the `v-for` element itself? - Did a profile show the list's re-render cost before it was added?
- Why does v-memo="[row]" fail when row.status is mutated in place?Memo entries are compared by identity. Mutating a field of the same object leaves the reference unchanged, so the array compares equal and Vue reuses the cached row. List the primitive fields the row renders instead, or replace the object when it changes.
- What does v-memo save that a stable :selected prop on a row component does not?A stable prop lets each unaffected row component skip its own update, but the parent still creates a vnode for every row and runs the props check on each. `v-memo` on the `v-for` element reuses the cached vnode for unchanged rows, skipping that creation and the diff.
saying these in an interview costs you the question
- v-memo tracks every value the row reads, so the array is only a hint.
- Listing the row object in v-memo catches any change to its fields.
- v-memo works the same when placed on an element inside the v-for.
- v-memo still builds the row's vnodes and only skips the DOM patch.
- v-memo should be added to every v-for list by default.