🗂️

判別可能なUnion型を使用してコンポーネントのバージョン管理をする

2022/09/05に公開

判別可能なUnion型とは

英語で言うところのDiscriminating Unionsです。
詳細な説明は以下のTypeScript Deep Dive 日本語版に譲りたいと思います。
https://typescript-jp.gitbook.io/deep-dive/type-system/discriminated-unions

モチベーション

そもそものコンポーネントのバージョン管理をするモチベーションとしては以下を想定しています。

  • バージョンの概念を持つドメインを表示するコンポーネントがある
  • バージョンが進んだ場合も過去のバージョンはその時点のデザインで表示したい

といったものです。
バックエンドやBFFで何とかするという方法もありそう(そちらの方が正攻法ではありそう)ですが、今回はフロントエンドでコンポーネントのバージョン管理をするという方法を考えてみます。

実装例

バージョンをリテラル型で表現する

今回はnumberで表現します。

export type QuestionnaireV1 = {
  version: 1
  name: string
  body: string
}

export type QuestionnaireV2 = {
  version: 2
  name: string
  age: number
  body: string
  remarks: string
}

export type Questionnaire = QuestionnaireV1 | QuestionnaireV2

バージョンに対応したコンポーネントを作成する

type QuestionnairePageV1Props = {
  questionnaire: QuestionnaireV1
}

export const QuestionnairePageV1 = ({
  questionnaire,
}: QuestionnairePageV1Props) => {
  return (
    <div>
      <div>{questionnaire.name}</div>
      <div>{questionnaire.body}</div>
    </div>
  )
}

type QuestionnairePageV2Props = {
  questionnaire: QuestionnaireV2
}

export const QuestionnairePageV2 = ({
  questionnaire,
}: QuestionnairePageV2Props) => {
  return (
    <div>
      <div>{questionnaire.name}</div>
      <div>{questionnaire.age}</div>
      <div>{questionnaire.body}</div>
      <div>{questionnaire.remarks}</div>
    </div>
  )
}

バージョンに対応したコンポーネントを返すコンポーネントを作成する

type QuestionnairePageProps = {
  questionnaire: Questionnaire
}

const QuestionnairePage = ({
  questionnaire,
}: QuestionnairePageProps) => {
  switch (questionnaire.version) {
    case 1: {
      return (
        // questionnaireの型はQuestionnaireV1に絞られる
        <QuestionnairePageV1 questionnaire={questionnaire} />
      )
    }
    case 2: {
      return (
        // questionnaireの型はQuestionnaireV2に絞られる
        <QuestionnairePageV2 questionnaire={questionnaire} />
      )
    }
    default: {
      const invalidVersion: never = questionnaire.version
      throw new Error("Invalid questionnaire.version")
    }
  }
}

おわりに

バージョンに対応したコンポーネントを作成するのところでcomponents/questionnaire/v1/index.tsxcomponents/questionnaire/v2/index.tsxのようなディレクトリ構成にして、それぞれをStorybookに登録するとバージョンごとのデザインの確認も簡単にできてよいと思います。

バージョン管理以外でも判別可能なUnion型を使用して型を絞り込むテクニックはよく見るので覚えておいて損はないかもしれません。

Discussion