Typescript書き方の速成まとめ


個人学習目的で作成していますが、間違ったところや捕捉などありましたがご指摘ください。

## 1. 概論・セットアップ

- transpile言語 (類似コンパイル言語)
- Javascriptと交換
- Typescriptの主な役目
    - コンパイル時にタイプチェックを行うこと

### Typescript setup & compile

```bash
# 初期化
npm init -y
npm -i typescript [-g]
%init tsconfig.json
./node_modules/.bin/tsc --init 

# compile
tsc {filename} 

# watch & auto compile, 実用的ではない(glup推奨)
tsc -w
```

### typescript online play

https://www.typescriptlang.org/play

## 2. tsconfig

http://json.schemastore.org/tsconfig

● Top Level Properties

```
compileOptions ★
compileOnSave //boolean : セーブと同時にコンパイル(IDE)
extends //relative path
files //path(glob)
include //path(glob)
exclude //path(glob)
typeAcquisition
```

### compileOptions : type

- TypeScript2からサポートするType Definition System関連オプション
- 何も設定しないと、自動で`./node_nodules/@types/*`をインポート
    - ex: `./node_nodules/@types/react/*`, `./node_nodules/@types/babel__*/*`
- @typesは、コンパイル時のタイプチェックはもちろん、IDEのコードアシスト、シンタクスチェックなどでも使われる大事な定義ファイル
- typeRoots
    - 設定すると、設定したパスだけインポート
- types
    - 設定すると、配列内に指定したモジュール、または`./node_nodules/@types/`内のモジュール名から探す。
    - `[]`だと使わない
- typeRootsはtypesは、どっちか一つだけ使う。

```json
"typeRoots": {
    "description": "Specify list of directories for type definition files to be included. Requires TypeScript version 2.0 or later.",
    "type": "array",
    ...
},
"types": {
    "description": "Type declaration files to be included in compilation. Requires TypeScript version 2.0 or later.",
    "type": "array",
    ...
},
```

### compileOptions : target, lib

- target
    - buildするバージョンを指定
    - 指定しないと、基本esバージョン
- lib
    - 基本type difinitionライブラリを指定
    - 指定しないと、esバージョンに依存したライブラリを使用
    - 指定すると、指定したライブラリのみを使う

```json
"target": {
    "description": "Specify ECMAScript target version: 'ES3', 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ESNext'",
    "type": "string",
    ...
},
"lib": {
    "description": "List of library files to be included in the compilation. Possible values are: 'ES5', 'ES6', 'ES2015', 'ES7', 'ES2016', 'ES2017', 'ES2018', 'ESNext', 'DOM', 'DOM.Iterable', 'WebWorker', 'ScriptHost', 'ES2015.Core', 'ES2015.Collection', 'ES2015.Generator', 'ES2015.Iterable', 'ES2015.Promise', 'ES2015.Proxy', 'ES2015.Reflect', 'ES2015.Symbol', 'ES2015.Symbol.WellKnown', 'ES2016.Array.Include', 'ES2017.object', 'ES2017.Intl', 'ES2017.SharedMemory', 'ES2017.String', 'ES2017.TypedArrays', 'ES2018.Intl', 'ES2018.Promise', 'ES2018.RegExp', 'ESNext.AsyncIterable', 'ESNext.Array', 'ESNext.Intl', 'ESNext.Symbol'. Requires TypeScript version 2.0 or later.",
    "type": "array",
    ...
},
```
### compileOptions : outDir, outFile

```json
"outDir": {
    "description": "Redirect output structure to the directory.",
    "type": "string"
},
"outFile": {
    "description": "Concatenate and emit output to single file.",
    "type": "string"
},
```

### compileOptions : module

- module
    - compileされた結果物のモジュールシステムを指定
    - `target`がes6だと、es6がデフォルト
    - `target`がes６じゃないとcommonjsがデフォルト
- moduleResolution
    - tsソースで使用されるモジュールを指定
    - CommonJSの場合`Node`、それ以外はだいたい`Classic`
-pathsと baseUrl
    - 指定すると、該当パスのモジュールをロード
    - 普通使わなくてOK。（細かいモジュール連携に必要）
- rootDirs
    - ロードするモジュールのルートパス配列
    - 普通使わなくてOK。（細かいモジュール連携に必要）

```json
"module": {
    "description": "Specify module code generation: 'None', 'CommonJS', 'AMD', 'System', 'UMD', 'ES6', 'ES2015', 'ES2020' or 'ESNext'. Only 'AMD' and 'System' can be used in conjunction with --outFile.",
    "type": "string",
    ...
},
"moduleResolution": {
    "description": "Specifies module resolution strategy: 'node' (Node) or 'classic' (TypeScript pre 1.6) .",
    "type": "string",
    ...
},
"baseUrl": {
    "description": "Base directory to resolve non-relative module names.",
    "type": "string"
},
"paths": {
    "description": "Specify path mapping to be computed relative to baseUrl option.",
    "type": "object",
    "additionalProperties": {
        "type": "array",
        "items": {
            "type": "string",
            "description": "Path mapping to be computed relative to baseUrl option."
        }
    }
},
"rootDirs": {
    "description": "Specify list of root directories to be used when resolving modules.",
    "type": "array",
    ...
},
```

## 3. TypeScript Basic Types

TypeScriptで定義した基本データタイプ
User Defined Typesも、基本データタイプからの拡張

### superset (ECMAScript Standard)

- boolean, number, string, null, undefined
- array (object, non-primitive)
- symbol (ecma6)
    - 固有で修正不可能なデータとしてアサイン
    - primitive値を指定

> Subtypes
> - undefined & nullは、すべてのタイプに対してのサブタイプ
> - すべてのタイプに、undefinedとnullはアサインできる。
> - しかし、compileOptionで, `--strictNullChecks`を使うと、`void`か、自分自身にだけアサインでできるようになる。
>   - その場合は、union typeで指定しなきゃいけない。
>   - ex : `let union: string | null | undefined = 'str'`

### Additional Type

- **void**
    - タイプがない、空の概念
    - 関数のリターンタイプくらいで使う（リターンする値がない時）
- **any**
    - 何のタイプにもなれる
    - Anyは非推奨、TypeScriptを使う意味が薄れる。
    - compileOptionで、エラーになるように指定も可能(`noImplicitAny`)
- **never**
    - 結果を返さないため、タイプを持たない。
    - あんまり使うところがないが、関数のリターンタイプくらいで使う
        - infinitely loop function
        - absolutely throw Error
        - absolutely return error('message')
- **enum**
    - 列挙型(他の言語と同じ)
    - 複数の変数に対して、一連の定数値をアサイン
    - `enum Color {Red, Green, Blue}; let c: Color = Color.Red; let colorName: string = Color[c];`
- **tuple** (object, non-privitive)
    - 複合タイプを持つ配列
    - `let x: [string, number] =['hello', 10];`
    - 値を持って使うときに、どういうタイプかチェックしない限りわからないため、使うのに注意は必要
- **Union Type**
    - タイプの共用体
    - `let someVar: string | number | boolean = false`

### Type Assigned by literal

- Literal値で、タイプを定義するのも可能。指定したLiteralのみ設定可能
    - `let someVar: "a" | 5 | false = 5`
- `Generic`パートで記述する`keyof (Indexed Type Query)`演算子の理解とつながる。

## 4. var, let, const

### VS var, let, const

- **var**
    - ES5
    - variable scope : function
    - hoisting : O
    - re-difinition : O
- **let, const**
    - ES6
    - variable scope : block
    - hoisting : X
    - re-difinitio : X
- **varより、let, const推奨**
    - コード分析が直感的になる
- **letとconstのタイプ推論**
    - `let a: string = "str"; //明示的string type`
    - `let b = "str"; //タイプ推論によるstring type`
    - `const c: string = "str"; //明示的string type`
    - `const d = "str"; //タイプ推論によるLiteral type`

## 5. Type Assertion

- とある変数を参照する時、タイプを明示的に絞ること
    - type castingとは違い、データを変換したりしない
- とある変数を、指定タイプであることを前提に使うという宣言
- もし使う場合があるなら、宣言に対しての信頼性が大事
- 使い方
    - someVar as TYPE
    - \<TYPE\>someVar (jsxと紛らわしいので、非推奨)

```typescript
// 主に曖昧なタイプから絞るときに使う。
// 曖昧なタイプである時点で、ベストプラクティスではないので、参考までに見ることs

let someVar: any = "some string";
let strLength: number =(someVar as string).length; 
```

## 6. Type Alias
特定のタイプに別称をつけて使うことができる。
あくまで、作られたタイプの参照を持つだけで、タイプを作ることではない。

```typescript
//alias to union type
let varA: string | number = 0;
varA = "A";

type StringOrNumber = string | number;
let varB = 0;
varB = "B";
```


## 7. Interface

実装を持たず、ステート（プロパティ）とビヘイビアの形式の定義のみを記述した抽象データタイプ。
- インタフェースの抽象というのは、インスタンス化観点から抽象的であり具体を持たいないため、単体では実態を持てないという意味を内包している。
- 継承するクラスたちに対してのプロトコル（約束）の役目を果たす。

### interface basics

```typescript
//interface
interface Person {
    name: string;

    //--optional type
    age?: number;

    //-- function interface
    say(): string;
}

const person:Person = {
    name: "Mark",
    age: 34,
    say: (): string {
        return `hello. name=${this.name}`;
    }
}
```

### interface - indexable type

indexのタイプとしては、stringか、numberを指定可能

```typescript
interface Person {
    //--indexable type (number or string)
    [index: string]: string;
}
const person:Person = {
    name: "Mark"
}
person.age = "34";
person["age"] = "34";
//person.age = 34; //index type error
```

```typescript
interface NumIndex {
    //--indexable type (number or string)
    [index: number]: object;
}
const queue:NumIndex = {}
queue[0] = new Object();
queue[1] = new Object();
//queue["abc"] = new Object(); //index type error
//queue.abc = new Object(); //index type error

```

### class implements interface

```typescript
interface IPerson {
    name: string;
    age?: number;
    say(): void;
}

class Person implements IPerson {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    say(): void {
        console.log(`hello. myname is ${this.name}.`);
    }
}

const person = new Person("Mark");
person.say();
```

### interface extends interface

```typescript
intrface Person {
    name: string;
    age: number;
}

interface SalaryMan extends Person {
    job: string;
}
```

### function with interface
```typescript
interface funcPerson {
    (name: string, age?: number): void;
}

const sayPerson: funcPerson = function (name: string) {
    console.log(`hello. myname is ${name}.`);
}

sayPerson("Mark");
```

## 8. Class

オブジェクトの初期ステート（プロパティ）とビヘイビアを記述したテンプレートであり、User Defined Data Type.

### class basics

```typescript
class Person {
    protected _name: string = null;
    private _age: number = null;

    set _age;
}

class SalaryMan extends Person {
    private _job: string = null;

    constructor(name: string) {
        super();
        this.name = name;
    }


}

const man: SalaryMan = new SalaryMan("Mark");
```

### Abstract class

```typescript
abstract class APerson {
    protected _name: string = "NoName";
    abstract setName(name: string): void;
}

class Person extends APerson {
    setName(name: string): void {
        this._name = name;
    }
}

const person = new Person();
```

### readonly keyword & static keyword & private constructor

※ typescriptでは、anti-partternだという意見もあり

```typescript
class Logger {
    private static singletonInstance: Logger;
    public readonly initTime: number;

    private constructor(){
        this.initTime = new Date().getTime();
    }
    
    public static getLogger = ():Logger => {
        if (Logger.singletonInstance === undefined) {
            Logger.singletonInstance = new Logger();
        }

        return Logger.singletonInstance;
    }

    logInfo = (msg: string):void => {
        console.log(`logger-${this.initTime} : ${msg}`);
    }
}

Logger.getLogger().logInfo("first log");

setTimeout(
    (): void => {
        Logger.getLogger().logInfo("after 2sec log");
    },
    2000
);

//Logger.getLogger().initTIme = 5; //error because readonly property

//--output
//logger-1590906992695 : first log
//logger-1590906992695 : after 2sec log
```

## 9. Generic

パラメータのデータタイプを、インスタンス化の後で明示するプログラミング手法（to-be-specified-later）
入力・出力のデータタイプを、任意のタイプに抽象化宣言する。
タイプチェックは、ランタイムの前にコンパイラでしてくれるが、内部動作メカニズムとしては、実際にどういうデータタイプで入力・出力されるかは、インスタンス化後、実際に呼び出されるときに確定される。

### Generic basics

```typescript
function doPingPong<T>(message: T): T {
    return message;
}

console.log(doPingPong<string>("text"));
console.log(doPingPong<number>(10));
console.log(doPingPong<object>({key: "value"}));
```

### Generic with class

```typescript
class Code<T extends string | number, O> {
    private _code: T;
    private _data: O;

    constructor(code: T, data: O) {
        this._code = code;
        this._data = data;
    }

    getCode = (): T => {
        return this._code;
    }

    getData = (): O => {
        return this._data;
    }
}

const abc = new Code<string,string>("ABC", "data");
const oneTwoThree = new Code<number, object>(123, {});
```

## 10. keyof -Lookup Types-

### `keyof` basics

- Indexed Type Lookup Query 演算子
- オブジェクトで、アクセスが許容されているプロパティのインデックスを`Literal Type`として算出する。
- `Generic`と一緒に使うと有用

```typescript
//-- indexed type query from Interface
interface IObj {
    keyA: number;
    keyB: number; 
}
type restrictedAsKeysOfInterface = keyof IObj;
let v1: restrictedAsKeysOfInterface = "keyA"; //same >> let v1: "keyA" | "keyB" = "keyA";
// let v2: restrictedAsKeysOfInterface = "key?"; // error

//-- indexed type query from Object(typeof)
const obj = {keyA : 0, keyB : 1};
type restrictedAsKeysFromObject = keyof typeof obj; //same >> let v1: "keyA" | "keyB" = "keyA";
// let v2: restrictedAsKeysOfInterface = "key?"; // error
```

### Generic with `keyof`

 - ランタイム前に、間違ったプロパティアクセスなどが検出できる。

```typescript
function getProperty<T, K extends keyof T>(obj:T, key:K) {
    return obj[key];
}

interface Person {
    name: string;
    age: number;
}

const person: Person = {
    name: "Mark",
    age: 35
}

getProperty(person, "name");
getProperty(person, "age");
//getProperty(person, "unknown"); //unasignable type error
```

## 11. Iterator

### 今までの巡回

#### Array巡回
```javascript
// es3
for (var i = 0; i < array.length; i++)

// es5
array.forEach() //breakができないので、anti pattern

// es6
for (const item of array) // arrayのみ使える
```

#### Object巡回
```javascript
// -- for in
// 推奨されない。理由は以下
//     - hello worldobject巡回時に使う。(arrayには柄はないはず)
//     - indexがnumberじゃなくstringで出る
//     - 配列内のプロパティも意図とは違って巡回できる可能性がある
//     - prototype chainのプロパティを巡回できる可能性もある
//     - 巡回の順序を保証しない
// for ofが推奨される。

// -- objectを巡回するときには、for ofで以下のように使うことも可能
for (const prop of Object.keys(obj))
```

#### example

```javascript
const array = ['first', 'second'];
const obj = {
    name: 'Mark',
    age: 35
};

// use for..of on Array
for (const item of array) {
    console.log(typeof item + ', ' + item);
}

// use for..in on Array
// item type is string. value is numeric string
for (const item in array) {
    console.log(typeof item + ', ' + item);
}


// use for..of on Object => Error
/*
for (const item of obj) {
    console.log(typeof item + ', ' + item);
}
*/

// use for..in on Object
for (const item in obj) {
    console.log(typeof item + ', ' + item);
}

// use for..on using keys on Object
for (const item of Object.keys(obj)) {
    console.log(typeof item + ', ' + item);
}
```

### Symbol.iterator

#### 概要

- プロパティ。巡回関数が具現されているとiterableなタイプになる。
- Array, Map, Set, String, Int32Array, Uint32Arrayなどには、内蔵された具現体があるので、iterableなタイプである。
- ただのobjectはiterableではない。
- Iteratorを使い、IterableなオブジェクトのSymbol.iterator関数を呼び出す。

#### target

- es3 or es5
    - Arrayのみfor..ofを使える
    - オブジェクトに使うとエラー
- es6
    - SYmbol.iteratorを具現すると、どんなオブジェクトにもfor..ofを使える


#### typescriptのIteratorインタフェース

```typescript
// lib.es6.d.ts
interface IteratorResult<T> {
    done: boolean;
    value: T;
}

interface Iterator<T> {
    next(value?: any): IteratorResult<T>;
    return?(value?: any): IteratorResult<T>;
    throw?(e?: any): IteratorResult<T>;
}

interface Iterable<T> {
    [Symbol.iterator](): Iterator<T>;
}

interface IterableIterator<T> extends Iterator<T> {
    [Symbol.iterator](): IterableIterator<T>;
}
```

#### Iterable具現

```typescript
class CustomIterable implements Iterable<string> {
    private _array: Array<string> = ['first', 'second'];

    [Symbol.iterator]() {
        var nextIndex = 0;

        return {
            next: () => {
                return {
                    value: this._array[nextIndex++],
                    done: nextIndex > this._array.length
                }
            }
        }
    }
}

const cIterable = new CustomIterable();

for (const item of cIterable) {
    console.log(item);
}

//[LOG]: first 
//[LOG]: second 
```

## 12. Decorator

- Decoratorを使うためには、config設定必要
- 各Decoratorパターンに対するシグニチャーを見ておくこと

### Setting

```
$ mkdir ts-decorator
$ cd ts-decorator
$ yarn init -y
$ yarn add typescript -D

# setting tsconfig
$ node_modules/.bin/tsc --init
-- tsconfig.jsonのexperimentalDecoratorsをtrueに設定
```

### Class Decorator Example

```typescript
function hello(constructFn: Function) {
    console.log(constructFn);
}

function helloFactory(show: boolean) {
    if (show) {
        return hello;
    } else {
        return null;
    }
}

@helloFactory(false)
class Person {}

@helloFactory(true)
class Person2 {}

//--output
//$ node dist/Test.js 
//[Function: Person2]
```

```typescript
function editable(canBeEdit: boolean) {

    return function(target: any, propName: string, description: PropertyDescriptor) {
        console.log(canBeEdit);
        console.log(target);
        console.log(propName);
        console.log(description);
        description.writable = canBeEdit;
    }
}

class Person {
    constructor() {
        console.log('new Person()');
    }

    @editable(true)
    hello() {
        console.log('hello');
    }
}

const person = new Person();
person.hello();
person.hello = function() {
    console.log('world');
}
person.hello();
```

```typescript
function addHello(constructorFn: Function) {
    constructorFn.prototype.hello = function() {
        console.log('hello');
    }
}

@addHello
class Person {
    constructor() {
        console.log('new Person()');
    }
}

const person = new Person();
(<any>person).hello(); //使い方に短所があるが、ライブラリやフレームワークなどの開発にはいいパターンかも

//--output
//$ node dist/Test.js 
//hello
```

### Method Decorator Example

```typescript
function editable(canBeEdit: boolean) {

    return function(target: any, propName: string, description: PropertyDescriptor) {
        console.log(canBeEdit);
        console.log(target);
        console.log(propName);
        console.log(description);

        //descriptorのwritable属性がランタイム時に変わる
        description.writable = canBeEdit;
    }
}

class Person {
    constructor() {
        console.log('new Person()');
    }

    @editable(true)
    hello() {
        console.log('hello');
    }
}

const person = new Person();
person.hello();
// editableをtrueにしていたので、上書きされる。
// ※ editableをfalseにした場合は上書きされない。
person.hello = function() {
    console.log('world');
}
person.hello();

// --output
// true
// Person {}
// hello
// {
//   value: [Function: hello],
//   writable: true,
//   enumerable: false,
//   configurable: true
// }
// new Person()
// hello
// world 
// ※上書きされた関数の結果。editableがfalseなら、結果は「hello」
```

### Property Decorator

```typescript
function writable(canBeWrite: boolean) {
    return function(target: any, propName: string): any {
        console.log(canBeWrite);
        console.log(target);
        console.log(propName);
        return {
            writable: canBeWrite
        }
    }
}

class Person {
    @writable(false)
    name: string = 'Mark';

    constructor() {
        console.log('new Person()');
    }
}

const person = new Person();
console.log(person.name);

// --output
//TypeError: Cannot assign to read only property 'name' of object '#<Person>'
//※ writable(true)にすると、エラーなく動作する
```

### Parameter Decorator

```typescript
function printInfo(target: any, methodName: string, paramIndex: number) {
    console.log(target);
    console.log(methodName);
    console.log(paramIndex);
}

class Person {
    private _name: string;
    private _age: number;

    constructor(name: string, @printInfo age: number) {
        this._name = name;
        this._age = age;
    }

    hello(@printInfo message: string) {
        console.log(message);
    }
}

//-- output
// Person { hello: [Function] }
// hello
// 0
// [Function: Person]
// undefined
// 1
```

## 13. Type Inference

- タイプを明示しなかった場合のタイプ推論規則
- letは、基本データ・タイプで推論
- constはリタラル・タイプで推論
    - objectタイプを使わないと、プロパティはletと同じように推論
        - const person = {name:'Mark', age: 35}
        - person => {name: string; age: number;}で推論される
- 大体は推論自体は簡単
    - 単純な変数
    - structuring, destructuring
- array, 関数のリターンに対しては推論が難しい場合が多い

### ArrayのType Inference

```typescript
const array1 = []; 
// any[]
const array2 = ['a', 'b', 'c']; 
// string[]
const array3 = ['a', 1, false]; 
// (string|number|boolean)[]  ※ inferenced as union type

class Animal {
    name: string;
}

class Dog extends Animal { 
    dog: string;
}

class Cat extends Animal {
    cat: string;
}

const array4 = [new Dog(), new Cat()];
// (Dog | Cat)[] 
```

### returnのType Inference

```typescript
function hello(message: string | number) {
    if (message === 'world') {
        return 'world';
    } else {
        return 0;
    }
}
// return type inference => ('world' | 0)
// literal union
```

### Union TypeとType Guard

```typescript
interface Person {
    name: string;
    age: number;
}

interface Car {
    brand: string;
    wheel: number;
}

//Type Guard
//tell this is Person when return
function isPerson(arg: any): arg is Person {
    return arg.name !== undefined;
}

function hello(arg: Person | Car) {
    if (isPerson(arg)) {
        //this is Person. not Car. 
        console.log(arg.name);
        // console.log(arg.brand); //error
    } else {
        //this is Not Person. so this is Car.
        // console.log(arg.name); //error
        console.log(arg.brand);
    }
}
```