Original article: https://www.ibrahima-ndaw.com/blog/advanced-typescript-cheat-sheet/
以下はIbrahima Ndaw( Twitter / GitHub / LinkedIn / Webサイト )によるTypeScriptの解説、Advanced TypeScript Types cheat sheet (with examples)の日本語訳です。
リンクなどは元記事のままであり、和訳にあたり変更していません。
Advanced TypeScript Types cheat sheet (with examples)
TypeScriptは型付き言語であり、変数、関数の引数および返り値、オブジェクトのプロパティに型を指定することが可能です。
この記事では、TypeScriptの型の高度な使い方を例示付きで紹介します。
Sorry for the interrupt!
TypeScriptを総合的に学びたい人には、こちらのベストセラーコースを強くお勧めします。
Understanding TypeScript - 2020 Edition
これはアフィリエイトリンクなので、よかったら応援してね。
Intersection Types
交差型とは、複数の型をひとつに結合した型です。
すなわち、型Aと型B、もしくはさらに他の型をマージして、それら全てのプロパティを持ったひとつの型を得ることができます。
type LeftType = {
id: number
left: string
}
type RightType = {
id: number
right: string
}
type IntersectionType = LeftType & RightType
function showType(args: IntersectionType) {
console.log(args)
}
showType({ id: 1, left: "test", right: "test" })
// Output: {id: 1, left: "test", right: "test"}
見てのとおり、交差型はLeftTypeとRightType両方の要素を持っています。
交差型をつくるには&
で結合するだけです。
Union Types
Union型は、与えられた型のうち何れかの型となることができます。
type UnionType = string | number
function showType(arg: UnionType) {
console.log(arg)
}
showType("test")
// Output: test
showType(7)
// Output: 7
関数showType
は、string型もしくはnumber型いずれかの値を引数として受け付けることができます。
Generic Types
ジェネリック型とは、与えられた型を再利用する手段です。
引数の型を変数のようにキャプチャすることができます。
function showType<T>(args: T) {
console.log(args)
}
showType("test")
// Output: "test"
showType(1)
// Output: 1
ジェネリック型を生成するには、関数名に<>
で括った型名T
( 実際は任意の名前でよい ) を指定します。
以下に、関数showType
を異なる型で呼び出す例を示します。
interface GenericType<T> {
id: number
name: T
}
function showType(args: GenericType<string>) {
console.log(args)
}
showType({ id: 1, name: "test" })
// Output: {id: 1, name: "test"}
function showTypeTwo(args: GenericType<number>) {
console.log(args)
}
showTypeTwo({ id: 1, name: 4 })
// Output: {id: 1, name: 4}
ジェネリック型Tを受け取るインターフェイスGenericTypeを定義しました。
これは再利用可能なので、ひとつめのGenericTypeはstring型の値を受け取り、ふたつめはnumber型を受け取っています。
interface GenericType<T, U> {
id: T
name: U
}
function showType(args: GenericType<number, string>) {
console.log(args)
}
showType({ id: 1, name: "test" })
// Output: {id: 1, name: "test"}
function showTypeTwo(args: GenericType<string, string[]>) {
console.log(args)
}
showTypeTwo({ id: "001", name: ["This", "is", "a", "Test"] })
// Output: {id: "001", name: Array["This", "is", "a", "Test"]}
ジェネリック型を複数渡すこともできます。
上の例では二つのジェネリック型TとUを渡しています。
interfaceを使用することで、異なる型の引数を渡す関数が提供できるようになりました。
Utility Types
TypeScriptでは、型を容易に操作することができるように便利な組込ユーティリティ型が提供されています。
これらを使うときは、変換したい型を<>
に入れて渡します。
Partial
・Partial<T>
Partial型は、該当する型の全てのプロパティをオプショナルにすることができます。
これはすなわち、全てのフィールドに?
を追加するようなものです。
interface PartialType {
id: number
firstName: string
lastName: string
}
// firstNameがstringからstring?になる
function showType(args: Partial<PartialType>) {
console.log(args)
}
showType({ id: 1 })
// Output: {id: 1}
showType({ firstName: "John", lastName: "Doe" })
// Output: {firstName: "John", lastName: "Doe"}
関数showType()
の引数としてPartialType
型を渡していますが、プロパティをオプショナルにするためにPartial
ユーティリティ型を通しています。
これだけで、PartialType
型の全ての値がオプショナルになりました。
Required
・Required<T>
Partial
型とは「逆に、Required
型は全てのプロパティを必須にします。
interface RequiredType {
id: number
firstName?: string
lastName?: string
}
// firstNameがstring?からstringになる
function showType(args: Required<RequiredType>) {
console.log(args)
}
showType({ id: 1, firstName: "John", lastName: "Doe" })
// Output: { id: 1, firstName: "John", lastName: "Doe" }
showType({ id: 1 })
// Error: Type '{ id: number: }' is missing the following properties from type 'Required<RequiredType>': firstName, lastName
Required
ユーティリティ型を通すことによって、オプショナルであるはずのRequiredType
型の全ての値が必須になります。
プロパティを省略した場合、TypeScriptはエラーを発生させます。
Readonly
・Readonly<T>
Readonly
ユーティリティ型は、全てのプロパティを変更不可能にします。
interface ReadonlyType {
id: number
name: string
}
function showType(args: Readonly<ReadonlyType>) {
args.id = 4
console.log(args)
}
showType({ id: 1, name: "Doe" })
// Error: Cannot assign to 'id' because it is a read-only property.
Readonly
ユーティリティ型によって、ReadonlyType
型の全ての値は再割り当て不能になります。
いずれかのフィールドに新しい値を設定しようとすると、エラーになります。
もっと単純に、プロパティの前にreadonly
キーワードを付けて再割り当て不能にすることもできます。
interface ReadonlyType {
readonly id: number
name: string
}
Pick
・Pick<T, K>
Pick
ユーティリティ型は、元の型からいくつかのプロパティを選んで新たな型を生成します。
interface PickType {
id: number
firstName: string
lastName: string
}
// PickTypeのうちfirstName,lastNameだけを使った新たな型
function showType(args: Pick<PickType, "firstName" | "lastName">) {
console.log(args)
}
showType({ firstName: "John", lastName: "Doe" })
// Output: {firstName: "John"}
showType({ id: 3 })
// Error: Object literal may only specify known properties, and 'id' does not exist in type 'Pick<PickType, "firstName" | "lastName">'
これまでに見てきたユーティリティ型とは少々異なる構文で、二つの引数が必要です。
Tは元の型、そしてKは抽出したいプロパティです。
複数のフィールドを|
で区切ることによって、複数のフィールドを抽出することも可能です。
Omit
・Omit<T, K>
Omit
ユーティリティ型はPick
のちょうど反対で、必要なプロパティを選ぶのではなく不要なプロパティを削除します。
interface PickType {
id: number
firstName: string
lastName: string
}
// PickTypeのうちfirstName,lastNameを使わない新たな型
function showType(args: Omit<PickType, "firstName" | "lastName">) {
console.log(args)
}
showType({ id: 7 })
// Output: {id: 7}
showType({ firstName: "John" })
// Error: Object literal may only specify known properties, and 'firstName' does not exist in type 'Pick<PickType, "id">'
Pick
と同じ使い方で、元となる型から削除するプロパティを指定します。
Extract
・Extract<T, U>
Extract
ユーティリティ型は、T型のプロパティのうち、U型に代入可能なプロパティを抽出します。
2つの型に共通するプロパティを取り出すと考えてよいでしょう。
interface FirstType {
id: number
firstName: string
lastName: string
}
interface SecondType {
id: number
address: string
city: string
}
type ExtractType = Extract<keyof FirstType, keyof SecondType>
// Output: "id"
上の例では、2つの型が同じプロパティid
を持っています。
この型にExtractを使用することで、両方に共通するプロパティid
を取り出すことができます。
共通するプロパティが複数存在する場合は、その全てが抽出されます。
Exclude
・Exclude<T, U>
Exclude
ユーティリティ型は、T型のプロパティのうち、U型に代入可能なプロパティを除外した型を生成します。
interface FirstType {
id: number
firstName: string
lastName: string
}
interface SecondType {
id: number
address: string
city: string
}
type ExcludeType = Exclude<keyof FirstType, keyof SecondType>
// Output; "firstName" | "lastName"
FirstTypeのプロパティfirstName
・lastName
はSecondTypeには存在しないため、Excludeで取り出すことができます。
SecondTypeの値address
やcity
は出てきません。
Record
・Record<K,T>
このユーティリティは、T型の値の集合を作るために役立ちます。
ある型のプロパティを別の型にマッピングする際に、非常に便利です。
interface EmployeeType {
id: number
fullname: string
role: string
}
let employees: Record<number, EmployeeType> = {
0: { id: 1, fullname: "John Doe", role: "Designer" },
1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" },
2: { id: 3, fullname: "Sara Duckson", role: "Developer" },
}
// 0: { id: 1, fullname: "John Doe", role: "Designer" },
// 1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" },
// 2: { id: 3, fullname: "Sara Duckson", role: "Developer" }
Record
の動作はシンプルです。
上の例ではキーの型がnumber
なので、0,1,2と数値を指定しています。
値はEmployeeType
型となっているので、id・fullname・roleを持つオブジェクトが必要です。
文字列を与えたりするとエラーになります。
NonNullable
・NonNullable<T>
型Tからnullとundefinedを消し去ります。
type NonNullableType = string | number | null | undefined
function showType(args: NonNullable<NonNullableType>) {
console.log(args)
}
showType("test")
// Output: "test"
showType(1)
// Output: 1
showType(null)
// Error: Argument of type 'null' is not assignable to parameter of type 'string | number'.
showType(undefined)
// Error: Argument of type 'undefined' is not assignable to parameter of type 'string | number'.
NonNullableユーティリティは、引数からnullとundefinedを消し去った新たな型を生成します。
その型にNullableな値を渡すと、TypeScriptはエラーを発します。
なお、tsconfig
ファイルにstrictNullChecks
を指定すると、自動的に全ての型にNonNullableが適用されます。
Mapped types
Mapped typesは、既存のモデルを流用しつつ、各プロパティを新しい型に変更することができるようになります。
先に解説したユーティリティ型も、一部の実体はMapped typesです。
type StringMap<T> = {
[P in keyof T] : string
}
function showType(arg: StringMap<{ id: number; name: string }>) {
console.log(arg)
}
showType({ id: 1, name: "Test" })
// Error: Type 'number' is not assignable to type 'string'.
showType({ id: "testId", name: "This is a Test" })
// Output: {id: "testId", name: "This is a Test"}
StringMap<>
は、渡された型が何であれ、とりあえず文字列型にします。
従って、showType
に渡す型はnumberではなくstringとなり、number型を渡した場合はエラーが出ることになります。
Type Guards
Type Guardsを使うと、変数やオブジェクトの型を演算子で判定することができます。
typeof
function showType(x: number | string) {
if (typeof x === "number") {
return `The result is ${x + x}`
}
throw new Error(`This operation can't be done on a ${typeof x}`)
}
showType("I'm not a number")
// Error: This operation can't be done on a string
showType(7)
// Output: The result is 14
上記コードでは、typeof
を用いて受け取った引数の型をチェックしています。
条件で型ガードすることができました。
instanceof
class Foo {
bar() {
return "Hello World"
}
}
class Bar {
baz = "123"
}
function showType(arg: Foo | Bar) {
if (arg instanceof Foo) {
console.log(arg.bar())
return arg.bar()
}
throw new Error("The type is not supported")
}
showType(new Foo())
// Output: Hello World
showType(new Bar())
// Error: The type is not supported
typeof
の例と同様、こちらでは引数の型がFooクラスであるかをチェックします。
in
interface FirstType {
x: number
}
interface SecondType {
y: string
}
function showType(arg: FirstType | SecondType) {
if ("x" in arg) {
console.log(`The property ${arg.x} exists`)
return `The property ${arg.x} exists`
}
throw new Error("This type is not expected")
}
showType({ x: 7 })
// Output: The property 7 exists
showType({ y: "ccc" })
// Error: This type is not expected
オブジェクトにプロパティが存在するかどうかはin
でチェックすることができます。
Conditional Types
以下では複数の型をテストし、結果に応じてその片方を選択しています。
type NonNullable<T> = T extends null | undefined ? never : T
NonNullable
ユーティリティ型は、型がnullであるかをチェックし、結果に応じて異なる処理をしています。
この例ではJavaScriptの三項演算子を使用していることに注意してください。
コメント欄
dev.toのコメント欄
「わかりやすくてよい記事。GJ!」
「簡潔によくまとめられてる。」
「独自のタイプガードの可能性を追求した。」
「もっとはやくPartialを知りたかった。わざわざ自力で書いてたよ。export type ObjectWithOptionalProps<T> = { [key in keyof T]?: T[key] };
」
「Maybe型type Maybe<T> = T | null;
をよく使ってる。」「普通にオプショナルfunction foo(bar?: string)
でよくない?」「Maybe型の場合は値が必須というところが異なるよ。」
「TypeScript複雑になりすぎてきたような」「ほとんどは糖衣構文なので使わなければいいだけだぞ」「せやな」
「行末にセミコロンを忘れるな」「セミコロンは必須ではないし使わない方が好き」「ない方がすっきりしててよい」「Google様に逆らうなら不要だね」
感想
TypeScript Guideシリーズの2番目で、主に型の変換について記述された記事です。
他のシリーズも通して読むことで、TypeScriptへの理解がより深まることでしょう。
参考になったらぜひコーヒーを買ってあげましょう。
実際に手を動かしてみたいという場合は、ちょうど最近日本語解説記事が出た type-challengesなどを試してみるとよいかもしれません。
しかし型に関するいろいろな機能はありますが、実際使うかといったら個人的にはほとんど使っていません。
私はフロントエンドエンジニアではないので本格的にがっつり使ってないからということもありますが、極めてふつーに、型を定義してそれを直接使う程度のことしかやっていませんし、それで不足を感じることもあまりありません。
日頃使ってるのはせいぜいUnion型くらいです。
OmitとかExcludeとか何に使うのか全くわからん。
みんなこういうのバキバキ使いこなしてるんですかね?