Web猫ブログ
MPAとして導入するVueのサービス設計を考える
09月 17日

MPAとしてVueを導入する

最近はReact/Serverlessのお仕事と並行して、CakePHPにVueを導入するお仕事を進めてます。既に一部リリース済みですが、既存のような新規案件。先日その導入編について書かせていただきました。

https://webneko.dev/posts/vue-config-and-more

後々になって変えたいと言われると、

いよいよMPAとしてVueを導入するぞ!、ということでまずはTypeScriptを選定するか否か。

結論は Yes!!!

プロジェクト内で実質自分1人しか、TypeScriptを理解していないが。。?

勉強してね、ことで mm

Lintを考える

早速 .eslintrc.js の初期設定では以下参考にさせていただきました🙏

@typescript-eslint ことはじめ

最初から黄金比率 (とまではいかないですけど) を求めなくても良いと思います。

module.exports = {
    extends: [
        'plugin:vue/essential',
        'plugin:prettier/recommended',
        '@vue/typescript'
    ],
    plugins: ['@typescript-eslint']
    parserOptions: {
        sourceType: 'module',
        parser: '@typescript-eslint/parser'
    }
}

ESLint+Prettierの組み合わせに加え、一番のキモは eslint-config-standard も継承すること。PRでのレビューを受け設定させていただきました。

  • eslint-config-standard
  • eslint-plugin-node
  • eslint-plugin-prettier
  • eslint-plugin-promise
  • eslint-plugin-standard

細々としたルールの記述が減り、よりメンテし易くなったと思います。

Gitlab-CIではベーシックに eslint コマンドに限定。

npx eslint ./ --ext vue,js,ts

プロジェクト内では tsc--noEmit を付けて(トランスパイルの結果JavaScriptには出力しない)ようにしました。

npx tsc --noEmit

順序立てて型安全を追求しましょう

なんせ、プロジェクト内で実質自分1人しか、TypeScriptを理解していないので。。笑

これを踏まえて、

  1. エンドポイントを interface で定義
  2. その他最初は (分かんなかったら) Anyで対応

エンドポイントを interface で定義

全てはサーバサイドとの通信を安全に完結させるため。通信処理を一つのmoduleとして切り分け、必要に応じてこのmoduleをインポートして返却値を厳密に管理します。

export default class SummaryService {
    public async fetchSummary(
        ids: string[],
        person: string,
        createdAt: string
    ): Promise<SummaryEndpoint> {
        const params = getParams(ids, person, createdAt)
        const res = await axios.get(`/summary?${params}`)
        return res.data
    }
}

今回MPAとしてVueを導入(将来的にはSPAとして一括導入を検討)しているが、画面全体でデータを管理する必要は無く vuex を一切採用していません。ちなみに Vue.observable を使ってアカウントステータスを管理していますが、今回は話が長くなるためまた後日共有させてください..🙏

上記のように非同期通信を最大限活用、Promise.allほんと便利!

mounted() {
    Promise.all([
        this.fetchSummaryA(),
        this.fetchSummaryB(),
        this.fetchSummaryC(),
        this.fetchSummaryD()
    ])
}

あとテーブルやグラフチャート用にデータを加工する訳ですが、必要に応じて props を渡します。

親コンポーネントから孫コンポーネントのように 2世代以上を超えて引数を渡すといったことの無いような設計を堅持しています。

最初はAnyで対応

おいおい自力で型定義を書ければ良いでしょう。

基本的にエンドポイントの型定義を書けると、特にプラグインをインストールしない限り問題無いと思います。今回トーストUIを別途入れていますが、流石に自力で型定義を書かなければいけません。

declare module "vue-toasted" {
    import Vue, { PluginFunction } from "vue";
    function install(): PluginFunction<any>;
    export interface VueToasted {
        show(message: string): void;
    }
}

コンポーネントで既存のCSSと競合..!?

実際にコンポーネントでは services にアクセスするための加工済データをローカルステートとして管理。こうしたコンポーネント化を進めるに当たって新たに導入検討を進めていた bootstrap-vue の存在。

想定と比べて深刻 (かも)

ボタンの背景色が表示されない、モーダルが動かない事象が発生。 Vue CLI内では Scoped CSS を採用、それはあくまで Vue Component内の話。既存のCSSはグローバルに書かれており、競合が発生するのも納得。良くも悪くも新規ではなく既存案件であることを痛感した場面の一つでした。

CSSのベタ書きに変更。今回は一例にモーダルを自作コンポーネント化しました。

<atoms-button
    ref="branchSelectButton"
    :text="title"
    @handleClick="displayModal"
></atoms-button>
<div v-if="showModal === true">
    <div class="modal-mask" @click.self="showModal = false">
        <div :style="modalWrapperStyle">
            <div class="modal-container">

            </div>
        </div>
    </div>
</div>

基本的な考え方としてボタンをタップした時に showModal フラグを true にする一方で、マスク領域をタップすると showModal フラグを false にしています。

最後に、

今回は上記の内容に加え v-model を使わないと言った、気付きを中心に v-kansai #10 でも喋らせていただいてます。

小さなことから改善してる話

今後も必要に応じて共有していきたいと思います。

コメントを残す

タイトル
メールアドレス
詳細

あわせてよみたい..

-----