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

Inside React's key: where it lives and how reconciliation actually uses it

By 古茗前端团队 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Misunderstanding React's key leads to subtle state and DOM bugs in lists, especially with inputs, animations, or reorderable data. Knowing that key is stored on the Fiber and that React compares key and type lets you pick stable identifiers and predict exactly when components will be reused or recreated.

Summary

React's key lives on the ReactElement and then on the Fiber, never on the actual DOM node or in props. When a list renders, React uses key to match old and new nodes during reconciliation: first compare key, then type. If both match, the old Fiber is reused and state is preserved. If the key matches but the type differs, React recreates the node and deletes the old one.

Multi-node reconciliation runs in three phases: a left-to-right scan that stops at the first key mismatch, a check for whether either side is exhausted, and a Map-based fallback for remaining nodes. The `lastPlacedIndex` heuristic decides which nodes need moving, but it does not guarantee minimal moves: reordering A B C D to D A B C marks A, B, and C as moves instead of just moving D.

For choosing keys, stable business IDs beat array indices. Because an absent key becomes null (not the index), React falls back to positional matching, which can couple a component's state to a slot rather than to the data. Composite keys like `${tenantId}:${id}` solve collisions when IDs are only unique within a scope. Index is acceptable only for static, fixed-order lists where the data-to-position correspondence never changes.

Takeaways
— Key is a reserved field, not a regular prop; reading props.key in a component triggers a warning.
— Key is stored on ReactElement and then copied onto the Fiber, and never appears in the DOM.
— React normalizes key to a string when creating a ReactElement; if no key is provided, ReactElement.key is null, not the array index.
— In single-node diff, React searches old sibling Fibers for a matching key, then compares type: same key and type reuse the Fiber; same key but different type recreates and deletes the old one.
— Multi-node diff runs a left-to-right scan, then checks for exhaustion, then uses a Map of remaining old Fibers keyed by explicit key or index.
— The lastPlacedIndex heuristic marks a reusable node as needing Placement when its oldIndex is less than lastPlacedIndex.
— React's diff does not guarantee minimal moves: changing A B C D to D A B C marks A, B, and C as moves instead of moving only D.
— A good key is unique among siblings and stable across renders; business IDs from the backend are preferred over array indices.
— When an ID is unique only within a scope, combine fields like `${tenantId}:${id}` to form a composite key.
— Index as key is safe only for static lists where content and order never change; for lists with insertions, deletions, or reordering it can cause state mismatches.
Conclusions

React's left-to-right scan and lastPlacedIndex heuristic are pragmatic but not optimal; the D A B C example shows React moving three nodes when moving one would suffice, so developers should not assume React minimizes DOM moves.

Because key becomes null when absent, React's Map fallback uses index as the map key, which is why index-as-key works for stable lists but silently couples component state to list position in dynamic lists.

The article's emphasis on type matching after key means a unique key alone does not guarantee reuse; changing component type at the same key forces recreation, which is a common gotcha for developers who swap components without changing keys.

Concepts & terms
Fiber
A work-in-progress data structure in React that represents a unit of work. Each component instance and DOM node has a corresponding Fiber to track state, effects, and the tree of updates during rendering.
Reconciliation
The process by which React compares the previous render's Fiber tree with the new ReactElement tree to determine what DOM changes to make: reuse, update, move, insert, or delete nodes.
ReactElement
A plain object describing what should appear on screen. Created by JSX compilation, it carries type, props, and key, and is later consumed by React to produce Fibers and DOM updates.
alternate
A pointer on a Fiber that links it to its counterpart in the other tree (current or work-in-progress). A non-null alternate indicates the node was reused from the previous render.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗