コンポーネント
以下のようなコンポーネントを考えます.
vue
<!-- Child.vue -->
<script setup>
defineProps<{ msg: string }>();
</script>
<template>
<p>{{ msg }}</p>
</template>vue
<!-- Parent.vue -->
<script setup>
import Child from "./Child.vue";
</script>
<template>
<Child msg="Hello!" />
</template>コンパイル結果と概要
基本的なコンポーネント
Parent.vue のコンパイル結果は以下のようになります.
js
import { createComponent as _createComponent } from "vue/vapor";
function _sfc_render(_ctx) {
const n0 = _createComponent(_ctx.Child, { msg: () => "Hello!" }, null, true);
return n0;
}createComponent は以下の引数を取ります:
- comp: コンポーネントの定義
- rawProps: props の配列
- slots: スロットの配列
- singleRoot: 単一ルートかどうか (fallthrough attributes 用)
asset からの解決
グローバルに登録されたコンポーネントの場合:
vue
<template>
<Foo />
</template>js
import {
resolveComponent as _resolveComponent,
createComponent as _createComponent,
} from "vue/vapor";
function _sfc_render(_ctx) {
const _component_Foo = _resolveComponent("Foo");
const n0 = _createComponent(_component_Foo, null, null, true);
return n0;
}resolveComponent でグローバルに登録されたコンポーネントを解決しています.
コンパイラを読む
IR
CREATE_COMPONENT_NODE の IR を確認します.
186export interface CreateComponentIRNode extends BaseIRNode {
187 type: IRNodeTypes.CREATE_COMPONENT_NODE
188 id: number
189 tag: string
190 props: IRProps[]
191 slots: IRSlots[]
192 asset: boolean
193 root: boolean
194 once: boolean
195}ts
export interface CreateComponentIRNode extends BaseIRNode {
type: IRNodeTypes.CREATE_COMPONENT_NODE;
id: number;
tag: string;
props: IRProps[];
slots: IRSlots[];
asset: boolean;
root: boolean;
once: boolean;
}tag: コンポーネントのタグ名props: props の配列slots: スロットの配列asset: グローバル登録コンポーネントかどうかroot: ルートコンポーネントかどうかonce:v-onceと組み合わせた場合
Transformer
transformElement 内でコンポーネントかどうかを判定します.
55 const isComponent = tagType === ElementTypes.COMPONENT
56 const propsResult = buildProps(
57 node,
58 context as TransformContext<ElementNode>,
59 isComponent,
60 )
61
62 ;(isComponent ? transformComponentElement : transformNativeElement)(
63 tag,
64 propsResult,
65 context as TransformContext<ElementNode>,
66 )ts
const { tag, tagType } = node;
const isComponent = tagType === ElementTypes.COMPONENT;
const propsResult = buildProps(
node,
context as TransformContext<ElementNode>,
isComponent
);
(isComponent ? transformComponentElement : transformNativeElement)(
tag,
propsResult,
context as TransformContext<ElementNode>
);transformComponentElement では:
ts
context.registerOperation({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: context.reference(),
tag,
props: propsResult[0] ? propsResult[1] : [propsResult[1]],
asset,
root,
slots: slotsResult,
once: context.inVOnce,
});Codegen
genCreateComponent 関数でコード生成を行います.
42export function genCreateComponent(
43 oper: CreateComponentIRNode,
44 context: CodegenContext,
45): CodeFragment[] {
46 const { vaporHelper } = context
47
48 const tag = genTag()
49 const { root, props, slots, once } = oper
50 const rawProps = genRawProps(props, context)
51 const rawSlots = genRawSlots(slots, context)
52
53 return [
54 NEWLINE,
55 `const n${oper.id} = `,
56 ...genCall(
57 vaporHelper('createComponent'),
58 tag,
59 rawProps,
60 rawSlots,
61 root ? 'true' : false,
62 once && 'true',
63 ),
64 ...genDirectivesForElement(oper.id, context),
65 ]
66
67 function genTag() {
68 if (oper.asset) {
69 return toValidAssetId(oper.tag, 'component')
70 } else {
71 return genExpression(
72 extend(createSimpleExpression(oper.tag, false), { ast: null }),
73 context,
74 )
75 }
76 }
77}ts
export function genCreateComponent(
oper: CreateComponentIRNode,
context: CodegenContext
): CodeFragment[] {
const { vaporHelper } = context;
const tag = genTag();
const { root, props, slots, once } = oper;
const rawProps = genRawProps(props, context);
const rawSlots = genRawSlots(slots, context);
return [
NEWLINE,
`const n${oper.id} = `,
...genCall(
vaporHelper("createComponent"),
tag,
rawProps,
rawSlots,
root ? "true" : false,
once && "true"
),
...genDirectivesForElement(oper.id, context),
];
function genTag() {
if (oper.asset) {
return toValidAssetId(oper.tag, "component");
} else {
return genExpression(
extend(createSimpleExpression(oper.tag, false), { ast: null }),
context
);
}
}
}Props の生成
props は getter 関数としてラップされます:
ts
function genProp(prop: IRProp, context: CodegenContext, isStatic?: boolean) {
return [
...genPropKey(prop, context),
": ",
...(prop.handler
? genEventHandler(context, prop.values[0])
: isStatic
? ["() => (", ...genExpression(prop.values[0], context), ")"]
: genExpression(prop.values[0], context)),
// ...
];
}これにより props は遅延評価され,リアクティブな更新が可能になります.
ランタイムを読む
createComponent は runtime-vapor の apiCreateComponent.ts に実装されています.
12export function createComponent(
13 comp: Component,
14 rawProps: RawProps | null = null,
15 slots: RawSlots | null = null,
16 singleRoot: boolean = false,
17 once: boolean = false,
18): ComponentInternalInstance {
19 const current = currentInstance!
20 const instance = createComponentInstance(
21 comp,
22 singleRoot ? withAttrs(rawProps) : rawProps,
23 slots,
24 once,
25 )
26 setupComponent(instance, singleRoot)
27
28 // register sub-component with current component for lifecycle management
29 current.comps.add(instance)
30
31 return instance
32}ts
export function createComponent(
comp: Component,
rawProps: RawProps | null = null,
slots: RawSlots | null = null,
singleRoot: boolean = false,
once: boolean = false
): ComponentInternalInstance {
const current = currentInstance!;
const instance = createComponentInstance(
comp,
singleRoot ? withAttrs(rawProps) : rawProps,
slots,
once
);
setupComponent(instance, singleRoot);
// register sub-component with current component for lifecycle management
current.comps.add(instance);
return instance;
}処理の流れ
- createComponentInstance: コンポーネントインスタンスを作成
- withAttrs:
singleRootの場合,fallthrough attributes を処理 - setupComponent: コンポーネントをセットアップ (props, slots, render 関数)
- current.comps.add: 親コンポーネントに子コンポーネントを登録
singleRoot と fallthrough attributes
singleRoot: true の場合,親から渡された attributes が子コンポーネントのルート要素に自動的に適用されます:
ts
const instance = createComponentInstance(
comp,
singleRoot ? withAttrs(rawProps) : rawProps,
slots,
once
);ライフサイクル管理
子コンポーネントは親の comps Set に登録されます:
ts
current.comps.add(instance);これにより,親コンポーネントの unmount 時に子コンポーネントも適切に破棄されます.
コンポーネントは Vapor Mode でも Virtual DOM 版と同様の API をサポートしています.
props は getter 関数としてラップされることで,リアクティブな更新が可能になっています.createComponentInstance と setupComponent の詳細については,別の章で詳しく見ていきます.
