はじめに
このページは自分用メモとして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の公式サイトに
と呼ばれるオンラインエディタがある
そのエディタ上に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の違いについて
自分からは説明がしづらいのでリンクを…
- 参考① TypeScriptのInterfaceとTypeの比較
- 参考② TypeScript の Interface と Type Alias の違い
- 参考③ TypeScript使いに質問です。InterfaceとTypeの使い分けはどうしていますか?どっちか一方だけに統一していますか?また有用なリンクがありますか?
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
未使用の関数の仮引数があるとエラーになる
コメントを書く
コメント一覧