コンポーネントを深く理解する(前半)

シングルファイルコンポーネント(SFC)

Vue.jsのシングルファイルコンポーネント(SFC)は、.vue拡張子を持つファイル内にHTML、JavaScript、CSSを一つのファイルにまとめることができる強力な機能です。SFCを使用することで、コンポーネントの構造が明確になり、開発の効率が向上します。以下に、SFCの構成と具体的な使用例を示します。

SFCの基本構造

一般的なSFCは以下の3つのセクションから構成されます。

  • <template>: コンポーネントのHTMLマークアップを含みます。
  • <script>: コンポーネントのJavaScriptロジックを含みます。Vue 3では<script setup>構文も利用できます。
  • <style>: コンポーネント専用のスタイルを定義します。スコープ付きCSSも利用可能です。

具体的な例

基本的なSFC

<template>
  <div class="my-component">
    {{ message }}
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello, Vue SFC!'
    }
  }
}
</script>

<style scoped>
.my-component {
  color: blue;
  font-size: 20px;
}
</style>

このコンポーネントは、青色で20pxのフォントサイズのテキストを表示します。scoped属性があるため、スタイルはこのコンポーネントに限定されます。

<script setup>を使用したSFC

Vue 3で導入された<script setup>構文を使用すると、より宣言的かつ簡潔にコンポーネントを定義できます。

<template>
  <div>{{ greeting }}</div>
</template>

<script setup>
import { ref } from 'vue'

const greeting = ref('Hello, Vue 3 with <script setup>!')
</script>

<style>
div {
  color: red;
}
</style>

この例では、refを使用してリアクティブなデータgreetingを定義しています。テンプレート内でこのデータを使用することで、テキストを表示しています。

SFCの利点

  • モジュール性: HTML、JavaScript、CSSが1つのファイル内に集約されているため、コンポーネントごとにファイルを分けることができ、プロジェクトの構造が明確になります。
  • スコープ付きスタイル: <style scoped>を使用することで、スタイルをコンポーネント内に限定でき、スタイルの衝突を避けられます。
  • 再利用可能: コンポーネントを再利用しやすくなり、大規模なアプリケーションの開発が効率的になります。
  • 開発ツールのサポート: Vue CLIやViteなどのツールはSFCをフルサポートしており、ホットリロードやコード分割などの機能を簡単に利用できます。

setup関数の使用

Vue 3の導入により、Composition APIが新たに追加され、その中心的な機能の一つがsetup関数です。setup関数は、コンポーネントのロジックとリアクティブなデータをより柔軟に扱うための新しいオプションです。この関数は、コンポーネントのライフサイクルの初期段階で実行され、propscontextにアクセスすることができます。

setup関数の使用前

Vue 2では、コンポーネントのデータ、メソッド、ライフサイクルフック、ウォッチャなどは、コンポーネントオブジェクトのオプションとして定義されました。

export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    console.log('コンポーネントがマウントされました。');
  }
}

この方法では、関連するロジックがコンポーネント内で分散されがちであり、大規模なコンポーネントでは管理が難しくなることがありました。

setup関数の導入後

Vue 3では、setup関数を使用して、コンポーネントのロジックをより構造化し、関連する機能をまとめて定義できます。これにより、コードの可読性と再利用性が向上します。

import { ref, onMounted } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const increment = () => {
      count.value++
    }

    onMounted(() => {
      console.log('コンポーネントがマウントされました。');
    })

    return { count, increment }
  }
}

setup関数内で使用されるrefはリアクティブなデータを作成し、onMountedはコンポーネントがマウントされた後に実行されるべき処理を定義します。setup関数から返された値は、テンプレート内で直接使用できます。

setup関数の機能

  • リアクティブな参照: refreactiveを使用して、リアクティブなデータを定義できます。
  • コンポーネントプロパティ: propscontextを通じて、親コンポーネントからのプロパティやスロットにアクセスできます。
  • ライフサイクルフック: onMountedonUpdatedなどのライフサイクルフックを使用できます。
  • ウォッチャ: watchwatchEffectを使用して、リアクティブなデータの変更を監視できます。
  • 提供/注入: provideinjectを使用して、コンポーネントツリー間でのデータの提供と注入が可能です。

変化

setup関数の導入により、Vueコンポーネントの定義方法が大きく変化しました。従来のオプションAPIでは、コンポーネントのオプションごとにロジックが分散していましたが、Composition API(とその中心であるsetup関数)により、関連する機能をまとめて記述できるようになり、コンポーネントの可読性と再利用性が向上しました。また、大規模なアプリケーションや複雑なコンポーネントの開発において、より柔軟かつ効率的なコーディングが可能になりました。

コンポーネントの基礎

Vue.jsのコア機能の一つは、再利用可能なコンポーネントを作成できることです。

Vue.componentを使用してグローバルコンポーネントを定義します。 グローバルコンポーネントはカスタム要素としてtemplate内で使用できます。

main.js
Vue.component('todo-item', {
  template: '<li>これはTODOアイテムです</li>'
});
app.vue
<template>
  <ol>
    <todo-item></todo-item>
  </ol>
</template>

ローカルで定義することもできます。

/components/todo-item.vue
<template>
  <li>これはTODOアイテムです</li>
</template>
app.vue
<script setup>
import { TodoItem } from './components/todo-item.vue'
</script>
<template>
  <ol>
    <TodoItem></TodoItem>
  </ol>
</template>

コンポーネント間のデータの受け渡し(PropsとEvents)

Props

/components/message.vue
<script setup>
defineProps(['message'])
</script>
<template>
  <span>
    メッセージは {{ message }}
  </span>
</template>
app.vue
<script setup>
import { Message } from './components/message.vue'
</script>
<template>
  <Message title="こんにちは!"></Message>
</template>

Emits

ChildComponent.vue
<script setup>
const emit = defineEmits(['increase']);

const emitIncrease = () => {
  emit('increase');
}
</script>
<template>
  <button @click="emitIncrease">クリックしてカウントアップ</button>
</template>
ParentComponent.vue
<script setup>
import ChildComponent from './ChildComponent.vue';

const count = ref(0);
</script>
<template>
  <div>
    <p>カウント: {{ count }}</p>
    <ChildComponent @increase="count++" />
  </div>
</template>

スロットを使ったコンテンツ配信

匿名スロット

コンポーネント内の未指定のコンテンツを受け入れます。

ChildComponent.vue
<template>
  <div>
    <slot>デフォルトコンテンツ</slot>
  </div>
</template>
ParentComponent.vue
<script setup>
import ChildComponent from './ChildComponent.vue';
</script>
<template>
  <ChildComponent>
    <p>これはスロットを通じて渡されたコンテンツです。</p>
  </ChildComponent>
</template>

名前付きスロット

特定のスロットにコンテンツを挿入するために名前を使用します。

ChildComponent.vue
<template>
  <div>
    <slot>デフォルトコンテンツ</slot>
    <slot name="header">Header</slot>
    <slot name="footer">Footer</slot>
  </div>
</template>
ParentComponent.vue
<script setup>
import ChildComponent from './ChildComponent.vue';
</script>
<template>
  <ChildComponent>
    <p>これはデフォルトスロットを通じて渡されたコンテンツです。</p>
    <template v-slot:header>これはHeaderです</template>
    <!-- v-slot: は # と書くこともできます -->
    <template #header>これはFooterです</template>
  </ChildComponent>
</template>

ダイナミックコンポーネントと非同期コンポーネント

ダイナミックコンポーネント

is属性を使用して、動的にコンポーネントを切り替えることができます。

MyComponent.vue
<template>
  <div>MyComponent</div>
</template>
AnotherComponent.vue
<template>
  <div>AnotherComponent</div>
</template>
DynamicComponent.vue
<script setup>
import MyComponent from './MyComponent.vue';
import AnotherComponent from './AnotherComponent.vue';

const currentComponent = ref('MyComponent');
const switchComponent = () => {
  if(currentComponent.value === 'MyComponent'){
    currentComponent.value = 'AnotherComponent'
  } else if(currentComponent.value === 'AnotherComponent'){
    currentComponent.value = 'MyComponent'
  }
}
</script>
<template>
  <component :is="currentComponent" />
  <button @click="switchComponent">Switch</button>
</template>

非同期コンポーネント

コンポーネントを非同期にロードすることで、初期ロード時のパフォーマンスを向上させることができます。

AsyncComponent.vue
<template>
  <div>私は非同期でロードされました!</div>
</template>
ParentComponent.vue
<script setup>
import { ref, defineAsyncComponent } from 'vue';

const isComponentLoaded = ref(false);

// 非同期コンポーネントの定義
const AsyncComponent = defineAsyncComponent(() =>
  import('./AsyncComponent.vue')
);

const loadComponent = () => {
  isComponentLoaded.value = true;
};
</script>
<template>
  <button @click="loadComponent">コンポーネントをロード</button>
  <AsyncComponent v-if="isComponentLoaded" />
</template>