Nuxt3 (Vue3)の変更点をまとめてみた(Plugins, Composables, Composition API)

ここのところはReactの記事を書いていましたが、最近業務でNuxt3へのアップデートを行っているのでその備忘録も兼ねてまとめていきたいと思います。

大きく分けてPlugins, Composables, Composition APIについて。

Plugins

Nuxtでは自作の関数や外部パッケージ等をプラグインとして使うことで拡張することができます。

Nuxt2ではこんな感じでした。

// /plugins/hoge.js
export default (context, inject) => {
  inject('hoge', () => {
    console.log('hoge')
  })
}

このようなファイルを作ってnuxt.config.js

plugins: ['~/plugins/hoge.js']

と書くとコンポーネントthis.$hoge()のように使うことができます。

第1引数のcontextNuxt Contextで、第2引数にはinjectという関数が入ります。

このinjectを使ってcontextプラグインを注入するわけです。

なのでcontextを直接参照できる箇所、例えばasyncDataでは以下のようにも使えます。

async asyncData({ $hoge }) {
  $hoge();
}

また、Vueのプラグインを使う場合は、

// /plugins/fuga.js
import Vue from 'vue';
import Fuga from 'fuga';

Vue.use(Fuga);

として、

plugins: ['~/plugins/fuga.js']

とすることでVueのプラグインを追加することもできました。

Nuxt3ではプラグインを使うにはdefineNuxtPlugin()を使います。

最初の例をNuxt3で書くと以下のようになります。

import { defineNuxtPlugin } from '#app';

export default defineNuxtPlugin(() => {
  return {
    provide: {
      hoge: () => {
        console.log('hoge');
      }
    }
  }
});

こうすることでNuxtのインスタンスhoge()が注入されます。

上述した通りNuxt2ではcontextがありましたが、Nuxt3ではuseNuxtApp()を使うことでNuxtのインスタンスを呼び出せます。

const nuxtApp = useNuxtApp();
nuxtApp.$hoge();

// 分割代入も可能
const { $hoge } = useNuxtApp();
$hoge();

Vueプラグインを使う場合も同様にdefineNuxtPlugin()を使います。

defineNuxtPlugin()の引数に入る関数はNuxtのインスタンスを引数に取ります。

import { defineNuxtPlugin } from '#app';
import Fuga from 'fuga';

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(Fuga);
});

ここでのnuxtAppは先程のuseNuxtApp()の返り値と同じNuxtインスタンスです。

NuxtインスタンスからVueインスタンスを取り出し、Nuxt2と同じくuse()で登録します。

もう1つ大きな違いとしてはNuxt3ではnuxt.config.jspluginsとして記載する必要がありません。/pluginsにファイルを追加するだけで使えます。

/pluginsに追加したプラグインはデフォルトではサーバーサイド・クライアントサイドどちらでも実行されます。

サーバーサイドのみ、またはクライアントサイドのみで実行したいプラグインはそれぞれファイル名をhoge.server.jsまたはhoge.client.jsのようにします。

Composables

Vue2ではグローバル関数を使うためのものとしてmixinという仕組みがありましたが、こちらは現在非推奨になっています(一応後方互換性のために現在も使えるようにはなっています)。

代わりにVue3ではComposablesという新しい仕組みが追加されました。

Nuxt3では/composablesディレクトリに追加したファイルは自動で読み込まれます。

なのでグローバルに使いたい関数等はここに追加しておくと便利です。

後で説明するComposition APIも同一の文脈であり、共通のロジックを切り出すための仕組みになります。

こうすることでロジックだけを単体テストすることも容易になります。

慣習としてuseXXXXというReactのパクリ命名にする、とのことですがそこは別にわかりやすい名前で良いと思います。

Composablesの一番の使い道としてはやはりStoreでしょうか。

Nuxt3ではVuexがバンドルされなくなったのでuseStateを使った方法かPiniaが推奨されています。

// /composables/hogeStore.js
import { useState } from '#app';

export const hogeStore = () => {
  const counter = useState('hoge', () => {
    return 0;
  });

  const increment = () => {
    counter.value++;
  }
  const decrement = () => {
    counter.value--;
  }

  return {
    counter,
    increment,
    decrement
  };
};

こうすることでコンポーネント側で以下のように呼び出すことができます。

<script setup>
import hogeStore from '~/composables/hogeStore';
const { counter, increment, decrement } = hogeStore();
</script>

<template>
  <div>
    <span>Count: {{ counter }}</span>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

Composition API

Vueコンポーネントも従来のOptions APIからComposition APIが標準となりました。

特にVue3.2から登場した<script setup>構文はこれまでよりシンプルに書くことができます。

例えばOptions APIで以下のようなコンポーネントがあったとします。

<template>
  <div>
    <button @click="$emit('click')">click</button>
</template>
<script>
export default {
  name: 'HogeComponent',
  props: {
    hoge: {
      type: String
    },
    fuga: {
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      title: 'hogehoge',
      description: 'fugafuga'
    }
  },
  computed: {
    piyo() {
      return this.fuga * 2;
    }
  },
  methods: {
    poyo() {
      // Do something.
    }
  }
}
</script>

これを<script setup>構文で書くとこんな感じ。

<template>
  <div>
    <button @click="emit('click')">click</button>
</template>
<script setup>
const props = defineProps({
  hoge: {
    type: String
  },
  fuga: {
    type: Number,
    default: 0
  }
});

const title = ref('hogehoge');
const description = ref('fugafuga');

const piyo = computed(() => {
  return props.fuga * 2;
});

const poyo = () => {
  // Do something.
}

const emit = defineEmits(['click']);
</script>

順番に見ていきます。

props

propsを定義するにはdefineProps()を使います。

渡す値は従来のpropsと同じでOKです。

TypeScriptを使う場合、型定義を渡してあげるだけでもOKです。

<script setup lang="ts">
interface Props {
  hoge: string,
  fuga?: number
}
const props = defineProps<Props>();
</script>

ただこれだとデフォルト値が設定できないのでそのような場合にはwithDefaults()を使います。

<script setup lang="ts">
interface Props {
  hoge: string,
  fuga?: number
}
const props = withDefaults(defineProps<Props>(), {
  hoge: 'hoge',
  fuga: 0
});
</script>

ref / reactive

従来data()だったもの、つまりコンポーネント内でリアクティブな値を保持する変数はref()またはreactive()で定義します。

ref()reactive()の違いは以下の通りです。

ref()

  • 全ての型が使える
  • 代入した値にアクセスするには.valueというプロパティを使う
const hoge = ref({a: 0, b: 1});
console.log(hoge.value.a); // 0

reactive()

  • オブジェクトのみ使える
  • 代入した値にアクセスするには代入したオブジェクトのプロパティを直接参照する
const hoge = reactive({a: 0, b: 1});
console.log(hoge.a); // 0

computed

Options APIではcomputedプロパティにまとめて定義していましたが、Composition APIではcomputed()という関数を使います。

ただ書き方は従来と同じです。getter / setter両方定義することももちろん可能です。

const hoge = ref(0);
const fuga = computed({
  get: () => {
    return hoge.value;
  },
  set: (value) => {
    hoge.value = value - 1;
  }
});

## methods

`methods`に定義していた関数は`<script>`直下にそのまま定義できます。

## emit

Vue3から[親コンポーネントに`emit`するイベントをコンポーネント内で定義しなければならなくなりました](https://v3-migration.vuejs.org/breaking-changes/emits-option.html)。

`emit`を定義するには`defineEmits()`を使います。

そのコンポーネントから`emit`するイベントを配列で渡してやるだけです。

`defineEmits()`の返り値はそのまま関数となり、テンプレート内の`@click`等で使用できます。

ちなみに`<script setup>`の中で定義した変数や関数はそのままテンプレート内で使用することができます。別途returnする必要はありません。

また、↑で出てきた`defineProps()``ref()``reactive()``computed()``defineEmits()`はNuxtが自動的にインポートしてくれるので明示的にインポートする必要がありません。

# まとめ

以上、Nuxt3及びVue3についてまとめてみました。

以前軽く記事にしましたが、やはり実際にアップデートを進めていく中でより理解が深まっている気がします。

Composition APIへの置き換えが一番大変そうですが、引き続き対応していきます。