In Vue 3 compiled templates, what does a patch flag like 2 /* CLASS */ mean, and how does the renderer use it?
answer
- last argument of createElementVNode
- bitmask, combined with OR
- PROPS lists names in dynamicProps
- bitwise AND in patchElement
- negative values are markers
basics
~20 sA patch flag is a bitmask the compiler adds to a dynamic vnode saying which parts can change: TEXT, CLASS, STYLE, PROPS and so on. When patching, the renderer checks those bits and updates only the flagged aspects, skipping static attributes entirely.
solid answer
~40 sThe template compiler passes a number from Vue's `PatchFlags` enum as the last argument of calls like `createElementVNode`, for example `2 /* CLASS */`. The values are bits, so a dynamic class plus dynamic text gives `3`. In `patchElement` the renderer tests them with bitwise AND: `CLASS` updates the class, `STYLE` patches the style, `PROPS` loops only over the names in the vnode's `dynamicProps` array, and `TEXT` sets the element text. `FULL_PROPS` (16), emitted for dynamic keys such as `v-bind="obj"`, forces a full props diff. Negative values are markers rather than bits: `-1 CACHED` for static content and `-2 BAIL` to leave optimized mode. A vnode with flag 0 outside optimized mode, such as one built with `h()`, gets a full props diff.
code
ts · 8 lines// Compiled from:
// <input :id="id" :value="value" class="field" :class="{ invalid }">
// (abridged; exact identifiers vary with the compile mode)
_createElementVNode('input', {
id: _ctx.id,
value: _ctx.value,
class: _normalizeClass(['field', { invalid: _ctx.invalid }])
}, null, 10 /* CLASS, PROPS */, ['id', 'value'])go deeper
Recall that the compiler tags dynamic vnodes with numbers that say what can change, like text or class.
Explain the bitmask values, how flags combine, the dynamicProps list for PROPS, and why dynamic keys force FULL_PROPS.
Read compiled output to find elements that fall back to FULL_PROPS or full diffs, and judge whether that matters on a hot path.
Use compiled-output reviews as a tool for performance-sensitive component design guidance, without making flag-tuning a default practice.
## What a patch flag is When Vue's template compiler generates code for an element with dynamic bindings, it adds a **patch flag**: a number passed as an extra argument to the vnode creation call, telling the renderer exactly which parts of that element can change. ```js createElementVNode("div", { class: _normalizeClass({ active: _ctx.active }) }, null, 2 /* CLASS */) ``` The comment after the number is emitted in development builds for readability; the number is what the runtime reads. The values come from Vue's `PatchFlags` enum and are **bit flags**, so one element with several kinds of dynamic binding gets them combined with a bitwise OR: a `<p>` with a dynamic class and interpolated text gets `3` (`TEXT | CLASS`). ## The main positive flags | Flag | Value | Emitted when | |---|---|---| | `TEXT` | 1 | the element's only children are dynamic text | | `CLASS` | 2 | `class` is bound dynamically | | `STYLE` | 4 | `style` is bound dynamically | | `PROPS` | 8 | other props are bound; their names go in a `dynamicProps` array | | `FULL_PROPS` | 16 | prop keys are dynamic, such as `v-bind="obj"` or `:[name]="x"` | | `NEED_HYDRATION` | 32 | the element needs props handled during hydration, such as event listeners | | `STABLE_FRAGMENT` | 64 | a fragment whose children never change order | | `KEYED_FRAGMENT` / `UNKEYED_FRAGMENT` | 128 / 256 | a `v-for` fragment with or without keys | | `NEED_PATCH` | 512 | a non-prop patch is needed, such as a `ref`, a directive or a vnode hook | | `DYNAMIC_SLOTS` | 1024 | a component's slots reference `v-for` values or use dynamic names; such components are always force-updated | Two **negative** values are special markers, not bits: `CACHED` (-1) marks cached static content, and `BAIL` (-2) tells the renderer to leave optimized mode for that subtree. ## How the renderer uses them When `patchElement` updates an element whose patch flag is greater than zero, it trusts that the old and new vnodes came from the same position in the same template and takes a fast path: 1. If `FULL_PROPS` is set, it diffs all props, because keys themselves may have changed. 2. Otherwise it checks the bits one by one: `CLASS` updates the class if it changed, `STYLE` patches the style, and `PROPS` loops over **only** the names listed in `dynamicProps` (`value` is always re-applied, so form inputs stay in sync). 3. If `TEXT` is set and the text differs, it sets the element's text directly. Each check is a bitwise AND such as `patchFlag & PatchFlags.CLASS`, which is extremely cheap. Everything not flagged, such as static attributes, is never compared. If the patch flag is `0` and the renderer is not in optimized mode, which is the case for hand-written `h()` vnodes, it does a full props diff instead. ## Reading flags in practice - Open the **Vue Template Explorer** or the SFC Playground's JS tab and hover your template: every dynamic node shows its number and flag names. - A surprising `16 /* FULL_PROPS */` often comes from `v-bind="$attrs"` or an object spread; it is correct, just a slower path for that element. - An element with only static attributes and static children gets no flag at all and is usually cached instead. ## Common misreadings - The flag is not a priority or a render count; it is a description of *what kind* of change is possible. - Flags are not only for elements. Component vnodes carry them too, and the renderer reads them when deciding whether a child component needs updating. ## Flags on component vnodes When a parent re-renders, the renderer has to decide whether each child component should update. With an optimized, compiler-generated vnode it reads the child vnode's flag: - `DYNAMIC_SLOTS` means the slot content may differ, so the child is always updated; - `FULL_PROPS` means the whole props object is compared; - `PROPS` means only the names in `dynamicProps` are compared, so an unchanged value lets the child skip its re-render. `CLASS` and `STYLE` are not used on components: bound `class` and `style` are ordinary props there. A child component also re-renders on its own whenever reactive state that its render function reads changes, independently of any parent flag.
- Why does v-bind="obj" produce FULL_PROPS instead of PROPS?With an object spread the compiler cannot know which keys will exist on the next render, so it cannot write a `dynamicProps` list. A key present last time may be gone now and must be removed from the element. `FULL_PROPS` tells the renderer to compare the whole old and new props objects for that element.
- What does NEED_PATCH (512) mean on an element that has no dynamic bindings?The element still needs the patch step for something that is not a prop: a template `ref`, a custom directive, or a vnode hook. The flag makes the renderer process the element on updates so the ref is set or the directive's `updated` hook runs, even though no attribute changed.
saying these in an interview costs you the question
- Thinks patch flags rank how urgently a node should update
- Believes one element can carry only one patch flag
- Says PROPS makes the renderer compare every attribute
- Assumes h() calls compute patch flags from their props
- Reads -1 as a bit flag that combines with others