TypeScript に取り組み始める

TypeScript をなんとなく使っていたものの、ちゃんとやり直したいので取り組んでみる。

目次

参考

TypeScript って何だろうね

TypeScript の README より抜粋です。

TypeScript is a language for application-scale JavaScript. TypeScript adds optional types to JavaScript that support tools for large-scale JavaScript applications for any browser, for any host, on any OS. TypeScript compiles to readable, standards-based JavaScrip

TypeScript は、アプリケーション規模ための JavaScript です。
「ちょこっとページに動きをつけるくらいならいいけど、アプリケーションみたいな規模だとつらいでしょ?だから型を提供するよ。」
ってことだそうな。
(翻訳を使いつつの解釈。)

環境設定

適当なディレクトリで以下を入力する。

1
npm install --save-dev typescript ts-node

(以降の説明で都度tscでビルドしているところもts-nodeで実行すれば、直接実行まで確認できます。)

tsconfig.json を作成します。
内容は、オライリーを参考にしながらES2015ES2020に変更しています。

tsconfig.json
1
2
3
4
5
6
7
8
9
10
11
{
"compilerOptions": {
"lib": ["ES2020"],
"module": "commonjs",
"outDir": "dist",
"sourceMap": true,
"strict": true,
"target": "ES2020"
},
"include": ["src"]
}

./dist./srcの二つのディレクトリを作成しておく。

最初の一歩

src/index.ts に以下を書いてみます。

src/index.ts
1
console.log("Hello world");

こちらを用意して、npx tsc index.tsを実行すると、index.jsが作成されます。

src/index.js
1
console.log("Hello world");

こちらをnode dist/index.jsで実行すると、見たままHello worldと表示されます。
コンパイルしても、同じなのは TypeScript が、JavaScript の記法自体は受け入れるから。
なので、index.ts を TypeScript らしく書き換えてみます。

以下のように、出力する文字列にstringという型をつけて宣言するようにしました。

src/index.ts
1
2
const msg: string = "Hello world";
console.log(msg);

改めてnpx tsc index.tsを実行すると、以下のindex.jsが作成されます。

src/index.js
1
2
var msg = "Hello world";
console.log(msg);

型に関する記述がなくなりました。
こちらをnode dist/index.jsで実行すると、見たままHello worldと表示されます。
ちなみに、最初のconsole.log("Hello world");だけ書いたindex.tsnode src/index.tsとしても実行できます。
型定義を記述した index.ts では、node src/index.tsと実行しても以下のエラーになりました。

1
2
3
4
5
6
7
8
9
10
const msg: string = "Hello world";
^^^

SyntaxError: Missing initializer in const declaration
at Module._compile (internal/modules/cjs/loader.js:892:18)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:973:10)
at Module.load (internal/modules/cjs/loader.js:812:32)
at Function.Module._load (internal/modules/cjs/loader.js:724:14)
at Function.Module.runMain (internal/modules/cjs/loader.js:1025:10)
at internal/main/run_main_module.js:17:11

少し探る

違う型のものを与えてしまったら

前述 index.ts を以下のように変更します。

src/index.ts
1
2
const msg: number = "Hello world"; // <= stringをnumberに変更しました
console.log(msg);

改めてnpx tsc index.tsを実行すると、以下のエラーが発生します。

1
2
3
4
5
6
7
index.ts:1:7 - error TS2322: Type 'string' is not assignable to type 'number'.

1 const msg: number = "Hello world";
~~~


Found 1 error.

変数 msg は、number 型だから string 型のものを入れてくれるな。というわけですね。
このチェックがあれば、文字列に数字を入れてしまったり、といったミスは確かに防ぎやすくなりますね。

型アノテーション と 暗黙的な型推論

前述の index.ts で記述したconst msg: string = "Hello world";: stringの部分のことを、明示的な型アノテーションと言うそう。
アノテーション = 注釈という意味で、確かに変数が文字列であると注釈しています。

以下のように型アノテーションを書かなかった場合の index.ts を用意します。

src/index.ts
1
2
3
4
let msg = "Hello world"; // <=msgに文字列を代入
msg = 123; // <=msgに整数を代入

console.log(msg);

こちらを用意して、npx tsc index.tsを実行すると、以下のエラーが発生します。

1
2
3
4
5
6
index.ts:2:1 - error TS2322: Type 'number' is not assignable to type 'string'.

2 msg = 123;
~~~

Found 1 error.

変数 msg は、string 型なので number 型を入れてれるなってことですね。
アノテーションを書いていないけど、ソースコードから型の不整合を見てくれるのが、暗黙的な型推論。だそうです。
生の JavaScript だと、文字列で数字を入れているのか?、数値として設定しているのか?なんてことがあるので、ミスが防止できそうです。

型の指定って結構自由?

オライリーには、サンプルコードに以下のものがあります。

1
2
3
let d: boolean = true;
let e: true = true;
let f: true = false; // <= 型 'false' を型 'true' に割り当てることはできません。

いわゆる型としてイメージできるもの以外も型に設定できるということです。

ここで気が付いて、以下のものを書いてみました。

1
2
3
4
const n1: number = 1;
const n2: 1 = 1;
const n3: 1 | 2 = 2;
const n4: 1 | 2 = 3; //<= 型 '3' を型 '1 | 2' に割り当てることはできません。

数字を型アノテーションに使用すると、その数字以外の入力でエラーになることを確認できました。
ということは、文字列でもできるはずです。

1
2
3
4
const s1: string = "text";
const s2: "ringo" = "ringo";
const s3: "ringo" | "banana" = "banana";
const s4: "ringo" | "banana" = "budou"; // <= 型 '"budou"' を型 '"ringo" | "banana"' に割り当てることはできません。

型に特定の文字列を指定できました。特定の文字列だけを受け取る変数の宣言をするようなとき生かせそうです。
こういった特定の値を型にしているるものをリテラル型と呼ぶそうです。

オブジェクト型の修飾子

型の定義には便利なオプションの設定がある。

1
2
3
const obj: { a?: number; readonly b: string } = { b: "readonly" };

obj.b = "change"; // <= 読み取り専用プロパティであるため、'b' に代入することはできません。

?をつけたものはオプション、設定をしなくてもよくなる。
readonlyをつけたものは、読んでの通り読み取り専用になる。

型に名前をつける

typeinterfaceで型に名前をつけることができる。

src/index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 二つのstring型の変数を含む型Name
type Name = {
firstName: string;
lastName: string;
};

/*
// interface で表現すると以下のようになる。
interface Name {
firstName: string;
lastName: string;
}
*/

// 関数の引数と返り値にも型アノテーションできる。引数は必須。返り値は省略できる。
const fullName = (name: Name): string => {
return `${name.lastName}${name.firstName}`;
};

const name1: Name = { firstName: "ichiro", lastName: "yamada" };

// name1は、Name型であると型アノテーションで指定している
console.log(fullName(name1));

//Name型でなくてもオブジェクト中身の内容が合っていればエラーにならない
console.log(fullName({ firstName: "jiro", lastName: "yamada" }));

//lastnameが不足している場合は、エラー
console.log(fullName({ firstName: "saburo" }));

列挙型

TypeScript には列挙型がある。
JavaScript にないのに?と思いはしたもののどうなっているのか確認します。
以下のindex.tsを用意します。

src/index.ts
1
2
3
4
5
6
7
enum Key {
X = 1,
Y = 2,
Z = 3,
}

console.log(Key.X);

こちらをコンパイルした index.js は次の記述になっていました。

dist.index.js
1
2
3
4
5
6
7
8
var Key;
(function (Key) {
Key[(Key["X"] = 1)] = "X";
Key[(Key["Y"] = 2)] = "Y";
Key[(Key["Z"] = 3)] = "Z";
})(Key || (Key = {}));

console.log(Key.X);

また、const をつけることでより厳密な、制限をすることができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum Key1 {
X = 1,
Y = 2,
Z = 3,
}

console.log(Key1.X);
console.log(Key1[0]); // <= 数字をキーにしてアクセスできる
console.log(Key1[4]); // <= 範囲外を指定してもエラーにならない

const enum Key2 {
X = 1,
Y = 2,
Z = 3,
}

console.log(Key2.X);
console.log(Key2[0]); // <= const 列挙型メンバーは、文字列リテラルを使用してのみアクセスできます。
console.log(Key2[4]); // <= const 列挙型メンバーは、文字列リテラルを使用してのみアクセスできます。

エラーになる箇所を除外して改めて、コンパイルしてみると次のようになっていました。(多少の改行あり)

dist.index.js
1
2
3
4
5
6
7
8
9
10
11
var Key1;
(function (Key1) {
Key1[(Key1["X"] = 1)] = "X";
Key1[(Key1["Y"] = 2)] = "Y";
Key1[(Key1["Z"] = 3)] = "Z";
})(Key1 || (Key1 = {}));
console.log(Key1.X);
console.log(Key1[0]);
console.log(Key1[4]);

console.log(1 /* X */); // <=直接enumで定義した値に書き換わっている。(インライン展開)

この動作だけ見ると、const enum しか使わない気がしました。

アンビエント宣言

例として、参照元jQuery の$に型を与える例があるんですが、他に試す例が出せないのでまた後で確認したいところです。
(チャレンジとしては jQuery 以外で。)


TypeScript を本格的に勉強し始めましたが、とりとめもなくメモだけの記事です。
正直有益な情報って現段階では提供 0 とは感じていますが、しばらく続く見込みです。

ではでは。