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

ライフサイクルフック

Vue3では、以下のライフサイクルフックをComposition API内で使用できます。

  1. onBeforeMount: コンポーネントがマウントされる前に実行されます。
  2. onMounted: コンポーネントがマウントされた後に実行されます。
  3. onBeforeUpdate: リアクティブな依存関係が更新され、かつDOMが再レンダリングされる前に実行されます。
  4. onUpdated: リアクティブな依存関係が更新され、DOMが再レンダリングされた後に実行されます。
  5. onBeforeUnmount: コンポーネントがアンマウントされる前に実行されます。
  6. onUnmounted: コンポーネントがアンマウントされた後に実行されます。
  7. onErrorCaptured: 子孫コンポーネントでエラーがキャプチャされたときに実行されます。
  8. onRenderTracked: レンダリング中にリアクティブなプロパティがトラックされたときに実行されます。
  9. onRenderTriggered: レンダリングがトリガーされたとき(リアクティブな依存関係が変更されたとき)に実行されます。

各フックの具体的な使用例を見ていきましょう。

index.vue
<script setup>
import { 
  onBeforeMount, onMounted, onBeforeUpdate, onUpdated,
  onBeforeUnmount, onUnmounted, onErrorCaptured,
  onRenderTracked, onRenderTriggered, ref 
} from 'vue';

const data = ref('初期データ');

onBeforeMount(() => {
  console.log('マウント前');
});

onMounted(() => {
  console.log('マウント後');
  data.value = '更新データ';
});

onBeforeUpdate(() => {
  console.log('更新前');
});

onUpdated(() => {
  console.log('更新後');
});

onBeforeUnmount(() => {
  console.log('アンマウント前');
});

onUnmounted(() => {
  console.log('アンマウント後');
});

onErrorCaptured((error, instance, info) => {
  console.error('エラーキャプチャ', error, instance, info);
});

onRenderTracked((event) => {
  console.log('レンダートラック', event);
});

onRenderTriggered((event) => {
  console.log('レンダートリガー', event);
});
</script>

Computedプロパティ

computedプロパティは、依存するデータに基づいて値を計算するために使われます。この計算された値はキャッシュされ、依存するデータが変更されたときにのみ再計算されます。

基本的な使用例

この例では、firstNamelastNameが変更されたときにのみfullNameが再計算されます。

<script setup>
import { ref, computed } from 'vue';

// リアクティブな参照
const firstName = ref('John');
const lastName = ref('Doe');

// computedプロパティを使ってフルネームを計算
const fullName = computed(() => {
  return `${firstName.value} ${lastName.value}`;
});
</script>

<template>
  <div>{{ fullName }}</div>
</template>

watchとwatchEffect

watch機能は、指定されたリアクティブな参照やリアクティブなオブジェクトのプロパティが変更されるたびに、指定されたコールバック関数を実行します。これは、データの変更に応じて非同期操作を行う場合や、特定のデータの変更に基づいて何らかの副作用をトリガーする場合に便利です。

watchの使用例

この例ではcountの値が変更されるたびにコンソールにメッセージが表示されます。

<script setup>
import { ref, watch } from 'vue';

const count = ref(0);

// countが変更されるたびに呼び出されるwatcher
watch(count, (newValue, oldValue) => {
  console.log(`countが${oldValue}から${newValue}に変更されました`);
});
</script>

<template>
  <button @click="count++">Count: {{ count }}</button>
</template>

watchの応用: 複数のソースを監視

watchは、複数のリアクティブな参照やリアクティブなオブジェクトのプロパティを同時に監視することもできます。 この例では、firstNameまたはlastNameのどちらかが変更されると、変更前後の名前がコンソールに表示されます。

<script setup>
import { ref, watch } from 'vue';

const firstName = ref('John');
const lastName = ref('Doe');

// firstNameとlastNameのどちらかが変更されたときに実行
watch([firstName, lastName], ([newFirstName, newLastName], [oldFirstName, oldLastName]) => {
  console.log(`名前が${oldFirstName} ${oldLastName}から${newFirstName} ${newLastName}に変更されました`);
});
</script>

watchEffectの使用例

watchEffectは、指定されたコールバック関数内で使用されるすべてのリアクティブな状態を自動的に追跡し、それらのいずれかが変更されるとコールバック関数を再実行します。watchEffectは、依存関係を明示的に指定する必要がないため、watchよりも宣言的なケースで便利です。

<script setup>
import { ref, watchEffect } from 'vue';

const count = ref(0);

watchEffect(() => {
  console.log(`countの現在値は${count.value}です`);
});
</script>

<template>
  <button @click="count++">Count: {{ count }}</button>
</template>

この例では、countの値が変わるたびに、watchEffect内のコールバックが自動的に再実行され、最新のcountの値がコンソールに出力されます。

watchとwatchEffectの違い

  • 明示的な依存関係の指定: watchでは、監視するリアクティブな参照やオブジェクトのプロパティを明示的に指定する必要があります。これに対して、watchEffectはコールバック関数内でアクセスされるすべてのリアクティブな状態を自動的に追跡します。
  • 遅延実行: watchは、指定された依存関係が変更された時にのみコールバック関数を実行します。一方で、watchEffectは初期実行時に自動的にコールバック関数を実行し、依存するリアクティブな状態のいずれかが変更されると再実行します。
  • 応答性: watchEffectは、関数内で参照されるすべてのリアクティブなプロパティを自動的に監視するため、より動的な依存関係の追跡に適しています。watchは、より制御が必要な場合や、特定のデータの変更を監視する場合に適しています。
  • 柔軟性と制御: watchは、古い値と新しい値をコールバック関数の引数として提供し、さらには監視のオプション(深い監視、即時実行など)を細かく設定できるため、より柔軟な制御が可能です。watchEffectは自動的な依存関係の追跡に重点を置いており、細かい制御は少ないですが、使いやすさに優れています。

refとreactiveとその違い

Vue 3のリアクティブシステムは、アプリケーションの状態管理を効果的に行うための核心的な機能です。このシステムの中心にあるのがrefreactiveです。これらはリアクティブなデータを作成するために使用されますが、用途と動作の仕方に違いがあります。ここでは、refreactiveの違いを具体的なコード例を交えて解説します。

ref

refは、プリミティブ型(文字列、数値、ブール値など)をリアクティブなデータとして扱うために使用されます。refによって作成されたデータにアクセスするには.valueプロパティを通じて行います。 この例では、messageはプリミティブな文字列値をリアクティブに保持しており、.valueを通じてその値にアクセスしています。

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

// プリミティブ型のリアクティブなデータ
const message = ref('Hello Vue 3!');

// .valueを通じてアクセスおよび更新
function updateMessage() {
  message.value = 'Updated message';
}
</script>

<template>
  <div>{{ message }}</div>
  <button @click="updateMessage">Update Message</button>
</template>

reactive

reactiveは、オブジェクトや配列などの複合型をリアクティブなデータとして扱うために使用されます。reactiveによって作成されたデータは、直接的にアクセスおよび更新が可能です。 この例では、stateは複数のプロパティを含むオブジェクトをリアクティブに保持しており、直接的にこれらのプロパティにアクセスして更新しています。

<script setup>
import { reactive } from 'vue';

// オブジェクトのリアクティブなデータ
const state = reactive({
  count: 0,
  message: 'Hello'
});

// 直接的にアクセスおよび更新
function increment() {
  state.count++;
}

function updateMessage() {
  state.message = 'Updated message';
}
</script>

<template>
  <div>{{ state.count }} - {{ state.message }}</div>
  <button @click="increment">Increment</button>
  <button @click="updateMessage">Update Message</button>
</template>

refとreactiveの違い

  • データ型: refはプリミティブ型をリアクティブにするために使用され、.valueを通じてアクセスします。reactiveはオブジェクトや配列などの複合型をリアクティブにします。
  • アクセス方法: ref作成されたデータは.valueプロパティを介してアクセスされますが、reactiveによってリアクティブ化されたオブジェクトは直接操作が可能です。
  • テンプレート内での振る舞い: テンプレート内でrefから作成されたリアクティブなデータを使用する際は.valueを省略できますが、reactiveオブジェクトはそのまま使用します。

Teleportとフラグメント

Teleport

Vue3のTeleport機能は、コンポーネントのHTMLを異なる場所にレンダリングするために使用されます。これは、ページ全体のどこにでもモーダルウィンドウやトーストメッセージを表示したい場合に特に便利です。以下は、Teleportを使用してモーダルコンポーネントをページのbodyタグ内に動的に挿入する簡単な例です。

app.vueでは、モーダルを開閉するボタンと、モーダルコンポーネントへのTeleportを含みます。ボタンをクリックすると、ModalComponent.vue<teleport to="body"> を通じて画面に表示されます。 ModalComponent.vueは、モーダルのオーバーレイとウィンドウを提供し、背景や閉じるボタンをクリックすることでモーダルを閉じることができます。

app.vue
<script setup>
import ModalComponent from './ModalComponent.vue';
const showModal = ref(false)
</script>
<template>
  <div>
    <button @click="showModal = true">Open Modal</button>
    <teleport to="body">
      <ModalComponent v-if="showModal" @close="showModal = false" />
    </teleport>
  </div>
</template>
ModalComponent.vue
<script setup>
const emit = defineEmits('close')
const close = () => {
  emit('close')
}
</script>
<template>
  <div class="modal-overlay" @click.self="close">
    <div class="modal-window">
      <p>This is a modal. Click outside to close.</p>
      <button @click="close">Close</button>
    </div>
  </div>
</template>
<style>
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
}
.modal-window {
  background: white;
  padding: 20px;
  border-radius: 8px;
}
</style>

フラグメント

Vue3以前のバージョンでは、各Vueコンポーネントは単一のルート要素を持つ必要がありました。しかし、Vue 3.0からはこの制限が撤廃され、コンポーネントが複数のルートノードを持つことができるようになりました。これは「フラグメント」として知られており、コンポーネントのテンプレートで複数の要素を直接返すことができるようになることを意味します。これにより、DOMの構造をより柔軟に扱うことが可能になりました。