Skip to content

v-once ディレクティブ

以下のようなコンポーネントを考えます.

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

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

コンパイル結果と概要

コンパイル結果は以下のようになります.

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;
}

注目するべきは setText の部分が renderEffect でラップされていない点です.
v-once は一度だけ描画されるため,renderEffect でラップする必要がありません.

コンパイラを読む

transformElement -> buildProps -> transformProps -> directiveTransform -> transformVOnce と辿っていきます.

非常にシンプルなので全文載せてしまいます.

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}

context が持っている inVOnce というフラグを有効にしているだけです.

inVOnce の場合は registerEffectregisterOperation を呼び出して終了,ということになっていてエフェクトが生成されません.

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    }

ランタイムは特に読むところがないので今回はなんとこれでおしまいです.