かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

TypeScript type を & でいい感じにマージしたい。

TypeScript はちゃんと学習していないのですが、関わっているプロダクトが ts, tsx なので触る時に雰囲気で使っています。

今回はあるコンポーネントをラップするコンポーネントを作成していて、バックエンドから渡される値が、ラップするコンポーネントに定義されているPropsと異なっていて、ラップする側で足りない場合はデフォルト値を設定したいという感じでした。
👇こんなイメージ

// ラップするコンポーネントの Props の型
type ModalProps = {
  modalMode: string;
  ...
  returnPath: string;
};

// ラップするコンポーネントの Props の型をこうしたい
type ModalButtonProps = {
  label?: string;
  className?: string;
  // 以下ラップするコンポーネントの Props
  modalMode: string;
  …
  returnPath?: string;
}

ラップするコンポーネントの type を export して returnPath の type を上書きする感じでマージしたい
type {} で囲われてるし JS のオブジェクトのマージの感覚で次のように型定義をしたのですが意図したとおりにはなりませんでした。

import { ModalProps } from './Modal';

type ModalButtonProps = ModalProps & {
  label?: string;
  className?: string;
  returnPath?: string; // 上書きしたい type
}

後に & で繋げば、後に書かれている方が優先されるのかな?と思ったのですが、returnPath が無い Props を受け取ると returnPath: string だと lint に怒られてしまいました。type ModalButtonProps = {...} & ModalProps と逆にしてみても同じエラーに…

& はマージではなく intersection型

チームの人に訊いたら & はマージではなく intersection という型で「積」だから A & BB & A も同じ結果になると教えてもらいました。これだけでは理解しきれなかったので調べてみた所 intersection というのは「 A かつ B 」という意味ということでした。
(画像で思考するタイプなのでこの図をみてやっと理解できました)

つまり先の例ではで同じキーである returnPath の型は「returnPath: string; かつ returnPath?: string;」という状態を指し、両方を満たすケースは 「returnPath: string;」の時だけとなるので、returnPath を渡さないとこの条件に合わないのでエラーとなっていた。という事のようでした!

完 全 理 解 !

Omit で 特定の型を取り除く

Omit

type Omit<T, K extends keyof any>
型 T の中から、キー名が K に当てはまるプロパティを除外した新しい型を返します。なお、Pick とは異なり K には T のキー名以外を指定することができます。
cf. TypeScript特有の組み込み型関数 - log.pocka.io / Announcing TypeScript 3.5 | TypeScript

今回は元々ある A: stringA?:string にしたい。他にも型を追加したいという感じだったので、Omit を使って元の型から A: string を除き & でくっつけるというのが良さそうです。

型の指定を次のように変更したら意図したとおりになりました

// Modal.tsx
export type ModalProps = {
  modalMode: string;
  returnPath: string;
};

// Button.tsx
import { ModalProps } from './Modal';

type ModalButtonProps = Omit<ModalProps, 'returnPath'> & {
  label?: string;
  className?: string;
  returnPath?: string;
}

ModalButtonProps は次のような型として解釈されます

type ModalButtonProps = {
  modalMode: string;
  label?: string;
  className?: string;
  returnPath?: string;
}

タイトルの マージ というのとは少し違いますが、今回はこの方法でマージっぽく型をつくる事ができました。

感想

学習時間を取れずプロダクトにあるコードを実践で見様見真似で触っていたので、&| の意味を勘違いしていたことがわかり、その意味を調べて知ることができてよかったです。

が、他にも typeinterface や色々な指定方法があって雰囲気で使うには JavaScript でどう作れば良いのかは分かってるけど、TypeScript にしたらプロダクトの本質ではない型宣言が知識不足でハマってしまい時間がかかってしまう状態というのが非常にストレスなので、そろそろちゃんと学習せねばという気になりかけています。自分の能力不足だと認識している問題ははストレス大きいですからね…


[参考]

実践TypeScript ~	BFFとNext.js&Nuxt.jsの型定義~

実践TypeScript ~ BFFとNext.js&Nuxt.jsの型定義~