跪拜 Guibai
← All articles
Vue.js · JavaScript · Frontend

Vue 3's v-for: Common Patterns, Eight Traps, and the Key Mistakes That Break Lists

By Tony费 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

A list that renders the wrong row or an input that carries another row's value is usually a v-for misuse, not a Vue bug. The concrete consequence is expensive rework in dynamic forms, draggable lists, and async data screens, where these eight patterns prevent the worst of it.

Summary

When Vue 3 developers render lists, v-for is the workhorse: arrays with (item, index), objects with (value, key, index), plain numbers for fixed counts, template wrappers that add no extra DOM, and custom components that still require a key. Filtering belongs in computed properties, not inside the template loop, so the render path stays cheap and the template stays readable.

The main value of this collection is its catalog of eight failure modes. v-for outranks v-if on the same element, so the official advice is to keep them apart, usually by moving v-if to an inner template or pre-filtering with computed. Binding key to index breaks once items are inserted, deleted, or dragged, because Vue's diff reuses the wrong DOM nodes and form inputs end up holding values from the wrong rows. Direct index assignment on a reactive array may not trigger an update; splice or a fresh array reference works. Duplicate keys produce warnings and random corruption.

Remaining traps live in interactive and async flows. A string ref on a looped component captures only the last instance; function refs collect them all. Sharing one v-model variable across looped inputs makes every row type in sync, so each input should bind to its own item's property. Async lists initialized to null throw a Symbol.iterator runtime error, and the fix is an empty array default plus an empty-state branch. Adding a brand-new property to a reactive object can silently fail to update the view; replacing the object reference restores the update.

Takeaways
— v-for accepts arrays with (item, index), objects with (value, key, index), numbers that start at 1, and template tags that group multiple elements without adding a wrapper DOM node.
— Vue 3 gives v-for higher priority than v-if on the same element, so the official docs recommend keeping them separate, either by putting v-if on a child element or by pre-filtering with a computed property.
— Binding :key to index breaks when items are inserted, deleted, or reordered; Vue's diff reuses wrong DOM nodes, and input values can end up attached to the wrong rows. A stable business ID fixes it.
— Directly assigning list[0] = value on a reactive array can silently fail to update the view; list.splice(0, 1, value) or replacing the whole array reference updates reliably.
— Duplicate keys inside one v-for loop trigger a console warning and can corrupt rendering in ways that are hard to reproduce.
— A string ref on a v-for component only returns the last instance; a function ref that pushes each element into an array collects all instances.
— Binding the same v-model variable to every input inside a loop makes all inputs share one value; binding to each item's property keeps rows independent.
— v-for over a null or undefined async list throws a TypeError because the value has no iterator; initializing the list to an empty array avoids the crash.
— Adding a new property directly to a reactive object may not trigger view updates in some scenarios; Object.assign with a new object or a spread-and-reassign approach does.
— Filtering a list with computed instead of v-if inside v-for keeps templates simple and avoids rerunning filter logic on every render.
Conclusions

Proxy-based reactivity is better than Vue 2's, but it has not removed the need for discipline: direct index writes to an array can still miss updates, so teams have to standardize on splice or reference replacement.

The input cross-fill bug from index keys is not a cosmetic rendering issue; editable lists and dynamic forms silently attach UI state to the wrong data, which is far harder to catch than a crashed render.

Vue's official warning against v-for with v-if is clear, but it remains the number one pitfall in practice, so linter rules or code review would do more than another doc paragraph.

The null-iterator crash from async data is trivial to avoid with an empty array default, yet it keeps happening because developers mirror backend nulls instead of modeling pending state as empty.

Concepts & terms
key (in v-for)
A unique identifier Vue uses to match each rendered DOM node to a list item during updates. A stable business ID keeps component state and DOM order in sync; using array index as key causes wrong node reuse when items are inserted, removed, or reordered.
Function ref
A Vue 3 ref that is a callback instead of a string name. In v-for loops, a string ref holds only the last matched instance, while a function ref receives each element or component as it mounts and can collect them into an array.
Symbol.iterator error
The runtime error thrown when v-for tries to iterate over null or undefined, since those values have no default iterator. Initializing async lists to an empty array prevents it.
computed property
A Vue reactive getter that caches its result and recomputes only when its dependencies change. Filtering a list inside computed keeps templates clean and avoids repeating the filter on every render.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗