LoginSignup
18
10

More than 3 years have passed since last update.

Typescript書き方の速成まとめ

Posted at

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

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

2. 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);
    }
}
18
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
10