こんにちは。新卒のid:w1pと申します。
今回業務でTypeScriptを導入するということで、いい機会なのでTypeScriptについていろいろ調べました。
目次
- 環境構築
- TypeScriptの基本的な文法
- 基本の型
- 複合型
- その他の型
- インターフェースと型エイリアス
- ジェネリクス
- tsconfig.json
- 型を操作する
- まとめ
- 参考
環境構築
Microsoft公式がブラウザ上で実行できるサンドボックスを提供しており、これを使用すると手軽にTypeScriptを試すことができます。
TypeScriptからJavaScriptへの変換結果や、型定義ファイル生成の結果まで見れます。
この記事で出てくるサンプルコードはすべてTS Playground v4.1.2上で行っています。
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