Open4
TSの知見(自分が思い出す用)
コンストラクタで初期化するときのオプションの中で特定のプロパティのみをクラス全体の型にしたい場合のTIPS
interface HogeOptions<S> {
fuga: S;
bar: Bar<S>;
}
class Hoge<S, T extends HogeOption<S>> {
constructor(option: T);
}
上記の様な定義があったときに、普通に下記のように呼び出しちゃうと第一型引数がunknownになってしまう。
new Hoge({
fuga: "aaaa"
bar: "aaaa"
})
理由としてはTの定義だけではSを推論できないため
これを後方互換を保ちつつSの型を推論するには、仮型パラメータを用意するのがいい
class Hoge<
_,
T extends HogeOption<_>,
S = T["fuga"] extends _ ? T["fuga"] : _
> {
constructor(option: T);
}
こうすると、Hogeの第一引数に型を定義した場合は _
がそのまま S
にアサインされ、constructorで型付きで指定した場合はその引数のパラメータから型推論してSがアサインされる形になる
MappedTypeを使った、Vuex風のオブジェクト平坦化の方法
const s = {
modules: {
foo: {
modules: {
hoge: {
actions: {
bar: (n: number) => "aaa",
},
},
},
},
bar: {
actions: {
bal: (x: string) => "aaa",
},
},
},
};
みたいなのがあったときに
{
"bar/bal": (x: string) => string,
"foo/hoge/bar": (n: number) => string
}
みたいに変換したいときのTIPS。
ここで必要になるのは下記3点
- Conditional Typesを使った、再帰的な展開
- MappedTypesのKey Remappingを使った、Keyの文字列結合
- UnionからIntersectionに変換する型
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
type ExtractObject<
T,
S extends string = "actions",
P extends string = ""
> = (T extends {
[_ in S]: infer O;
}
? { [K in keyof O as `${P}${string & K}`]: O[K] }
: {}) &
(T extends {
modules: infer O;
}
? UnionToIntersection<
{
[K in keyof O]: ExtractObject<O[K], S, `${P}${string & K}/`>;
}[keyof O]
>
: {});
を作ると平坦化できる。
参考になります。便利ですね!
おお良かったです!解説しようと思ったものの、書いてたらめんどくなってしまった…