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 しているだけで,エフェクトの登録などは行われていません.
実はここに関しては直接 transformElement
の buildProps
に直接実装されています.
以下のあたりがその実装です.
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-bind
に arg
がない場合 (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}