こんにちは。新卒の id:w1p と申します。 今回業務でTypeScriptを導入するということで、いい機会なのでTypeScriptについていろいろ調べました。 目次 環境構築 TypeScriptの基本的な文法 型アノテーションの書き方 基本の型 number型 bigint型 string型 boolean型 symbol型 null型 undefined型 型エイリアスで型に別名をつける リテラル型 複合型 object型 array型 tuple型 enum 数値型enumの注意点 class Union型 Intersection型 その他の型 Function型 Index Signitures 特殊な型 any unknown void never インターフェースと型エイリアス インターフェース 2つの違い 宣言のマージ プリミティブ型とUnion型 その他 ジェネリクス ジェネリクスを導入できる場所 関数型 型エイリアス インターフェース tsconfig.json 型を操作する 型のキーワード typeof keyof Lookup Types in extends infer Utility Types Partial / Required / Readonly Pick<T, K> Record<K, T> Exclude<T, ExcludedUnion> Union Distribution Extract<T, Union> Omit<Type, Keys> NonNullable Parameters ConstructorParameters ReturnType InstanceType ThisParameterType OmitThisParameter ThisType Uppercase / Lowercase Capitalize / Uncapitalize まとめ 参考 環境構築 Microsoft 公式がブラウザ上で実行できる サンドボックス を提供しており、これを使用すると手軽にTypeScriptを試すことができます。 TypeScriptから JavaScript への変換結果や、型定義ファイル生成の結果まで見れます。 この記事で出てくるサンプルコードはすべてTS Playground v4.1.2上で行っています。 Playground Link Visual Studio Code や Intellij IDEAなど手元のエディタで書いてローカルで実行したい場合には以下の手順を踏む必要があります。 npm or yarnがインストールされていることが前提です。 Step1. TypeScriptをインストールする $ mkdir ts && cd ts $ npm init -y # yarn init -y $ npm i -D typescript # yarn add -D typescript Step2. .ts拡張子のファイルを作成する $ echo ' console.log("Hello, TypeScript!"); ' > ./hello.ts Step3. TypeScriptを実行する ここでは手軽にTypeScriptを実行する方法として、 tsc を使用する方法と、 ts-node というパッケージを使用する方法を紹介します。 tsc を使用する場合 tsc はTypeScriptを JavaScript へトランスパイルするための公式ツールです。 tsc の挙動は tsc 実行時にオプションとして渡したり、 tsconfig.json というファイルで変更することができます。 hello.ts のあるフォルダで以下を実行します。 $ npx tsc ./hello.ts # yarn run tsc ./hello.ts tsc はトランスパイルした結果を指定された ディレクト リに出力します。 今回は特に指定していませんのでカレント ディレクト リに出力されます。 あとは通常のjsファイルと同様に実行できます。 $ node hello.js Hello, TypeScript! ts-nodeを使用する場合 ts-node を使用すると、 tsc → node という2度手間を省略できます。 $ npm i -D ts-node # yarn add -D ts-node 実行は以下のように行えます。 $ npx ts-node ./hello.ts # yarn run ts-node ./hello.ts TypeScriptの基本的な文法 ここまではどのようにTypeScriptファイルを実行するのかについて見てきました。 ここからは基本的なTypescriptの文法について見ていきます。 型 アノテーション の書き方 型 アノテーション とは、変数や関数の戻り値などの型が何であるかを明示的に指定することです。 基本的には : type の形式で書くことができます。 // 変数nの型をnumberに let n: number = 123 n = 'string' // Type 'string' is not assignable to type 'number'. // argをstring, fの戻り値はvoidに function f ( arg: string ) : void { /* */ } f ( 123 ) // Argument of type 'number' is not assignable to parameter of type 'string'. TypeScriptは 型推論 (型をコンテクストから推論すること)により、全ての場所に型 アノテーション を必要としない仕組みになっています。 // nはnumber型と推論される let n = 123 // tはboolean型と推論される let t = true ; 基本の型 早速ですが、TypeScriptの核となる型についてみていきます。 ここで紹介する型はプリミティブ型と呼ばれる基本的な型です。 number型 number型は実数に加え、無限を表す Infinity , 非数を表す NaN が含まれます。 const n1: number = 123 ; const n2: number = -0 . 1 ; const n3: number = Infinity ; const n4: number = NaN ; bigint型 ES2020で導入されたbigint型は Number.MAX_SAFE_INTEGER(2^53 - 1) よりも大きな整数を扱うことができます。 精度が落ちる可能性があるためnumber型変数へ直接代入することはできず、明示的な変換が必要になります。 let big = 123n ; let num = 123 ; num = big ; // Type 'bigint' is not assignable to type 'number'. big = num ; // Type 'number' is not assignable to type 'bigint'. num = Number ( big ); big = BigInt ( num ); string型 文字列全般をとりうる型です。 const s1: string = "Hello, World!" ; const s2: string = s1.slice ( 0 , 5 ); boolean型 true と false の2つの値をとり得る型です。 let b: boolean ; b = true ; b = false ; b = !!1 ; b = 1 ; // Type 'number' is not assignable to type 'boolean'. symbol型 ES2015から取り入れられた、一意の値を生成するための仕組みです。 const s1: unique symbol = Symbol ( 'abc' ); // typeof s1 let s2 = Symbol ( 'abc' ); // symbol let s3 = Symbol . for( 'abc' ); // symbol const s4 = Symbol . for( 'abc' ) // symbol console .log ( s1 === s2 ); // false console .log ( s2 === s3 ); // false console .log ( s3 === s4 ); // true null型 nullもしくはany型のみを受け付ける型です。 const n1: null = null ; const n2: null = 123 as any ; const n3: null = undefined ; // Type 'undefined' is not assignable to type 'null'. const n4: null = 123 as unknown ; // Type 'unknown' is not assignable to type 'null'. undefined型 undefinedとany型のみを受け付けます。 const u1: undefined = undefined ; const u2: undefined = 123 as any ; const u3: undefined = null ; // Type 'null' is not assignable to type 'undefined'. const u4: undefined = 123 as unknown ; // // Type 'unknown' is not assignable to type 'null'. 型 エイリアス で型に別名をつける 型 エイリアス と呼ばれる機能により、任意の型に別名をつけることができます。 型 エイリアス は type キーワードで作成できます。 type ID = number ; type Name = string ; type Cond = boolean ; リテラル 型 TypeScriptの面白い特徴として リテラル 型があります。 リテラル 型とは、プリミティブ型に含まれる一部の値のみをとりうるような型です。 const で変数を定義する際に、TypeScriptはその値はもう変化しないだろうと考え リテラル 型で推論します。 // numberのリテラル型 let num1 = 123 ; // number型 const num2 = 123 ; // 123型 // stringのリテラル型 let str1 = "abc" ; // string型 const str2 = "abc" ; // "abc"型 // booleanのリテラル型 let bool1 = true ; // boolean型 const bool2 = false ; // false型 後述するUnion型とよく組み合わせて使われます。 // 許容するHTTPメソッドの一覧 type AllowedHttpMethods = "GET" | "HEAD" | "POST" | "PUT" ; let method: AllowedHttpMethods = "GET" ; // OK method = "PUT" ; // OK method = "DELETE" ; // Type '"DELETE"' is not assignable to type 'AllowedHttpMethods'. 複合型 object型 プリミティブ型以外の値をとりうる型です。 オブジェクトの構造を指定することはできません。 const o1: object = { a: 123 , b: "abc" } ; const o2: object = {} const o3: object = 123 ; // Type 'number' is not assignable to type 'object'. const o4: object = null ; // Type 'null' is not assignable to type 'object'. array型 配列を表す型です。 型 アノテーション の方法として T[] と Array<T> の2パターンありますが、前者の方が多く使われています。 const arr1: number [] = [ 1 , 2 , 3 ] ; const arr2: Array < string > = [ "a" , "b" , "c" ] ; tuple型 TypeScriptではタプル型を宣言できます。 タプルは複数の要素からなる組み合わせのような型です。 配列と違うのは、異なる型の要素が共存できる点です。 const tup1: [ number , string ] = [ 1 , "str" ] ; const tup2: [ number , number , number ] = [ 1 , 2 , 3 ] ; const arr: number [] = tup2 ; // number型の要素しかないため配列に割り当て可能 enum tuple同様、TypeScriptは独自に enum を使用できます。 enum は プログラマ が事前に定義した特定の値のみをとりうるような型です。 enum Suits { Diamonds , Clubs , Hearts , Spades } console .log ( Suits.Diamonds === 0 ) // true それぞれの列挙子には値を割り当てることができます。 上の Suits には値を割り当てていませんが、そのような場合TypeScriptは0から数値を割り当てます。 数値型 enum の注意点 「TypeScriptの Enum は使わない方が良い」という意見を聞いたことがある方もいらっしゃるかと思いますが、 1番の問題としては数値型の enum にはすべての数値を割り当てることができてしまう点かと思います。 const suit: Suits = 10000 ; // OK これを回避するためには文字列型の enum を使用するか、もしくはUnion型を使用する方法があります。 enum Suits { Diamonds = "Diamonds" , Clubs = "Clubs" , Hearts = "Hearts" , Spades = "Spades" } const suit: Suits = Suits.Diamonds ; type Suits = "Diamonds" | "Clubs" | "Hearts" | "Spades" ; const suit: Suits = "Diamonds" ; class ES2015から導入されたclass宣言はもちろんTypeScriptでも利用できます。 class Animal { // TypeScriptではprivate / protected / publicが使用できる private species?: string ; constructor( species: string ) { this .species = species ; } } const animal = new Animal ( 'dog' ); animal.species = 'cat' ; // Property 'species' is private and only accessible within class 'Animal'. speciesフィールドはprivateなため、クラス外部から触ることはできずエラーになっています。 Union型 TypeScriptの大きな特徴としてよく挙げられるのがこのUnion型です。型同士を | でつなぐことで作成できます。 Unionの名前通り、Union型は構成する型がとる値それぞれの和集合になります。 type StrOrNum = string | number ; let val: StrOrNum = "str" ; val = 123 ; val = true ; // Type 'boolean' is not assignable to type 'string | number'. この例の StrOrNum はstringがとりうる値とnumber型がとりうる値の両方をとります。 Intersection型 Intersection型はそれぞれの型いずれにも割り当てられる型を作り出します。 Union型を和集合とみるなら、Intersection型は積集合を作り出すイメージです。 type StrOrNum = string | number ; type BoolOrNum = boolean | number ; type I = StrOrNum & BoolOrNum ; let val: I = 123 ; // valはnumber型になる val = 'str' ; // Type 'string' is not assignable to type 'number'. val = true ; // Type 'boolean' is not assignable to type 'number'. その他の型 Function型 Function型は全ての関数を受け付ける型です。 引数の型や数、戻り値の型などは指定できないため、object型と同じく使用頻度は高くありません。 let fun: Function = ( arg: string ) => console .log ( `Hello, ${ arg } !!` ); fun ( 'TypeScript' ); // "Hello, TypeScript!!" fun () // "Hello, undefined!!" let fun2: ( arg: string ) => void = ( arg: string ) => console .log ( `Hello, ${ arg } !!` ); fun2 (); // Expected 1 arguments, but got 0. fun は arg という引数が必要ですが、 Function 型で宣言しているためにTypeScriptは警告を出しません。 基本的には上の例で使用した (arg: type) => returnType という形式で アノテーション します。 Index Signitures キーの型、値の型を指定してプロパティの個数は指定しないようなオブジェクトも定義できます。 type T = { [ key: number ] : string } const t: T = { 1 : 'one' , 2 : 'two' , 3 : 'three' } ; // このコードはエラーにならないので注意 console .log ( t [ 0 ] .length ); // Cannot read property 'length' of undefined 特殊な型 any anyは何でもありの型です。どのような型でも受け付け、どのような型にも割り当てられます。 let any : any ; any = 123 ; any = { a: 123 , b: 'string' } ; any = null ; any = 123 as unknown ; const s: undefined = any ; any型は実際の値がどのような型か全くわからないため、TypeScriptのメリットを享受しづらくなってしまいます。 プロジェクトを JavaScript からTypeScriptに置き換える際に、とりあえずanyとして型をつけておくのは便利ですが、 できるなら使用は避けるべきです。 型が実行時までわからないような場合には、unknown型の使用をまずは検討しましょう。 unknown TypeScript 3.0から導入されました。 unknown型の変数はどのような値も代入可能な点でanyと同様ですが、変数を利用するときには その変数の値の型が実際は何であるかが判明するまで利用することができません。 let unknown : unknown ; unknown = 123 ; unknown = { a: 123 , b: 'string' } ; // 比較は可能 if ( !! unknown ) { console .log ( 'truthy' ); } unknown = 'Hello unknown' ; if (typeof unknown === 'string' ) { console .log ( unknown .substring ( 0 , 5 )); } 上の例では typeof 演算子 を使用し、変数 unknown の実際の型が何であるかをチェックしてから String.substring メソッドを使用しています。 型をチェックしなければ変数を使用できないという面で any 型の変数よりも安全です。 void return文で値を返さない関数の戻り値はvoid型として推論されます。 function f () {} type R = typeof f ; // () => void // undefinedはvoid型に代入可能 let r = f (); r = undefined ; never never型はとりうる値が一つもない型です。 never型の変数にはanyすら代入できません。 let never : never ; never = {} ; // Type '{}' is not assignable to type 'never'. never = 123 as any ; // Type 'any' is not assignable to type 'never'. とりうる値が一つもないため、never型とその他の型のUnion型においてnever型は無視されます。 type T = string | number | never ; // Tは string | number 型 使い所としては、実行したら2度と呼び出し元の関数に処理が戻らないような関数の戻り値があります。 以下はNodeの Process.exit 関数の型です。 NodeJS.Process.exit ( code?: number | undefined ) : never exitを呼び出した時点で指定された ステータスコード でプロセスを終了するため、呼び出し元に処理が戻ることはありません。 その他、後述するConditional Typesにおいても使用されています。 インターフェースと型 エイリアス インターフェース TypeScriptにはインターフェースという仕組みがあり、オブジェクトの構造を指定することができます。 interface User { name: string , id: number , registeredDate: Date } const user: User = { name: 'Guest' , id: 0 , registeredDate: new Date ( '1900-01-01' ) } Classに実装することもできます。 class UserClass implements User { constructor( public name: string , public id: number , public registeredDate: Date ) { } } 型 エイリアス もしくは他のインターフェースを継承できます。 type Animal = { name: string , age: number } interface Cat extends Animal { mew () : void } const tama: Cat = { name: 'tama' , age: 2 , mew () { console .log ( "mew" ) } } 2つの違い TypeScriptには型 エイリアス とインターフェースが存在し、共にオブジェクトの構造を定義することができます。 この2つは何が違うのでしょうか? 宣言のマージ インターフェースは同名のインターフェースを重複して宣言できます。 その場合はインターフェースの定義をTypeScriptが自動的にマージします。 interface User { id: number } interface User { // 同一のプロパティは同じ型でなければならない // id: string, name: string , } const user: User = { id: 0 , name: "Guest" } ; 外部ライブラリの型を拡張したい場合インターフェースで宣言されていれば、 同名のインターフェースを再度宣言するだけで拡張が可能です。 対して、同名の型 エイリアス はエラーになります。 // Duplicate identifier 'User'. type User = { id: number } type User = { name: string } プリミティブ型とUnion型 インターフェースでは型 エイリアス で表現できる以下のような型を表現できません。 // プリミティブ型 type ID = number ; // Union型 type NumOrStr = number | string ; その他 インデックス シグネチャ を型のプロパティとして持つ際に違いがあるようです。 assignabiltity between interfaces and types · Issue #14736 · microsoft/TypeScript · GitHub ジェネリクス ジェネリクス はTypeScriptに限らず様々な言語で導入されている、型を抽象化するための仕組みです。 配列のそれぞれの要素に関数を適用する map 関数は ジェネリクス を使用して以下のように定義できます。 // <T>によって型変数Tを導入できる function map < T >( arr: T [] , f: ( element: T ) => T ) : T [] { const result = [] ; for ( const e of arr ) { result.push ( f ( e )) } return result ; } console .log ( map ( [ 1 , 2 , 3 ] , elm => elm * 2 )) ジェネリクス を導入できる場所 関数型 上のmap型のような書き方も可能ですが、アロー関数でももちろん ジェネリクス を使用できます。 const map = < T >( arr: T [] , f: ( e: T ) => T ) : T [] => { /* ... */ } 型 エイリアス type F < T , R > = ( arg: T ) => R ; const f: F < number , string > = arg => arg.toString (); インターフェース interface F < T , R > { ( arg: T ) : R ; } const f: F < number , string > = arg => arg.toString (); tsconfig. json tsconfig.json はTypeScript コンパイラ の挙動を指定するための設定ファイルです。 以下のコマンドを実行するとカレント ディレクト リ上にデフォルトの tsconfig.json が作成されます。 $ tsc -- init tsconfigで設定可能なオプションの一覧は以下で確認できます。 TypeScript: TSConfig Reference - Docs on every TSConfig option 型を操作する 型のキーワード ここではTypeScriptの型にまつわるキーワードについて紹介します。 typeof typeof val でvalの値の型を取得できます。 const n = 123 ; const s = 'str' ; const o = { n , s } ; const u = undefined ; // T1 = 123 type T1 = typeof n ; // T2 = "str" type T2 = typeof s ; // T3 = {s: string, n: number} type T3 = typeof o ; // T4 = undefined type T4 = typeof u ; keyof keyof は keyof Type とすることで、 Type が持つプロパティをUnion型として得ることができる 演算子 です。 type User = { id: number ; name: string ; age: number ; } // type UserKey = "id" | "name" | "age" type UserKey = keyof User ; Lookup Types Lookup Typesは特別なキーワードがあるわけではありませんが、 keyof と組み合わせて使われることが 多いためここで紹介します。 ある型TのプロパティKの型を T[K] として参照できる機能です。 type User = { id: number ; name: string ; age: number ; } // type UserIdType = number type UserIdType = User [ 'id' ] ; in for..in 構文でも使用されるinですが、TypeScriptでは Mapped Types と呼ばれる構文のためのキーワードとしても用いられます。 // type U = {A: any, B: any, C: any} type U = { [ K in "A" | "B" | "C" ] : any } inの後ろにある要素(Union型の場合には1つずつ取り出すイメージ)をキーとしたプロパティを作成します。 extends classやinterface,typeなどを継承する際に用いられる extends キーワードですが、 Conditional Types と呼ばれる機能でも使用されています。 // T1 = true type T1 = number extends number | string ? true : false ; // T2 = false type T2 = number extends null ? true : false ; T extends U ? X : Y となっている箇所が Conditional Types です。TypeScript 2.8から導入されました。 TがUに割り当て可能であればX、割り当て不可であればYを返します。 上の例では number 型は number | string 型にもちろん割り当てられますので、T1は true 型になります。 しかし null 型には割り当てられませんので、T2は false 型になります。 infer inferは推論した型を型変数として使用するための仕組みです。Conditional Types内で使用できます。 例としてこの後紹介するUtility Typesから ReturnType という型を見てみます。 type ReturnType < T extends ( ...args: any ) => any > = T extends ( ...args: any ) => infer R ? R : any ; ジェネリクス T extends (...args: any) => any の部分はTに割り当てられる型を関数型に制限しています。 右辺の T extends (...args: any) => infer R はTが関数型かどうかをextendsで判定するとともに、 関数の戻り値の型を R という新たな型変数にバインドしています。 当然Tが関数型でない場合にはRに値がバインドされないので、Rが使用できるのはTが関数型の場合のみです。 その他の場合にはany型になります。 Utility Types 最後にTypeScript組み込みの、いくつかの便利な型について紹介します。 上のkeyofやextendsなどの使用例が盛り沢山です。 Partial / Required / Readonly この3つは似たような定義なのでまとめて紹介します。 type Partial < T > = { [ P in keyof T ] ?: T [ P ] ; } ; type Required < T > = { [ P in keyof T ] -?: T [ P ] ; } ; type Readonly < T > = { readonly [ P in keyof T ] : T [ P ] ; } ; Partialはオブジェクトのプロパティを省略可能( key?: value の構文)にします。 Mapped Type を用いて T のプロパティを列挙して、プロパティに ? をつけることで全てのプロパティを省略可能にしています。 T[P] の箇所は Lookup Types で、各プロパティの値の型を参照しています。 Requiredは全てのプロパティを省略不可に、Readonlyは変更不可にする型です。 補足: readonlyについて readonly はプロパティを変更不可にするキーワードです。 type U = { a: string , b: number } type T = Readonly < U > const t: T = { a: "str" , b: 123 , } t.a = "modified" ; // Attempt to assign to const or readonly variable readonly も ? と同様に -readonly とすることで、各プロパティから readonly を取り除くことができます。 Pick<T, K> Pickはオブジェクトの型から指定したプロパティのみを持つオブジェクトを作る型です。 type Pick < T , K extends keyof T > = { [ P in K ] : T [ P ] ; } // T = { a: string, b: number } type T = Pick < { a: string , b: number , c: boolean } , "a" | "b" > K extends keyof T の部分は、Kが指定できるプロパティをTが持つプロパティのみに制限しています。 Record<K, T> Recordは2つの型K, Tを受け取り、Kの各プロパティに対してTを値としたオブジェクトの型を作る型です。 type Record < K extends keyof any , T > = { [ P in K ] : T ; } ; keyof any は確認してみるとわかりますが、 string | number | symbol というUnion型です。 type K = string | number | symbol つまり K extends keyof any はキーとする型Tを、キーとして指定できる型に制約しているということになります。 Exclude<T, ExcludedUnion> Excludeは型TからExcludedUnionの各要素を除いたものを作る型です。 type Exclude < T , U > = T extends U ? never : T ; // type E = 123 | "str" type E = Exclude < 123 | "str" | (() => any ), Function > 123 | "str" | (() => any) というUnion型から、Function型に割り当て可能な型のみを取り除いています。 never 型を返却する理由は、先の方でも述べましたが、以下のようにUnion型における never はないものとして扱えるためです。 // U1 = string | number type U1 = string | number | never ; // U2 = void type U2 = null | void | never ; Union Distribution 上の例のように、 T extends U のT が型変数かつUnion型の場合、TypeScriptは Union Distribution という特殊な挙動になります。 type Exclude < T , U > = T extends U ? never : T ; // type E = 123 | "str" type E1 = Exclude < 123 | "str" | (() => any ), Function > // E1はこんな感じに分配されるイメージ type E2 = 123 extends Function ? never : 123 | "str" extends Function ? never : "str" | (() => any ) extends Function ? never : (() => any ); 参考: TypeScript: Documentation - Advanced Types#Distributive conditional types Extract<T, Union> Excludeの逆です。 type Extract < T , Union > = T extends Union ? T : never ; Omit<Type, Keys> TypeScript 3.5から導入されました。 Pickとは対照的に、プロパティをKeysとしてUnion型で指定しTypeから取り除きます。 type Omit < T , K extends keyof any > = Pick < T , Exclude <keyof T , K >>; // T = { c: boolean } type T = Omit < { a: string , b: number , c: boolean } , "a" | "b" > 既出のUtility TypesであるPickとExcludeを使用しています。 ExcludeでTのプロパティからKを除き、残ったプロパティのみの型をPickで作成しています。 NonNullable 型Tを受け取り、名前通りnullはもちろん、undefinedもTから取り除きます。 type NonNullable < T > = T extends null | undefined ? never : T ; // U = string | (() => any) type U = NonNullable < string | null | undefined | (() => any )> Parameters 関数型を受け取り、引数の型をタプル型として返却します。 type Parameters < T extends ( ...args: any ) => any > = T extends ( ...args: infer Arg ) => any ? Arg : never ; // P1 = [a: number, b: string] type P1 = Parameters <( a: number , b: string ) => void > // P2 = type P2 = [a: { b: number, c: boolean }] type P2 = Parameters <( a: { b: number , c: boolean } ) => string > // P3 = unknown[] type P3 = Parameters < any > infer を使用して引数の型を推論し、結果をArgという新たな型変数にバインドしています。 ConstructorParameters Parameterのコンスト ラク タ版です。 type ConstructorParameters < T extends new ( ...args: any ) => any > = T extends new ( ...args: infer Arg ) => any ? Arg : never ; class A { constructor( private id: number , private name: string , ) {} } // U = [id: number, name: string] type U = ConstructorParameters <typeof A > T extends new (...args: any) => any でTの取りうる型をコンスト ラク タに制限しています。 ReturnType Parametersの戻り値版です。 type ReturnType < T extends ( ...args: any ) => any > = T extends ( ...args: any ) => infer R ? R : any ; // U1 = void type U1 = ReturnType <() => void > function f () { return Math .random () < 0.5 ? "OK" : "NG" } // U2 = "OK" | "NG" type U2 = ReturnType <typeof f > infer で今度は戻り値の型を推論しています。 InstanceType constructorの戻り値の型を取得します。 ReturnTypeのコンスト ラク タ版です。 type InstanceType < T extends new ( ...args: any ) => any > = T extends new ( ...args: any ) => infer Class ? Class : any ; class C { constructor( private name: string = "Guest" , private id: number = 0 ) {} } // U1 = C type U1 = InstanceType <typeof C > // U2 = any type U2 = InstanceType < any > ThisParameterType Tが関数型で this をパラメータとして持つ場合にその型を返却します。 type ThisParameterType < T > = T extends ( this : infer This , ...args: any ) => any ? This : unknown ; function f1 ( this : number ) {} function f2 ( arg: number ) {} // F1 = number type F1 = ThisParameterType <typeof f1 > // F2 = unknown type F2 = ThisParameterType <typeof f2 > this パラメータは必ず引数の先頭である必要があるため、それを利用してthisの型を推論しています。 OmitThisParameter 関数型Tを受け取り、Tが this パラメータを含んでいればそれを除いた関数型を返します。 type OmitThisParameter < T > = unknown extends ThisParameterType < T > ? T : T extends ( ...args: infer A ) => infer R ? ( ...args: A ) => R : T ; // F1 = (a: string) => any type F1 = OmitThisParameter <( this : number , a: string ) => any > // F2 = 123 type F2 = OmitThisParameter < 123 > unknown extends ThisParameterType<T> でTがthisパラメータを含むかどうか確認しています。 thisは可変長引数のパラメータに含まれないようなので、関数型であれば可変長引数の型を推論して返します。 ThisType この型を利用するためには --noImplicitThis を有効にする必要があります。 公式の例を引用して説明します。 TypeScript: Documentation - Utility Types#ThisType type ObjectDescriptor < D , M > = { data?: D ; methods?: M & ThisType < D & M >; // Type of 'this' in methods is D & M } ; function makeObject < D , M >( desc: ObjectDescriptor < D , M >) : D & M { let data: object = desc.data || {} ; let methods: object = desc.methods || {} ; return { ...data , ...methods } as D & M ; } let obj = makeObject ( { data: { x: 0 , y: 0 } , methods: { moveBy ( dx: number , dy: number ) { this .x += dx ; // Strongly typed this this .y += dy ; // Strongly typed this } , } , } ); obj.x = 10 ; obj.y = 20 ; obj.moveBy ( 5 , 5 ); ObjectDescriptor 内の methods?: M & ThisType<D & M> は、methods内で使用される this の型をobjの型である D & M 型に アノテーション します。 D & M 型はdataの型とmethodsの型の交差型なので、今回は以下のような型になります。 { x: number ; y: number ; moveBy ( dx: number , dy: number ) : void ; } makeObjectの引数が ObjectDescriptor 型なので、method内の this が上のような型に推論されるため this.x の形式でxにアクセスできる、ということだと思われます。 ちなみにThisTypeで アノテーション しない場合には関数内のthisは以下の型になりますので、TypeScriptはエラーを出します。 moveBy ( dx: number , dy: number ) { // Property 'x' does not exist on type '{ moveBy(dx: number, dy: number): void; }' this .x += dx ; // Property 'y' does not exist on type '{ moveBy(dx: number, dy: number): void; }' this .y += dy ; } Uppercase / Lowercase ここから紹介する4つの型はTypeScript 4.1から導入された新しめの型です。せっかくですので紹介します。 いずれも文字列型を操作できます。 // U1 = "FOO" type U1 = Uppercase < "foo" >; // U2 = "bar" type U2 = Lowercase < "BAR" > Capitalize / Uncapitalize // C1 = "Foo" type C1 = Capitalize < "foo" >; // C2 = "bar" type C2 = Uncapitalize < "Bar" > これらは単体で使うというよりかは、同じく4.1から導入された仕組みである Template literal types の補助として使われるのではと思います。 Template literal types の説明は流石に入門記事の範囲外のため割愛します。詳しく知りたい方はこちらをご覧ください。 TypeScript: Documentation - Template Literal Types まとめ 入門記事ということで長めになってしまいましたが、これをきっかけに少しでもTypeScriptに興味を持っていただけたなら嬉しい限りです。 紹介していないTypeScriptの機能もまだまだあるようですので、また機会があれば書かせていただきたいと思います。 参考 O'Reilly Japan - プログラミングTypeScript 実践TypeScript | マイナビブックス TypeScript: Handbook - The TypeScript Handbook TypeScript: TSConfig Reference - Docs on every TSConfig option TypeScript: Documentation - Utility Types TypeScript Ninja TypeScriptの型入門 - Qiita TypeScriptの型初級 - Qiita さようなら、TypeScript enum | Kabuku Developers Blog TypeScriptのenumを使わないほうがいい理由を、Tree-shakingの観点で紹介します - LINE ENGINEERING TypeScriptのInterfaceとTypeの比較 - Qiita TypeScriptにヤバい機能が入りそうなのでひとしきり遊んでみる|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社 TypeScript: Interfaces vs Types - Stack Overflow assignabiltity between interfaces and types · Issue #14736 · microsoft/TypeScript · GitHub エンジニア 中途採用 サイト ラク スでは、エンジニア・デザイナーの 中途採用 を積極的に行っております! ご興味ありましたら是非ご確認をお願いします。 https://career-recruit.rakus.co.jp/career_engineer/ カジュアル面談お申込みフォーム どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。 以下フォームよりお申込みください。 forms.gle イベント情報 会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください! rakus.connpass.com