ナメやがってこの型ァ!!超イラつくぜぇ~~~~~ッ!!
TypeScriptのイラつく型について書くぜぇ~
Lookup Type
まずはこれだァ
const colors = ["赤","青"] as const;
type Colors = typeof colors[number]; //"赤" | "青"
typeof colors[number]
・・・ってよォ~~~~~
typeof
はわかる。スゲーよくわかる
TypeScritの世界では、typeof
で変数から型を作れるんだよなァ
だがnumber
ってのはどういう事だああ~~~~っ!?
配列にnumber
を渡せるかっつーのよ───────ッ!!
ナメやがってこの型ァ!! 超イラつくぜぇ~~~~ッ!!
解説
これはよォ
「Lookup Type」ってやつらしいぜぇ
たとえばよォ
👇みたいに書くとPerson
型からプロパティの型を取り出せるよなァ~??
type Person = {
name: string,
age: number
}
type name = Person["name"]; // string
type nameAndAge = Person["name" | "age"]; // string | number
こういうのを「Lookup Type」って呼ぶらしいんだけどよォ
「Lookup Type」を配列に応用するとよォ
👇みたいな感じで考えられるぜぇ
const colors = ["赤","青"] as const;
type Colors1 = typeof colors[0 | 1]; //"赤" | "青"
type Colors2 = typeof colors[number]; //"赤" | "青"
要するに、number
って書くとユニオン(0 | 1
)に展開されるってイメージだなァ
keyof
次はこれだァ
const Person = {
name: "yamada",
age: 50
}
type PersonProperty = keyof typeof Person; //"name" | "age"
keyof
は「プロパティのキーをユニオンとして取り出す」ってだけだぜぇ
たとえばこのコードは、keyof
だけで書くと👇みたいになるぜぇ
type Person = {
name: string,
age: number
}
type PersonProperty = keyof Person; //"name" | "age"
分解して考えると簡単じゃねぇかァ──────ッ!!
なめてんのかァ───ッ!!このオレをッ!!
クソッ!!クソッ!!クソッ!!
コールシグネチャ
次はこれだァ
type Add = {
bonus: number;
(): number;
};
type Add
・・・ってよォ~~~~~
bonus
はわかる。スゲーよくわかる
Add
というオブジェクトがあって、プロパティとしてbonus
を持ってるって意味だよなァ~~
だが(): number
ってのはどういう事だああ~~~~っ!?
プロパティに無名関数を渡せるかっつーのよ───────ッ!!
ナメやがってこの型ァ!! 超イラつくぜぇ~~~~ッ!!
解説
結論からいうと、これは「コールシグネチャ」っていう書き方だぜぇ
たとえばよォ~
以下のサンプルコードのadd
がtype Add
に該当するぜぇ
function add(a: number, b: number) {
return a + b + (add.bonus ?? 0);
}
add.bonus = 100;
console.log(add(1, 2)); // 103
add
は「プロパティを持ってるし実行もできるぜ」なオブジェクトになってるよなァ~?
こういう「実行もできるぜ」なオブジェクトを表現したいときによォ
オブジェクト内に無名関数を書くと「実行もできるぜ」を表現できるってわけだァ
んで、この書き方のことを「コールシグネチャ」って呼ぶらしいぜぇ
でもオレはぜーんぜん納得いってねえぜぇ~
なんなんだよ『コールシグネチャ』って名前はよォ~~~ッ!!
なめてんのかァ───ッ!!このオレをッ!!
日本語で呼べ!! 日本語で!!
「実行もできるぜ記法」って呼べ!! チクショオ──────ッ!!
extends
次はこれだァ
type DarkModeCheck<T extends boolean> = T extends true ? "dark" : "light";
type LightMode = DarkModeCheck<false>; // "light"
type DarkMode = DarkModeCheck<true>; // "dark"
T
はわかる。スゲーよくわかる
これはジェネリクスってやつで、色々な型を設定できるんだよなァ
だがextends
が左辺と右辺にあるのはどういう事だああ~~~~~~っ!?
extends
は継承って意味じゃねぇのかよォ──────ッ!!
ナメやがってこの型ァ!!超イラつくぜぇ~~~~ッ!!
解説
extends
は👇の3つの意味を持つぜぇ
- 型分岐
- 型制約
- 継承
それぞれ全然違うからよォ
順番に見ていくぜぇ
まず、さっきのコードを「型分岐」だけのコードに直してみるぜぇ👇
type DarkModeCheck<T> = T extends true ? "dark" : "light";
type LightMode = DarkModeCheck<false>; // "light"
type DarkMode = DarkModeCheck<true>; // "dark"
これだと意味が『分かる』よなァ~??
-
T
がtrue
だったら"dark"
になる -
T
がfalse
だったら"light"
になる
「型分岐」は「三項演算子と同じ」って覚えると良いぜぇ
さらに、このコードに「型制約」を加えると👇になるぜぇ
type DarkModeCheck<T extends boolean> = T extends true ? "dark" : "light";
これで「boolean
を満たす型じゃないとT
は許可しないィィ───ッ!!」って意味にできるぜぇ
ここでextends
が2つ出てきて混乱すると思うからよォ
とりあえず👇で覚えるといいぜぇ
- 型制約:左辺に
extends
がある - 型分岐:右辺に
extends
がある
あと最後の「継承」は、JavaScriptと一緒だなァ
たとえば👇みたいにして『継承』できるぜぇ
interface Animal {
name: string;
}
interface Human extends Animal {
think(): void;
}
ちなみに『型分岐』は「Conditional Type」って呼ばれることが多いからよォ
覚えとくといいかもなァ~
Distributive Conditional Types
次はこれだァ
type ToArray<Type> = Type extends any ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>; //string[] | number[]
これはわかる。スゲーよくわかる
型分岐、もとい「Conditional Type」ってやつだよなァ
だが結果がstring[] | number[]
になるのはどういう事だああ~~~~~~っ!?
(string | number)[]
になるんじゃねぇのかよォ─────ッ!!
ナメやがってこの型ァ!!超イラつくぜぇ~~~~ッ!!
解説
これはマジで初見殺しなんだけどよォ
「Conditional Types」にユニオンを渡すと、分配されて計算される
っていう俺様ルールが存在するらしいぜぇ
この俺様ルールを「Distributive Conditional Types」と呼ぶぜぇ
そうだよなァ~??
意味わかんねェよなァ~??
要するに、これは👇のように計算されるんじゃなくて
type ToArray<Type> = (string | number) extends any ? Type[] : never;
= (string | number)[]
👇のように計算されるってことらしいぜぇ~
type ToArray<Type> = (string extends any ? Type[] : never) | (number extends any ? Type[] : never)
= string[] | number[]
こんなの初見で分かるわけねぇだろがァ──────ッ!!
クソッ!!クソッ!!クソッ!!
ちなみに最初のコードを👇に書き直すと、結果を (string | number)[]
にできるぜぇ
> type ToArray<Type> = [Type] extends [any] ? Type[] : never;
type StrArrOrNumArr = ToArray<string | number>; // (string | number)[]
さらに意味がわかんねェぞォォォォ──────ッ!!
クソッ!!クソッ!!クソッ!!
infer
次はこれだァ
type MyAwaited<T extends Promise<any>> = T extends Promise<infer U> ? U : never;
type Res = MyAwaited<Promise<string>>; // string
「extends」はさっき『理解』したから分かるよなァ~~??
問題は<infer U>
って部分だけどよォ
これは「型分岐の判定に使った型の一部を『型分岐』として使う」ができる機能だぜぇ
このコードの場合、「Promiseを継承している場合、Promiseの戻り値を『型分岐』として使う」ができるぜぇ
つまり、infer
は『型分岐』とセットで使うことになるってことだなァ
infer と template literal
次はこれだァ
type MyCapitalize<S extends string> = S extends `${infer F}${infer Tail}`
? `${Uppercase<F>}${Tail}`
: S;
type Hoge = MyCapitalize<"hoge">; // "Hoge"
これもマジで初見殺しだぜぇ
template literal の中でinfer
を2つ使うとよォ
「先頭1文字」と「残り」という分かれ方をする
・・・らしいぜぇ
つまり、 ${infer F}${infer Tail}
のF
とTail
には、👇が入るってことだぜぇ
- F:
"h"
- Tail:
"oge"
こんなの初見で分かるわけねぇだろがァ──────ッ!!
クソッ!!クソッ!!クソッ!!
インデックスシグネチャ
次はこれだァ
type User = {
[key: string]: string;
};
const user: User = {
name: "yamada",
email: "hoge@hoge.com"
}
このコードで新しく出てきてるのは[key: string]
の部分だぜぇ
これは「インデックスシグネチャ」っていう機能で
「任意の型のプロパティを好きなだけ追加できる」ってやつだぜぇ
ちなみに、このkey
の部分は、好きな名前を付けれるぜぇ
Mapped Type
次はこれだァ
type User = {
name: string;
email: string;
};
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
type Email = MyPick<User, "email">; // { email: string }
このコードで新しく出てきてるのは [P in K]
の部分だぜぇ
この機能を「Mapped Type」って呼ぶぜぇ
Mapped Typeでは、「in演算子+ユニオン」でユニオンの数だけプロパティを作れるぜェ
たとえば、👇の3つは同じ意味だぜぇ
type User = {
name: string;
email: string;
};
type User = {
[key in "name" | "email"]: string;
};
type User = Record<"name" | "email", string>;
要するに、「プロパティ名を[ ]
で囲って、さらにin
の右にユニオンを書くと、それらがプロパティ名として展開される」って感じだなァ
そんなわけでkeyof
と組み合わせたのが最初のコード👇ってわけだなァ
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
ちなみに、インデックスシグネチャと似てるが、プロパティの数が👇のように違うぜぇ
- インデックスシグネチャ:制限なし
- Mapped Type:ユニオン分だけ
あとよォ
インデックスシグネチャでは他のプロパティも宣言できるけどよォ👇
type User = {
[key: string]: string;
hoge: string; //✅
};
「Mapped Type」では他のプロパティは宣言できねぇからなァ👇
type User = {
[key in "name" | "email"]: string;
hoge: string; //❌
};
これは「なんで事前に分かってんのに1箇所に書かねえんだ この・・・ド低脳がァ───ッ!!」ってことだろうなァ
Key Remapping
最後はこれだァ
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
//type LazyPerson = {
// getName: () => string;
// getAge: () => number;
// getLocation: () => string;
//}
as
を見ると「強制的に型をキャストするやつ」ってイメージがあるかもしれねェが
ここではまったく違う意味だぜぇ
最後のLazyPerson
を見れば、どういう機能なのか『分かる』と思うけどよォ
これは「Mapped Type」にas
をつけると別名を付けれるってやつだぜぇ
ほとんど使うことがねェと思うからよォ
「こういう文脈で出てくることもある」って程度に知っとけばいいんじゃねぇかァ~??
まとめ・・・ッ!!
「型付け」とはッ!!
コードの荒野にッ!! 進むべき『道』を切り開くことだッ!!
おまえの命がけの「型付け」ッ!!
ぼくは敬意を表するッ!!
第5部完
Discussion
勢いがおもしろかったですw ジョジョを知らないので、もしかしてそこからのネタだったら申し訳ないんですが
ここの「動的」というのは(色々な意味があると思うですが)たとえばよくあるものだと
といった感じで「ランタイム」「実行時」という意味で使うことが多いと思います。一方でTypeScriptの型は実行時には消えてしまうと思うため、ここはたとえば「色々な型を設定できるんだよなァ」とかにしておくとより正確と思いました。
(誰かこれを記事と同じテンションに翻訳してほしい)
コメントありがとうございます。
たしかに「色々な型を設定できるんだよなァ」のほうが適切だと思いますので、そのまま使わせて頂きたいと思います。
ありがとうございます!🙇
人の成長は……………
未熟な過去に打ち勝つことだな
ディアボロだっけ?
ギアッチョかと
超笑いました😆
知りたかった内容ばかりでした!
めちゃくちゃ分かりやすかったです🤓
Pick
ではキーがサジェストされるのにOmit
ではサジェストされないのはどういうことだァ~~??場合によっちゃァこれで
Omit
のイラつきから解放されるぜぇ