skip to content

In Vue 3 compiled templates, what does a patch flag like 2 /* CLASS */ mean, and how does the renderer use it?

level: middleimportance: should knowfreq 36%

answer

  1. last argument of createElementVNode
  2. bitmask, combined with OR
  3. PROPS lists names in dynamicProps
  4. bitwise AND in patchElement
  5. negative values are markers

basics

~20 s

A 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 s

The 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
ts
// 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

for a junior

Recall that the compiler tags dynamic vnodes with numbers that say what can change, like text or class.

for a middle

Explain the bitmask values, how flags combine, the dynamicProps list for PROPS, and why dynamic keys force FULL_PROPS.

for a senior

Read compiled output to find elements that fall back to FULL_PROPS or full diffs, and judge whether that matters on a hot path.

for a principal

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