📄

2020/7/26 Typescript書き方の速成まとめ

2024/05/24に公開

この記事は2020/7/26に書きました。

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

1. 概論・セットアップ

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

Typescript setup & compile

# 初期化
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は、どっちか一つだけ使う。
"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バージョンに依存したライブラリを使用
    • 指定すると、指定したライブラリのみを使う
"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

"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がes6じゃないとcommonjsがデフォルト
  • moduleResolution
    • tsソースで使用されるモジュールを指定
    • CommonJSの場合Node、それ以外はだいたいClassic
      -pathsと baseUrl
    • 指定すると、該当パスのモジュールをロード
    • 普通使わなくてOK。(細かいモジュール連携に必要)
  • rootDirs
    • ロードするモジュールのルートパス配列
    • 普通使わなくてOK。(細かいモジュール連携に必要)
"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と紛らわしいので、非推奨)
// 主に曖昧なタイプから絞るときに使う。
// 曖昧なタイプである時点で、ベストプラクティスではないので、参考までに見ることs

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

6. Type Alias

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

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

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

7. Interface

実装を持たず、ステート(プロパティ)とビヘイビアの形式の定義のみを記述した抽象データタイプ。

  • インタフェースの抽象というのは、インスタンス化観点から抽象的であり具体を持たいないため、単体では実態を持てないという意味を内包している。
  • 継承するクラスたちに対してのプロトコル(約束)の役目を果たす。

interface basics

//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を指定可能

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
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

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

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

interface SalaryMan extends Person {
    job: string;
}

function with interface

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

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

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だという意見もあり

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

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

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と一緒に使うと有用
//-- 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

  • ランタイム前に、間違ったプロパティアクセスなどが検出できる。
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巡回

// es3
for (var i = 0; i < array.length; i++)

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

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

Object巡回

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

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

example

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インタフェース

// 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具現

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

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]
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();
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

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

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

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

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

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

Union TypeとType Guard

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);
    }
}

Discussion