TypeScript の基礎を学ぶ初心者向けチートシート

TwitterFacebookHatena

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!"

nullundefinedは、それぞれ JavaScript における特別な値で、無効な値または値が存在しないことを示すために使用されます。

// null型
const n: null = null
console.log(n) // null

// undefined型
const u: undefined = undefined
console.log(u) // undefined

プリミティブ型

null と 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 alias)

関数型エイリアス

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

関数型インターフェース

interface を使用して、関数の型も定義することができます。以下の例では、AddFunc という名前の関数の型を定義しています。この関数は、2 つの数値を引数に取り、数値を返すと定義されています。そして、add という変数は AddFunc 型として初期化され、その後アロー関数が割り当てられています。このアロー関数は、2 つの引数を取り、その合計を返します。

interface AddFunc {
  (num1: number, num2: number): number
}

const add: AddFunc = (n1, n2) => {
  return n1 + n2
}

type と interface

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関数は、最初の引数として文字列の挨拶を受け取り、その後に追加で任意の数の名前を受け取ります。残余引数namesstring[]と注釈されており、...namesという記法により、それらは配列として扱われます。

Rest パラメータ(残余引数)...を使う

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は不変性を強制するための便利な機能で、コードの予測可能性と安全性を高めます。

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関数はジェネリック型 TU を受け取り、それぞれの型を引数 firstsecond に対応づけています。この関数の戻り値の型は [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 を使うことで、型 Tlength プロパティを持つことを保証できるので、エラーになりません。

function logLength<T extends { length: number }>(arg: T) {
  console.log(arg.length)
  return arg
}

ここで T extends { length: number } は、型 Tlength プロパティを持つオブジェクトでなければならないという制約を設けています。これにより、関数内で安全に arg.length を使用することができます。

型引数を持つ型

型を定義するときにパラメータを持たせることができます。型引数はジェネリックの「具体化」の一部で、ジェネリック型パラメータを特定の型で置き換えます。

以下のPair<T> 型は、型 T の要素からなる長さ 2 のタプルを表します。たとえば、型 Tnumber の場合、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)
}

Rest パラメータ(可変長引数)

オプショナル引数

関数の引数が必須でない場合、その引数はオプショナル引数となります。オプショナル引数は、引数の型の後に ? を追加することで定義できます。

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)
}

型ガード

型ガードは、特定のスコープ内で変数の型を確定する機能です。typeofinstanceof を使用することで型ガードを実装できます。

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'

keyof オペレーター

lookup 型

lookup 型(または、インデックスアクセス型)は、特定のプロパティの型を取得することができます。

type User = {
  name: string
  age: number
}

type UserName = User['name'] // 'string'

lookup 型

never 型

never 型は、値が存在しない(発生しない値)ことを示します。例えば、関数が常にエラーを投げる(つまり、戻り値が存在しない)場合、その戻り値の型は never となります。

function throwError(message: string): never {
  throw new Error(message)
}

never 型

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 を記述するときには、ReactNodeReactElementJSX.Element といった型がよく使われます。

ReactNode は、React のコンポーネントの出力や JSX を扱うための型です。React コンポーネントが返すことができるものすべてを含みます。具体的には、以下の型を含みます。

  • ReactChildReactElement または 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.ElementReactElement として扱うことができます。ただし、JSX を実装している他のライブラリ(例えば Preact)では、JSX.Element が異なる型となる可能性があります。

const Element = (): JSX.Element => <div>Hello, world!</div>

TypeScript の基礎を学ぶ初心者向けチートシート