Typescript書き方の速成まとめ



Typescript書き方の速成まとめ

目次

1. 概論・セットアップ

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

Typescript setup & compile

1
2
3
4
5
6
7
8
9
10
11
# 初期化
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

1
2
3
4
5
6
7
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は、どっちか一つだけ使う。
1
2
3
4
5
6
7
8
9
10
"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バージョンに依存したライブラリを使用
    • 指定すると、指定したライブラリのみを使う
1
2
3
4
5
6
7
8
9
10
"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

1
2
3
4
5
6
7
8
"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。(細かいモジュール連携に必要)
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
30
"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と紛らわしいので、非推奨)
1
2
3
4
5
// 主に曖昧なタイプから絞るときに使う。
// 曖昧なタイプである時点で、ベストプラクティスではないので、参考までに見ることs

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

6. Type Alias

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

1
2
3
4
5
6
7
//alias to union type
let varA: string | number = 0;
varA = "A";

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

7. Interface

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

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

interface basics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//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を指定可能

1
2
3
4
5
6
7
8
9
10
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
1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

1
2
3
4
5
6
7
8
intrface Person {
name: string;
age: number;
}

interface SalaryMan extends Person {
job: string;
}

function with interface

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

1
2
3
4
5
6
7
8
9
10
11
12
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だという意見もあり

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
30
31
32
33
34
35
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

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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と一緒に使うと有用
1
2
3
4
5
6
7
8
9
10
11
12
13
//-- 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

  • ランタイム前に、間違ったプロパティアクセスなどが検出できる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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巡回

1
2
3
4
5
6
7
8
// es3
for (var i = 0; i < array.length; i++)

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

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

Object巡回

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

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

example

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
30
31
32
33
34
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インタフェース

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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具現

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

1
2
3
4
5
6
7
8
$ 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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]
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
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();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

1
2
3
4
5
6
7
8
9
function hello(message: string | number) {
if (message === 'world') {
return 'world';
} else {
return 0;
}
}
// return type inference => ('world' | 0)
// literal union

Union TypeとType Guard

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