こんにちは、開発チームでエンジニアをしている和田です。
最近は、Vue2 で記述されたプロジェクトを Vue3 および Nuxt3 に書き換える毎日です👨💻
ということで今回は、Options API で記述された Vue2 のコンポーネントを Composition API で記述する過程について記事にしたいと思います。
Vue2からVue3・Nuxt3 へ移行する背景
Vue2 のプロジェクトを Nuxt3 へと移行するに至った背景としては、主に Vue2 の公式サポートが終了することにあります。
2023年12月に Vue2 の公式サポートが終了することが発表されています。このままプロジェクトを放置すると、Vue2 に依存するライブラリなどもアップデートできず脆弱性を抱え続けることになるため、一念発起してプロジェクト全体の変更をすることになりました🤓
- Options API で記述された Vue2 コンポーネントを Composition API に書き換える
- Composition API で記述されたプロジェクト全体を Vue3 に引き上げる
- Vue3 のプロジェクトをベースに Nuxt3 に移行する
このような流れで作業をしていきます。
この記事の対象読者: Vue2からVue3への乗り換えを検討している人
今回の記事では、Vue2 の Options API で記述されたコンポーネントを Composition API に書き換えるまでを解説します。
Composition API で記述されたコンポーネントを Nuxt3 のプロジェクトで使えるようにする方法については、今後の記事で解説させていただきます🙇♂️
Vue2とVue3、Options APIとComposition APIの関係について
前提知識として Vue2, Vue3, Options API, Composition API の関係性について述べておきます。
Options API について
Options API とは、Vue2 の代表的な記法であり下記のようなサンプルコードで書けます。
<script> export default { name: 'Counter', data: () => { return { count: 0, }; }, methods: { countUp() { this.count++; }, reset() { this.count = 0; }, }, }; </script>
data
の中でリアクティブな値 count
を管理し、リアクティブに値を操作するための処理を methods
内に記述するという形式です。
この記法の問題として、this
を使ってリアクティブに値を操作することになるため、View と処理を分離することができないということが挙げられます。
Composition API について
Composition API とは、Vue3 の代表的な記法です。
Vue2 のプロジェクトにも導入することができ、リアクティブな値の操作と View を切り離せるのが特徴です。
import { ref } from "vue"; export const useCounter = () => { const count = ref(0); const addCounter = () => { count.value++; }; const resetCounter = () => { count.value = 0; }; return { count, addCounter, resetCounter, }; };
import { useCounter } from '@/ ... /useCounter' export default defineComponent({ name: 'Counter' setup() { const { count, addCounter, resetCounter } = useCounter(); } });
composables
というディレクトリに、リアクティブな値の処理を記述しておけば、どのコンポーネントからでも簡単に呼び出すことができます。
Vue2 Options API で記述されたコンポーネント
今回は Options API で記述された下記のコンポーネントについて見ていきます。
説明の都合上、<style />
の記述は省略します。
<template> <button :class="{ [`btn-${buttonType}`]: true }" :disabled="hasPermission === false || isDisabled === true" > <span>{{ buttonText }}</span> </button> </template> <script> import { mapGetters } from 'vuex'; export default { name: 'ButtonBase', props: { buttonText: { type: String, required: true }, buttonType: { type: String, default: 'primary' }, isDisabled: { type: Boolean, default: false }, rejectedRoleIds: { type: Array, default: () => [] }, }, computed: { ...mapGetters('staff', ['staff']), hasPermission() { return this.judgeHasPermission( this.staff, this.rejectedRoleIds, ); }, }, methods: { judgeHasPermission(staffObj, rejectedRoleIds) { const staffObject = staffObj; if (typeof staffObject !== 'object') return false; if (rejectedRoleIds.length === 0) return true; return ( staffObject.role && rejectedRoleIds.includes(staffObject.role.id) === false ); }, }, }; </script>
それぞれの props
の説明は下記のようになります。
props | 用途 |
---|---|
buttonText | ボタン中央に表示するテキスト |
buttonType | ボタンの class を利用するための文字列 |
isDisabled | ボタンの非活性フラグ |
rejectedRoleIds | ボタンの押下制限を行うユーザーの role ID の配列 |
Composition API に書き換える
先程のコードの仕様をほとんど変更せずに Composition API に書き換えると次のようになります。
<template> <button :class="{ [`btn-${buttonType}`]: true }" :disabled="hasPermission === false || isDisabled === true" > <span>{{ buttonText }}</span> </button> </template> <script> import { computed, defineComponent } from '@vue/composition-api'; export default defineComponent({ name: 'ButtonBase', props: { buttonText: { type: String, required: true }, buttonType: { type: String, default: 'primary' }, isDisabled: { type: Boolean, default: false }, rejectedRoleIds: { type: Array, default: () => [] }, }, setup(props, context) { const store = context.root.$store; const staff = store.getters['staff/staff']; const hasPermission = computed(() => judgeHasPermission(staff, props.rejectedRoleIds), ); const judgeHasPermission = (staffObj, rejectedRoleIds) => { const staffObject = staffObj; if (typeof staffObject !== 'object') return false; if (rejectedRoleIds.length === 0) return true; return ( staffObject.role && rejectedRoleIds.includes(staffObject.role.id) === false ); }; return { hasPermission, }; }, }); </script>
変更前は、this.staff
としてユーザーの情報を取得していたところを const staff = store.getters['staff/staff'];
として this
を使わないので簡単です。
setup
の中に関数をシンプルに記述できる点でも便利ですね。
今回の変更について
今回、変更したのはシンプルなコンポーネントだったため、劇的に変わった点はありませんでした😅
defineComponent()
を使った記法ではなく、<script setup />
を使った setup 構文というものを使えば、もっとシンプルな記述ができるようなので次回以降の記事で解説していきたいと思います!