本記事について
目的
- TypeScript について学習したことをまとめ、理解していることを確認する
- 記憶喪失になった場合でも、この記事を見て TypeScript のプログラムを作成できるようにする
参考書籍
プログラミング TypeScript(ISBN978-4-87311-904-5)を使用して学習。原著者の Boris Cherny さんと監訳者の今村謙士さんに感謝します。
著者のスペック
大学で C を学習し、社会人になり C++を約 3 年使っている(使っていた)人間。学習前、TypeScript および JavaScript の知識はほぼなし。(言語の名前を知ってたぐらい)
TypeScript の概要
TypeScript はプログラミング言語の 1 つ。特徴は以下の通り。
- 型システムを導入したプログラミング言語
- コンパイルすると JavaScript ファイルになる
- ファイル拡張子は.ts
コンパイルと実行
用語
用語 | 説明 |
---|---|
抽象構文木 | 言語の意味に関係ある情報のみを取り出した木構造のデータ。 |
バイトコード | 仮想マシン上で動作させるために作られた実行可能な中間コード。 |
ランタイム | 実行時に必要な物・環境。 |
コンパイルと実行順
- TypeScript を抽象構文木に変換
- 型チェッカーが抽象構文木をチェック
- 抽象構文木を JavaScript に変換
- JavaScript を抽象構文木に変換
- 抽象構文木をバイトコードに変換
- ランタイムがバイトコードを評価
TypeScript 実行環境(Node.js+ESLint+Prettier)
用語
用語 | 説明 |
---|---|
Node.js | Chrome の V8JavaScript エンジン上に構築された JavaScript 実行環境。 |
V8JavaScript エンジン | Google によって開発され、C++で記述された JavaScript 実行環境。「V8」は V 型 8 気筒エンジンに由来。 |
npm | Node Package Manager の略。パッケージ管理を簡単にするもの。 |
パッケージ(Node.js) | Node.js において便利な機能をまとめたもの。 |
package.json | npm がパッケージの管理に使用する json ファイル。インストールすべきパッケージのバージョンの範囲が記載される。 |
package-lock.json | npm がパッケージの管理に使用する json ファイル。npm install によって実際にインストールしたパッケージのバージョンが記載される。 |
グローバルインストール(npm) | OS の特定のディレクトリにパッケージをインストールする。対象のパッケージを全てのプロジェクトで使用できる。 |
ローカルインストール(npm) | プロジェクト内の node_modules ディレクトリにパッケージをインストールする。対象のパッケージを 1 つのプロジェクトのみで使用できる。移行が楽で他のプロジェクトに影響しないことから基本的にこちらが推奨される。 |
.gitignore | Git で管理しないファイル・ディレクトリを記載するファイル。npm においてインストールしたパッケージは、package.json または package-lock.json に記載されているため、node_modules 配下を Git で管理する必要がない。よって node_modules/を記載しておく。 |
npx コマンド | ローカルにインストールしたパッケージを実行するコマンド。 |
tsconfig.json | コンパイル対象のファイル・ディレクトリやコンパイル結果出力箇所や出力する JavaScript のバージョン指定を記載するファイル。TypeScript プロジェクトには必須。 |
ESLint | JavaScript 用の静的コード分析ツール。プラグインを導入することで TypeScript のチェックも可能となる。 |
Prettier | TypeScript にも対応したコードフォーマッター。ESLint では整形できないコードを整形できる。 |
環境構築手順
Node.js+ESLint+Prettier を用いた TypeScript 実行環境構築手順は以下の通り。
- プロジェクトディレクトリ作成:
mkdir test
- 対象ディレクトリに移動:
cd test
- 初期値で npm プロジェクト初期化:
npm init -y
- TypeScript コンパイラ・Node.js 用の TypeScript 型情報・ESLint・ESLint の TypeScript 用プラグイン・Prettier をインストール:
npm install -D typescript @types/node eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier eslint-config-prettier eslint-plugin-prettier
- tsconfig.json の作成:
npx tsc --init
- .eslint.js の作成:
npx eslint --init
- .prettierrc.js の作成:
touch .prettierrc.js
- .gitignore の作成:
touch .gitignore
筆者環境では以下ファイルはテンプレートを作成しておき、シェルでコピーしている。
- .prettierrc.js
- .gitignore
ソースファイル作成
tsconfig.json の"include"に記載しているディレクトリにソースファイルを作成する。
以下は"include"が"src"の場合。
touch src/abc.ts
コンパイル
JavaScript ファイルへのコンパイルは以下のコマンドで行う。
npx tsc
tsconfig.json の"outDir":に記載しているディレクトリに js ファイルがコンパイルされる。"outDir":を記載していない場合はプロジェクトディレクトリ直下にコンパイルされる。
"outDir":dist の場合のコンパイルまで行ったディレクトリの状態は以下の通り。
test
├── .eslintrc.js
├── .gitignore
├── .prettierrc.js
├── dist
│ └── abc.js
├── node_modules
│ ├── @babel
~~~~~省略~~~~~
│ └── yallist
├── package-lock.json
├── package.json
├── src
│ └── abc.ts
└── tsconfig.json
実行
コンパイルされた js ファイルの実行は以下のコマンドで行う。
node dist/test.js
変数宣言
変数は以下で宣言する。var は再宣言可能なため、使用しないほうが良い。
- let:再宣言不可、再代入可能
- const:再宣言不可、再代入不可
- var:再宣言可能、再代入可能
型
any
何でも扱える型。any を使ってしまうと JavaScript と同じような動作になってしまうため、使用すべきでない。
let va: any = 123;
unknown
any と同様に何でも扱えるが、何の型であるかを示さないと使用できない。本当に型がわからない場合にanyの代わりに使用すべき型。
let vu: unknown = "abc";
boolean
true(真)と false(偽)を扱う型。
let vbo: boolean = true;
number
数値を扱う型。四則演算等数値に対する操作ができる。以下の値を格納できる。
- 整数
- 浮動小数点数
- 正数
- 負数
- Infinity(無限大)
- -Infinity(-無限大)
- Nan(非数)
- 2 進数
- 8 進数
- 10 進数
- 16 進数
let vn1: number = 100;
let vn2: number = -5;
let vn3: number = Infinity;
let vn4: number = 0x1234;
bigint
とても大きな数値を扱う型。約 9000 兆(2 の 53 乗)より大きな数値を扱う際に使用する。(ES2020以降)
let vbi: bigint = 50n;
string
文字列を扱う型。連結や分割ができる。
let vst: string = "あいうえお";
symbol
既存のキーに影響を与えることなしに新たなキーを設定する場合等に使用される。
let vsy: Symbol = Symbol("a");
オブジェクト
まとまったデータを扱う型。要素へのアクセスは文字列で行う。
let vo: { i: number } = {
i: 10,
};
//v8.iで要素にアクセスできる
undefined/null/void/never
存在しないもの、存在しないことを表す値として undefined/null/void/never が存在する。
undefined
未定義であることを表す。
null
値の欠如を表す。
void
返すものがない関数の戻り値の型を表す。
never
戻ることのない関数の型を表す。
型に関連するオブジェクト等
型エイリアス
型に別名をつけることができる機能。
type Grade = number;
type Student = {
name: string;
grade: Grade; //grade:numberと等価
};
合併型
提示された型において最低でもどれか 1 つを満たす値を扱う型。
type Car = { price: number, wheel: number }
type Ship = { price: number, screw: number }
type CarOrShip = Car | Ship
let CarOrShip1 = {
price: 700000,
wheel: 4
}
let CarOrShip2 = {
price: 500000,
screw: 2
}
let CarOrShip3 = {
price: 1000000,
wheel: 4,
screw: 2
} //CarOrShip型には価格(price)と車輪数(wheel)またはスクリュー数(screw)が必要。全てあってもよい。
交差型
提示された型において全てを満たす値を扱う型。
type Lion = { name: string, hasMane: boolean }
type Tiger = { name: string, hasStripe: boolean }
type Liger = Lion & Tiger;
let liger1:Liger = {
name: "ライガー次郎",
hasMane: false,
hasStripe: true,
} //Liger(ライガー)型には名前(name)・たてがみの有無(hasMane)・縞模様の有無(hasStripe)の3要素が必須
配列
同じ型のデータの集まり。データには添え字を使ってアクセスできる。
let array1: number[] = [1, 1, 2, 3, 5, 8, 13, 21];
let array2: string[] = ["abcde", "あいうえお", "アイウエオ"];
let array3: boolean[] = [true, false, true];
let varr = array2[1]; //varr:あいうえお
タプル
明示的に片付けする配列の派生型。固定長。
let tuple1: [string, string, number] = ["田中", "太郎", 20];
let tuple2: [number, number, string] = [1909, 1948, "太宰治"];
列挙型
挙げられている要素に対して順番に値が固定されているオブジェクト。
enum food {
Rice,
Bread,
Pasta,
Udon,
Soba,
}
let ve = food.Bread; //ve:1
enum に数値が存在する場合、全ての数値が割り当て可能となり安全ではなくなるため、列挙型は使用しないほうが良い。
enum Appliance {
Refrigerator,
Microwave,
RiceCooker,
}
function turnOn(A: Appliance) {
return "Turn on the power!";
}
turnOn(Appliance.Refrigerator); //'Turn on the power!'
turnOn(Appliance.Microwave); //'Turn on the power!'
turnOn(10); //'Turn on the power!'
型推論について
TypeScript の便利な機能として、型推論がある。上記までは変数宣言時に型を記載していたが、TypeScript が推論してくれるため、記載する必要はない。
let vbot = true; //: boolean
let vn1t = 100; //: number
let vbit = 50n; //: bigint
let vstt = "あいうえお"; //: string
let vsyt = Symbol("a"); //: Symbol
変数等関連知識
リテラル型
リテラル型とは、ただ一つの値を表す型。ほかの値を割り当てることはできない。
let vbol: true = true; //vbolにはfalseを割り当てることはできない
let vnl: 123 = 123; //vbnlには123以外の値を割り当てることはできない
可変長要素
'...型名[]'を使用して配列を宣言すると、可変長の配列を宣言することができる。
let vsa: [...string[]] = ["q", "w", "e", "t", "y"]; //要素が最低0個のstring配列
let vna: [number, ...number[]] = [12, 3, 45, 5, 6, 7]; //要素が最低1個のnumber配列
オプション
型名の後に'?'をつけることによって、省略可能な要素を宣言することができる。
let card: [number, string?][] = [[10], [11, "J"], [12, "Q"], [13, "K"]];
読み込み専用(配列)
'readonly'を使用して配列を宣言すると、変更不可の配列が宣言できる。
let vnar: readonly number[] = [3, 2, 1];
関数
関数種別一覧
関数は大きく分けると以下4種類の書き方が主に存在する。
名前 | 宣言時の特徴 |
---|---|
名前付き関数 | "function"と関数名記述が必要 |
関数式 | "function"と変数宣言が必要 |
アロー関数 | "=>"と変数宣言が必要 |
関数コンストラクター | "new Function"と変数宣言が必要 |
名前付き関数
何も省略しない場合の書き方。以下構文。
function 関数名(引数): 返り値の型 {
return 返り値
}
例
function addOne(a: number): number {
return a + 1
}
関数式
"function"を記載した場合の書き方。以下構文。
変数 = function(引数): 返り値の型 {
return 返り値
}
例
let funcA = function (a: number): number {
return a + 1
}
アロー関数
アロー(=>)を使用した書き方。名前付き関数や関数式と比較してより簡潔な表現になる。以下は省略を行わず記載した場合の構文。
変数 = (引数): 返り値の型 =>{
return 返り値
}
例
let funcC = (a: number): number => {
return a + 1
}
関数の型注釈は省略可能。
return文のみの関数では"{}"と"return"も省略可能。
省略できるものを省略した例
let funcD = (a: number) => a + 1
以降では可能な限りアロー関数表記で関数を記載する。また、関数を書き換えることはまずないため、"const"を使用して宣言する。
関数コンストラクター
引数と戻り値が型付けされておらず危険なため使用すべきではない。
記述方法は省略。
オプションパラメーター
変数と同様に引数に?"をつけることにより省略可能な引数を設定することができる。
const printStr = (s?: string): void => {
console.log(s || 'string doesn\'t exist')
}
printStr('aiueo')//aiueo
printStr()//string doesn't exist'
デフォルトパラメーター
引数に規定値を設定することができる。
const printStr = (s = 'string'): void => {
console.log(s)
}
printStr('aiueo')//aiueo
printStr()//string'
可変長引数
任意の数の引数を配列変数として受け取ることができる。
複数の引数が存在する場合、可変長引数は最後の引数である必要がある。
const printNum = (...ns: number[]): void => {
for (let item in ns) {
console.log(item);
}
}
printNum(0, 1, 2, 3, 4, 5)//0\n(改行)1\n(改行)2\n(改行)3\n(改行)4\n(改行)5\n(改行)
ジェネレーター
ジェネレーターを使用すると一連の値を作成することができる。'function'の後に'*'を付けて宣言する。
アロー関数の書き方では作成できない。
function* createOddGenerator(){
let a = 1;
while(true){
yield a;
a = a+2
}
}
let odd = createOddGenerator()
odd.next()//1
odd.next()//3
odd.next()//5
オーバーロード
複数の呼び出し方法(引数の組み合わせ)を持つ関数。
function printNumStr(n: number):void;
function printNumStr(n: number, s: string):void;
function printNumStr(n: number, s?: string):void{
if (s !== undefined) {
console.log(n + ':' + s);
} else {
console.log(n);
}
}
printNumStr(1, 'a');//1:a
printNumStr(2)//2
アロー関数でのオーバーロードについては参考文献の(TypeScript | hatakoya memo)参照。
ジェネリック(ジェネリクス)
事前にはどの型になるか分からない変数に対して設定する。不明な型を"T"など任意の文字に置き換え、"<>"を使用してジェネリック型変数を宣言する。
ジェネリックを使用することにより、1つのコードで様々な型のデータを処理することができる。
type Filter = {
<T>(array: T[], f: (item: T) => boolean): T[]
}
let filter: Filter = (array, f) => {
let result = []
for (let item of array) {
if (f(item)) {
result.push(item)
}
}
return result
}
let even = filter([0, 1, 2, 3, 4, 5, 6, 7], (_) => (_ % 2)==1)
console.log(even)//[ 1, 3, 5, 7 ]
let includeA = filter(['abc', 'ccb', 'cca', 'bcb', 'aac', 'cab', 'bbc', 'aaa'], (_) => (_.indexOf('a') != -1))
console.log(includeA)//[ 'abc', 'cca', 'aac', 'cab', 'aaa' ]
クラスとインターフェース
クラス
オブジェクト指向プログラミングにおいてプログラム実行時に使用するインスタンス(実体)を生成するためのひな型。extendsを使用するとサブクラス(子クラス)を作成することができる。
アクセス修飾子
クラスのメソッドおよびメンバ変数(プロパティ)のアクセス範囲は以下のアクセス修飾子で設定することができる。
修飾子 | アクセス可能範囲 |
---|---|
public | どこからでもアクセス可能 |
protected | 設定したクラスとサブクラスからアクセス可能 |
private | 設定したクラスからのみアクセス可能 |
抽象クラス
直接インスタンス化できないクラス。abstractを使用し宣言する。
super
子クラスから親クラスのメソッドを呼び出す際に使用する。super.関数名 の場合に該当の親クラスの関数を呼び出す。super()のみの場合、親クラスのコンストラクターを呼び出す。メンバ変数にはアクセスできない。
インターフェース
あるメソッドや変数を持っているオブジェクトに名前を付ける方法。クラスと一緒に使用される。
implements
あるクラスがインターフェースを満たす場合に使用する。インターフェースのメソッドと変数がクラスに存在しない場合、エラーとなる。
クラス及びインターフェースの例
interface OrganismsInHumanSociety {
name: string;
age: number;
}
abstract class Pet implements OrganismsInHumanSociety{
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
protected cry(crying: string) {
console.log(crying);
}
}
class Dog extends Pet {
constructor(name: string, age: number) {
super(name, age)
}
cry() {
super.cry("woof!");
}
}
class Cat extends Pet {
constructor(name: string, age: number) {
super(name, age)
}
cry() {
super.cry("Meow.");
}
}
let pet1 = new Dog("ポチ", 3)
pet1.cry();//"woof!"
let pet2 = new Cat("ミケ", 2)
pet2.cry();//"Meow."
例外処理
TypeScriptのエラー処理には主に以下4つの方法が存在する。
- nullを返す
- 例外をスローする
- 例外を返す
- option型
nullを返す
例外処理の中では一番簡単に実装できるが、例外発生時の状況が詳しく分からないという欠点がある。
また、処理後にnullチェックが必要となり、プログラムが冗長になりやすい。
const strToNum = (str: string): number | null => {
let n = Number(str);
if (isNaN(n)) {
return null;
} else {
return n;
}
}
strToNum("1");//ok
strToNum("a");//return null
例外をスローする
関数内で例外を発生させて関数内で受け取る。nullを返すのと比較すると例外発生原因が詳しく把握できる。関数内で例外を受け取るため、呼び出し元で例外に対しての処理は行えない。
const strToNum = (str: string): number => {
let n =0;
try {
n = Number(str);
if (isNaN(n)) {
throw new RangeError("The argument must be number string.")
}
} catch (error) {
if (error instanceof RangeError) {
console.error(error);
}
}
return n;
}
strToNum("1");//ok
strToNum("123a");//RangeError
例外を返す
関数内で例外を発生させて呼び出し元で受け取る。例外をスローする場合と同様に、例外発生原因が詳しく把握できる。関数外で例外を受け取るため、呼び出し元で例外に対しての処理を行うことができる。
const strToNum = (str: string): number | RangeError => {
let n = 0;
n = Number(str);
if (isNaN(n)) {
return RangeError("The argument must be number string.")
}
return n;
}
const callStrToNum = () => {
let result1 = strToNum("1");
let result2 = strToNum("123a");
catchError(result1);
catchError(result2);
}
const catchError = <T>(result: T) => {
if (result instanceof RangeError) {
console.error(result);
}else{
console.log(result);
}
}
callStrToNum();
option型
失敗する可能性のある一連の操作に対して行うことができるエラー処理方法。型(None)によって失敗を知らせるため、原因の詳細はわからない。値の代わりにコンテナ(値があったりなかったりするもの)を返すことによって動作する。
interface Option<T> {
flatMap<U>(f: (value: T) => None): None
flatMap<U>(f: (value: T) => Option<U>): Option<U>
getOrElse(value: T): T
}
class Some<T> implements Option<T>{
constructor(private value: T) { }
flatMap<U>(f: (value: T) => None): None
flatMap<U>(f: (value: T) => Some<U>): Some<U>
flatMap<U>(f: (value: T) => Option<U>): Option<U> {
return f(this.value);
}
getOrElse(): T {
return this.value;
}
}
class None implements Option<never>{
flatMap(): None {
return this;
}
getOrElse<U>(value: U): U {
return value;
}
}
function Option<T>(value: null | undefined): None
function Option<T>(value: T): Some<T>
function Option<T>(value: T): Option<T> {
if (value == null) {
return new None;
}
return new Some(value);
}
let result3 = Option(3)
.flatMap(n => Option(n * 5))
.flatMap(n => Option(n * 2))
.getOrElse();
console.log(result3);//30
let result4 = Option(4)
.flatMap(n => Option(n * 3))
.flatMap(n => new None)
//.getOrElse();を記述すると値がないためエラーとなりコンパイルできない
console.log(result4);//None {}
今後の課題(まだ理解/勉強できていない範囲)
- 高度な型(変性、完全性、レコード型など)
- 非同期プログラミング(async、awaitなど)
参考文献
- 抽象構文木 - Wikipedia
- バイトコード | は | IT 用語辞典
- ランタイム(runtime)とは|「分かりそう」で「分からない」でも「分かった」気になれる IT 用語辞典
- Node.js
- V8 (JavaScript エンジン) - Wikipedia
- V8 エンジンでの JavaScript の機能と最適化コードの書き方に関する 5 つのベストプラクティス | POSTD
- npm 入門 - Qiita
- npm の基礎知識 - Qiita
- package-lock.json ってなに? - Qiita
- .gitignore で git にコミットしないディレクトリを管理する【node_modules】 - Qiita
- npm 5.2.0 の新機能! 「npx」でローカルパッケージを手軽に実行しよう - Qiita
- tsconfig.json の全オプションを理解する(随時追加中) - Qiita
- TypeScript: TSConfig Reference - Docs on every TSConfig option
- TypeScript の esModuleInterop フラグについて - 30 歳からのプログラミング
- TypeScript の skipLibCheck を理解する
- ESLint - Wikipedia
- TypeScript + Node.js プロジェクトに ESLint + Prettier を導入する手順 2020 - Qiita
- TypeScript のプロジェクトで ESLint+Prettier を活用する | Tips Note by TAM
- TypeScript のプロジェクトに ESLint と Prettier を導入する方法(+VSCode での設定) - Qiita
- Prettier と ESLint を VSCode で使う(Vue) - Qiita
- Prettier 入門 ~ ESLint との違いを理解して併用する~ - Qiita
- 【JavaScript】var / let / const を本気で使い分けてみた - Qiita
- JavaScript での変数定義は var を捨てて let, const を使うべき理由 – 自主的 20%るぅる
- TypeScript 3.0 の unknown 型 - タイプセーフな any - Qiita
- 基本の型 | TypeScript 日本語ハンドブック | js STUDIO
- 【JavaScript & TypeScript】bigint 型とは(rounding error と丸め誤差とは) | 武骨日記
- ECMAScript6 にシンボルができた理由 - Qiita
- 複合型 — 仕事ですぐに使える TypeScript ドキュメント
- TypeScript の readonly プロパティを使いこなす - Qiita
- TypeScript | hatakoya memo
- ジェネリクス(generics)とは - IT用語辞典 e-Words
- クラス (コンピュータ) - Wikipedia
- クラス - TypeScript Deep Dive 日本語版
- 【TypeScript】インターフェースの使い方 - Qiita
- 【typescript】Option型|フリーランスwebデザイナーのブログ | scleapt