TypeScript
メインページ > 工学 > 情報技術 > プログラミング > TypeScript
TypeScriptは、JavaScriptに型のための構文を加えたもので、これにより事前に静的な型チェックを可能にしています。JavaScriptに比べて型安全性が高く、エラーの早期発見ができるため、大規模なアプリケーションの開発に適しています。 また、TypeScriptはJavaScriptライブラリの型情報を含む定義ファイルをサポートすることで、既存のJavaScriptライブラリをTypeScriptで利用することができます。これにより、TypeScriptで開発したアプリケーションがJavaScriptのライブラリとシームレスに連携できるようになります。 TypeScriptで書かれたソースコードは、TypeScriptコンパイラによってJavaScriptにトランスパイルされます。トランスパイルされたJavaScriptは、通常のJavaScriptコードと同じようにJavaScriptエンジンによって実行されます。
環境準備[編集]
以下の方法の中から1つを選択することでTypeScriptを利用できます。
- Node.jsとnpmがインストールされた環境であれば、
npm install typescript
コマンドを使用します。トランスパイルを自動化したい場合は、npm install typescript ts-node
コマンドを使用します。 - Denoを利用することもできます。詳細については、Deno公式サイト(英語)を参照してください。
- ブラウザで動作するPlaygroundを使用することもできます。
トランスパイラ |
トランスパイラとは、プログラミング言語で書かれたプログラムのソースコードを入力として、同じまたは異なるプログラミング言語で同等のソースコードを生成するコンパイラの一種です。
従来のコンパイラが高レベルのプログラミング言語から低レベルのプログラミング言語に変換するのに対し、トランスパイラは、ほぼ同じ抽象度で動作するプログラミング言語間を変換します。 たとえば、従来のコンパイラはC言語からアセンブラ、Javaからバイトコードに変換しますが、トランコンパイラの ratfor は Ratfor から FORTRAN に変換します。 また、コンパイル型言語の筆頭と言える C++ の最初の処理系である cfront は、C++(当時は C with class) のソースコードを入力にC言語のソースコードを生成しました。 多くの TypeScript のトランスパイラは TypeScript 自身で書かれています。 |
npmプロジェクトの作成[編集]
プロジェクトを作成しなくても typescriptは利用可能である。
npm init
プロジェクトを作成するために必要な情報が聞かれるので、それに答える。
npm install typescript
TypeScriptの最新バージョンをインストールする。
nodeでの実行方法[編集]
node 本体はJavaScriptしか解釈できないので、付属のアプリケーションでTypeScriptのコードをJavaScriptのコードに変換する必要がある(トランスパイル)。
このトランスパイル的な作業の実行の仕方として、事前にコンパイル済みのjsファイルを作成しておくことで処理速度を高める方式と(C言語やJava的な方式)、インタープリター的にコマンド1回だけで実行できるが処理速度を犠牲にする方式がある。用途に応じて使い分けよう。
コンパイルして使う場合[編集]
コマンド
npx tsc ファイル名.ts
でトランスパイルをすることで、JavaScriptファイルが作成される。
あとはこれを、例えば
node ファイル名.js
で実行すればいい。
tsc で作成されるのは単なる JavaScript ファイルなので、node を使わずとも、例えばHTMLに組み込めばwebブラウザ上でも実行できる。
また、コンパイルはjsファイルを作る際の1度だけなので、サーバー業務などで繰り返しjsファイルを使う場合はこのコンパイル方式のほうが効率的である。
ビルド アンド エグゼキュート[編集]
- インストール
# npm install typescript ts-node
- ビルドルド アンド エグゼキュート
% npx ts-node ファイル名.ts
- でコンパイルし即時に実行されます(あらかじめ実行したいファイルを作成しておくこと。また、拡張子は .ts が一般的である。)。
以降の利用では、
% npx ts-node ファイル名.ts
だけでコンパイルと実行が行われ、ソースコードが変更されていない場合は、キャッシュ上のコンパイル後の .js を直接実行します。
学習や開発途上ではコンパイル作業を省略できて効率的で、ソースコードが変更されていない場合はトランスパイル(tsファイル→jsファイルの作成)しないので好都合です。
また、ディブロイするにあたっては、トランスパイルした時期などを把握するために全ソースをトランスパイルし、完パケにします(通常は、ビルドツールで clean all をターゲットにするなど定型処理化します)。
Hello, World[編集]
- hello.ts
const message: string = "Hello, World!"; console.log(message);
このコードは、次のような JavaScript のコードにトランスパイルされます。
- hello.js
const message = "Hello, World!"; console.log(message);
この、hello.js
を実行すると
- 実行結果
Hello, World!
- の様になります。
const message: string = "Hello, World!";
青色で示した部分が JavaScript との違いで、型アノテーションと呼ばれ、TypeScript を特徴づける機能です。
変数とその型[編集]
型システムは、JavaScriptとTypeScriptでは明確に違います。
- JavsScriptは、変数に型がなくオブジェクトに型がある
let a = 0; console.log(a, typeof a); a = "abc"; console.log(a, typeof a);
- 実行結果
0 number abc string
- エラーなく実行できました
- 変数 a が最初は 0(number) を参照していたのに、後半は "abc"(string) を参照していることで変数に型がなくオブジェクトに型があると言われる所以です
- TypeScriptは、変数にもオブジェクトにも型があり整合していないとコンパイルエラー
let a = 0; console.log(a, typeof a); a = "abc"; console.log(a, typeof a);
- コンパイルエラー
Main.ts(3,1): error TS2322: Type 'string' is not assignable to type 'number'.
- 全く同じコードを TypeScript としてコンパイルしました
- 3 行目で、変数 a に "abc"(string) を代入しているところでコンパイルエラーになりました。
- この様に、TypeScriptは、変数にもオブジェクトにも型があり整合していないとコンパイルエラーとなります。これが、TypeScript と呼ばれる所以です。
- このコードでは(JavaScriptとしても実行するため) 変数 a を
number
と明示的に宣言していませんが、初期値の 0 から型推論されています。
上記のコードでは、number
と string
が出てきましたが、TypeScriptには
- 要素型を限定したArray
- 値を false, true に限定した boolean
- Objectのキー毎に値の型を限定する interface
- Arrayの順位毎に値の型を限定する tuple
- 列挙したグループに値を限定する列挙 enum
- JavaScript と同じくどんな型でも代入できる any
|
演算子を使ったUnion- 型アサーションを可能にする never
など、静的な型チェックを可能にするための多様な型と支援機構があります。
変数の宣言と型[編集]
TypeScriptでは、JavaScript の変数宣言に加え、変数名の後に ':' に続けて 型名 を書きます。
- 構文
let 変数名 : 型名 = 初期値 ;
- このように明示的型を指定する仕組みを、型アノテーションと言います。
数値型(number)[編集]
TypeScript の数値型は、number です。
let n: number = 19; console.log(n, typeof n);
- 実行結果
19 number
TypeScriptのnumberは浮動少数点数型 |
JavaScriptの数値は IEEE 754 の64ビット倍精度浮動小数点数です。
TypeScriptのプログラムは、JavaScriptにトランスパイルされ、JavaScriptエンジンで実行されるので、同じく型numberも64ビット倍精度浮動小数点数です。
実行時に、 どうしても整数に限定した型がほしい場合は、BigInt オブジェクトがあります。
BigInt オブジェクトのリテラルは、 |
文字列型(string)[編集]
TypeScript の文字列型は、string です。
let s: string = "good morning"; console.log(s, typeof s);
- 実行結果
good morning string
配列型[編集]
配列型の変数宣言の方法は 3 つあります。
const prime: number[] = [2, 3, 5, 7, 11] console.log(prime[3], typeof prime[3]) // prime[0] = "abc" // !!! error TS2322: Type 'string' is not assignable to type 'number'. !!! const mdays: Array<number> = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] console.log(mdays[6 - 1], typeof mdays[6 - 1]) // mdays[2 - 1] = "29" // !!! error TS2322: Type 'string' is not assignable to type 'number'. !!! let price : [ string, number ] price = [ "lemon", 128 ] console.log(price[0], typeof price[0], price[1], typeof price[1]) // price[0] = 119 // !!! error TS2322: Type 'number' is not assignable to type 'string'. !!! const month : Array<[string, string]> = [ [ "睦月", "January"], [ "如月", "February"], [ "弥生", "March"], ] console.log(month[2 - 1], typeof month[2 - 1], month[2 - 1].constructor.name) // month[1 - 1][1] = 1 // !!! error TS2322: Type 'number' is not assignable to type 'string'. !!!
- 実行結果
7 number 30 number lemon string 128 number [ '如月', 'February' ] object Array
- 最初の配列型の宣言は
型名[]
の形式です。単純な配列に適しています。 - 2つ目の配列型の宣言は
Array<型名>
の形式です。要素型が複雑な配列に適しています。型パラメータを利用しています。 - 3つ目の配列型の宣言は
[型名0, 型名0, … 型名n ]
の形式です。順位ごとに型を指定します。タプル(Tuple)とも呼びます。 - 最後の配列型の宣言は 2つ目の
Array<型名>
の型名に2つ目の[型名0, 型名0, … 型名n ]
の形式(タプル)を指定しました。- この様に複合的な配列を宣言する場合は、型パラメータを使と便利です。
型推論[編集]
TypeScript では、C++ の auto
・decltype
や、C# の var
の様な型推論が可能です[2]。
TypeScript では、変数の宣言のときに : number
の様な型アノテートを省略し、かつ初期値が与えられたときは、初期値の型が変数の型になります。
var i = 1; console.log(typeof i); // => number let s = "abc"; console.log(typeof s); // => string let v = [2,3,5,7,11]; v[2] = "xyz" // !!! error TS2322: Type 'string' is not assignable to type 'number'. !!!
この型推論される構文は、JavaScript そのものなので、従前のJavaScriptで書かれたソースコードをTypeScriptとしてトランスパイルするだけで型の一貫性の検証が出来ます。
意図的に複数の型のオブジェクトを変数に入れるケースもコンパイルエラーになりますが、その場合もいきなり any
にするのではなく number | boolean
の様な共用体型として型チェックの余地を残すべきです。
any, Union と type[編集]
let a: any = 0; console.log(a, typeof a); a = "abc"; console.log(a, typeof a); let b: number | string = 1; console.log(b, typeof b); b = "xyz"; console.log(b, typeof b); type myType = number | string; let c: myType = 2; console.log(c, typeof c); c = "uvw"; console.log(c, typeof c);
- 実行結果
0 number abc string 1 number xyz string 2 number uvw string
- TypeScriptで新登場の型
any
を使うと JavaScript と同様に全てのオブジェクトを代入可能な変数を宣言できます。ですがこれではnumber
とstring
以外の型のオブジェクト(例えば配列)も代入可能になってしまいます。 number
とstring
に限定したい場合、型をnumber | string
として宣言します。- 新しい型には、キーワード type で名前をつけることが出来、以後は、型を要求される文脈に名前を使って参照出来るようになります。
interface[編集]
- かつてFirefoxにあった toSource メソッドを模倣
Object.defineProperty(Object.prototype, "toSource", { value: function () { return JSON.stringify(this); }, }); interface Object { toSource(): string; } [false, true, 0, 1, "", "0", "1", "abc", {}, []].forEach((x) => console.log(`${x.toSource()} => ${Boolean(x)}`) );
- 実行結果
false => false true => true 0 => false 1 => true "" => false "0" => true "1" => true "abc" => true {} => true [] => true
- Object.defineProperty() を使って TS の認識の外側から Object に toSource メソッドを生やしています。
- JSON.stringify で toSource を模倣しています。function のように直列化出来ないオブジェクトはサポートしていません。
- toSoure() のシグネチャは TS は知らないので、このまま toSource メソッドを呼出そうとするとトランスパイルでエラーになります。
- これを回避するため 7,8,9 行の interface で toSource を定義しています。
このコードの働きですが、色々なオブジェクトの真理値を一覧化しています。 TypeScriptの残念なところの1つとして度々あげられるのは、if や while の様な制御構造の条件式に boolean 以外の式も受付けてしまう点です。 上のように数値や文字列が与えられた時の真理値は、納得できるものとは言い難くJavaScriptの悪癖を引きずっていると言えます。 TSへのプロポーザルに、度々「条件式のブール値限定化」が上がりますが、毎回リジェクトされています。 ユーザーの自衛策としては tslint のような外部ツールでの検証が有効です。
enum, namespace, switch, throw と never 型[編集]
JavaScript には列挙型がありませんが、TypeScript が独自の拡張として キーワードenum
で列挙型を提供しています。
列挙型には、namespace
を使うことで、メソッドを定義することが出来ます。
- 列挙型
- 有限の識別子の集合しか値に出来ないスカラー型
- 列挙型の型定義の構文
enum 列挙型名 { 識別子1, 識別子2, ; 識別子n, }
- 列挙型で定義した識別子の参照
列挙型名 . 識別子
の構文で参照します- 列挙型のメソッド
- 列挙型にメソッドを追加するには、列挙型と同じ名前の名前空間(namespace)を定義し、その名前空間でメソッドを定義します。:
namespace 列挙型名 { export functionメソッド名(引数リスト) : 戻値型 { // 処理 } }
- enum, namespace, switch, throw
enum Colours { Red, Green, Blue, } namespace Colours { export function isWarm(colour: Colours) { switch (colour) { case Colours.Red: return true; case Colours.Green: return false; // case Colours.Blue: return false; default: throw new Error(`${colour} is unknown Colour.`); } } } const red = Colours.Red; const blue = Colours.Blue; console.log(`Use enums methods: Colours.isWarm(red) => ${Colours.isWarm(red)} Colours.isWarm(blue) => ${Colours.isWarm(blue)} `);
- 実行結果
/workspace/Main.ts:16 throw new Error(`${colour} is unknown Colour.`); ^ Error: 2 is unknown Colour. at Object.isWarm (/workspace/Main.ts:16:15) at Object.<anonymous> (/workspace/Main.ts:25:35) at Module._compile (node:internal/modules/cjs/loader:1103:14) at Object.Module._extensions..js (node:internal/modules/cjs/loader:1155:10) at Module.load (node:internal/modules/cjs/loader:981:32) at Function.Module._load (node:internal/modules/cjs/loader:822:12) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12) at node:internal/main/run_main_module:17:47
- 意図的に、Colours.Blue のケースをコメントにし、default まで落ちるようにしました。
- この方法で、全部列挙条件のテストを書き実行すれば、switch文の網羅性を担保出来ます。
- しかし、テスト自体が網羅していなかった場合は検証漏れしますし、そもそも全てのケースを実行することが難しい事もあります。
- テスト自体が網羅しなくなる主な原因は、列挙型のメンバーの追加で、これこそ回帰テストで重要な検証対象です。次では網羅性の担保について考えてみます。
- しかし、テスト自体が網羅していなかった場合は検証漏れしますし、そもそも全てのケースを実行することが難しい事もあります。
never を使った網羅性の担保[編集]
- enum, namespace, switch, throw と never
enum Colours { Red, Green, Blue, } namespace Colours { export function isWarm(colour: Colours) { switch (colour) { case Colours.Red: return true; case Colours.Green: return false; // case Colours.Blue: return false; default: let _ : never = colour; throw new Error(`${colour} is unknown Colour.`); } } } const red = Colours.Red; const blue = Colours.Blue; console.log(`Use enums methods: Colours.isWarm(red) => ${Colours.isWarm(red)} Colours.isWarm(blue) => ${Colours.isWarm(blue)} `);
- コンパイルエラー
Main.ts(30,13): error TS2322: Type 'Colours' is not assignable to type 'never'.
- 16行目に
let _ : never = colour;
を追加しました。 - この変更で、今度はコンパイルエラーが出るようになりました。
never
は、どんな型のオブジェクトも代入できない型です。- このため、TypeScriptのコンパイル時のフロー解析で never 型の変数の初期化に到達するとコンパイルエラーになります。
- もし列挙型を網羅していれば、default には至らないので、コンパイルエラーにはなりません。
- これで、switch の網羅性が担保できます。また、後に Colours のメンバーを追加した時の網羅性のほころびもエラーに出来ます。
- フロー解析は、switch 文だけでなく if 文や while 文など全ての制御構造で行われます。
namespace と enum のイテレーション[編集]
- namespace と enum のイテレーション
enum Colours { Red, Green, Blue, } namespace Colours { export function isWarm(colour: Colours) { switch (colour) { case Colours.Red: return true; case Colours.Green: return false; case Colours.Blue: return false; default: let _ : never = colour; throw new Error(`${colour} is unknown Colour.`); } } } const red = Colours.Red; const blue = Colours.Blue; console.log(`Use enums methods: Colours.isWarm(red) => ${Colours.isWarm(red)} Colours.isWarm(blue) => ${Colours.isWarm(blue)} `); console.log(`Value of Indifier: Colours.Red = ${Colours.Red} Colours.Green = ${Colours.Green} Colours.Blue = ${Colours.Blue} `); console.log("Use iteration:"); Object.entries(Colours).forEach(([key, value]) => { console.log(`${key}: ${value}`); });
- 実行結果
Use enums methods: Colours.isWarm(red) => true Colours.isWarm(blue) => false Value of Indifier: Colours.Red = 0 Colours.Green = 1 Colours.Blue = 2 Use iteration: 0: Red 1: Green 2: Blue Red: 0 Green: 1 Blue: 2 isWarm: function isWarm(colour) { switch (colour) { case Colours.Red: return true; case Colours.Green: return false; case Colours.Blue: return false; default: var _ = colour; throw new Error("".concat(colour, " is unknown Colour.")); } }
- Colours.Blue のコメントを外し、コンパイル可能にしました。
- また改めてプログラムの末尾で、列挙型のメンバの個別に表示するコードと列挙型オブジェクトのプロパティとその値をイテレーションするコードを加えました。
- namespace で型オブジェクトColoursを指定し追加したメソッド isWarm() もプロパティの中に見つけることが出来ます。
- 生成された JavaScript
"use strict"; var Colours; (function (Colours) { Colours[Colours["Red"] = 0] = "Red"; Colours[Colours["Green"] = 1] = "Green"; Colours[Colours["Blue"] = 2] = "Blue"; })(Colours || (Colours = {})); (function (Colours) { function isWarm(colour) { switch (colour) { case Colours.Red: return true; case Colours.Green: return false; case Colours.Blue: return false; default: let _ = colour; throw new Error(`${colour} is unknown Colour.`); } } Colours.isWarm = isWarm; })(Colours || (Colours = {})); const red = Colours.Red; const blue = Colours.Blue; console.log(`Use enums methods: Colours.isWarm(red) => ${Colours.isWarm(red)} Colours.isWarm(blue) => ${Colours.isWarm(blue)} `); console.log(`Value of Indifier: Colours.Red = ${Colours.Red} Colours.Green = ${Colours.Green} Colours.Blue = ${Colours.Blue} `); console.log("Use iteration:"); Object.entries(Colours).forEach(([key, value]) => { console.log(`${key}: ${value}`); });
- 2…7:Enum型の正体は、Objectで値=>識別子、識別子=>キーの双方向の定義がされていることがわかります。
- 値は、識別子の出現順に、0,1,2,3 とGoのiotaの様に付きます。
- 値を、型宣言のとき明示的に、
Red = 0xff0000,
の様につけることは可能ですが、重複チェックを掻い潜ってしまい、列挙型を使う意味がなく、本当に意味がある用途(例えばキャラクターコード表)以外では明示的な値指定は避けるべきです。また、値指定を行いたいモチベーションがある時は、class
のstatic
メンバとして実装することも検討の価値があります。
関数[編集]
TypeScriptでは、関数の定義でも、型アノテーションを付け引数の型や数、そして戻値の型の整合性を静的に、、つまり実行することなくトランスパイルするだけで調べることができます。
- 型アノテーションのある関数定義
function add(a: number, b: number): number { return a + b; } console.log(add(12, 43)); console.log(add(10**777, 43)); console.log(add(10**777, 0/0)); // console.log(add("123", 0/0)); // !!! error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. !!!
- 実行結果
55 Infinity NaN
- 2つのnumberを取り、number を返す関数 add を定義しています。
- 引数に number 以外を渡すと、トレンスパイルがエラーになります。
function sum(v: number[]): number { return v.reduce((x, y) => x + y) } const ary = new Array(10).fill(0).map((_,i) => 1 + i) console.log(ary) console.log(sum(ary)) // console.log(sum0(["X", "Y", "Z"])) // !!! Main.ts(9,19): error TS2322: Type 'string' is not assignable to type 'number'. !!! function cat(v: string[]): string { return v.reduce((x, y) => x + y) } console.log(cat(["X", "Y", "Z"]))
- 実行結果
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] 55 XYZ
- 関数 sum と関数 cat の本体は、シグネチャーが異なります(関数の引数と戻値の組合せの事を、シグネチャーといいます)。
- シグネチャー通りの型で呼び出さないと、トランスパイルでエラーになります。
- ラムダ式の型アノテーション
const add = (a: number, b: number): number => a + b const i : number = add(1,2)
- ラムダ式も型アノテーションできます。
型パラメータを使ったジェネリック関数[編集]
function mkAry<T>(value: T, n: number): Array<T> {
const result: Array<T> = new Array(n);
return result.fill(value);
}
const ary = mkAry<number>(1, 3)
console.log(ary)
定義と定義ファイル[編集]
declare function mkAry<T>(value: T, n: number): Array<T>
クラス[編集]
JavaScriptにECMAScript2015(ES6)で導入された、キーワード class (クラス)を TypeScript もサポートしています。
- コード例
class Hello { name: string; constructor(name: string = "world") { this.name = name; } toString(): string { return `Hello ${this.name}`; } print(): void { console.log(String(this)); } } const hello: Hello = new Hello(); hello.print(); const hello2: Hello = new Hello("my friend"); hello2.print(); console.log( `typeof Hello === ${typeof Hello} Object.getOwnPropertyNames(hello) === ${Object.getOwnPropertyNames(hello)} ` );
- 表示結果
Hello world Hello my friend typeof Hello === function Object.getOwnPropertyNames(hello) === name
- クラスのメンバーには、型を伴った宣言が必要です。
少しまとまったサイズのクラス[編集]
Ruby#ユーザー定義クラスの都市間の大圏距離を求めるメソッドを追加した例を、TypeScriptに移植。
- ユーザー定義クラス
class GeoCoord { longitude: number; latitude: number; constructor(longitude: number = 0, latitude: number = 0) { // console.info(`GeoCoord::constructor${longitude} ${latitude}`); return Object.assign(this, { longitude, latitude }); } toString(): string { let ew = "東経"; let ns = "北緯"; let long = this.longitude; let lat = this.latitude; if (long < 0.0) { ew = "西経"; long = -long; } if (lat < 0.0) { ns = "南緯"; lat = -lat; } return `(${ew}: ${long}, ${ns}: ${lat})`; } distance(other: GeoCoord): number { const i = Math.PI / 180; const r = 6371.008; return ( Math.acos( Math.sin(this.latitude * i) * Math.sin(other.latitude * i) + Math.cos(this.latitude * i) * Math.cos(other.latitude * i) * Math.cos(this.longitude * i - other.longitude * i) ) * r ); } } const Sites = { "東京駅": [139.7673068, 35.6809591], "シドニー・オペラハウス": [151.215278, -33.856778], "グリニッジ天文台": [-0.0014, 51.4778], }; for (const prop in Sites) { // console.log(`${prop}: ${Sites[prop]}`); Sites[prop] = new GeoCoord(Sites[prop][0], Sites[prop][1]); console.log(`${prop}: ${Sites[prop]}`); } const keys: string[] = Object.keys(Sites); const len = keys.length; keys.forEach(function (x, i) { let y = keys[(i + 1) % len]; console.log(`${x} - ${y}: ${Sites[x].distance(Sites[y])} [km]`); });
- 実行結果
東京駅: (東経: 139.7673068, 北緯: 35.6809591) シドニー・オペラハウス: (東経: 151.215278, 南緯: 33.856778) グリニッジ天文台: (西経: 0.0014, 北緯: 51.4778) 東京駅 - シドニー・オペラハウス: 7823.269299386704 [km] シドニー・オペラハウス - グリニッジ天文台: 16987.2708377249 [km] グリニッジ天文台 - 東京駅: 9560.546566490015 [km]
JavaScriptのメソッドはTypeScriptから呼出し可能[編集]
TypeScriptのメソッド呼出しは、そのままJavaScriptにトランスパイルされるので、 ECMAScript2022の Array.prototype.at メソッドの様な新しい仕様も はTypeScript から使うことが出来ます。
ただし、これはメソッドに限られ「新しい演算子」や「新しい構文」が追加された場合は、トランスパイラーが対応するまではコンパイルエラーになったり意図と違ったコードが生成される可能性があります。 安全を期するなら実行してみるだけでなく、Babelなどで実際にどんなJavaScriptに変換されるかの確認を行うべきでしょう。
Array.prototype.at メソッドとは、配列にアクセスする際の読み取りを、後ろからの位置で読み取る機能です。
マイナスの場合、後ろから読み取る。at(-1)
でいちばん後ろの項にアクセスする。at(-2)
なら後ろから2番目。
let p: number[] = [15 ,23 ,9, 41]; console.log(p.at(-2) ); // 9 console.log(p.at(-1) ); // 41 console.log(p.at(0) ); // 15
- 実行結果
9 41 15
制御構造[編集]
TypeScriptは、JavaScriptと同じ制御構造の構文を持ちます。
条件文[編集]
- if文の例
let num: number = 0.0 / 0.0 if (num < 0.0) { console.log('負') } else if (num > 0.0) { console.log('正') } else if (num == 0.0){ console.log('零') } else { console.log('NaN') }
- 実行結果
NaN
- switch文の例
let num: number = 0.0 / 0.0; switch (true) { case num < 0.0: console.log("負") break case num > 0.0: console.log("正") break case num == 0.0: console.log("零") break default : console.log("NaN") }
- 実行結果
NaN
反復文[編集]
型アノテート以外は、JavaScriptと違いはありません。
- while文
let i: number = 0; while (i < 10) { console.log(i); i++; }
- do-while文
let i: number = 0; do { console.log(i); i++; } while (i < 10);
- for文
for (let i: number = 0; i < 10; i++) { console.log(i); }
- for-in文
const obj: object = { x: 2, y: 3, z: 5 }; for (const prop in obj) { console.log(`${prop}: ${obj[prop]}`); } // x: 2 // y: 3 // z: 5
- for-of文
const ary: string[] = [..."XYZ"]; for (const el of ary) { console.log(el); } // X // Y // Z
スプライス構文 [..."XYZ"]
を使うには
npx tsc --target es2015
とES2015以降をターゲットに指定する必要がありました。
ES2015以前で、スプライス構文相当のことを行うには
[..."XYZ"]
を
"XYZ".split("")
に置換えます。これで、同じ
['X', 'Y', 'Z']
となります。
スプライス構文は、他にも引数リストなどでも使えるので、この方法が全てのスプライス構文の置換えにはなりません。
ちなみに Babal は、
const ary: string[] = [..."XYZ"];
から
"use strict"; function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } var ary = _toConsumableArray("XYZ");
を生成します。
tsconfig.json [編集]
tsc は、tsconfig.json で動作を変える事ができます。
最初は
npx tsc --init
とすると雛形が生成されます。
設定項目(一部):
noImplicitAny
(boolean): 暗黙的にany
と推論された場合は即座にエラーにする。strictNullChecks
(boolean):undefined
やnull
の可能性がある式に対して操作を試みることを禁止する。strictFunctionTypes
(boolean): 関数の引数の型、型変数を持つクラスの型引数に対するチェックが厳しくなる。strictBindCallApply
(boolean):Function
及びそのサブタイプに対するbind
、call
、apply
の呼び出しが厳格化される。strictPropertyInitialization
(boolean): 定義時にもコンストラクタ内でも初期化されないクラスのインスタンス変数をエラーにする。noImplicitThis
(boolean):this
がany
と推論される場合はエラーにする。strict
(boolean):noImplicitAny
、strictNullChecks
、strictFunctionTypes
、strictBindCallApply
、strictPropertyInitialization
、noImplicitThis
を全て有効化する。
enum[編集]
TypeScript には、enum(列挙型)というデーター構造があります[3]。 enum は、(2022年11月現在)JavaScript にはなく TypeScript にだけあるデーター構造なので、どのようなコードが生成されるか注意が必要です。 列挙型は、ANSI Cからある比較的ポピュラーなデーター構造で、実体はスカラーで、状態遷移やコード体系の表現を簡素に行えるので、tsc などでも多用されています
- 構文
enum 列挙型名 { 列挙子名1 [ = 初期値1 ] , 列挙子名2 [ = 初期値2 ] , 列挙子名3 [ = 初期値3 ] , ︙ 列挙子名n [ = 初期値n ] , }
- コード例(TypeScript)
enum Direction { Up, Down, Left, Right, } console.log(Direction.Up); console.log(Direction.Down); console.log(Direction.Left); console.log(Direction.Right);
- 実行結果
0 1 2 3
- トランスパイル結果(JavaScript)
"use strict"; var Direction; (function (Direction) { Direction[(Direction["Up"] = 0)] = "Up"; Direction[(Direction["Down"] = 1)] = "Down"; Direction[(Direction["Left"] = 2)] = "Left"; Direction[(Direction["Right"] = 3)] = "Right"; })(Direction || (Direction = {})); console.log(Direction.Up); console.log(Direction.Down); console.log(Direction.Left);
- 宣言の順番に列挙子に整数値が 0, 1, 2, ... と割り振られます。
- トランスパイルされた結果からわかる通り、列挙子名 ⇒ 整数値と整数値 ⇒ 列挙子名 の双方向のハッシュになっており、
列挙型名 . 列挙子
の構文(ドット記法)で整数値が、列挙型名 [ 整数値 ]
の構文(ブラケット記法)で列挙子名の文字列が返ります。
- enumは他の言語ではよく条件分岐と組み合わせて使われますが、TypeScript/JavaScript では、switch文が多方向分岐に対応しています。
- コード例(TypeScript)
enum Direction { Up, Down, Left, Right, } console.log(Direction[2]);
- 実行結果
Left
下記のように、別々の列挙子に同じ整数値を割り当てることはできますが、生成されるコードからわかる通り、これが望ましいというケースはまれでしょう。
- コード例(TypeScript)
enum Direction { Up = 1, Down = 1, Left, Right, } console.log(Direction[1]);
- 実行結果
Down
- トランスパイル結果(JavaScript)
"use strict"; var Direction; (function (Direction) { Direction[(Direction["Up"] = 1)] = "Up"; Direction[(Direction["Down"] = 1)] = "Down"; Direction[(Direction["Left"] = 2)] = "Left"; Direction[(Direction["Right"] = 3)] = "Right"; })(Direction || (Direction = {})); console.log(Direction.Up); console.log(Direction.Down); console.log(Direction.Left);
- "Up" が ”Down” の別名(値は 1)となっています。
- これは名前からするとおかしな状況です。
- 「"朱" が ”紅” の別名」のような用途では有用かもしれません。
- enumは何のためにあるのか?
- enum は、「識別子によって区別される有限の集合」と考えられます。
- 要素が「識別子によって区別されている」ので、順序が変わったり新しい識別子が追加されても、変更を局所化できます。
- 「有限の集合」という特徴がは、switch文での条件の充足性の評価(網羅性の担保)で役に立ちます。
- 独立の方なので、列挙型のインスタンスに間違えて違う型の値(例えば整数)を代入してもエラーになり、コンパイル時に発見することができます。
附録[編集]
TypeScript チートシート[編集]
// 変数宣言 let x = 5 x = 6 // 可 // 定数宣言 const y = 5 y = 6 // 不可 // 型アノテーションを伴った宣言 let z: number = 5 // メソッド function f1(x: number): number { return x * x } function f2(x) { console.log(x) } // 不正:引数に型アノテーションがない function f3(x: any): void { console.log(x) } // 適合:なんでもこい // 型エイリアス type T = number let u: T // 型とリテラル /* ToDo: number bigint bool string object Array TypedArray map set enum interface never any null undefined void namespace **/ // 無名関数, アロー関数 a.k.a. ラムダ式 const e1 = (x: T, y: T): T => { return x * y } const e2 = (x: T, y: T): T => x * y const e3 = (x: T): T => x * 2; // JS と違い パタメータの型アノテーションがあるので括弧が外せない // ジェネリックス function gf<T>(items: T[]): T[] {} // ユニオン型(Union Type) type U = number | string // 交差型(Intersection Type) type Q = number & string // タプル型(tuple) type R = [string, number] // モジュール /* ToDo: import export */ // データ構造 [1, 2, 3] // 配列リテラル const [x1, y1, z1] = [1, 2, 3] // 構造化代入: パターンマッチによる配列の展開。 const x2, y2, z2 = [1, 2, 3] // アカン奴:変数 z2 が配列で初期化される(x2, y2 は undefined) const ary = [2, 3, 5, 7, 11] ary[3] // 添字を使った配列要素の参照 // 制御構文 if ( 条件式 ) 文真 else 文偽 // 条件分岐 if ( 条件式 ) 文真 // 条件なし switch ( 式 ) { case 式1: 文1 : case 式2: 文2 case 式n: 文n default: 文d } // break を書かないとフォールスルーします while (x < 5) { console.log(x) x++ } for (let i: number = 0; i < 10; i++) { console.log(i); } // オブジェクト指向 // classリテラルとclass式がある class C { member1: number member2: number constructor(仮引数リスト) {/*...*/} method1() {/*...*/} method2() {/*...*/} } // TSも単一継承 const obj: C = new C(実引数リスト)