Reactのpropsをunion typeにする方法について
Reactのpropsをunion typeにする方法をいくつか紹介する。ただし、これらの利用やそもそもunion typeをpropsに使うこと自体が適切かどうかは慎重に検討する必要がある。
Table of Contents
Union types of props
複雑なサービスを作っていると、Union typesを1つのメソッドで処理したくなることがあるかもしれない。しかし安直に書いてしまうと上手くいかない。このような型定義にライブラリで出会ってしまうと最悪な気持ちになる。
export type Props =
| {
text: Item;
}
| {
multilineText: Item;
}
| {
select: Item;
};
export const SomeComponent = (props: Props) => {
return (
<div>
{props?.text ? (
<div>{props?.text.value}</div>
) : props?.multilineText ? (
<div>{props?.multilineText.value}</div>
) : props?.select ? (
<div>{props?.select.value}</div>
) : null}
</div>
);
};
Kind pattern
各型に共通のプロパティを持たせ、それぞれの値を固有のLiteral typeなどにするパターン。
このプロパティを比較することで、どの型を利用するかの識別が可能になる。
export type Props =
| {
kind: "text";
text: Item;
}
| {
kind: "multilineText";
multilineText: Item;
}
| {
kind: "select";
select: Item;
};
const SomeComponent = (props: Props) => {
return (
<div>
{props?.kind === "text" ? (
<div>{props?.text.value}</div>
) : props?.kind === "multilineText" ? (
<div>{props?.multilineText.value}</div>
) : props?.kind === "select" ? (
<div>{props?.select.value}</div>
) : null}
</div>
);
};
Optional never Trick
次のように、擬似的に全ての型へ同様のプロパティを持たせることで対応できる。
このパターンの場合、レスポンスなどで受け取ったオブジェクトを編集しなくていい場合があるというメリットと、プロパティを列挙しなければならないというデメリットがある。
export type Props =
| {
text: Item;
multilineText?: never;
select?: never;
}
| {
text?: never;
multilineText: Item;
select?: never;
}
| {
text?: never;
multilineText?: never;
select: Item;
}
| {
text?: never;
multilineText?: never;
select?: never;
};
const SomeComponent = (props: Props) => {
return (
<div>
{props?.text ? (
<div>{props?.text.value}</div>
) : props?.multilineText ? (
<div>{props?.multilineText.value}</div>
) : props?.select ? (
<div>{props?.select.value}</div>
) : null}
</div>
);
};
次のようなUtility typeを生やしておけば、デメリットを解消できる。
type XOR<
T extends Record<string, unknown>,
U extends string = T extends T ? keyof T : never,
> = T extends T
? {
[k in keyof T]: T[k];
} & {
[k in Exclude<U, keyof T>]?: never;
}
: never;
export type Props = XOR<
| {
text: Item;
}
| {
multilineText: Item;
}
| {
select: Item;
}
>;
export const SomeComponent = (props: Props) => {
return (
<div>
{props?.text ? (
<div>{props?.text.value}</div>
) : props?.multilineText ? (
<div>{props?.multilineText.value}</div>
) : props?.select ? (
<div>{props?.select.value}</div>
) : null}
</div>
);
};
Utility types of TypeScript
https://www.typescriptlang.org/docs/handbook/utility-types.html
TypeScriptには便利なユーティリティタイプがいくつか同梱されている。
個人的に最低限知っておいた方がいいと思うものを列挙する。
- Required