コンポーネント

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 でリッスン)
  • detailevent.detail として渡るペイロード
  • optionsVEmitOptions)—
    • bubbles — バブリングするか(既定: true
    • cancelablepreventDefault() を有効にするか(既定: true)。リスナが preventDefault() を呼ぶと $emitfalse を返す
    • 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 などのヘルパーは共通です。違いは、コンポーネントが templateprops を持ち、カスタム要素として登録される点です。

非推奨: v-component

⚠️ v-component ディレクティブと VComponentRegistry非推奨で、将来削除されます。新しいコードでは defineComponent(Custom Elements)を使ってください。

次のステップ