Claudeくんが頭いい型定義を教えてくれたので共有。
↓のような型定義があるとき、
type AB = "A" | "B";
↓のような型を定義したい。どうするか。
"AA" | "BB"
真っ先に思いつくのは↓の形。
type AABB = `${AB}${AB}`;
しかし、これは実際には "A"
と "B"
の組み合わせになってしまう↓。
ではどうするかというと、↓のようにすればよい。
type Hoge<T extends string> = T extends any ? `${T}${T}` : never;
type AABB = Hoge<AB>;
ポイントは↓。
-
T extends any
が必ずtrue
になる - ↑の
T
が"A"
or"B"
のいずれかが選ばれた状態で、${T}${T}
により2連続で並ぶことが表現できる
以上、TypeScriptは奥が深いね〜って話でした。
余談
この話の使い所はAIお絵かき用の自作ツール。
"red" | "blue" | "green"
のような色を定義したUnion型と、
別途指定した文字列型を組み合わせて、
"red hair ribbon -> red ribbon"
"blue hair ribbon -> blue ribbon"
"green hair ribbon -> green ribbon"
のような文字列を型として定義したい。
そんなときにClaudeくんがこの記事のノウハウを教えてくれた。
実際のコードは↓のようなもの。
export type ColorPalette = "default" | "unchi";
export const COLOR_PALETTE = {
default: [
"aqua",
"black",
"blue",
"brown",
"green",
"grey",
"orange",
"pink",
"purple",
"red",
"white",
"yellow",
],
unchi: ["💩"],
} as const satisfies Record<ColorPalette, readonly string[]>;
type Color<T extends ColorPalette> = (typeof COLOR_PALETTE)[T][number];
type AliasableColorTag<
T extends Color<ColorPalette>,
U extends string,
V extends string,
> = T extends any ? `${T} ${U} -> ${T} ${V}` : never;
export const generateColorAliasableTagTable = <
T extends string,
U extends string,
V extends ColorPalette,
W extends object,
>(
fromTag: T,
toTag: U,
colorPalette: V,
obj: W,
) => {
type C = Color<V>;
const entries = COLOR_PALETTE[colorPalette].map((color: C) => {
return [
`${color} ${fromTag} -> ${color} ${toTag}` as AliasableColorTag<C, T, U>,
{ ...obj, to: `${color} ${toTag}` },
] as const;
});
return ObjectModule.fromEntries(entries); // これはTSの型推論が💩だから作った自作のオブジェクト操作用モジュール
};