LayerX エンジニアブログ

LayerX の エンジニアブログです。

BootstrapVueのmodal満足してる?

こんにちは!LayerXバクラク事業部エンジニアのcocoです(@coco_tyw)。 私は内定者インターンとしてバクラク請求書チームにコミットしています。 本記事ではBootstrapVueのmodal(b-modal)のちょっとした改善を提案したいと思います。

※本記事は LayerX Tech Advent Calendar 2022 5日目の記事となります。

背景

私が所属しているバクラク請求書チームではフロントエンドの開発速度を高めるためにVueのUIフレームワークとしてBootstrapVueを使用しています。 そのBootstrapVueに含まれるmodalを表現するb-modalコンポーネントですが、使い方に悩む部分がいくつかあります。 b-modalのドキュメントの最上部にある以下のコードを例に取ると

<div>
  <b-button v-b-modal.modal-1>Launch demo modal</b-button>

  <b-modal id="modal-1" title="BootstrapVue">
    <p class="my-4">Hello from modal!</p>
  </b-modal>
</div>
  • template部分の適応した位置にcomponentの内容が表示されるわけではなくページの最上部に表示されるため書いたtemplateを直感的に読み取れない。
  • vue3以前ではコンポーネントのtemplate部分のrootは1つの要素で表現されなければならないので、divなどの要素で囲む必要があり、このコンポーネントを使用した場合、本来必要ないdivが描画されてしまう
  • modal-1というidをuniqueに、modalを表示しているが、このidをどうuniqueにたもつのか
  • このmodalの表示に関わっているコードを調べたいときはIDEの全体検索でmodal-1の文字列を探しますか?

この例から読み取れるものだとこのくらいでしょうか。 プロダクションで使っていると使い方に悩んでしまう部分は出てきてしまうものですよね。 こういった、むず痒いところに対する解決策を提案するのが本記事の目的です。

やったこと

github.com こちらのコードを書いてみました。useModalです。 早速ですが使用例から説明していきます。

useModalの使用例

まずmodal単位でコンポーネント分けします。 これはpropsでtitileを受け取り、それを表示するためのシンプルなコンポーネントです。 例で使用するのは簡素なものですが、TagCreateModalなどの名前でapiリクエストまで含めたformのmodalなどがあると便利そうですね。

# TestModal
<template>
  <b-modal :title="title">
  </b-modal>
</template>

<script lang="ts">
import {defineComponent} from 'vue';
export default defineComponent({
  props: {
    title: {
      type: String,
      required: true
    }
  }
})
</script>

<style scoped>
</style>

次は使用する側のコンポーネントです。今回書いたuseModalという関数を用いて、先ほど定義したmodalを使用します。 useModalのinterfaceが以下のようになっており、第一引数では使用するmodalのコンポーネント、第二引数では第一引数で指定したmodalのprops, 第三引数では第一引数で指定したmodalのlistenersを入れます。

export const useModal = <T>(
  ModalComponent: T,
  props: GetProps<T>,
  modalListeners?: ModalListeners
) => {

具体的には以下のように利用します。

<template>
  <button @click="onClickShow">
    show
  </button>
</template>

<script lang="ts">
import {defineComponent, getCurrentInstance} from 'vue';
import {useModal} from '@/plugins/programmatic-modal';
import TestModal from '@/components/modals/TestModal.vue';
export default defineComponent({
  setup(props, ctx) {
    const modal = useModal(TestModal, {
      title: 'this is programmatic!!'
    })
    const onClickShow = () => {
      modal.show()
    }
    return {
      onClickShow,
    }
  }
})
</script>

いかがですか?背景のところで挙げた悩みポイントが解消されているかと思います。 具体的には以下です。

  • template部分を見たときにボタンのみが表示されることがわかる
  • modalを使用するために不要なdomが生成されていない
  • idのunique管理が不要になっている
  • modalの使用箇所、表示・非表示に関するコードの検索はIDEの参照検索で行うことができる

さらに、modalに適応するprops, listenersで型の恩恵が得られるなどのメリットもあります。 それではどのようにしてこれらのことが実現できているか説明していきます。

props, listenersの型

composition-apiではgenericsを使用することでcomponentのpropsの型を抜き出すことができます。 この型を使用することでscript部分で型安全にpropsを使用することができます。

export type GetProps<T> = T extends new () => {
  $props: infer Props;
} ? Props : never

これに関してはこの記事がわかりやすいかと思います。 logaretm.com listenersの型はb-modalのドキュメントに従って自分で定義しました。

type ModalListeners = Partial<{
  cancel: (e: BvModalEvent) => void
  change: (isVisible: boolean) => void
  close: (e: BvModalEvent) => void
  hidden: (e: BvModalEvent) => void
  hide: (e: BvModalEvent) => void
  ok: (e: BvModalEvent) => void
  show: (e: BvModalEvent) => void
  shown: (e: BvModalEvent) => void
}>

modalの表示・非表示の制御

b-modalは表示・非表示を制御するためのmethodを外部に公開しているためそれらを利用しました。 このようにしてjavascriptのmethodをそのまま利用することでtemplate部分を汚染せずにmodalの表示・非表示を制御できるようになりますし、IDEによる検索もmethodの参照を辿るだけです。

まとめ

いかがだったでしょうか?もう少しブラッシュアップすればプロダクション環境でも使用できそうです。 このようにエンジニアリングは改善できる部分で溢れています。

LayerXのインターンに興味がある方や、もっと具体的な内容に興味があるという方がいらっしゃいましたら、下記のリンクからカジュアル面談のご応募お待ちしております!

open.talentio.com

jobs.layerx.co.jp