コンポーネント
ichigo.js のコンポーネントは、リアクティビティを備えた本物の Custom Elements です。defineComponent で定義し、<template> をマークアップとして指定します。Shadow DOM ではなく Light DOM を使います。
defineComponent
<!-- コンポーネントのマークアップ -->
<template id="my-list">
<ul v-if="items.length > 0">
<li v-for="item of items" :key="item.id">{{ item.name }}</li>
</ul>
<!-- 親から差し込まれるフォールバック -->
<slot></slot>
</template>
import { defineComponent } from '@mintjamsinc/ichigojs';
defineComponent('my-list', {
template: '#my-list', // <template> を指す CSS セレクタ
props: ['items'], // 親から受け取る props
data() {
// props は this から参照でき、ここで既定値や変換を与えられる
return { items: this.items ?? [] };
}
});
<!-- 利用側 -->
<my-list :items="searchResults">
<span slot="empty">該当なし。</span>
</my-list>
defineComponent(tagName, options) のオプションは createApp と同じものに加えて、次の 2 つを受け付けます。
template— マークアップを定義する<template>の CSS セレクタ(必須)props— 親から属性/プロパティで受け取るプロパティ名の配列
props
props配列で宣言します。宣言した各 prop はカスタム要素のプロパティになり、親はv-bind/:でバインドできます(例::items="searchResults")。- props は最初からリアクティブで、コンポーネントの data に自動的に含まれます。
data()が返す値が優先されるので、this.items ?? []のように既定値や変換を与えられます。
defineComponent('user-badge', {
template: '#user-badge',
props: ['name', 'role'],
computed: {
label() { return `${this.name}(${this.role})`; }
}
});
slot
コンポーネントの <template> 内にネイティブの <slot> を置くと、親のコンテンツを差し込めます。名前付きスロットにも対応します。
<template id="modal-box">
<div class="modal">
<header><slot name="title">タイトル</slot></header>
<div class="body"><slot></slot></div>
</div>
</template>
<modal-box>
<span slot="title">確認</span>
<p>本当に削除しますか?</p>
</modal-box>
カスタムイベント($emit)
コンポーネント(およびアプリ)は $emit でカスタムイベントを発火できます。テンプレートとメソッドの双方から使え、既定ではコンポーネントのルート要素からバブリングするため、親は @ でリッスンできます。
defineComponent('my-button', {
template: '#my-button',
// 任意: 発火するイベントを宣言。未宣言のイベントを発火すると
// 開発時に警告が出る(検証のみ。発火自体はブロックしない)。
emits: ['selected'],
methods: {
onClick() {
// $emit(name, detail?, options?)
this.$emit('selected', { id: 42 });
}
}
});
<!-- 親はカスタムイベントをリッスン。ペイロードは event.detail -->
<my-button @selected="onSelected"></my-button>
methods: {
onSelected(event) {
console.log(event.detail.id); // 42
}
}
$emit(name, detail?, options?)
name— イベント名(親側では@nameでリッスン)detail—event.detailとして渡るペイロードoptions(VEmitOptions)—bubbles— バブリングするか(既定:true)cancelable—preventDefault()を有効にするか(既定:true)。リスナがpreventDefault()を呼ぶと$emitはfalseを返すcomposed— Shadow DOM 境界を越えるか(既定:false)target— 発火対象(既定: アプリのルート要素)。document/windowを指定すればグローバルなイベントバスになる
コンポーネントへの ref
コンポーネントに付けた ref は、そのホストとなるカスタム要素を指します。focus() や getBoundingClientRect() など DOM のメソッドを呼べます。詳しくは「基本ガイド」のテンプレート参照を参照してください。
<my-card ref="card"></my-card>
this.$refs.card; // <my-card> 要素
アプリとコンポーネント
VDOM.createApp で作るアプリと、defineComponent で作るコンポーネントは、同じリアクティビティ基盤の上に立ちます。data / computed / methods / watch / emits / logLevel といったオプションや、$markRaw / $nextTick / $emit / $refs などのヘルパーは共通です。違いは、コンポーネントが template と props を持ち、カスタム要素として登録される点です。
非推奨: v-component
⚠️
v-componentディレクティブとVComponentRegistryは非推奨で、将来削除されます。新しいコードではdefineComponent(Custom Elements)を使ってください。
次のステップ
- 文法の詳細は「ディレクティブ・リファレンス」へ
- リアクティビティは「基本ガイド」へ