コンポーネント共通化の秘伝の方法

media thumbnail

こんにちは。株式会社Gizumoでメンターをしている木原です。
新卒で弊社に内定後、研修プログラムを経て入社しました。
その後、客先常駐で経験を積み現在は社内でメンターをしています。

さて、みなさん、コンポーネントライフいかがお過ごしでしょうか。
この記事では、VSCode を使用した Vue コンポーネントの共通化 の方法ついて紹介していきたいと思います。

前提・対象とする読者

この記事は以下を前提として読み進めてください。

  • サンプルコードはVueとVuetifyで記述
  • エディタはVSCodeを使用
  • 環境構築方法は記載していません

また、対象とする読者は以下のような方を想定しています。

  • コンポーネントという概念を理解している方
  • Vueや他のフロントエンドフレームワークのコードに目を通したことがある方

プロジェクトで経験したVueコンポーネントの共通化

以前参画していたプロジェクトで Vue コンポーネントをとにかく共通化するというタスクがありました。
共通化が必要となった経緯は、

  • フロントエンドのリポジトリが 15個 ほどある
  • 申請管理システムのプロジェクトだったのですが、40種類 ほどの申請画面において 微妙なスタイルの違い微妙な入力項目の違い があった

といった感じです。
計り知れないコンポーネントの量と先が見えない共通化設計について毎日のように話し合っていた時期がありました。

共通化が必要だと思う理由

そもそもなんで共通化が必要なのでしょう?
僕が経験したプロジェクトのように、とにかくコンポーネント数が多いということも理由の1つになるとは思いますが、他にも以下のような理由が挙げられます。

  • メンテナンス・保守性
  • Vue コンポーネントに限った話ではありませんが、共通化しておけば、仕様変更があった時や修正が必要になった時にその共通化した箇所だけを修正すればよくなる。
  • 各ファイルのコード量が減る
  • バックエンドの Laravel などのフレームワーク同様、Vue もオブジェクト指向のような感覚を個人的には持っています。
  • Laravel も class などで共通化すると、各ファイルのコード量が減って可読性も上がると思います。そういった状態をコンポーネントを共通化することで Vue でも再現できます。

共通化の方法

共通化の方法にも TPO があり、さまざまな方法があると思いますが、自分がプロジェクトで活用していた共通化の方法の1つを紹介したいと思います。

ちなみに、とてもシンプルで機械的だけど結構画期的な方法 だと思います。(笑)

それでは、共通化を試していきたいと思います!

まず、以下のようなコンポーネントがあるとします。
似ているところ、共通化できそうなところが何箇所かあるかなと思います!

※ちなみに今回は Vuetify のコンポーネントを使った共通化をしていきます。

コードはそれぞれこんな感じになります。

/components/TripCard.vue (左のカードコンポーネント)

<template>
  <v-card class="mx-auto">
    <v-img
      src="https://cdn.vuetifyjs.com/images/cards/sunshine.jpg"
      height="200px"
    ></v-img>
    <v-list-item class="grow">
      <v-list-item-avatar color="grey darken-3">
        <v-img
          class="elevation-6"
          src="https://avatars0.githubusercontent.com/u/9064066?v=4&s=460"
        ></v-img>
      </v-list-item-avatar>
      <v-list-item-content>
        <v-list-item-title>Evan You</v-list-item-title>
      </v-list-item-content>
      <v-spacer></v-spacer>
      <v-rating
        :value="1"
        dense
        small
        color="yellow darken-3"
        background-color="grey darken-1"
      ></v-rating>
    </v-list-item>
    <v-card-title> Top western road trips </v-card-title>
    <v-card-subtitle> 1,000 miles of wonder </v-card-subtitle>
    <v-card-actions>
      <v-btn color="orange lighten-2" text> Explore </v-btn>
      <v-spacer></v-spacer>
      <v-btn icon @click="show = !show">
        <v-icon>{{ show ? "mdi-chevron-up" : "mdi-chevron-down" }}</v-icon>
      </v-btn>
    </v-card-actions>
    <v-expand-transition>
      <div v-show="show">
        <v-divider></v-divider>
        <v-card-text>
          I'm a thing. But, like most politicians, he promised more than he
          could deliver. You won't have time for sleeping, soldier, not with all
          the bed making you'll be doing. Then we'll go with that data file!
          Hey, you add a one and two zeros to that or we walk! You're going to
          do his laundry? I've got to find a way to escape.
        </v-card-text>
      </div>
    </v-expand-transition>
  </v-card>
</template>

<script lang="ts">
import Vue from "vue";
export default Vue.extend({
  data() {
    return {
      show: false,
    };
  },
});
</script>

/components/SceneryCard.vue (右のコンポーネント)

<template>
  <v-card class="mx-auto">
    <v-img
      class="white--text align-end"
      height="200px"
      src="https://cdn.vuetifyjs.com/images/cards/docks.jpg"
    ></v-img>
    <v-list-item class="grow">
      <v-list-item-avatar color="grey darken-3">
        <v-avatar color="indigo">
          <v-icon dark> mdi-account-circle </v-icon>
        </v-avatar>
      </v-list-item-avatar>
      <v-list-item-content>
        <v-list-item-title>Alice</v-list-item-title>
      </v-list-item-content>
      <v-spacer></v-spacer>
      <v-rating
        :value="3"
        dense
        small
        color="yellow darken-3"
        background-color="grey darken-1"
      ></v-rating>
    </v-list-item>
    <v-card-subtitle class="pb-0"> Number 10 </v-card-subtitle>
    <v-card-text class="text--primary">
      <div>Whitehaven Beach</div>
      <div>Whitsunday Island, Whitsunday Islands</div>
    </v-card-text>
    <v-card-actions>
      <v-text-field placeholder="Input send message"></v-text-field>
      <v-btn text color="orange" class="ml-auto">Send</v-btn>
    </v-card-actions>
  </v-card>
</template>

<script lang="ts">
import Vue from "vue";
export default Vue.extend({});
</script>

この2つのコンポーネントを vscode の「比較機能」 を使って比較します。

1. 共通化したいファイルの1つ目のファイルを右クリックし、「比較対象の選択」をクリック

2. 共通化したいファイルの2つ目のファイルを右クリックし、「選択項目と比較」をクリック

3. エディタが以下のようになります。表示されたコードの 赤 と 青 の部分以外のコードが共通しているコードということになります。

4. 新しい /components/CommonCard.vue というようなコンポーネントを作成して、以下の画像のように3の状態のものを右に配置しながら共通しているコードの部分だけ、/components/CommonCard.vue にコピペしていきます。共通していない 赤 と 青 の部分は、slotprops などを使って、/components/CommonCard.vue で可変な状態にします。

今回の場合、/components/CommonCard.vue は以下のようになります。

<template>
  <v-card class="mx-auto">
    <v-img height="200px" :class="imgClass" :src="imgUrl"></v-img>
    <v-list-item class="grow">
      <v-list-item-avatar color="grey darken-3">
        <slot name="avatar"></slot>
      </v-list-item-avatar>
      <v-list-item-content>
        <v-list-item-title>{{ name }}</v-list-item-title>
      </v-list-item-content>
      <v-spacer></v-spacer>
      <v-rating
        :value="rating"
        dense
        small
        color="yellow darken-3"
        background-color="grey darken-1"
      ></v-rating>
    </v-list-item>
    <slot name="content"></slot>
    <v-card-actions>
      <slot name="actions"></slot>
    </v-card-actions>
    <slot name="footer"></slot>
  </v-card>
</template>

<script lang="ts">
import Vue from "vue";

export default Vue.extend({
  props: {
    imgClass: { type: String },
    imgUrl: { type: String },
    name: { type: String },
    rating: { type: Number },
  },
});
</script>

5. 共通化したかった元の2ファイルのコードを再度比較して、最低限必要なコード以外の部分が 赤 と 青 だけになっていれば共通化は成功です!

最後に

  • 今回は、簡単なコンポーネント同士の共通化だったので、コード量的にもそこまで変わりはなかったのですが、実際のプロジェクトなどで扱うコード量が多いようなコンポーネント同士の共通化にはとても役立つ方法になると思います。
  • 共通化をすることで次も同じようなコンポーネントを作る際に、差分があるところだけを書けばいいので楽になるかなと思います。
  • 仕様変更があっても1つのコンポーネントだけを気にすればいいというのも、実際にプロジェクトで共通化のタスクを行なっている際に感じることができました。
  • 僕はまだ試せていませんが、他の言語でもできるやり方だと思うので是非試してみてください!

少しでも開発にお困りの方は
相談しやすいスペシャリストにお問い合わせください

お問い合わせ
  1. breadcrumb-logo
  2. メディア
  3. コンポーネント共通化の秘伝の方法