最近 TypeScript の勉強をしているので、学んだことメモ
TypeScript とは
- JavaScript を拡張した言語
- 最終的に JavaScript にコンパイルして使用される
- 型を持つため、潜在的なエラーを把握することができる
- JavaScript にない新しい機能を含む
- インターフェース
- ジェネリクス
- デコレータ
インストール
-
node.js をインストール
- node.js の公式サイトからインストーラを入手し、インストール
-
npm で TypeScript をインストール
npm install -g typescript
コンパイラ
コンパイルする ts ファイルを指定して、tsc コマンドを実行する
tsc app.ts
ウォッチモード
ts ファイルを変更すると、自動でコンパイルが行われるようになる
tsc app.ts --w
プロジェクト全体のコンパイル
まず、プロジェクトフォルダであることを指定する
cd [プロジェクトのルートフォルダ]
tsc --init
tsconfig.json ファイルが作成される
tsc コマンドを実行すると、プロジェクト内のすべての ts ファイルがコンパイルされる
tsc
コンパイル する/しない ファイルを指定する
tsconfig.json に include/exclude を指定し、コンパイラの対処に する/しない ファイルを指定する
- include
- コンパイルするファイル
- 指定しなければ、すべての ts ファイルをコンパイルする
- exclude
- コンパイルしないファイル
- フォルダを指定することも可能
- node_modules フォルダは、デフォルトでコンパイルしないようになっている
型定義
const number1 = 5; // この場合、number 型であることが明確なので型指定は不要(推論)
let number2: number; // この場合、型がわからいないので要指定
number2 = 'aa'; // エラーになる
let number3; // 型を指定しないと、any 型になる
number3 = 'bb'; // any 型なのでエラーにならない
引数
function add(num1: number, num2: number, isShow: boolean) {
const result = num1 + num2;
if (isShow) {
console.log(result);
}
return result;
}
Object
const book = {
title: "aaa",
page: 200,
}
上記のようなオブジェクトの場合、下記のように型が定義される
{
title: string;
page: number;
}
明示的に型を指定する場合は以下のように書く(ただし、推論が可能な場合は推奨されない)
const book: {
title: string;
page: number;
} = {
title: 'aaa',
page: 200,
}
Array
let bookshelf: string[]; // string 型の Array と指定
bookshelf= ["aaa", 1]; // string じゃないので「1」のところでエラー
Tuple : 長さが指定された配列。JavaScript にはない
let book: [number, string] = [2, "author"];
book[1] = 10; // string じゃないのでエラー
book = [0, 'bbb', 'ccc']; // 長さが一致しないのでエラー
Enum : 列挙型。JavaScript にはない
enum Genre {
BUSINESS,
COMIC,
NOVEL,
}
const book = {
// ~
genre: Genre.COMIC,
};
Any : 型をチェックしない。JavaScript にはない
let bookshelf: any[]; // 配列であれば、中身はどんな型でもOK
bookshelf= ["aaa", 1]; // エラーにならない
any は極力使わないようにすべき
Union : 柔軟な型定義
// input1, input2 は number型か string型を受け取れる
function add(input1: number | string, input2: number | string) {
// ~
}
Literal : 値そのものを定義する
// resultType には、"as-number" か "as-text" のみ指定可能
function add(input1: number, input2: number, resultType : "as-number" | "as-text") {
// ~
}
エイリアス(type) : Union 型や Literal 型を別の名前で定義する
type InputType = number | string;
type ResultDescriptor = "as-number" | "as-text";
function add(input1: InputType, input2: InputType, resultType : ResultDescriptor) {
// ~
}
function、void
// return の型を指定する場合(推論される場合は記述不要)
function add(n1: number, n2: number): number {
return n1 + n2;
}
// return がない場合 void 型になる
function printNum(num: number): void {
console.log("Result: " + num);
}
// function の型を指定(2つの number の引数、number の戻り値)
let addValues: (a: number, b: number) => number;
addValues = add; // OK
addValues = printNum; // 関数の引数、戻り値の型が異なるのでエラー
unknown : どの型が入るかわからない
let unknownInput: unknown;
let strInput: string;
unknownInput= 5;
unknownInput= "aaa";
strInput= unknownInput; // unknownInput が string と保証されないのでエラー
// unknown を代入する場合は、型チェックが必要
if (typeof unknownInput=== "string") {
strInput= unknownInput;
}
unknownInput を any型 とするとエラーにならない(any は型チェックをしないため)
never : 何も返さない関数の戻り値
function generateError(message: string, code: number): never {
throw { message: message, errorCode: code };
}
generateError("エラーが発生しました", 500);
戻り値がない場合 void でもいいが、戻り値は絶対にありえないということを明示的に示す場合に never を使う
型キャスト
TypeScript が型を取得できない場合に、開発者が明示的に指定する方法
- 方法1 : <型名> 式
- 方法2 : 式 as 型名
// 下式のどちらか
const element1 = <HTMLInputElement>(document.getElementById("id1")!);
const element2 = document.getElementById("id2")! as HTMLInputElement;
インターフェース
- オブジェクトがどんな形かを定義するもの
- 具体的な値を設定することはできない
- オブジェクトの型として使用でき、構造が合っているかチェックできる
interface MsgInterface {
name: string;
showMessage(phrase: string): void;
}
// interface の実装クラス
class Message implements MsgInterface {
constructor(public name: string) {}
showMessage(phrase: string) {
console.log(phrase + " " + this.name);
}
}
const msg1 = new Message("World");
msg1.showMessage("Hello");
インターフェースは継承も可能
interface Named {
readonly name: string;
}
interface MsgInterface extends Named {
showMessage(phrase: string): void;
}
関数の型の定義
interface AddInterface {
(a: number, b: number): number;
}
let add: AddInterface;
add = (n1: number, n2: number) => {
return n1 + n2;
};
任意のプロパティ
必須でないプロパティには「?」を付けることでオプションにできる
interface Named {
readonly name: string;
outputName?: string; // outputName はオプション
}
ジェネリクス
ジェネリクス関数
関数の定義時には引数を明確にしておらず、関数の使用時に引数を明確する
// Generic 関数
function merge<T, U>(objA: T, objB: U) {
return Object.assign(objA, objB);
}
const mergedObj = merge({ name: "aaa" }, { age: 30 });
ジェネリック型に制約を付ける
T extends *** で型に制限を付ける
// Generic 関数(引数をobjectで制限)
function merge<T extends object, U extends object>(objA: T, objB: U) {
return Object.assign(objA, objB);
}
const mergedObj = merge({ name: "aaa", hobbies: ["sports"] }, { age: 30 });
keyof 制約
引数の一つが、別のオブジェクト型引数のキーになっていることを制約する
function getValue<T extends object, U extends keyof T>(
obj: T,
key: U
) {
return "Value: " + obj[key];
}
getValue({ name: "Max" }, "name");
ジェネリック型のユーティリティ
-
Partial
- ジェネリックを一時的にオプションにする
-
readonly
- オブジェクトのプロパティの追加や変更、配列の push や pop が使えない
ジェネリック型と Union 型の使い分け
- Generic
- クラス、関数の最初に型を一つに制限することができる
- Union
- Union に指定されている、いずれかの型を常に受け入れることができる
デコレータ
事前準備
- tsconfig.json で
"experimentalDecorators": trueのコメントを外す
基本的な Decorator
-
関数をクラスに適用させる
-
クラスを定義した時に実行される(インスタンス化したときではない) → クラスの初期設定に関わる処理に使える
function Logger(constructor: Function) {
console.log("ログ出力中...");
console.log(constructor);
}
@Logger
class Person {
name = "aaa";
constructor() {
console.log("Person オブジェクトを作成中...");
}
}
Decorator ファクトリー
Decorator 関数に引数を渡す
function Logger(logString: string) {
return function (constructor: Function) {
console.log(logString);
console.log(constructor);
};
}
@Logger("ログ出力中...")
class Person {
name = "aaa";
constructor() {
console.log("Person オブジェクトを作成中...");
}
}
1つのクラスに複数ので Decorator を付けることが可能
- Decorator の作成は上から順に実行
- Decorator は下から順に実行
function Logger(logString: string) {
console.log("Loggerファクトリー");
return function (constructor: Function) {
console.log(logString);
console.log(constructor);
};
}
function WithTemplate(template: string, hookId: string) {
console.log("Templateファクトリー");
return function (_: Function) {
console.log("テンプレートを表示");
const hookEl = document.getElementById(hookId);
if (hookEl) {
hookEl.innerHTML = template;
}
};
}
@Logger("ログ出力中")
@WithTemplate("<h1>Person オブジェクト<h1>", "app")
class Person {
name = "Max";
constructor() {
console.log("Person オブジェクトを作成中...");
}
}
// --- ブラウザの Console の出力 ---
// Loggerファクトリー
// Templateファクトリー
// テンプレートを表示
// ログ出力中
Decorator を配置できる場所
- クラス
- プロパティ
- メソッド
- パラメータ
function Log(target: any, propertyName: string | Symbol) {
console.log("Property Decorator");
console.log(target, propertyName);
}
function Log2(target: any, name: string, descripter: PropertyDescriptor) {
console.log("Accessor Decorator");
console.log(target);
console.log(name);
console.log(descripter);
}
function Log3(
target: any,
name: string | Symbol,
descripter: PropertyDescriptor
) {
console.log("Mehotd Decorator");
console.log(target);
console.log(name);
console.log(descripter);
}
function Log4(target: any, name: string | Symbol, position: number) {
console.log("Parameter Decorator");
console.log(target);
console.log(name);
console.log(position);
}
class Product {
@Log
title: string;
private _price: number;
@Log2
set price(val: number) {
if (val > 0) {
this._price = val;
} else {
throw new Error("不正な価格です - 0 以下は設定できません");
}
}
constructor(t: string, p: number) {
this.title = t;
this._price = p;
}
@Log3
getPriceWithTax(@Log4 tax: number) {
return this._price * (1 + tax);
}
}