概要
いまさらながらTypeScriptの基本的なところ振り返りました。
やっぱり基本を何度も反復することは大事だと思う今日この頃。
備忘録的な感じで本記事に纏めます。
TypeScriptとは
TypeScrtiptは斬新的型付け言語の一つ。
JavaScriptの開発を促進させてくれる。
例) JavaScriptの開発において...
// yamadaという姓を持つuserオブジェクトを定義した。
const user = { lastName: 'yamada' }
// yamadaからtanakaという姓に変える処理を開発者は強いられることになったが、間違えて名として代入してしまった!!
user.firstName = 'tanaka'
// 結果、userオブジェクトにfirstNameというプロパティを誤って追加してしまった...
console.log(user) // {lastName: "yamada", firstName: "tanaka"}
極端すぎるが、上例みたいな事が起きないように事前に阻止してくれる。
TypeScriptで開発を行っている場合、上例の場合だと、user.firstName = 'tanaka'
の行でuserオブジェクトにfirstNameというプロパティは存在しないというエラーを出してくれる。
TypeScriptのセットアップ
TypeScriptを利用した開発を行う場合は、Node.jsが必要になるので事前にPCにインストールしておく。
Node.jsがPCにインストール出来たならば、下記コマンドでprojectディレクトリがTypeScript環境となる。
$ cd project
$ npm install --save-dev typescript tslint @types/node
tsconfig.json
TypeScriptのプロジェクトのルートには、tsconfig.json
を設置しておく必要がある。
tsconfig.json
に定義するオプションの意味はここが分かりやすい。
下記のように、tsc
コマンドでtsconfig.json
を生成することも可能。
$ cd project
$ npm install --save-dev typescript tslint @types/node
$ ./node_modules/.bin/tsc --init # make tsconfig.json
tslint.json
一貫したコーディングルールスタイルを強制したい時に使用する。(tslintのDocuments)
下記のように、tslint
コマンドでtslint.json
を生成することが可能。
$ ./node_modules/.bin/tslint --init
コンパイル&実行
TypeScriptファイル ( 拡張子が.ts
のファイル) を./node_modules/.bin/tsc
コマンドでコンパイルするとJavaScriptファイルが生成される。
生成されたJavaScriptはNode環境であればnode hoge.js
のように実行する。
別の方法として、ts-nodeを別途プロジェクトにインストールしてts-node hoge.ts
のように一回のコマンドでコンパイルおよび実行も可能。
ts-node
でコンパイル&実行した場合は、JavaScriptファイルは生成されない。
$ cd project
$ npm install --save-dev ts-node
$ vi index.ts
console.log('hello !!');
$ ./node_modules/.bin/ts-node index.ts
hello !!
Microsoftが用意しているスキャフォールディングツールを利用すると即座にTypeScriptのプロジェクトを構築できる。
$ git clone https://github.com/Microsoft/TypeScript-Node-Starter.git project
$ cd project
$ npm install
プリミティブ型
基本の型。
boolean型
trueとfalse。
let hoge: boolean = false
number型
整数,浮動小数点数,正数,負数,Infinity (無限大),NaN (非数)。
let hoge: number = 1000
let fuga: number = Infinity * hoge
let huga: number = huga * NaN
string型
文字列。
let hoge: string = 'huga'
symbol型
Symbolとは、ES2015から新たに導入されたプリミティブデータ型の一つでありSymbol関数によってのみ作成できる。
競合しないユニークキーのような存在。
let sym1 = Symbol()
let sym2 = Symbol()
console.log(sym1 === sym2) // false
ユニークなので、オブジェクトのプロパティキーとして使用したりする。
(注意) Symbolをキーとしたオブジェクトをfor...in
でループしてもスキップされる。
// for...inループはスキップされる
const id = Symbol("id")
const obj = {
name: "hoge",
[id]: 123,
};
for (let key in obj) console.log(key); // name
// 直接アクセスは出来る
console.log(obj[id]) // 123
unique symbol
と明示的に型付けをすることが出来る。
const key: unique symbol = Symbol("key") // エディタ上では typeof key と表示される
let key2: unique symbol = Symbol("key") // let の場合はエラーが発生。変数はconstである必要がある
リテラル型
ただ一つの値を表し、それ以外の値は受け付けないような型を指す。
真偽値、文字列、数値。
const sample: true = false // trueしか無理。
const sample2: 'hoge'= 'fuga' // 'hoge'しか無理。
const sample3: 123 = 321 // 123しか無理。
オブジェクト型
以下のような型を使った表現方法が存在する。
const obj1: object = {}
const obj2: {} = {}
const obj3: Object = {}
object
は、非プリミティブ型を表す型である。
{}
は、いわゆる任意の値を持つプロパティが存在するオブジェクトを表す型である。
Object
は{}
とほぼ同じである。(厳密には違う。Objectのプロトタイプの型に割当が可能であることを強制する。)
{}
は、プリミティブ型でもObject型に定義されたメソッドやプロパティが使えるので、エラーを検知できない。
object
は、完全に非プリミティブ型を表しているのでエラーを検知できる。
const obj: {} = new Object(true) // 型エラーは出ません。
const obj2: {} = true // 型エラーは出ません。
console.log(obj) // true
console.log(obj2) // [Boolean: true]
const obj3: object = new Object(true) // 型エラーは出ません。
console.log(obj3) // [Boolean: true]
const obj4: object = true // 型エラーが出ます!!
上記から分かるように、JavaScriptの仕様上const obj2: Object = true
は許される。
trueや'hoge'や123はプリミティブな値だが、暗黙的にオブジェクトが生成されるためである。(true.toString()
みたいなことができる)
object
型の存在経緯として、暗黙的にObjectの型として満足できるプリミティブをJavaScriptの実行時エラーから回避するためにTypeScriptが用意したともいえる。
構造的型付け
オブジェクト型に注釈を付けたい場合は、オブジェクトの形状を指定する。
例えば、Object
型は、オブジェクトの形状を{}
の中に明示的に注釈することができる。
const obj: { name: string } = { name: true } // 型エラー
TypeScriptは、あるオブジェクトが特定のプロパティを持つことを重視し、名前を気にしないスタイルを構造的型付けを採用している。
// オブジェクトリテラルの形状
let obj: { name: string } = { name: '' }
// クラスの形状
class Hoge {
name string = ''
constructor(name: string) {
this.name = name
}
}
obj = new Hoge('yamada') // オブジェクトリテラルとクラスインスタンスの形状は一致しているので型エラーが起きない。(構造的型付け)
オブジェクト型を宣言するときに使用できる修飾子として、?
やreadonly
が存在する。
let obj: {
a?: number
readonly b: number
} = {
b: 100
}
console.log(obj.a) // undefined
obj.b = 200 // 値を割り当てることができずにエラー
?
は省略可能なプロパティであることを指している。(undefinedでもよい)
readonly
は読み取り専用プロパティであることを指している。
インデックスシグネチャ
[key: T]: U
のような構文を指す。
あるオブジェクトが複数のキーを含む可能性があることをTypeScriptに伝える方法である。
T
やU
は型を指す。(T
はstring
もしくはnumber
のみである。)
[key: T]: U
は「このオブジェクトは、型Tのキーは型Uの値を持つ」ことを意味する。
let obj: { [key: string]: boolean } = { 'isOpen': true, 'isClose': false }
配列型
ある配列の型を表現する。
宣言方法はT[]
とArray<T>
の2種類存在し意味も同じ。
例えば、数値の要素しか許可しない配列は下記の通りである。
const nums: number[] = [1,2,true] // 型エラー
上例は、数値以外の要素追加仕様としているため型エラーが起きている。
複数型を扱いたい場合配下のように宣言する。
const nums: (number|boolean)[] = [1,2,true]
タプル型
タプルは配列の派生の型である。
固定長の配列を型付けするための特別な方法である。
const nums: [number] = [1] // ok
const nums2: [number] = [1, 2] // 型エラー
const nums3: [number, number?] = [1] // ok (2つ目の要素が省略可能)
可変長な要素もサポートしている。
const nums: [...number[]] = [1,2,3,4] // 要素何個でもOK
const nums2: [number, ...number[]] = [1,2,3,4] // 少なくとも要素が1つ必要(0個はNG)
any型
何でも許す。開発者の最終手段。下記のような予期せぬミスが起きる可能性もある。
let a: any = 1
let b: any = '1'
let c: number = a + b // number型と注釈しているのにstringと推定される値を格納できてしまう
console.log(c) // "11" <- 文字列として結合されてしまっている
console.log(typeof c) // cの型を確認してみるとstringとなっている
unknown型
前もって型が分からない値がある場合に使う。anyと同様に任意の値を許す。anyの変わりに使ったりする。
typeof
等の演算子で絞り込みが可能。
let a: unknown = 'hoge'
let b: number = a // 型 'unknown' を型 'number' に割り当てることはできません
if (typeof a === 'string') {
let c: number = a // 絞り込みができる。( 型 'string' を型 'number' に割り当てることはできません )
console.log(c);
}
null型
null型はnull
という値の型。
null
とは値が存在しないことを意味する。
const a: null = null // OK
const b: [] | null = {} // NG: []型もしくはnullでないとエラー
undefined型
undefined型はundefined
という値の型。
undefined
とはまだ定義されていないことを意味する。
const a: undefined = undefined // OK
const b: [] | undefined = {} // NG: []型もしくはundefinedでないとエラー
void型とnever型
void型は、明示的に何も返さない関数の戻り値の型を意味する。
never型は、決して戻ることのない関数の型を意味する。
// void型を返す関数例 (何も返さない)
function func1() {
const sum = 1 + 1
}
// never型を返す関数例 (エラーを投げる)
function func2() {
throw TypeError('Error !!')
}
// never型を返す関数例 (処理が永久に続く)
function func3() {
while(true) console.log('Infinite Loop !!')
}
厳格なNullチェックとは
tsconfig.json
の設定でstrictNullChecks
を無効にした場合はnullが許容されるが、有効にした場合nullが許されないこと。
// strictNullChecks: false の場合
const a: number = null // OK
// strictNullChecks: true の場合
const a: number = null // NG
strictNullChecks
を有効にしておくことで、NullPointerException
を事前に防ぐことができる。
function talk(user: User) { // strictNullChecksが有効であれば Null を許さない
user.talk()
}
talk(null) // 事前に NullPointerException を阻止
型エイリアス
TypeScriptでは型エイリアスを宣言することができる。
例えば、下のような例である。
type Category = string
type Post = { title: string; category: Category; }
宣言した型エイリアスは下のように型として使用することが出来る。
let category: Category = 'TypeScript'
let post: Post = { title: 'TypeScript is cool!!', category: category }
Union型
Union型はいわゆる合併型である。
Union型は|
で表現する。
複数の型のうち一つの型が成立することを指している。
type Hoge = number | string | boolean
let hoge: Hoge = true // OK
Intersection型
Intersection型はいわゆる交差型である。
Intersection型は&
で表現する。
複数の型を一つに結合する。
type Yagi = { name: string; body: boolean}
type Raion = { name: string; head: boolean}
type Hebi = { name: string; tail: boolean}
type Kimera = Yagi & Raion & Hebi
// Kimeraは、name, body, head, tail の各プロパティが全て含まれている必要がある
const kimera:Kimera = { name: 'Kimera!!', body: true, head: true, tail: true}
列挙型
関連する値を列挙する方法。順序付けされていないデータ構造で、キーを値と対応付ける。
文字列から文字列へとマッピングするものが文字列列挙型である。
文字列から数値へとマッピングするものが数理列挙型である。
enumの振る舞いをより安全なサブセットに制限するconst enum型が存在する。
// 文字列列挙型
enum Color {
Red = '#red',
Blue = '#blue'
}
console.log(Color.Red) // '#red'
console.log(Color.Blue) // '#blue'
console.log(Color.Green) // 型エラー: 'Green' は存在しない
// 数理列挙型
enum Color {
Red,
Blue
}
console.log(Color.Red) // 0 <- 値でアクセスできる
console.log(Color[1]) // 'Blue' <- キーでアクセスできる
console.log(Color[2]) // undefined <- 型エラーが起きない!!
console.log(Color[Color.Red]) // 'Red' <- 値アクセスして取り出したキーでアクセスできる
// const enum型
const enum Color {
Red,
Blue
}
console.log(Color.Red) // 0
console.log(Color[1]) // 型エラー: 文字列リテラルを使用してのみアクセスできる
console.log(Color[2]) // 型エラー: 文字列リテラルを使用してのみアクセスできる
console.log(Color[Color.Red]) // 型エラー: 文字列リテラルを使用してのみアクセスできる
以上になります。