Skip to content

v-bind ディレクティブ

さて,どんどん読み進めましょう.

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

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

<template>
  <div :data-dynamic="dynamicData">Hello, World!</div>
</template>

コンパイル結果と概要

コンパイル結果は以下のようになります. (だんだん小慣れてきて説明が雑になってきましたね (笑))

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

    const dynamicData = ref("a");

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

import {
  renderEffect as _renderEffect,
  setDynamicProp as _setDynamicProp,
  template as _template,
} from "vue/vapor";

const t0 = _template("<div>Hello, World!</div>");

function _sfc_render(_ctx) {
  const n0 = t0();
  _renderEffect(() => _setDynamicProp(n0, "data-dynamic", _ctx.dynamicData));
  return n0;
}

特に,

js
function _sfc_render(_ctx) {
  const n0 = t0();
  _renderEffect(() => _setDynamicProp(n0, "data-dynamic", _ctx.dynamicData));
  return n0;
}

_setDynamicProp の部分です.
流石にもう実装方法も予想がつくようになってきましたね.
概要的にも,n0 に対して data-dynamic という属性に _ctx.dynamicData を設定するエフェクトだ,というのがすぐにわかります.

コンパイラを読む

お馴染み,transformElement -> buildProps -> transformProps -> directiveTransform -> transformVBind と辿っていきます.

packages/compiler-vapor/src/transforms/vBind.ts

...と思いきや?
実はここには shorthand のハンドリングなど,本当に v-bind を transform しているだけで,エフェクトの登録などは行われていません.

実はここに関しては直接 transformElementbuildProps に直接実装されています.
以下のあたりがその実装です.

236            context.registerEffect(
237              [prop.exp],
238
239              {
240                type: IRNodeTypes.SET_DYNAMIC_EVENTS,
241                element: context.reference(),
242                event: prop.exp,
243              },
244            )

その少し上あたりには v-bindarg がない場合 (e.g. v-bind="obj") のハンドリングもあります.

208    if (prop.type === NodeTypes.DIRECTIVE && !prop.arg) {
209      if (prop.name === 'bind') {
210        // v-bind="obj"
211        if (prop.exp) {
212          dynamicExpr.push(prop.exp)
213          pushMergeArg()
214          dynamicArgs.push({
215            kind: IRDynamicPropsKind.EXPRESSION,
216            value: prop.exp,
217          })
218        } else {

とりあえず,SET_DYNAMIC_EVENTS を登録しているところが見れたので OK です.

このまま Codegen も読んでしまいましょう.

33export function genOperation(
34  oper: OperationNode,
35  context: CodegenContext,
36): CodeFragment[] {
40    case IRNodeTypes.SET_DYNAMIC_PROPS:
41      return genDynamicProps(oper, context)
63export function genDynamicProps(
64  oper: SetDynamicPropsIRNode,
65  context: CodegenContext,
66): CodeFragment[] {
67  const { vaporHelper } = context
68  return [
69    NEWLINE,
70    ...genCall(
71      vaporHelper('setDynamicProps'),
72      `n${oper.element}`,
73      ...oper.props.map(
74        props =>
75          Array.isArray(props)
76            ? genLiteralObjectProps(props, context) // static and dynamic arg props
77            : props.kind === IRDynamicPropsKind.ATTRIBUTE
78              ? genLiteralObjectProps([props], context) // dynamic arg props
79              : genExpression(props.value, context), // v-bind=""
80      ),
81    ),
82  ]
83}

特に難しいところはなかったはずです.

ランタイムを読む

こちらもほとんど読むところがありません.

key"class""style" だった場合に少々フォーマットしているだけです.

112export function setDynamicProp(el: Element, key: string, value: any): void {
113  // TODO
114  const isSVG = false
115  if (key === 'class') {
116    setClass(el, value)
117  } else if (key === 'style') {
118    setStyle(el as HTMLElement, value)
119  } else if (isOn(key)) {
120    on(el, key[2].toLowerCase() + key.slice(3), () => value, { effect: true })
121  } else if (
122    key[0] === '.'
123      ? ((key = key.slice(1)), true)
124      : key[0] === '^'
125        ? ((key = key.slice(1)), false)
126        : shouldSetAsProp(el, key, value, isSVG)
127  ) {
128    setDOMProp(el, key, value)
129  } else {
130    // TODO special case for <input v-model type="checkbox">
131    setAttr(el, key, value)
132  }
133}
22export function setClass(el: Element, value: any): void {
23  const prev = recordPropMetadata(el, 'class', (value = normalizeClass(value)))
24  if (value !== prev && (value || prev)) {
25    el.className = value
26  }
27}
12export function setStyle(el: HTMLElement, value: any): void {
13  const prev = recordPropMetadata(el, 'style', (value = normalizeStyle(value)))
14  patchStyle(el, prev, value)
15}