Wikipedia
ウィキペディアTypeScriptの記事があります。

メインページ > 工学 > 情報技術 > プログラミング > TypeScript


TypeScriptは、JavaScriptに型のための構文を加えたもので、これにより事前に静的な型チェックを可能にしています。 TypeScriptは、既存のJavaScriptライブラリの型情報を含む定義ファイルをサポートするなど、大規模なアプリケーションの開発のために設計されました。 TypeScriptで書かれたソースコードは、JavaScriptにトランスパイルされJavaScriptエンジンにより実行されます。

環境準備編集

下記のいくつかの方法がある。いずれかひとつを選べば十分である。

なお、Google Apps script (GAS)は、web上での利用では typescript に対応していない。web上でのGASに無理やりtypescript のコード入力をしても、エラーになる。Google の提供する clasp というローカル開発環境ではtypescriptで開発できるが、しかし最終的に Google Apps script で動かすためには通常のJavaScriptに変換される(トランスパイル)。このため、本書ではGASには深入りしない。

なお、もしECMAScriptの新仕様を使いたい場合、node.jsおよびdenoが比較的に早く新仕様を取り入れるので、それを使うとよい。

トランスパイラ
トランスパイラとは、プログラミング言語で書かれたプログラムのソースコードを入力として、同じまたは異なるプログラミング言語で同等のソースコードを生成するコンパイラの一種です。

従来のコンパイラが高レベルのプログラミング言語から低レベルのプログラミング言語に変換するのに対し、トランスパイラは、ほぼ同じ抽象度で動作するプログラミング言語間を変換します。 たとえば、従来のコンパイラは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としても実行するため) 変数 anumber と明示的に宣言していませんが、初期値の 0 から型推論されています。

上記のコードでは、numberstring が出てきましたが、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ビット倍精度浮動小数点数です。 実行時に、Number.isInteger()を使って動的に判定することは出来ますが、コンパイル時に整数に限定した型をアノテートすることは出来ません。

どうしても整数に限定した型がほしい場合は、BigInt オブジェクトがあります。 BigInt オブジェクトのリテラルは、123n の様に数値に続けて n を補います。 TSでの型名は bigint です。 bigint と number の間では四則演算や比較演算は出来ません[1]。 Number() あるいは BigInt() で型を合わせてから演算する必要があります。 C言語などは、浮動小数点数に自動変換されますが、JS(=TS)の場合 BigInt は名前の通り多倍長整数なので 10n ** 309n の様な大きな値を、Number() で型変換すると Infinity になってしまいますので、一概に Number に揃えるべきとも言えません。


文字列型(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++ の autodecltype や、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 と同様に全てのオブジェクトを代入可能な変数を宣言できます。ですがこれでは numberstring 以外の型のオブジェクト(例えば配列)も代入可能になってしまいます。
  • numberstring に限定したい場合、型を 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, の様につけることは可能ですが、重複チェックを掻い潜ってしまい、列挙型を使う意味がなく、本当に意味がある用途(例えばキャラクターコード表)以外では明示的な値指定は避けるべきです。また、値指定を行いたいモチベーションがある時は、 classstatic メンバとして実装することも検討の価値があります。

関数編集

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): undefinednullの可能性がある式に対して操作を試みることを禁止する。
  • strictFunctionTypes (boolean): 関数の引数の型、型変数を持つクラスの型引数に対するチェックが厳しくなる。
  • strictBindCallApply (boolean): Function及びそのサブタイプに対するbindcallapplyの呼び出しが厳格化される。
  • strictPropertyInitialization (boolean): 定義時にもコンストラクタ内でも初期化されないクラスのインスタンス変数をエラーにする。
  • noImplicitThis (boolean): thisanyと推論される場合はエラーにする。
  • strict (boolean): noImplicitAnystrictNullChecksstrictFunctionTypesstrictBindCallApplystrictPropertyInitializationnoImplicitThisを全て有効化する。

enum編集

TypeScript は公式サイトに列挙体 enum というデータ構造が規程されています( "TypeScript: Handbook - Enums" )。ただし、JavaScript には存在しない仕様なので、やや互換性に問題があります。 インターネット周りでいうと、PHPやRustにはenumが存在しますので、JavaScript よりもRustなど別言語との類似性を考慮していると言えるでしょう。やや実験的な機能ですので、自己責任でお使いください、

enum Direction {
  Up ,
  Down,
  Left,
  Right,
}

console.log(Direction.Up);
console.log(Direction.Down);
console.log(Direction.Left);
実行結果
0
1
2

なお、上記の実行結果の 0,1,2 は、node.js では赤い文字で表されます。気にはなるでしょうが、playgroundでは普通に表示されます。

特に指定しないかぎり、宣言の順番に数値が 0 ,1, 2 , ・・・と割り振られていきます。

enum宣言した要素にアクセスするには グループ名.要素名 です。

当然ですが、enumの要素名(上記コードでは Up, Down, Left, Right の部分)は、すべて異なっていなければなりません。要素名が重複して宣言されると、エラーになります。

enumは他の言語ではよく条件分岐と組み合わせて使われますが、TypeScript および JavaScript には、条件分岐の match は存在しません(JavaやPHPにはmatchが存在している)。javaScript ではmatch は、Javaなどとは別の意味で使われています。

if文はtypescript でも普通に存在していますので、条件分岐をするなら if 文で行うと良いでしょう。


TypeScript のenum には、割り当てされた値から逆引きして要素名を求める機能があります。下記コードのように使います。

グループ名.[値]

で、enum中の要素名にアクセスできます。

コード例

enum Direction {
  Up ,
  Down,
  Left,
  Right,
}

console.log(Direction[2]);
実行結果
Left

node.js では、これは普通の文字色で表示されます(たとえば黒背景色なら白文字で)。playground でも普通に表示されます。


下記コードのように、別々の要素に同じ値を割り当てることはできますが、しかしプログラマー(人間側)の混乱の原因になりますので、なるべく避けたほうが安全でしょう。

コード例

enum Direction {
  Up =1,
  Down=1,
  Left,
  Right,
}

console.log(Direction[1]);
実行結果
Down

node.js でも playground でも、結果は「Down」になります。おそらく、「1」というキーの辞書のようなものに、宣言順にまず「Up」を代入されて、その場所が続いて「Down」で上書きされるのでしょう。

これを確認するため、さらにRight部分を Roght=1, と変更してみると、結果は「Right」に変わります。

また、enumの要素名(Up や Down などの部分)が重複しているとエラーになり実行不能です。たとえばRight を Up に書き換えると、エラーになります。おそらく要素名をキーとした辞書として管理しているのでしょう。

ともかく、上述のように逆引き命令が返す要素は、1つだけです。


  • enumが何のためにあるか

もしenumを使わずにswitch文で条件分岐をコーディングすると、言語にもよりますが一般的に、各条件ごと数値を割り当てしなければなりません。条件Aなら1、条件Bなら2、のように。それは、条件が増えてきたとき、条件の並び替えや追加条件の挿入などで、とても面倒になります。場合のよっては、追加しようとする条件以外にもすべての条件の値を割り当てしなおし、なんてことにも、つながりかねません。一方、enumを使うことで、そういう問題を回避できます。

附録編集

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(実引数リスト)

関連リンク編集

このページ「TypeScript」は、まだ書きかけです。加筆・訂正など、協力いただける皆様の編集を心からお待ちしております。また、ご意見などがありましたら、お気軽にトークページへどうぞ。
  1. ^ JSのレベルで出来ません。
  2. ^ キーワードを増やさず、型アノテートが省略されたら型推論する仕組みはGoScalaRustとよく似ています。