⌨️

初心者向けTypeScript超入門!

2024/12/21に公開

はじめに

TypeScriptを初めて触れる方を対象にTypeScriptがどういうものなのかのイメージを掴んでもらえることをを目的としています。高度な内容やコンパイルオプションなどについての詳細は割愛します。

TypeScriptとは

  • TypeScriptとはaltJSの一種です。JavaScriptの型による不足を埋めるために型定義を加えた言語、いわばスーパーセットです。
  • TypeScriptではトランスコンパイラーによって最終的にJavaScriptに変換します。

実行環境

本稿のコードを試したいときはウェブ上で試せるTypeScript Playgroundをお勧めします。

https://www.typescriptlang.org/play/

型注釈 (型アノテーション)

[変数: 型名]の形式で型注釈(型アノテーション)を付与するのが基本です。

// 変数
let val: number = 0

// 関数
function add(value1: number: value2: number): number {
  return value1 + value2
}

// クラス
class User {
  id: number = 0
  name: string = ''

  getInfo() {
    return `User: ${this.id} ${this.name}`
  }
}

型推論

型推論とは、型注釈を明示的に指定せず、代入した値によって型を推論させることです。

// number型の数値が代入された時点でvalueの型はnumber型と推論される
let value = 0
// number型にstring型のものを代入することができないのでエラーになる
value = 'zero'

基本的な型

データ型

// 論理型
let booleanlVal: boolean = false

// 数値型
let numberVal: number = 3.14

// 文字列型
let stringVal: string = 'Hello World!'

// 配列型
let listVal: number[] = [0, 1, 2]

// タプル型
let TuppleValue: [string, number] = ['Hello World!', 0]

// インデックス型 (インデックスシグネチャ)
let indexSignatureVal: {[index: string]: number} = {
  'Hello': 0,
  'World!': 1,
}

// オブジェクト型
let ObjectVal: {
  stringProp: string,
  numberProp: number,
  // ?は省略可能意味する
  optionalProp?: boolean
} = {
  stringProp: 'Hello World!',
  numberProp: 0,
}

配列型

配列型は「型[]」の形式で型を宣言します。

タプル型

タプル型は[型, ...]の形式で型を宣言します。配列の型が固定されているに対してタプル型は配列の個々の要素に対して型を指定します。

インデックス型 (インデックスシグネチャ)

インデックス型はオブジェクト型は似ているが以下の違いがあります。

  • 任意の名前のプロパティ名を指定することができる
  • プロパティを複数追加することができる

オブジェクト型

オブジェクト型は「プロパティ名: 型」の形式で型を宣言します。オブジェクトリテラルは型宣言で定義したプロパティ名と同じ名前を指定する必要があります。

特殊な型

void型

void型は値を返さないを表す型です。一般的に関数の戻り値として使います。

function hello(): void {
  console.log('Hello World!')
}

any型

any型はどんな型の値も受け取る型です。型宣言がない変数定義のみの場合はany型となります。
型の安全性を壊すことになるので使用を避けるべき型です。

// 変数定義のみなのでany型が推と型推論される
let value
value = 0
value = 'Hello World!'

never型

never型は決して何も返さないを表す型です。void型と似ているが関数が終了するかどうかで使い分けます。
void型は関数が最後まで実行されるのに対して、never型は関数の処理が中断(例外を発生させる)もしくは永遠に続く無限ループのときに使用します。

function throwError(): never {
  throw new Error('Error!')
}

unknown型

unknown型は型が不明であることを表す型です。any型と似ているが代入された値を使用するときに型ガードを使用する必要があります。
any型は型の安全性を壊すのに対してunknown型はanyをより安全にした型と言い換えられます。

let value: unknown
value = 0
value = 'Hello World!'

// 型ガードで型を明確することでその型が持っている機能を使用することができるようになる
if (typeof value === 'string') {
  console.log(value.substring(1))
}

// 型ガードを行なっていないのでstring型の機能を使用することができないため、エラーになる
value.substring(0)

高度な型

Union型

Union型は「複数の型のいずれか」を表す型です。

// stringもしくnumber型の値を代入することができる
let value: string | number
value = 0
value = 'Hello World!'

インターセクション型

インターセクション型は、複数の型を組み合わせて新しい型を作成する機能です。複数の型のすべてのメンバーを含む型を作るために使用されます。

type Id = { id: number }
type Name = { name: string }

type User = Id & Name

let user: User = {
    id: 0,
    name: 'Hello World!',
}

null許容型

null許容型はnull/undefinedを代入ことができるを表す型です。TypeScript規定ではすべての型に対してnull/undefinedを代入することができる。
ただし、バグの原因になりやすいので、設定ファイルtsconfig.jsonでstrictまたはstrictNullChecksオプションをtrueに設定することで、規定でnull/undefinedの代入を禁止することをおすすめします。こういった設定がされた場合にnull許容型を明示に宣言することでnull/undefinedの代入を許します。
null許容型に対してnull/undefinedを禁止する型のことをnull非許容型です。

// nullの代入を許す
let value: string | null
value = 'Hello World!'
value = null

// undefinedの代入を許す
let value2: string | undefined = undefined
value2 = 'Hello World!'
value2 = undefined

リテラル型

リテラル型は取りうる値を限定するための型です。

let value:'Hello'
value = 'Hello'

// 主に以下のようにUnion型と見合わせて使う
let gender: 'male' | 'female'
gender = 'male'
gender = 'female'

ユーティリティ型

ユーティリティ型とは、TypeScriptが組み込みで用意してくれている型です。
よく利用するユーティリティ型をいくつか紹介します。

Required

すべてのプロパティからオプショナルであることを意味する?を取り除くユーティリティ型です。

type User = {
  id: number
  name?: string
}
type RequiredUser = Required<User>

変換後のRequiredUserの型は以下のようになります。

type RequiredUser = {
  id: number
  name: string
}

Readonly

プロパティをすべて読み取り専用にするユーティリティ型です。

type User = {
  id: number
  name?: string
}
type ReadonlyUser = Readonly<User>

変換後のReadonlyUserの型は以下のようになります。

type ReadonlyUser = {
  readonly id: number
  readonly name?: string | undefined
}

Partial

すべてのプロパティをオプションプロパティにするユーティリティ型です。

type User = {
  id: number
  name?: string
}
type PartialUser = Partial<User>

変換後のPartialUserの型は以下のようになります。

type PartialUser = {
  id?: number | undefined
  name?: string | undefined
}

高度な機能

型アサーション

型アサーションは、明示的に型を割り当てるための仕組みです。たとえば、TypeScriptはDOM APIの種類から、戻り値はある程度推論できるが、具体的な要素までの絞り込みはできないため、型アサーションを使用して明示的に型を指定してあげる必要があります。

// HTMLタグ
// <input id="nameInput" type="text" />

const input = document.getElementById("nameInput") as HTMLInputElement;
input.placeholder = "please input your name..."

構文は、「as 型」構文と「<型>」アングルブラケット構文の2種類あります。React.jsではアングルブラケット構文とJSXの見分けがつかないことがあるため、基本的にas構文を使用するとよいです。

// as構文
const value: string | number = 'Hello World!'
const strLength: number = (value as string).length

// アングルブラケット構文
const value2: string | number = 'Hello World!'
const strLength2: number = (<string>value2).length

非nullアサーション

非nullアサーションは、「!」構文を使用してnullやundefinedではないことを伝えるための仕組みです。

function getInfo(user: { id: number } | null) {
    return user!.id;
}

ジェネリクス

ジェネリクスとは、汎用的なクラスや関数(メソッド)に対して、特定の型を紐づけるための仕組みです。

// 関数の引数と戻り値に同じ型
function getValue<T>(value: T): T {
  return value
}

getValue<number>(0)
// コンストラクターの引数と関数の戻り値は同じ型
class User<T> {
  constructor(public name: T) {}

  getInfo(): T {
    return this.name
  }
}

let user = new User<string>('Hello World!')

型エイリアス

型エイリアスは、型に別名(エイリアス)を設定するための仕組みです。同じ型を複数の場所で使い回したい場合などで使用する。

type ObjecType = {
  stringProp: string,
  numberProp: number
}

let ObjectVal: ObjecType = {
  stringProp: 'Hello World!',
  numberProp: 0,
}

インターフェース

オブジェクト構造を定義するための型を提供する仕組みです。オブジェクトがどのようなプロパティやメソッドを持つべきかを宣言することで、型安全性を向上させ、コードの意図を明確にします。
型エイリアスと似ているが主に以下が違います。

宣言の仕方

// interfaceを使った定義
interface User {
  id: number
  name: string
}

// typeを使った定義
type User = {
  id: number
  name: string
}

interfaceのマージ

同じ名前のインターフェースを再定義すると、自動的にマージされる。型エイリアスでは、同じような複数の名前を定義することはできません。

interface User {
  id: number
}

interface User {
  name: string
}

const user: User = {
  id: 1,
  name: "Hello World!",
}

クラスの制約

クラスが実装するべき契約として使用できます。特定のインターフェースの構造に合致するするように制約を設けることができる。型エイリアスに対して、implementsすることはできません。

interface Info {
  printInfo(id: number, name: string): void
}

class User implements Info {
  printInfo(id: number, name: string) {
    console.log(`User: ${id} ${name}`)
  }
}

Discussion