Skip to content

The v-once Directive

Consider a component like the following.

vue
<script setup>
import { ref } from "vue";
const count = ref(0);
</script>

<template>
  <p v-once>{{ count }}</p>
</template>

Compilation Result and Overview

The compilation result is as follows.

js
const _sfc_main = {
  vapor: true,
  __name: "App",
  setup(__props, { expose: __expose }) {
    __expose();

    const count = ref(0);

    const __returned__ = { count, ref };
    Object.defineProperty(__returned__, "__isScriptSetup", {
      enumerable: false,
      value: true,
    });
    return __returned__;
  },
};

import { setText as _setText, template as _template } from "vue/vapor";

const t0 = _template("<p></p>");

function _sfc_render(_ctx) {
  const n0 = t0();
  _setText(n0, _ctx.count);
  return n0;
}

What stands out is that the setText part is not wrapped in renderEffect.
Since v-once is rendered only once, there is no need to wrap it in renderEffect.

Reading the Compiler

We will follow transformElement -> buildProps -> transformProps -> directiveTransform -> transformVOnce.

It's very simple, so I'll include the entire text.

1import { NodeTypes, findDir } from '@vue/compiler-dom'
2import type { NodeTransform } from '../transform'
3
4export const transformVOnce: NodeTransform = (node, context) => {
5  if (
6    // !context.inSSR &&
7    node.type === NodeTypes.ELEMENT &&
8    findDir(node, 'once', true)
9  ) {
10    context.inVOnce = true
11  }
12}

It simply enables the inVOnce flag that context holds.

When inVOnce is true, it calls registerOperation with registerEffect and finishes, meaning no effect is generated.

137  registerEffect(
138    expressions: SimpleExpressionNode[],
139    ...operations: OperationNode[]
140  ): void {
141    expressions = expressions.filter(exp => !isConstantExpression(exp))
142    if (this.inVOnce || expressions.length === 0) {
143      return this.registerOperation(...operations)
144    }

Since there is nothing particularly to read in the runtime, this concludes it for now.