TypeScript入門の入門

Published: 2020年10月28日 by tomsato

はじめに

このページは自分用メモとしてTypeScriptをチートシート的に入門内容をまとめています

どんなことをまとめているかについては目次を見てもらえればと思います

チートシートとは言いつつ、そもそもTypeScriptについてや、必要性などもまとめていくことで

TypeScriptについてざっくり理解できる程度の内容を目指し

何かわからなくなった時はこのページを見て思い出せば良いという程度にまとめていきたいと思います

TypeScriptとは

2014年頃にMicrosoftによって開発された、JavaScriptを拡張して作られたオープンソースプログラミング言語

特徴を一言で表すと「型定義できるJavaScript」で「JavaScriptの上位互換」としてもよく言われている

TypeScriptをコンパイルするとJavaScriptのコードに変換され、JavaScriptが動く環境であればすぐに使える

当然JavaScriptのライブラリを使えるため互換性は良い

TypeScriptの必要性

フロントエンドの発展

昨今フロントエンドの開発領域が広くなってきており、フロントエンドのコードが大規模化してきているという背景があり、TypeScriptの需要が高くなってきている

特に2017年にGoogleの社内標準言語としてTypeScriptが採用されてから、注目を浴びるようになり

「Angular」「Vue.js」「React」など現在の主流フロントエンドフレームワークの公式でTypeScriptが紹介されているためそのニーズの高さが伺える

参考:フロントエンドの歴史について

DXの発展

DX(Developer Experience 開発体験)が発展して、VS Code(Visual Studio Code)やIntelliJを使うことでIDE(統合開発環境)ならではの恩恵を受けることができる

補完機能等でTypeScriptの開発速度を格段に向上することができ、型の間違いによるエラーはリアルタイムにIDE上に表示されるのでバグを見つけやすくなっている

型定義による品質の向上

TypeScriptでは型システムを導入することができるため、修正の影響を見つけやすく、また開発時にバグを埋めにくいコードにすることができる

書き方を覚える必要があったり、型を書くことに慣れていない人にとっては面倒で開発スピードが下がるのでは?と懸念はあるが

数十行程度で終わる簡単なツールを作る程度だったら確かにTypeScriptは不要かもしれないが、それが数百行のファイルが行くつもあるなど規模が大きい開発を行う際には開発の中盤から後半にかけてはその恩恵を受けることができるためむしろ開発スピードは上がることになる

  • TypeScript使ってよかった例①:「フォームで受け取った金額の値を関数に渡してと…あれ関数引数はstringになってる…ああカンマも含まれるかもだからフォームの受け取りも文字列にしないと」
  • TypeScript使ってよかった例②:「このObjectの要素を1つ消すか…とりあえず型も直して一通り消したと思ったけど…おっIDEで赤くなってる、ここ忘れてた」
  • TypeScript使ってよかった例③:「この変数って中身何が入ってるんだっけ…booleanだから前行の式のチェック結果が入ってるのか…」

また開発が一旦完了して作成したアプリケーションが保守、運用モードになった際にはTypeScriptがあるかないかではソースコードの品質には雲泥の差ができており、その後月日が経って追加開発があった際には「TypeScriptがあってよかった…」と思える程度にはTypeScriptの恩恵を受けることになる

導入の容易さについて

現在の主要なフロントエンドフレームワークにTypeScriptを取り込むためのドキュメントは公式を始め色々な方が資料にまとめていたりするので、手軽に必要な情報を調べることができる

またJavaScriptオンリーで実装されているプロダクトに追加する際にも、一旦anyを使って段階的に取り込むなどの工夫をすることで、比較的容易にソースコードに型システムを取り込むことができる

TypeScript Hello World

TS Playground

とりあえずTypeScriptを触ってみたい、という人向けにTypeScriptの公式サイトに

TS Playground

と呼ばれるオンラインエディタがある

ts playground

そのエディタ上にTypeScriptのソースコードを記述すると右側の「.JS」タブにJavaScriptに変換されたソースコードが表示され

「Run」を実行するとソースコードが実行されconsole.logなどの出力結果を右側の「Logs」に表示することができる

対話形式で実行する

対話形式でTypeScriptを実行することもできる

$ npm i -g typescript ts-node

$ ts-node -v
v9.0.0

// 型の不一致によるエラーが発生できることを確認する
$ ts-node
> const a: string = 1
[eval].ts:1:7 - error TS2322: Type 'number' is not assignable to type 'string'.

1 const a: string = 1
        ~

型の種類について

boolean

true or false

let flag: boolean = false

flag = true

// 数字を入れるとエラーになる
flag = 1
[eval].ts:3:1 - error TS2322: Type 'number' is not assignable to type 'boolean'.

number

数字

let decimal: number = 256

// 文字列を入れるとエラーになる
decimal = 'hoge'

string

文字列

let color: string = 'white'

// 数字を入れるとエラーになる
color = 1

array

配列

const list1: number[] = [1, 2, 3]
console.log(list1[0]) // 出力: 1

const list2: string[] = ['one', 'two', 'three']
console.log(list2[0]) // 出力: one

// 指定した型以外の型を入れようとするとエラー
const list3: boolean[] = [true , 1]
[eval].ts:5:34 - error TS2322: Type 'number' is not assignable to type 'boolean'.

touple

複数の値を保持することができる

// 順番には注意
const x: [string, number] = ['age', 10] // OK
const y: [string, number] = [10, "age"] // NG

console.log(x[0]) // age
console.log(x[1]) // 10

any

型が不明な変数を扱う時に利用する、特定の値の型チェックを無効にする TypeScriptがないプロジェクトに導入する際に、まずはanyで埋めて、段階的に型制約を増やすなどを行うと移行しやすくなる

let all: any = 0
all = 1
all = 'hoge'
all = false

unknown (TypeScript 3.0で追加された)

any型のタイプセーフ対応版

任意の値を代入できるが、参照時には厳しく、型チェックが無いと参照できない

const one: any = 1
const two: unknown = 2
one.indexOf('1') // 実行時エラー
two.indexOf('2') // コンパイルエラー

if (typeof two === 'string') {
  two.indexOf('2');  // 型チェック後ならOK
}

void

型が無いことを表す、一般的に値を返さない関数の戻り型として利用する

// console.logは値を返さないのでconsole.logを実行する関数はvoid型になる
function printMassage(message: string): void {
  console.log(message)
}

null / undefined

undefinedとnull両方に、undefined型とnull型が存在している

null及びundefinedは全ての型のサブタイプであり、全ての型にnullとundefinedを代入できる

ただし--strictNullCheckフラグをtrueにすると代入できなくなる

never

発生し得ない値の型を表す

無限ループや例外発生時など値を返さないものに対してもnever型として表現することができる

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

object

オブジェクトを表す型

const obj1: object = {}
const obj2: object = { name: "hoge"}
console.log(obj2['name']) // hoge

関数の引数・戻り値に型を指定する

指定方法

function test(name: string): string {
  return 'hello ' + name
}

test('tomsato') // 出力結果: 'hello tomsato'

関数の引数をオプションにする

function test(name?: string): void {
  console.log(name)
}

test()      // 出力結果: undefined
test('aiu') // 出力結果: aiu

デフォルト引数を設定する

function test(name: string = 'tomsato'): void {
  console.log(name)
}

test()      // 出力結果: tomsato
test('aiu') // 出力結果: aiu

独自で作成した型に名前をつける

// 数字か文字列かnull(入力がないなど)を受け取る型を定義(Union Typesについては後述)
type PersonAge = number | string | null

// 変数の型として利用できる
let userTomsatoAge: PersonAge = null
userTomsatoAge = 18

// 関数の引数の型や戻り値の型としても利用できる
function fixAge(age: PersonAge): PersonAge {
  return age * 2
}

オブジェクトの型指定について

オブジェクトのプロパティを型指定する

type User = {
  name: string,
  age: number
}

const userA: User = { name: 'hoge', age: 18 } // OK

// 指定したプロパティの要素が足りないとエラー
const userB: User = { name: 'hoge' } // エラー
[eval].ts:7:7 - error TS2741: Property 'age' is missing in type '{ name: string; }' but required in type 'User'.

// 指定した要素以外にプロパティを指定した場合はエラー
const userC: User = { name: 'hoge', age: 18, other: '' } // エラー
[eval].ts:7:46 - error TS2322: Type '{ name: string; age: number; other: string; }' is not assignable to type 'User'.
  Object literal may only specify known properties, and 'other' does not exist in type 'User'.

プロパティの指定を任意にする

? をつけることでプロパティの指定を任意にすることができる

type User = {
  name: string,
  age?: number
}

const userD: User = { name: 'hoge' } // OK

プロパティを読み込み専用にする

type User = {
  readonly id: number,
  name: string
}

const userA: User = { id: 1, name: 'tomsato' }

userA.name = 'hoge' // OK
userA.id = 2        // OK
[eval].ts:9:7 - error TS2540: Cannot assign to 'id' because it is a read-only property.

Readonly型を使ってオブジェクトの書き換えを禁止する

オブジェクトはconstで定義しても書き換えができてしまう

type User = {
  name: string
}

const userA: User = { name: 'tomsato' }
userA.name = 'hoge' // OK

Readonly型を使うことで書き換えを禁止する

type User = {
  name: string
}

const userA: Readonly<User> = { name: 'tomsato' }
userA.name = 'hoge' // エラー
[eval].ts:5:7 - error TS2540: Cannot assign to 'name' because it is a read-only property.

オブジェクトに動的にプロパティを追加する

任意のプロパティを動的に追加する方法

// nameは必須だが、それ以外は任意で設定できるようにしたい場合の設定方法
type User = {
  name: string,
  [k: string]: any
}

const userA: User = { name: 'tomsato', age: 18 } // OK
userA.id = 1 // OK

[k: string] の部分を「インデックスシグネチャ」と呼び、プロパティを動的に追加することができるようになる

しかし注意点としては必須で追加しているプロパティと互換性のある型指定にしなければならない

// nameはnumberではなく互換性が無いためエラーになる
type User = {
  name: string,
  [k: string]: number
}
[eval].ts:2:3 - error TS2411: Property 'name' of type 'string' is not assignable to string index type 'number'.

// Union Typeを使って回避する
type User = {
  name: string,
  [k: string]: number | string
} // OK

// または1個階層を深くすることで対応する
type User = {
  name: string,
  enquete: {
    [k: string]: number
  }
}

任意のプロパティではなく、指定可能なプロパティ名を制限することができる

type Additional = 'age' | 'id'
type ValueType = string | number
type User = {
  name: string,
  enquete: {
    [K in Additional]?: ValueType
  }
}

const userA: User = { name: 'tomsato', enquete: {} }
userA.enquete.age = 18  // OK
userA.enquete.hoge = 18 // エラー

高度な型について

Intersection Types(交差)

複数の型を1つに結合する

type A = {
  x: string
}

type B = {
  y: number
}

// AとBの型を結合する
type C = A & B

const hoge: C = { x: 'aiu', y: 1 }

プリミティブ型のIntersection Typesは意味がないので使わない方が吉

// 代入できる値がないのでエラー
const age: string & number = 1
[eval].ts:4:7 - error TS2322: Type 'number' is not assignable to type 'never'.

Union Types

複数の型を並べていづれかであることを示す

// 文字列か数字
let age: string | number = 18 // OK
age = '18'  // OK
age = false // NG

配列にUnion Typesを使う

const list: (string | number)[] = [0, '1', 2]

Union Typesを使って変数や関数引数をNullable(Null許容)にすることができる

let arg: string | null = null
arg = 'hoge'

function test(arg: string | null): void {
  if (!arg) return
  console.log(arg)
}
test(null)  // 出力結果: 無し
test('aiu') // 出力結果: aiu

Literal Types

以下のように取り合える値を定義できる

// 0か1しか受け付けない
let isDeleted: 0 | 1 = 0 
isDeleted = 1 // OK
isDeleted = 2 // NG
[eval].ts:6:1 - error TS2322: Type '2' is not assignable to type '0 | 1'.

// 文字列の場合
const result: 'OK' | 'NG' = 'OK'

enum

列挙型を定義できる

enum Fruits {
  Banana,
  Apple,
  Orange
}

console.log(Fruits.Banana) // 出力結果: 0
console.log(Fruits.Apple)  // 出力結果: 1
console.log(Fruits.Orange) // 出力結果: 2

使い方例

let f: Fruits = Fruits.Banana
console.log(f) // 出力結果: 0

f = Fruits.Apple
console.log(f) // 出力結果: 1

f = 'Pear'
[eval].ts:11:1 - error TS2322: Type '"Pear"' is not assignable to type 'Fruits'.

// これはOKになってしまうので注意
f = 3
console.log(f) // 出力結果: 3

enumの要素しか受け付けないようにすることで、意図しない要素が来た時にエラーにできる

たまにenumは使わないでUnion Typeを使いましょう的な記事を見かけるので使い分けは要検討

型推論について

TypeScriptは型を推論してくれる

// 代入している値が文字列なのでstringと型推論している
let hoge = 'tomsato'
// 代入している値が数字なのでnumberと型推論している
let huge = 18

// 実際には明示していないが、型推論の結果違う型同士で代入をしようとしているのでエラー
hoge = huge
[eval].ts:4:1 - error TS2322: Type 'number' is not assignable to type 'string'.

型アサーションについて

型推論された型や、すでに型定義されている型を上書きする

例えば以下だと、最初にプロパティが無いオブジェクトと推論されてしまっているためエラーとなっている

const user = {}
console.log(typeof user) // 出力結果: object

user.name = 'tomsato'
[eval].ts:6:5 - error TS2339: Property 'name' does not exist on type '{}'.

しかし型アサーションによって型を解釈させるとエラーが出なくなる

interface Person {
  name: string,
  age: number
}

// Person型と解釈させる
const user = {} as Person
user.name = 'tomsato' // OK
user.age = 18 // OK

型アサーションの指定の仕方は2種類ある

const user1 = {} as Person
const user2 = <Person> {}

JSXの構文と区別ができなくなってしまうためasの方が推奨されているらしい

クラスについて

定義方法と呼び出し方法

// クラス定義
class Person {
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}

// 呼び出し
const user = new Person('tomsato', 18)
console.log(user.name) // 出力結果: 'tomsato'
console.log(user.age)  // 出力結果: 18

継承とサブクラス

class Student extends Person {
  grade: number

  constructor(name: string, age: number, grade: number) {
    super(name, age)
    this.grade = grade
  }
}

const user = new Person('tomsato', 18, 1)

クラスメンバー修飾子

以下3つが使える、何も暗黙的にpublicとなる

  • public
  • private
  • protected

Generics

関数でGenericsを使う

function getAge<T>(age: T): T {
  return age
}

getAge<number>(18)    // 戻り値: 18
getAge<string>('18')  // 戻り値: '18'

// 型推論できる場合は型指定を省略できる
getAge(true) // 戻り値: true

型引数に制約をつける時

function getAge<T extends string | number>(age: T): T {
  return age
}

getAge<number>(18)    // 戻り値: 18
getAge<string>('18')  // 戻り値: '18'
getAge<boolean>(true) // エラー
[eval].ts:6:8 - error TS2344: Type 'boolean' does not satisfy the constraint 'string | number'.

複数の型引数を定義する

慣習的に「T」「U」「K」などの型エイリアス名称が利用されることが多い

function getName<T, U>(name: T, age: U): T {
  return name
}

getName<string, number>('tomsato', 18) // 戻り値: 'tomsato'

型エイリアスの由来について(諸説あり)

慣習的にTypeを表す「T」、Keyを表す「K」、Unknownを表す「U」、Elementを表す「E」が利用されることが多い

クラスでGenericsを使う

class Person<T> {
  age: T

  constructor(age: T) {
    this.age = age
  }
}

const userA: Person<number> = new Person<number>(18) // OK

typeやinterfaceでGenericsを使う

Type

type Person<T> = {
  id: T
}

const userA: Person<string> = { id: '1' } // OK

interface

interface Person<T> {
  id: T
}

const userA: Person<string> = { id: '1' } // OK

tsconfig.json

tsconfig.jsonについて

コンパイラに渡すコンパイラオプションと、コンパイル対象のファイルをひとまとめにした設定ファイル

Intro to the TSConfig Reference

tsconfig.jsonを利用するメリット

  • プロジェクト内でコンパイルオプションを統一することができる
  • プロジェクト内で利用するファイルの管理ができる
  • IDE等でその設定を読み込んで、ファイルが保存されたらコンパイルを行うようにIDEに指示することができる

作成方法

$ tsc --init
message TS6071: Successfully created a tsconfig.json file.

// コメントの部分含めて全部表示すると長くなるのでコメント部分は省略
$ cat tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

後述にあるTipsに推奨設定を記述

Tips

設定値の型定義を行う const assertion (TypeScript 3.4で追加された)

as const を付与することでオブジェクトや配列に対して再帰的にreadonlyを付与した型として扱う

const settings = {
  host: 'https://xxx',
  port: 3306
} as const

// readonlyなので上書きできない
settings.host = 'hoge'
[eval].ts:6:10 - error TS2540: Cannot assign to 'host' because it is a read-only property.

TypeとInterfaceの違いについて

自分からは説明がしづらいのでリンクを…

tsconfig.jsonで有効にすべきオプション

  • "strict": true 全てのstrictタイプオプション(noImplicitAny, strictNullChecks, noImplicitThis, alwaysStrict)を有効化する
    • "noImplicitAny": true any型はNGとする
    • "strictNullChecks": true defaultではstringはnullやundefinedを含むが、trueにすることで暗黙的にstringにnullやundefinedを入れることができなくなる
    • "noImplicitThis": true thisの型が不明瞭な関数内でthisへアクセスするとエラーになる
    • "alwaysStrict": true “use strict”をファイルに出力する
  • "noImplicitReturns": true 関数内で条件分岐の条件によって明示的なreturnがされないルートがある場合にコンパイルエラーになる
  • "noFallthroughCasesInSwitch": true switch文のcase内でbreakが無い場合にエラーとなる
  • "noUnusedLocals": true 未使用のローカル変数があるとエラーになる
  • "noUnusedParameters": true 未使用の関数の仮引数があるとエラーになる

コメントを書く

※ 個別に返信が必要な時のみご記入ください

※ Emailは公開されません

※ 承認されると名前・コメントが下記に表示されます

コメント一覧

最近の投稿

ビジュアルリグレッションテストについてまとめ、ネットで調べると数多くのライブラリがありどれがどんな立ち位置なのか全体像がわかりずらかったのでどんな種類があるのか入門の入門としてまとめます、またPlaywrightを使って実際に触ってみました

社内ツールなどの超小規模なAPIをGolangで実装する際にフレームワークを使うべきかを、実際にnet/httpを使った実装とフレームワークを使った実装を比較することでどれだけ優位性があるかを見ていきたいと思います。今回はフレームワークにはシンプルで使いやすそうなEchoを使うことにします。

vue-pdfを使ってNuxt.jsで作成しているアプリケーションに pdfスライドを表示させるサンプルを作成しました README.md通りに実装してもうまくいかないところがあったのでそのあたり含めてまとめます

Vue.js / Nuxt.jsにおけるログインの実装方法をまとめる Auth0やNuxt.jsのAuth Moduleとmiddlewareについて調べつつサンプルを作成することで理解を深める

コンポーネント設計について考える Atomic DesignやPresentational Component, Container Componentについてまとめつつ 自分だったらVue.js / Nuxt.jsでどういうコンポーネント設計にするかについてまとめます

カテゴリ一覧

タグ一覧