初めまして!フロントエンドチーム小林です。
今回バックエンド側がAPIの改修を行ったことをきっかけに、フロントエンドも大幅に書き換え、Vue2からVue3へアップグレードも行いました。その感想をお届けします。
改修の内容
- Vue2からVue3へのアップグレード
- Vuex4へのアップグレード
- element-uiからelement-plusへの変更
- TypeScriptの導入
- 自動テストの導入
よかった点
バックエンド側からの型情報をそのまま利用できる
例えば、APIからuser_nameを取得し、表示するといった処理はよくあると思います。それだけでなく、フォームに使ったり、SessionStorageのkeyに使ったりする部分をみてみると、useNameだったり、nameだったり、個々で名前が変わっていたりします。
TypeScriptでAPI側での型を定義して、それを流用することで、プロパティ名の乱立を防ぐことができるようになりました。API側への送るパラメータが不足している場合にもすぐ気づくことができます。また、開発段階でAPIの型が変わった際にも、すぐに対応できました。
// api/account.ts (API側と通信するためのtsファイル)
interface FormBase {
password: string;
}
interface FormName extends FormBase {
name: string;
}
interface FormEmail extends FormBase {
email: string;
}
export type Login = FormName | FormEmail;
export type Register = FormName & FormEmail;
class Account {
register(params: Register) {
return axios.post(process.env.VUE_APP_API_URL + "account", params);
}
}
export default new Account();
// vueファイル
<template>
<div class="register_form" v-loading="sending.value">
<div class="message">登録する</div>
<el-form
:model="formData"
:rules="rules"
ref="ruleForm"
>
<el-form-item label="ニックネーム" prop="name">
<el-input
v-model="formData.name"
></el-input>
</el-form-item>
<el-form-item label="メールアドレス" prop="email">
<el-input
v-model="formData.email"
></el-input>
</el-form-item>
<el-form-item label="パスワード" prop="password">
<el-input
type="password"
v-model="formData.password"
></el-input>
</el-form-item>
<div class="agreement">
<el-checkbox v-model="checked">
<router-link :to="{ name: 'userProtocol' }" target="_blank"
>利用規約</router-link
>に同意する
</el-checkbox>
</div>
</el-form>
<div class="submit">
<WhiteButton
@this-click="submitForm('ruleForm')"
text="新規アカウント登録"
/>
</div>
</template>
<script lang="ts">
import { useRouter } from "vue-router";
import AccountApi from "@/api/page/account";
import CheckForm from "@/untils/form";
import WhiteButton from "@/components/WhiteButton.vue";
import { defineComponent, ref, reactive } from "@vue/runtime-core";
import { Register } from "@/api/page/account";
import { ElMessage } from "element-plus";
import { useStore } from "vuex";
export default defineComponent({
components: { WhiteButton, SubButton },
setup() {
// API側で定義したRegister型をformの型としても利用
const formData = ref<Register>({
name: "",
email: "",
password: ""
});
const submitForm = () => {
ruleForm.value.validate(valid => {
if (valid && checked.value) register();
});
};
const register = async () => {
sending.value = true;
const res = await AccountApi.register(formData.value).catch(err => {
ElMessage.error("不明なエラーが発生しました");
});
sending.value = false;
if (res) {
ElMessage.success(
"アカウント登録が完了し、登録メールアドレスを認証するためのメールを送信しました。メールアドレス認証を行ってください"
);
store.commit("logout");
}
};
});
</script>
設定ファイルにあまり書かなくて済む
以前は、cssだったらcss-loaderを使い、scssだったらscss-loaderを使い、さらにbabelでtypescript、webpackもよろしくね・・・と全部を設定ファイルに書く必要がありました。じっくり読みときながら職人芸の見せ所ではありましたが、Vue3からはインストールの際に「Typescript, Babel」を選ぶだけで一瞬で終わるようになりました。現在でも1, 2箇所記述するくらいで済んでいます。
CompositionAPIでエレガントにコードの再利用
Vue3からはCompositionAPIという新しい書き方ができるようになりました。ほかで使いたいstateやstateに関するロジックをexportし、setup関数内にimportをすると、使うことができます。例えば、テーブルに入れるデータとstatusのtoggle処理やvalidateをセットにしておく、画像アップロード処理を使い回すなどしても便利です。
下記の例では、使い回すコードを、useCardTables関数内に定義し、index.vueでuseCardTablesをprovide、display.vueでinject、分割代入することで、テンプレート内やscriptタグ内で使えるようにしています。provideした関数は、親子の関係であれば、孫ひ孫であっても、injectすれば使用できます。そのため一番の親からprovideするのがおすすめです。これまでstoreを使っていた処理も十分書き換えることができます。
// composables/use-card-tables.ts
import { ElMessage } from "element-plus";
import { computed, onMounted, reactive, ref, watch } from "vue";
export type Card = {
image_url: string;
name: string;
info?: string;
};
1;
// カード情報の管理(左側)
export default function useCardTables() {
const cardTables = reactive<Card[]>([]);
const addEmptyCard = () => {
const emptyCard: Card = {
image_url: "",
name: "",
info: ""
};
cardTables.push(emptyCard);
};
const validate = () => {
// どれかが空の場合
cardTables.forEach(card => {
if (
Object.values(card).filter(c => c).length !== Object.values(card).length
) {
ElMessage.error("画像、画像の名前、カード情報を入力してください");
return false;
}
});
return true;
};
const destroyCardAttribute = (index: number) => {
cardTables.splice(index, 1);
};
// アップロードされた画像を画面に反映する
const setImg = (index: number, image_url: string) => {
cardTables[index].image_url = image_url;
};
return {
cardTables,
addEmptyCard,
validate,
destroyCardAttribute,
setImg
};
}
// index.vue
<template>
<Display />
</template>
<script lang="ts">
import { defineComponent, provide } from "vue";
import Display from "@/views/display.vue";
import useCardTables from "@/views/composables/use-card-tables";
export default defineComponent({
components: {
Display
},
setup() {
provide("CardStore", useCardTables());
}
});
</script>
// display.vue
<template>
<div class="card_set">
<div class="center">
<div class="left">
<CardTable
v-model="cardTables"
@destroy="destroyCardAttribute"
/>
</div>
</div>
</div>
</template>
<script lang="ts">
import {
inject
} from "vue";
import { defineComponent } from "vue";
export default defineComponent({
setup(props) {
const { cardTables, addEmptyCard, validate, destroyCardAttribute } = inject(
"CardStore",
useCardTables()
);
return {
cardTables,
addEmptyCard,
validate,
destroyCardAttribute
};
}
});
</script>
悪かった点
書き方が大幅に変更
vue3になると、element-ui(Bootstrap的なもの)やVuex(ストア)などの周辺ライブラリもアップデートすることになります。その書き方がだいぶ変わっていて、書き換えが増えました。特に今回は、TypeScriptの導入もあったので、1つのエラーに対して、Vue3?TypeScript?ライブラリ?といくつも候補がある状態で、一つ一つ潰していく必要がありました。
新し過ぎて情報が少ない
検索しても古いバージョンの記事ばかり・・・
例えば、CompositionAPIで再利用を行うには、provideとinjectもセットで使う必要があるのですが、公式にも記述がなく、ひたすら彷徨い、ようやく先進的な個人ブログで記述を見つけました。
他ツールがVue3対応できていない
Nuxt.jsなどまだVue3対応できていないツールがあります。
そこら辺は待つしかないですね。
今後
Vue3、TypeScriptの導入で、再利用の幅が広がりました。
コンポーネントわけも意識しながら、バグが少なく更新しやすいプロジェクトにしていきます。