Vue3&TypeScript導入してよかった点悪かった点

お知らせ
Table of Contents

初めまして!フロントエンドチーム小林です。
今回バックエンド側が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の導入で、再利用の幅が広がりました。
コンポーネントわけも意識しながら、バグが少なく更新しやすいプロジェクトにしていきます。

タイトルとURLをコピーしました