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、もしくはさらに他の型をマージして、それら全てのプロパティを持ったひとつの型を得ることができます。
typeLeftType={id:numberleft:string}typeRightType={id:numberright:string}typeIntersectionType=LeftType&RightTypefunctionshowType(args:IntersectionType){console.log(args)}showType({id:1,left:"test",right:"test"})// Output: {id: 1, left: "test", right: "test"}
見てのとおり、交差型はLeftTypeとRightType両方の要素を持っています。
交差型をつくるには&
で結合するだけです。
Union Types
Union型は、与えられた型のうち何れかの型となることができます。
typeUnionType=string|numberfunctionshowType(arg:UnionType){console.log(arg)}showType("test")// Output: testshowType(7)// Output: 7
関数showType
は、string型もしくはnumber型いずれかの値を引数として受け付けることができます。
Generic Types
ジェネリック型とは、与えられた型を再利用する手段です。
引数の型を変数のようにキャプチャすることができます。
functionshowType<T>(args:T){console.log(args)}showType("test")// Output: "test"showType(1)// Output: 1
ジェネリック型を生成するには、関数名に<>
で括った型名T
( 実際は任意の名前でよい ) を指定します。
以下に、関数showType
を異なる型で呼び出す例を示します。
interfaceGenericType<T>{id:numbername:T}functionshowType(args:GenericType<string>){console.log(args)}showType({id:1,name:"test"})// Output: {id: 1, name: "test"}functionshowTypeTwo(args:GenericType<number>){console.log(args)}showTypeTwo({id:1,name:4})// Output: {id: 1, name: 4}
ジェネリック型Tを受け取るインターフェイスGenericTypeを定義しました。
これは再利用可能なので、ひとつめのGenericTypeはstring型の値を受け取り、ふたつめはnumber型を受け取っています。
interfaceGenericType<T,U>{id:Tname:U}functionshowType(args:GenericType<number,string>){console.log(args)}showType({id:1,name:"test"})// Output: {id: 1, name: "test"}functionshowTypeTwo(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型は、該当する型の全てのプロパティをオプショナルにすることができます。
これはすなわち、全てのフィールドに?
を追加するようなものです。
interfacePartialType{id:numberfirstName:stringlastName:string}// firstNameがstringからstring?になるfunctionshowType(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
型は全てのプロパティを必須にします。
interfaceRequiredType{id:numberfirstName?:stringlastName?:string}// firstNameがstring?からstringになるfunctionshowType(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
ユーティリティ型は、全てのプロパティを変更不可能にします。
interfaceReadonlyType{id:numbername:string}functionshowType(args:Readonly<ReadonlyType>){args.id=4console.log(args)}showType({id:1,name:"Doe"})// Error: Cannot assign to 'id' because it is a read-only property.
Readonly
ユーティリティ型によって、ReadonlyType
型の全ての値は再割り当て不能になります。
いずれかのフィールドに新しい値を設定しようとすると、エラーになります。
もっと単純に、プロパティの前にreadonly
キーワードを付けて再割り当て不能にすることもできます。
interfaceReadonlyType{readonlyid:numbername:string}
Pick
・Pick<T, K>
Pick
ユーティリティ型は、元の型からいくつかのプロパティを選んで新たな型を生成します。
interfacePickType{id:numberfirstName:stringlastName:string}// PickTypeのうちfirstName,lastNameだけを使った新たな型functionshowType(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
のちょうど反対で、必要なプロパティを選ぶのではなく不要なプロパティを削除します。
interfacePickType{id:numberfirstName:stringlastName:string}// PickTypeのうちfirstName,lastNameを使わない新たな型functionshowType(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つの型に共通するプロパティを取り出すと考えてよいでしょう。
interfaceFirstType{id:numberfirstName:stringlastName:string}interfaceSecondType{id:numberaddress:stringcity:string}typeExtractType=Extract<keyofFirstType,keyofSecondType>// Output: "id"
上の例では、2つの型が同じプロパティid
を持っています。
この型にExtractを使用することで、両方に共通するプロパティid
を取り出すことができます。
共通するプロパティが複数存在する場合は、その全てが抽出されます。
Exclude
・Exclude<T, U>
Exclude
ユーティリティ型は、T型のプロパティのうち、U型に代入可能なプロパティを除外した型を生成します。
interfaceFirstType{id:numberfirstName:stringlastName:string}interfaceSecondType{id:numberaddress:stringcity:string}typeExcludeType=Exclude<keyofFirstType,keyofSecondType>// Output; "firstName" | "lastName"
FirstTypeのプロパティfirstName
・lastName
はSecondTypeには存在しないため、Excludeで取り出すことができます。
SecondTypeの値address
やcity
は出てきません。
Record
・Record<K,T>
このユーティリティは、T型の値の集合を作るために役立ちます。
ある型のプロパティを別の型にマッピングする際に、非常に便利です。
interfaceEmployeeType{id:numberfullname:stringrole:string}letemployees: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を消し去ります。
typeNonNullableType=string|number|null|undefinedfunctionshowType(args:NonNullable<NonNullableType>){console.log(args)}showType("test")// Output: "test"showType(1)// Output: 1showType(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です。
typeStringMap<T>={[PinkeyofT]:string}functionshowType(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
functionshowType(x:number|string){if(typeofx==="number"){return`The result is ${x+x}`}thrownewError(`This operation can't be done on a ${typeofx}`)}showType("I'm not a number")// Error: This operation can't be done on a stringshowType(7)// Output: The result is 14
上記コードでは、typeof
を用いて受け取った引数の型をチェックしています。
条件で型ガードすることができました。
instanceof
classFoo{bar(){return"Hello World"}}classBar{baz="123"}functionshowType(arg:Foo|Bar){if(arginstanceofFoo){console.log(arg.bar())returnarg.bar()}thrownewError("The type is not supported")}showType(newFoo())// Output: Hello WorldshowType(newBar())// Error: The type is not supported
typeof
の例と同様、こちらでは引数の型がFooクラスであるかをチェックします。
in
interfaceFirstType{x:number}interfaceSecondType{y:string}functionshowType(arg:FirstType|SecondType){if("x"inarg){console.log(`The property ${arg.x} exists`)return`The property ${arg.x} exists`}thrownewError("This type is not expected")}showType({x:7})// Output: The property 7 existsshowType({y:"ccc"})// Error: This type is not expected
オブジェクトにプロパティが存在するかどうかはin
でチェックすることができます。
Conditional Types
以下では複数の型をテストし、結果に応じてその片方を選択しています。
typeNonNullable<T>=Textendsnull|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とか何に使うのか全くわからん。
みんなこういうのバキバキ使いこなしてるんですかね?