🔖

UIフレームワーク依存に立ち向かうための、Vue.js コンポーネント実装

2021/07/21に公開

UIフレームワーク依存に立ち向かうための、Vue.js コンポーネント実装

TL;DR

  • iCAREのフロントエンド開発では、通常プロジェクトとは別軸でデザインシステムPJを進行
  • 基本はデザインの共通化、統一化がメインで、UIフレームワークを交換可能にするコンポーネントも併せて作成している

Vue.jsにおけるUIフレームワークとは?

Vue.jsを拡張した、コンポーネントを提供するフレームワーク

  • Vuetify
  • BootstrapVue
  • Bulma

※ BootstrapやTailwind、Bulma等はCSSフレームワークの位置付け

Buefyの依存から脱却したい...

  • やめ太郎さんのQiitaに以前、ライブラリのラッパーについての記事がありました

ワイ「なに!?ライブラリをラップするやと!?」 - Qiita

  • iCAREのフロントエンドとしては、Buefyを使い続けるかどうかの議論は常に行われていて、いつでもBuefyから別のUIフレームワークまたはCSSフレームワークに乗り換えが行えるように、 交換可能なコンポーネントなコンポーネントの実装を行っています

交換可能なコンポーネントとは?

  • 再利用可能であること(Interchangeable)
  • UIフレームワークに依存しない形で、実装するコンポーネント(Reusable)
  • 併せて、Interchangeable & Reusable components?
  • 要は、導入しているUIフレームワークを移行したい場合でも、大きなコストを掛けずに移行できるコンポーネント

どうやって実装するの?

  • BuefyChart.js のコンポーネントをラップする要領でコンポーネントを実装する
  • コンポーネントを呼び出すたびに、指定するオプションなどのpropsをまとめて指定できるように実装する

モチベーション

  • BuefyもChart.jsもいつまで使うかわからない
  • Vue3へバージョンアップさせたいけど、Buefyの対応に左右される

といったペインを解決したい

このコンポーネントはデザインシステム化の一環であり、全体的なデザインの共通化にも繋がる

交換可能なモーダルコンポーネント

  • 画像のようなモーダルを実装する
  • Buefyのb-modalを利用して、ラッパーコンポーネントを実装

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/48434560-49c4-406c-97a4-8727e282fa87/Untitled.png

イメージ図

  • アイデアをホワイトボードに書き出してみる
  • メモの新規作成と編集で使い回すので削除は非表示の可能性あり

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/21b2b948-33b2-428d-9f8c-b7491984ee1c/Untitled.png

モーダルコンポーネントの実装

carely-modal.vue

<template>
  <div class="modal-card">
    <slot name="header" />
    <slot name="content" />
    <footer class="modal-footer">
      <slot name="footer-left" />
      <slot name="footer-right" />
    </footer>
  </div>
</template>

<style lang="scss" scoped>
.modal-card {
  width: 970px;
  height: 700px;
}
.modal-footer {
  justify-content: space-between;
}

.footer-right-container {
  margin-left: auto;
}
</style>

parent.vue

<template>
  <carely-modal>
    <template #header>
      <carely-modal-header title="タイトル" @close="close" />
    </template>
    <template #content>
      <b-field label="メモ">
        <b-input v-model="memo" type="textarea" />
      </b-field>
    </template>
    <template #footer-left>
      <b-button type="is-danger" class="width-100" @click="delete">
				削除
			</b-button>
    </template>
    <template #footer-right>
			<-- TODO: carely-modal-footerを作る -->
      <b-button type="is-text" class="width-100" @click="close">キャンセル</b-button>
      <b-button type="is-primary" class="width-100" @click="save">保存</b-button>
    </template>
  </carely-modal>
</template>

交換可能なChartコンポーネント

Chart.jsのラッパーコンポーネントを作る

  • Chart.js には Vueコンポーネントとして拡張されたvue-chartjsがある
  • 各チャートタイプを呼び出すのではなく、タイプごとにVue インスタンス作成するコンポーネントを実装

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/88bc8fa3-51af-445b-b154-1a3b2d81d16f/Untitled.png

carely-chart.ts

import AdditionalChartContent from '@/component/page/stress_check/surveys/additional-chart-content.vue'
import { InsidePropsType, OutsidePropsType } from '@icare-jp/vue-props-type'
import { defineComponent } from '@vue/composition-api'
import { HorizontalBar, Scatter } from 'vue-chartjs'

const chartTypeConverter = (chartType: ChartType) => {
  // ここにtypeを追加する
  switch (chartType) {
    case 'Horizontal':
      return HorizontalBar // コンポーネント名
    case 'Scatter':
      return Scatter
    default:
      throw new Error('chartType does not exist.')
  }
}

const chartGenerator = (chartType: ChartType) =>
  defineComponent({
    components: { AdditionalChartContent },
    extends: chartTypeConverter(chartType),
    props: {
		  chartOptions: {
		    type: Object,
		    required: true,
		  },
		  height: {
		    type: Number,
		    required: false,
		    default: null,
		  },
		  styles: {
		    type: Object,
		    required: false,
		    default: () => ({ width: '100%', height: '280px' }),
		  },
		},
    mounted() {
      this.renderChart(this.chartOptions.data, this.chartOptions.options)
    },
  })

// ここでチャートの種類を増やす
export const CarelyHorizontalChart = chartGenerator('Horizontal') // 横棒
export const CarelyScatterChart = chartGenerator('Scatter') // 散布図
  • extendsもしくはmixinでチャートのタイプを指定するため、今回はReduxのAction Typeのように、文字列をchartGeneratorの引数に渡し、extendsでchartTypeConverterでチャートタイプ名を返すようにする

Discussion