TL;DR
このページは、TypeScript の基本的な文法や機能を噛み砕いて説明したチートシートです。本来は自分の学習用に作ったもので、毎日サラッと見返せるようにセクションの文章量は減らしてあります。とはいいましても、TypeScript の主要な特性を網羅しているので、TypeScript の復習にもご活用できるかと思います。
項目 | 内容 |
---|---|
対象者 | TypeScript の基礎を学び直したい方 |
特徴 | 短いセクションなので流し見しやすい |
使い方 | 流し見する |
学習時間 | 60 分 ~ 120 分 |
TypeScript とは?
一言で言うと TypeScript は、JavaScript に静的な型システムを追加したプログラミング言語です。型チェックを行うことができます。TypeScript の静的型付け以外の部分は JavaScript と同じです。
型注釈(Type Annotation)
TypeScript では、変数の宣言時に「型注釈」と呼ばれる型情報を使用して、その変数がどのような型の値を持つべきかを指定することができます。型注釈はコロン(:)に続いて型名を書くことで行います。
const name: string = 'Hanako'
プリミティブ型
プリミティブとは、基本的な値です。TypeScript は JavaScript と同様に、以下のようなプリミティブ(基本)型をサポートしています。
boolean は、論理値を表すために使用されます。真または偽の値を持つことができます。
const hasCompleted: boolean = true
console.log(hasCompleted) // true
number は、整数や浮動小数点数を表すために使用されます。JavaScript と同様に、TypeScript のすべての数値は浮動小数点数として表現されます。
const age: number = 20
console.log(age) // 20
string は、テキストデータを表すために使用されます。ダブル("")またはシングル('')クォートで囲むか、バックティック(``)を使用してテンプレート文字列を作成できます。
const greeting: string = 'Hello, World!'
console.log(greeting) // "Hello, World!"
null
とundefined
は、それぞれ JavaScript における特別な値で、無効な値または値が存在しないことを示すために使用されます。
// null型
const n: null = null
console.log(n) // null
// undefined型
const u: undefined = undefined
console.log(u) // undefined
型推論
開発者が変数に明示的に型を指定しなくても、TypeScript がその型を推論する機能です。以下の例では、x
に直接数字の 3
が割り当てられているため、TypeScript は x
の型を number
と推論します。そのため、後続のコードで x
に文字列を割り当てようとすると、TypeScript のコンパイラはエラーを報告します。
const x = 3 // 'number' type is inferred
メソッド記法の型
オブジェクトのメソッドの型を定義します。
const person = {
name: 'Hanako',
greet: (text: string): string => {
return `${text}, ${this.name}!`
},
}
型エイリアス(type alias)
型エイリアスは、型名を宣言する文。特定の型に名前を付けるためのエイリアスを作成し、同じ型を複数回利用させます。これにより型を再利用し、コードの可読性を向上させることができます。type
キーワードを使用して型エイリアスを作成します。
また、任意の型に対して使用できるため、プリミティブ型、ユニオン型、インターセクション型、タプル、その他の型エイリアスなど、さまざまな型に名前を付けることができます。ただし、一度作成した型エイリアスは拡張や実装を行うことはできません。
type User = {
name: string
age: number
}
const user: User = {
name: 'John',
age: 30,
}
関数型エイリアス
type
を使用してアロー関数の型を定義することも可能です。
type AddFunctionType = (x: number, y: number) => number
const add: AddFunctionType = (x, y) => {
return x + y
}
上記の例では、AddFunctionType
は 2 つの数値を引数に取り、数値を返す関数の型として定義されています。その後、add
関数はこの型を持つことが指定されています。このように型エイリアスを使用することで、複雑な型を一度定義し、それを再利用することができます。
インターフェース(interface)
interface
宣言は、オブジェクトの型を定義します。type
キーワードとは異なり、=
は必要ありません。また、インターフェースは他のインターフェースや型エイリアスを拡張(継承)することができます。
interface Person {
name: string
age: number
}
const bob: Person = {
name: 'Bob',
age: 25,
}
interface
は主にオブジェクトの形状を定義するために使用されますが、それだけではありません。関数、クラス、配列、インデックス可能な型など、様々な形の型に使用できます。また、ジェネリクスを使用したインターフェースの定義も可能です。
関数型インターフェース
interface
を使用して、関数の型も定義することができます。以下の例では、AddFunc
という名前の関数の型を定義しています。この関数は、2 つの数値を引数に取り、数値を返すと定義されています。そして、add
という変数は AddFunc
型として初期化され、その後アロー関数が割り当てられています。このアロー関数は、2 つの引数を取り、その合計を返します。
interface AddFunc {
(num1: number, num2: number): number
}
const add: AddFunc = (n1, n2) => {
return n1 + n2
}
export interface
TypeScript では、interface
を他のモジュールから利用できるようにするためにexport
キーワードを使って公開することができます。これにより、そのinterface
は他の TypeScript ファイルからインポートして利用することができます。
// interfaces.ts
export interface Person {
name: string
age: number
}
上記のPerson
インターフェースを別のファイルで利用するためには、以下のようにimport
ステートメントを使ってインポートします。
// main.ts
import { Person } from './interfaces'
const Hanako: Person = {
name: 'Hanako',
age: 25,
}
上記の例では、Person
インターフェースがinterfaces.ts
からmain.ts
へとインポートされ、main.ts
内で利用されています。
オブジェクトに型をつける
オブジェクトに型をつけるには、主にinterface
またはtype
エイリアスを使います。
interface
を使用する場合
// オブジェクト型
interface Person {
name: string
age: number
}
// オブジェクトの定義
const person: Person = {
name: 'Alice',
age: 25,
}
type
エイリアスを使用する場合
type Person = {
name: string
age: number
}
const person: Person = {
name: 'Alice',
age: 25,
}
オプショナルなオブジェクト型
TypeScript では、オブジェクトの特定のプロパティがオプショナルであることを示すことができます。これは、特定のプロパティが存在するかもしれない、または存在しないかもしれない場合に便利です。プロパティ名の後に?
を付けることでそのプロパティをオプショナルとして宣言できます。
オブジェクト型を使う場合
const person: { name: string; age?: number } = {
name: 'Hanako',
// age does not need to be defined
}
interface を使う場合
interface OptionalPerson {
name: string
age?: number // age is optional
}
const alice: OptionalPerson = {
name: 'Alice', // age does not need to be defined
}
インデックスシグネチャ
インデックスシグネチャは、オブジェクトのプロパティに対する動的な型付けを可能にします。「動的な型付け」というのは、オブジェクトのプロパティ名とその値が実行時に決まる(つまり、コードが書かれたときではなく、コードが実行されるときに決まる)ことを指します。
文字列か数値でアクセスできるプロパティに対して型付けを提供します。オブジェクトが多数のプロパティを持つ場合に役立ちます。
基本
// 任意の名前のプロパティが型を持つ
[キー名: string]: 型
例
interface StringDictionary {
[index: string]: string
}
const dictionary: StringDictionary = {
hello: 'こんにちは',
goodbye: 'さようなら',
}
TypeScript のインデックスシグネチャで最も一般的に使用されるのは、文字列キーに対するものです。JavaScript のオブジェクトのプロパティ名は基本的に文字列であるため、それに対応する形で文字列キーのインデックスシグネチャをよく使います。
オプショナルなプロパティ宣言
オブジェクトの型定義で、プロパティ名の後に ?
を付けることで、そのプロパティが必須ではないことを示すことができます。あるかわからないプロパティ、あってもなくてもよいプロパティのことです。
interface Person {
name: string
age: number
email?: string
}
const charlie: Person = {
name: 'Charlie',
age: 20,
}
配列
型の後に []
を付けることで、その型の要素からなる配列の型を表します。例えば、number[]
という型は、number
型の要素からなる配列を表します。また、Array<型>
のように、ジェネリクスを使って表現することもできます。
const numbers: number[] = [1, 2, 3, 4, 5] // number 型の要素からなる配列
const names: string[] = ['Hanako', 'Bob', 'Charlie'] // string 型の要素からなる配列
const hoge: Array<string> = ['Hanako', 'Bob', 'Charlie'] // Array ジェネリックを使った string 型の要素からなる配列
配列をループする
TypeScript で配列をループするための基本的な方法は、JavaScript と基本的に同じです。
for
ループ
const numbers: number[] = [1, 2, 3, 4, 5]
for (let i = 0; i < numbers.length; i++) {
console.log(numbers[i])
}
for...of
ループ
const numbers: number[] = [1, 2, 3, 4, 5]
for (const num of numbers) {
console.log(num)
}
forEach
メソッド
const numbers: number[] = [1, 2, 3, 4, 5]
numbers.forEach((num) => {
console.log(num)
})
map
メソッド
const numbers: number[] = [1, 2, 3, 4, 5]
const squares: number[] = numbers.map((num) => {
console.log(num)
return num * num
})
注意:map
メソッドは新しい配列を生成します。ここでは各要素を二乗して新しい配列square
を作成しています。
タプル型
タプル型(Tuple)は、異なる型の要素を固定の数だけ持つことができる配列の一種です。各要素の型と順序が既知であり、一定です。
// Define a tuple
let tuple: [string, number, boolean]
// Initialize it
tuple = ['hello', 10, true] // OK
// Incorrect initialization
// tuple = [10, 'hello', true]; // Error
この例では、tuple
という名前のタプルを定義し、それをstring
, number
, boolean
の順序で初期化しています。もし順序を間違えると、TypeScript はエラーを出します。なお、タプルはそれぞれの位置に特定の型を期待しているため、順序は重要です。
アロー関数の型注釈
アロー関数を使用する場合、パラメータの型を注釈し、関数の返り値の型も注釈することができます。
const add = (a: number, b: number): number => a + b
高階関数
高階関数(Higher-Order Function)とは、関数を引数として受け取ったり、関数を結果として返したりする関数のことを指します。JavaScript や TypeScript では、関数は第一級オブジェクトなので、他のオブジェクトと同様に扱うことができます。これにより、関数を引数として渡したり、戻り値として返したりすることが可能となります。
const greet = (name: string) => (greeting: string) => `${greeting}, ${name}!`
const greetJohn = greet('John') // greetJohn は高階関数 greet によって返される関数です。
console.log(greetJohn('Hello')) // これは "Hello, John!" を出力します。
console.log(greetJohn('Good morning')) // これは "Good morning, John!" を出力します。
上記の高階関数 greet 関数は、は引数として name(文字列)を受け取り、その name を使用して別の関数を返します。返される関数は、あいさつのタイプ(文字列)を引数に取ります。
残余引数の型注釈
TypeScript では関数の引数の一部が固定で、残りの部分が可変長である場合には、固定部分の引数に型を指定した後、残りの部分を配列として型注釈をします。これを「残余引数(Rest Parameters)」と呼びます。
const greet = (greeting: string, ...names: string[]): void => {
return names.forEach((name) => console.log(`${greeting} ${name}`))
}
greet('Hello', 'Hanako', 'Bob', 'Charlie')
// Outputs: "Hello Hanako", "Hello Bob", "Hello Charlie"
このgreet
関数は、最初の引数として文字列の挨拶を受け取り、その後に追加で任意の数の名前を受け取ります。残余引数names
はstring[]
と注釈されており、...names
という記法により、それらは配列として扱われます。
readonly
readonly
修飾子を使用して特定のオブジェクトのプロパティや配列が「変更不可能」であることを指定できます。この修飾子は変数、プロパティ、または配列に使用することができます。
オブジェクトのプロパティをreadonly
として定義すると、そのプロパティの値はオブジェクトの初期化後に変更することができません。
interface Point {
readonly x: number
readonly y: number
}
let p1: Point = { x: 10, y: 20 }
p1.x = 5 // Error, x is readonly
同様に、配列をreadonly
として宣言すると、その配列は後で変更することができません。
let a: readonly number[] = [1, 2, 3, 4]
a.push(5) // Error, a is readonly
a[2] = 10 // Error, a is readonly
readonly
は不変性を強制するための便利な機能で、コードの予測可能性と安全性を高めます。
ジェネリック型
"ジェネリック"という言葉は、"一般的な"や"普遍的な"といった意味を持っています。プログラミングの文脈では、ジェネリクスはコードを一般化(汎用化)するためのツールとして使用されます。
TypeScript のジェネリック型は、型をパラメータとして受け取ることができる汎用的な型です。型を先に抽象化して、呼び出されるときに具体的な型を指定できます。使われる前まで型が確定しません。
何かを「入れる」ことでその型になる機能を持っています。これにより、同じ関数やクラスで様々な型を扱うことができます。T
の文字は、どんなアルファベットでも OK です。
// 1. 最初のTは、この関数がジェネリックであることを示しています
// 2. 第二のTは、この関数が引数として受け取る値の型を表しています
// 3. 第三のTは、この関数が返す値の型を表しています
const returnSame = <T>(arg: T): T => {
return arg
}
let outputString = returnSame<string>('hello')
// outputString は 'hello'
// あるいは
let outputNumber = returnSame<number>(100)
// outputNumber は 100
ジェネリック型(複数の引数)
ジェネリック型は一つの引数だけでなく、複数の引数も受け取ることができます。
const pair = <T, U>(first: T, second: U): [T, U] => {
return [first, second]
}
const result = pair('Hanako', 2023)
// Type of result is inferred to be [string, number]
この例では、pair
関数はジェネリック型 T
と U
を受け取り、それぞれの型を引数 first
と second
に対応づけています。この関数の戻り値の型は [T, U]
となり、呼び出し元に応じて型が決定します。result
の型は呼び出し元である pair("Hanako", 2023)
によって [string, number]
と推論されます。
interface にジェネリクスを使う
TypeScript のインターフェースでもジェネリクスを使用することができます。これにより、汎用的で柔軟なインターフェースを作成することが可能となります。
例えば、Container
というインターフェースを定義し、このインターフェースが任意の型のitem
を保持できるようにしたいとします。ジェネリクスを使用すると以下のようになります。
interface Container<T> {
item: T
}
上記の、T
はジェネリクスの型引数で、具体的な型が指定されるまで未定義です。
このContainer
インターフェースを使用する際には、型引数を渡して具体的な型を決定します。例えば、文字列を保持するコンテナを作るには次のようにします。
const stringContainer: Container<string> = {
item: 'Hello!',
}
または、数値を保持するコンテナを作るには次のようにします。
const numberContainer: Container<number> = {
item: 42,
}
ジェネリック型の extends
ジェネリック型の extends
は、制約または条件を設けるために使用されます。これにより、ジェネリック型パラメータに特定の型のみを許可したり、特定のプロパティを持つ型を必要とするなど、より具体的な要件を設定できます。
以下の例だと、ジェネリック型の extends
を使うことで、型 T
が length
プロパティを持つことを保証できるので、エラーになりません。
function logLength<T extends { length: number }>(arg: T) {
console.log(arg.length)
return arg
}
ここで T extends { length: number }
は、型 T
が length
プロパティを持つオブジェクトでなければならないという制約を設けています。これにより、関数内で安全に arg.length
を使用することができます。
型引数を持つ型
型を定義するときにパラメータを持たせることができます。型引数はジェネリックの「具体化」の一部で、ジェネリック型パラメータを特定の型で置き換えます。
以下のPair<T>
型は、型 T
の要素からなる長さ 2 のタプルを表します。たとえば、型 T
が number
の場合、Pair<number>
は [number, number]
、つまり 2 つの数値からなるペアを表します。
type Pair<T> = [T, T]
let pairOfNumbers: Pair<number> = [1, 2]
let pairOfStrings: Pair<string> = ['hello', 'world']
上のコードでは、Pair<number>
と Pair<string>
という 2 つの異なる型を作成していますが、どちらも元々は同じ Pair<T>
型から派生しています。ある基本的な型(この場合は Pair<T>
)から、さまざまな具体的な型(この場合は Pair<number>
や Pair<string>
など)を作成することができます。
可変長引数
関数が任意の数の引数を受け取ることができます。そのための型定義は以下のようになります。
function sum(...numbers: number[]): number {
return numbers.reduce((acc, current) => acc + current, 0)
}
オプショナル引数
関数の引数が必須でない場合、その引数はオプショナル引数となります。オプショナル引数は、引数の型の後に ?
を追加することで定義できます。
function greet(name: string, age?: number): string {
return age ? `Hello ${name}, you are ${age} years old.` : `Hello ${name}!`
}
コールシグネチャ
関数の型を表すために、関数のシグネチャ(引数と戻り値の型)を定義することができます。type
またはinterface
内で関数型を定義するために使います。
type Greet = (name: string, age?: number) => string
const greet: Greet = (name, age) => (age ? `Hello ${name}, you are ${age} years old.` : `Hello ${name}!`)
Async/Await
非同期処理を扱うための async/await 構文を使用することができます。Promise の型は、Promise<T>
のように表され、T
は Promise が解決したときの値の型を表します。
async function fetchUser(): Promise<User> {
const response = await fetch('/api/user')
const user: User = await response.json()
return user
}
ユニオン型
ユニオン型は、複数の型のいずれか一つを表すことができる型です。ユニオン型は、|
演算子を使用して定義します。
// ユニオン型の例
const numberOrString: number | string = 1
console.log(numberOrString) // 結果は: 1
インターセクション型
インターセクション型は、複数の型を全て含む新しい型を作成することができます。インターセクション型は、&
演算子を使用して定義します。
type User = {
name: string
age: number
}
type Employee = User & {
manager: string
}
const employee: Employee = {
name: 'Hanako',
age: 30,
manager: 'Bob',
}
リテラル型
リテラル型は、「特定の値のみ」を表す型です。決まった数値・文字列だけ指定できるようにしたい場合に使用します。文字列リテラル型や数値リテラル型、真偽値リテラル型などがあります。
type Direction = 'up' | 'down' | 'left' | 'right'
const move: Direction = 'up' // 'up', 'down', 'left', 'right' のいずれかを設定できます
console.log(move) // 結果は: "up"
テンプレートリテラル型
型システムの中で文字列の結合や文字列からの抽出を行うことができます。JavaScript のテンプレートリテラルでは ${ }
の中に式が入りますが、TypeScript のテンプレートリテラル型では型が入ります。
type EventName = 'click' | 'hover'
type Handler = {
on(eventName: `${EventName}`, callback: () => void): void
}
let handler: Handler = {
on(eventName, callback) {
// implementation...
},
}
handler.on('click', () => console.log('Clicked!')) // Valid
handler.on('hover', () => console.log('Hovered!')) // Valid
handler.on('touch', () => console.log('Touched!')) // Error
Optional Chaining
オプショナルチェイニングは、プロパティが存在するか確認するための短い構文です。null や undefined の可能性があるオブジェクトに対して安全に処理を書くことができます。オブジェクトのプロパティが存在しない場合にエラーを投げるのではなく、undefined
を返します。
type User = {
name: string
address?: {
street: string
city: string
}
}
const user: User = {
name: 'Hanako',
}
console.log(user.address?.city) // undefined
Non-null Assertion Operator
Non-null assertion operator (!
) は TypeScript の特殊な構文で、値が null または undefined でないことを明示的に示すことができます。これは主に、開発者が null または undefined が存在しないことを確認しているが、コンパイラがその事実を認識できない場合に使用します。しかし、過度に使用すると型安全性を損なう可能性があるため、注意が必要です。
function myFunction(maybeString: string | undefined | null) {
const onlyString: string = maybeString!
// I'm sure this is not null or undefined
console.log(onlyString)
}
型ガード
型ガードは、特定のスコープ内で変数の型を確定する機能です。typeof
や instanceof
を使用することで型ガードを実装できます。
const doSomething = (value: number | string) => {
if (typeof value === 'string') {
console.log(value.toUpperCase()) // ここでは value は string として扱われます。
} else {
console.log(value.toFixed(2)) // ここでは value は number として扱われます。
}
}
const value1: string = 'hello'
const value2: number = 42
doSomething(value1) // HELLO
doSomething(value2) // 42.00
keyof 型
keyof
型は、オブジェクトのすべてのプロパティ名を文字列リテラル型のユニオンとして取得します。
type User = {
name: string
age: number
}
type UserKeys = keyof User // 'name' | 'age'
lookup 型
lookup 型(または、インデックスアクセス型)は、特定のプロパティの型を取得することができます。
type User = {
name: string
age: number
}
type UserName = User['name'] // 'string'
never 型
never
型は、値が存在しない(発生しない値)ことを示します。例えば、関数が常にエラーを投げる(つまり、戻り値が存在しない)場合、その戻り値の型は never
となります。
function throwError(message: string): never {
throw new Error(message)
}
unknown 型
unknown
型は、任意の型を表します。ただし、unknown
型の値は、具体的な型が確定するまで利用することはできません。
let value: unknown = 'Hello'
value = 123
if (typeof value === 'number') {
console.log(value.toFixed(2))
}
mapped types
マップドタイプは、既存の型から新しい型を生成する機能です。例えば、既存の型のすべてのプロパティをオプショナルにする、あるいは読み取り専用にするなどが可能です。
type User = {
name: string
age: number
}
type OptionalUser = {
[K in keyof User]?: User[K]
}
const optionalUser: OptionalUser = {
name: 'Hanako',
}
条件型
条件型は、型の条件分岐を表す型です。条件型は T extends U ? X : Y
のような形で定義します。
type IsString<T> = T extends string ? true : false
type X = IsString<'Hello'> // 'true'
type Y = IsString<123> // 'false'
TypeScript のテスト(Jest)
TypeScript では、テストフレームワークとして Jest が有名かと思います。TypeScript の型安全性と Jest の強力なテスト機能が組み合わさることで、より堅牢なテストコードを書くことが可能です。
// someFunction.ts
export function someFunction(a: number, b: number) {
return a + b
}
// someFunction.test.ts
import { someFunction } from './someFunction'
test('adds 1 + 2 to equal 3', () => {
expect(someFunction(1, 2)).toBe(3)
})
補足
ここからは、React の型について説明します。
React props の型定義
type を使って、コンポーネントの props の型を定義することができます。
type Props = {
text: string // 文字列型
quantity: number // 数値型
isActive: boolean // 真偽値型
tags: string[] // 文字列の配列型
settings: {
// オブジェクト型
theme: string
}
items: {
// オブジェクトの配列型
id: string
value: number
}[]
execute: () => void // 関数型
}
const ExampleComponent = ({ text, quantity, isActive, tags, settings, items, execute }: Props) => {
// プロパティを使った処理をここに書く
// 例えば...
execute()
console.log(text)
return <>{/* 何かしらの表示 */}</>
}
ReactNode
TypeScript を使って React を記述するときには、ReactNode
、ReactElement
、JSX.Element
といった型がよく使われます。
ReactNode
は、React のコンポーネントの出力や JSX を扱うための型です。React コンポーネントが返すことができるものすべてを含みます。具体的には、以下の型を含みます。
ReactChild
(ReactElement
またはReactText
)ReactFragment
({}
またはReactNodeArray
)ReactPortal
string
number
null
undefined
boolean
以下は、ReactNode
を使った一例です。
import { ReactNode } from 'react'
type Props = {
children: ReactNode
}
const Component = ({ children }: Props) => <div>{children}</div>
ReactElement
ReactElement
は、React の要素(つまり、コンポーネントのインスタンス)を表す型です。具体的には、JSX から返されるものや React.createElement
から返されるものはすべて ReactElement
です。以下は、ReactElement
を使った一例です。
import { ReactElement } from 'react'
const Element = (): ReactElement => <div>Hello, world!</div>
JSX.Element
JSX.Element
は、JSX から返される要素を表す型です。この型は通常、ReactElement
を拡張しており、React を使っている場合、JSX.Element
は ReactElement
として扱うことができます。ただし、JSX を実装している他のライブラリ(例えば Preact)では、JSX.Element
が異なる型となる可能性があります。
const Element = (): JSX.Element => <div>Hello, world!</div>