| ページ一覧 | ブログ | twitter |  書式 | 書式(表) |

MyMemoWiki

TypeScript

提供: MyMemoWiki
2020年7月20日 (月) 14:52時点におけるPiroto (トーク | 投稿記録)による版 (→‎Promise)
ナビゲーションに移動 検索に移動

目次

TypeScript

Node.js | Angular | Google Cloud Platform |

準備

install

> npm install -g typescirpt

init

tsconfig.json の生成
> tsc --init

Visual Studio Code タスク

F1 - task run - build tsconfig.json

Playground

1217 ts playground.jpg

変数・型

変数・定数

変数宣言 ブロックスコープ 同じスコープでの重複宣言 備考
var 認識しない 認める
let 認識する 認めない
const 認識する 認めない 再代入不可

説明
any 任意、型がわからない場合。型チェックされずなんでもできてしまうため、できれば避ける。
unknown any同様任意の型をあらわすが、前もって型がわからない場合、anyではなくこちらの利用を推奨
number 数値
bigint まるめ誤差なく大きな整数を扱うことができる(プラットフォームでサポートされているか確認が必要) let bi = 123n
string 文字列
boolean 真偽
symbol シンボル
object オブジェクト

unknown型

  • 値の比較、否定、typeof,instanceofが使用可能。以下のように利用する
let foo: unknown;
foo = 1;
// let bar = foo + 2; // unknown のためエラー
if (typeof foo == 'number') {
    let bar = foo + 2;  // OK
    console.log(bar);
}

undefinedも許可

  • string もしくは undefined
  • aa 初期化時に必須だが、bbは指定しなくてもエラーとならない。
interface hoge {
    aa : string,
    bb? : string
}
var h: hoge = {aa:'aaa'};

文字列リテラル

`バッククオートで囲む(変数展開)

  • 改行もそのまま
  • ${...} ...に変数、式を埋め込み
class Program {
    static main() {
        let name: string = "Hiroto Yagi";
        let age = 46;   // 型推論
        // age = "Hello"; // 数値型に文字列代入エラー
        let msg = null; // 初期値未指定、null指定など、any型と類推され型チェックは働かない
        msg = 100;      // noImplicitAny:true とすると、暗黙的 anyは許容しない
        msg = "Hello";
    
        // テンプレート文字列
        console.log(`
        ${msg} 
        my name is ${name} 
        age ${age}
        ${new Date()}`);
    }
}

Program.main();
結果
> node ./src/app.js

       Hello
       my name is Hiroto Yagi
       age 46
       toda Tue Nov 07 2017 18:59:23 GMT+0900 (東京 (標準時))

型アサーション

キャスト

function show(message: string) {
    console.log(`${message}`);
}
class TypeAssartion {
    static main() {
        // show(10);    // ERROR
        show(<any>10);  // OK
        show(10 as any); // as もOK
    }
}

コレクション/インターフェース

  • 配列
  • 連想配列
  • インデクスシグネチャはinterfaceとして事前定義可
interface StringMap {
    [index:string]:string;
}
class Collection {
    static main() {
        // 配列
        let languages: string[] = ['java','C#','python'];
        console.log(languages);

        // 連想配列
        let fwMap: {[key:string]:string} = {
            'java':'Spring',
            'C#':'Asp.net',
            'python':'Django'
        };
        console.log(fwMap);

        // インデクスシグネチャはinterfaceとして事前定義可
        let fwMap2: StringMap = {
            'java':'Spring',
            'C#':'Asp.net',
            'python':'Django'
        };
        console.log(fwMap2);
    }
}

列挙

enum Animal {
    DOG,
    CAT,
    MOUSE,
    ELEPHANT
}
enum Fruit {
    APPLE = "A",
    ORANGE = "O",
    PAINAPPLE = "P"
}
class EnumSample {
    static main() {
        let mypet: Animal = Animal.DOG;
        // デフォルトで0からの数値が割り当てられる
        console.log(mypet);
        console.log(Animal[mypet]);
        
        let myfavarit: Fruit = Fruit.PAINAPPLE;
        // 列挙子に値を割り当てることも可能
        console.log(myfavarit);
    }
}
結果
0
DOG
P

タプル

  • 配列のサブタイプ
  • 固定長の配列を型づけ
let tpl: [string, number, boolean] = ['Yagi', 46, true];
console.log(`name:${tpl[0]} age:${tpl[1]} marriged:${tpl[2]}`);

合併型(共用型)と交差型

  • 型の合併と交差を表現する特別な演算子が用意されている。
  • 合併 | と交差 &
  • 型エイリアスの例
type Sales = { dept: string, product: string};
type Engineer = { dept: string, project: string };

type SalesOrEngineewOrBoth = Sales | Engineer;  // 合併
type DeptPerson = Sales & Engineer; // 交差
  • 合併型(共用型)使用例
let data: string | boolean;
data = 'foo';// OK
data = true; // OK
// data = 1; // NG

let datas: (boolean | number)[] = [true, false, 1, 0];

型エイリアス

  • 型の別名を定義できる
  • ブロックスコープ
type Age = number;
let myage: Age = 48;
  • 主にタプル、共用型に別名を付与。インターフェースでできる場合に使用しないほうがよい
type ProfileTuple = [string, number, boolean];
let me = ['yagi', 46, true];        
console.log(`name:${me[0]} age:${me[1]} marriged:${me[2]}`);

文字列リテラル

type Season = 'spring' | 'summer' | 'autumn' | 'winter';
class StringLiteralType {
    printSeason(s: Season) {
        console.log(s);
    }
    static main() {
        let me = new StringLiteralType();
        me.printSeason('summer');  // OK
        // me.printSeason('hoge'); // NG
    }
}

keyof演算子

関数

  • function命令
  • 関数リテラル
  • アロー関数

関数、関数リテラル、アロー関数

  • 関数リテラル(型推論)
  • 関数リテラル(型明示)
  • アロー関数(ラムダ式)
function squire(x: number, y: number) :number {
    return x * y;
}
class FunctionSample{
    static main() {
        // 関数の使用
        console.log(squire(8,8));

        // 関数リテラル(型推論)
        let m3 = function(x :number, y: number, z: number): number {
            return x * y * z;
        } ;
        console.log(m3(8,8,8));

        // 関数リテラル(型明示)
        let m2: (x: number, y: number) => number = function(x: number, y: number) {
            return x * y;
        };
        console.log(m2(9,9));
        
        // アロー関数(ラムダ式)
        // this は宣言された場所によって変わる
        let m22 = (x: number, y: number): number =>  x * y;
        console.log(m22(5,5));
    }
}

引数の省略、デフォルト引数、可変長引数

  • TypeScriptでは宣言された引数はすべて必須
    • 引数を省略したい場合、仮引数の名前の後ろに ? を付与
  • 関数のデフォルト値を設定するには、仮引数の後に=デフォルト値
    • デフォルト値には、式も利用できる
    • 明示的に undefined を渡した場合もデフォルト値が利用される
  • 可変長引数
    • 仮引数の前に、... とすることで可変長引数となる
    • 関数に1つ、最後のパラメータのみ
class FunctionApplySample {
    static main(){
        // TypeScript]は宣言された引数はすべて必須
        // 引数を省略したい場合、仮引数の名前の後ろに ? を付与
        let greet = function(message?: string){
            message = (message === undefined)?"hello":message;
            console.log(message);
        }
        greet();
        greet("good evening");
    
        // 関数のデフォルト値を設定するには、仮引数の後に=デフォルト値
        // デフォルト値には、式も利用できる
        // 明示的に undefined を渡した場合もデフォルト値が利用される
        let greet2 = function(message: string=`hello ${new Date()}`) {
            console.log(message);
        }
        greet2();
        greet2("good night");
        greet2(undefined);

        // 可変長引数
        // 仮引数の前に、... とすることで可変長引数となる
        let sum = function(...nums: number[]):number {
            let ttl: number = 0;
            nums.forEach((value,index,array) => ttl += value);
            return ttl;
        }
        console.log(sum(1,2,3,4,5));
    }
}

thisの型を明示

  • thisが望むものであることを強制
  • thisが関数シグネチャの一部として用いられる場合、予約語
function printMonth(this:Date) {
    console.log(`${this.getMonth()+1}`);
}
printMonth.call(new Date());
printMonth.call(1); // チェックでエラー表示される

関数のオーバーロード1

  1. シグネチャのみの関数を用意
  2. 具体的な実装を直後に定義
  3. 実装の中で、typeof/instanceof を用いて型を判定し処理を記述
function showMessage(value: number): void;
function showMessage(value: boolean): void;
function showMessage(value: any): void {
    let message: string | undefined;
    if (typeof value === 'number') {
        message = `${value} is number.`;
    }
    if (typeof value === 'boolean') {
        message = `${value} is boolean`;
    }
    console.log(message);
}
 
class FunctionApplySample2 {
    static main(){
        console.log("*** 関数応用2 ***");
        // 1.シグネチャのみの関数を用意
        // 2.具体的な実装を直後に定義
        // 3.実装の中で、typeof/instanceof を用いて型を判定し処理を記述
        showMessage(123);
        showMessage(false);
        showMessage("Hello" as any);
    }
}

ユーザー定義型ガード

  • typeaof/instanceof は組み込みの型を絞り込むための機能
  • is 演算子を使用して独自に定義可能
 function hoge(val:string | number) {
     if (isString(val)) {
         console.log(val.toUpperCase());
     } else {
         console.log(val);
     }
 }
 // boolean を返す関数の場合、型の絞り込みを行うことができる
 function isString(val: unknown): val is string { 
     return typeof val === 'string';
 }
 hoge(1);
 hoge('a');
*結果
1
A

関数のオーバーロード2

  • オーバーロードされた関数2つのシグネチャを宣言
  • 実装のシグネチャは2つの宣言を結合したもの
  • 呼び出すときに結合されたシグネチャは見えない
// オーバーロードされた関数2つのシグネチャを宣言
type ShowMessage = {
    (value: number):void,
    (value: boolean):void
}    

// 実装のシグネチャは2つの宣言を結合したもの 
let showMessage: ShowMessage = (
    value : number | boolean
) => {
    let message: string | undefined;
    if (typeof value === 'number') {
        message = `${value} is number.`;
    }
    if (typeof value === 'boolean') {
        message = `${value} is boolean`;
    }
    console.log(message);
}
  • 実行
// 呼び出すときに結合されたシグネチャは見えない
showMessage(12);
showMessage(true);

*結果

12 is number.
true is boolean

ジェネレーターとイテレーター

  • ジェネレーター関数(function* )により生成
  • 生成されたオブジェクトは、反復可能なイテレーター
  • 利用者が要求したときだけ値を生成する
function* increament(start: number) {
    while (true) {
       start++;
       yield start;
    }
}

let i = increament(10);
console.log(i.next().value);
console.log(i.next().value);
console.log(i.next().value); 
  • 結果
11
12
13

オブジェクト指向

クラス定義

class Person {
    // メンバー
    name: string; // デフォルト public
    private _age: number; 

    // 静的メンバー
    public static CHEAT_AGES: number = 2;
    
    // コンストラクター
    constructor(name: string, age: number){
        this.name = name;
        this._age = age;
    }

    // 以下のコンストラクターでメンバーの宣言と代入を省略できる
    // アクセス修飾子必須
    // constructor(public name: string, private _age: number) {
    // }

    // アクセッサー
    get age(): number {
        return this._age - Person.CHEAT_AGES;
    }
    set age(value: number) {
        this._age = value;
    }

    // メソッド
    printProfile(){
        console.log(`name=${this.name},age=${this.age}`);
    }
}
console.log(Person.CHEAT_AGES);
let me = new Person('Yagi', 46);
//console.log(me.age); // NG
me.printProfile();
me.age = 50;
me.printProfile();
結果
2
name=Yagi,age=44
name=Yagi,age=48

コンストラクタ

  • コンストラクター
 name: string; // デフォルト public
 private _age: number; 
 constructor(name: string, age: number){
   this.name = name;
    this._age = age;
 }

  • 以下のコンストラクターでメンバーの宣言と代入を省略できる
    • アクセス修飾子必須
constructor(public name: string, private _age: number) 
}

アクセス修飾子

修飾子 説明
public どこからでもアクセス可能。デフォルト。
protected このクラスと、サブプラスのインスタンスからアクセス可能。
private このクラスのインスタンスのみからアクセス可能

名前空間

  • namespace ブロックで定義
  • exportで外側からアクセス可能なことを明示
  • 完全修飾名で呼び出す
namespace NameA {
    export class Foo {
        name() {
            console.log("NameA.Foo");
        }
    }
    export function bar(){
        console.log("NameA.bar()");
    }
}
let foo = new NameA.Foo();
foo.name();
NameA.bar();

名前空間のネスト

namespace NameB {
    export namespace NameC {
        export function bar(){
            console.log("NameB.NameC.bar()");
        }
    }
}
NameB.NameC.bar(); 

継承

class Guiter {
    constructor(public maker: string, public type: string){}
    print() {
        console.log(`${this.type} by ${this.maker}`);
    }
}
class Lespaul extends Guiter {
    constructor(){
        super("Gibson","Lespaul");
    }
}
(new Lespaul()).print();

抽象クラス

  • 直接インスタンス化しようとするとエラー
// 抽象クラス
abstract class Viecle {
    // 抽象メソッド
    abstract getEngineDisplacement(): number;
    printDisplacement() {
        console.log(`${this.getEngineDisplacement()}`);
    }
}
class Premacy extends Viecle {
    // 抽象メソッドのオーバーライド
    getEngineDisplacement(): number {
        return 2000;
    }
    // メソッドオーバーライド
    printDisplacement() {
        console.log("******");
        super.printDisplacement();
        console.log("******");
    }
}
(new Premacy()).printDisplacement();

インターフェース

  • 型エイリアスと同様、インターフェースは型に名前をつけるための方法
    • ほぼ同じだが、細かな差異もある。
  • インラインで型を定義する必要がなくなる。
interface Color {
    // メソッドはすべて抽象メソッド
    // abstract 指定は不要
    getRGB(): string;
}
class Red implements Color {
    getRGB(): string {
        return "#FF0000";
    }
}
console.log((new Red()).getRGB());

型とインターフェースの違い

  • 型は右辺に任意の値をおけるがインターフェースはそうではない。
  • 以下のようなコードはインターフェースに書き換えられない
type A = number;
type B = A | string;
  • インターフェースを拡張する場合、拡張元が拡張先に適用できるかチェックする。型エイリアスの場合拡張元と先を結合するために最善を尽くす
  • 同じスコープに同じ名前のインターフェースが複数存在する場合、自動的にマージされる。

super

  • 親クラスのメソッドを呼び出すことができる。super.hoge();
  • コンストラクターとして呼び出す。super();

構造的部分型(Structual subtyping)

class Hoge {
    constructor(public name: string, public age: number){}
}
class Spam {
    constructor(public name: string, public age: number){}
}
// 継承による明示的な互換性(Nominal subtyping)がなくても構造が一致していれば代入可能
let h: Hoge = new Spam("spam",10);
console.log(h);

型注釈としてのインターフェース

  • インターフェースを型注釈として利用できる
interface Shape {
    // プロパティシグネチャ
    name: string;
    // メソッドシグネチャ
    draw():void;
}
let c : Shape = {
    name:'circle',
    draw() {
        console.log(`${this.name}:○`);
    } 
};
c.draw();

オブジェクトリテラル

  • インターフェースを定義するまでもないが、型情報は明示しておきたい場合
let t : { name: string, sides: number} = {name: 'triangle', sides: 3};

型としてのthis

  • 自分自身を返すことで、メソッドチェーンのような記述が可能となる
  • 派生クラスなど、呼び出し元のクラスに応じ型が変化する
class StringBuilder {
    constructor(protected _value: string){
    }
    toString(): string {
        return this._value;
    }
    wrap(value: string) :string{
        return `${value}`;
    }
    // 自分自身を返すことで、メソッドチェーンのような記述が可能となる
    // 派生クラスなど、呼び出し元のクラスに応じ型が変化する
    append(value: string): this {
        this._value = this._value + this.wrap(value);
        return this;
    }
}
let buf = new StringBuilder('a');
buf.append('b').append('c').append('d');
console.log(buf.toString());

class CsvBuilder extends StringBuilder {
    constructor(protected _value: string){
        super(_value);
    }
    wrap(value: string) {
        return `,${value}`;
    }
}
let csv = new CsvBuilder('a');
csv.append('b').append('c').append('d');
console.log(csv.toString());
出力
abcd
a,b,c,d

ジェネリック

let numary: Array<number> = [1,2,3,5,8];

ジェネリック型の定義

  • StringBuilder型は、上記で定義参照
  • 型引数に制約を付与する場合、extends を使用する。
  • 使用しなければ、あらゆる型を渡すことができる
class XmlBuilder extends StringBuilder {
    constructor(protected _value: string){
        super(_value);
    }
    wrap(value: string) {
        return `<value>${value}</value>`;
    }
    toString(): string {
        return `<values>${this._value}</values>`;
    }
}

// 型引数に制約を付与する場合、extends を使用する。
// 使用しなければ、あらゆる型を渡すことができる
class Report<T extends StringBuilder> {
    builder: T;
    getBuilder(): T {
        return this.builder;
    }
    addLine(line: string) {
        this.getBuilder().append(line);
    }
    report() {
        console.log(this.getBuilder().toString());
    }
}
let rep = new Report<XmlBuilder>();
// ジェネリック型に具体的なインスタンスを紐づけ
rep.builder = new XmlBuilder();
rep.addLine("this is lesson.");
rep.addLine("of TypeScript.")
rep.report();
出力例
<values><value>this is lesson.</value><value>of TypeScript.</value></values>

ジェネリックメソッド

function reverse<T>(data: T[]): T[] {
    let ary: T[] = new Array();

    let j=0;
    for(let i=data.length-1; i>=0; i--) {
        ary[j++] = data[i];
    }
    return ary;
}
console.log(reverse<number>([1,2,3,4]));
console.log(reverse<string>(["a","b","c","d"]));
出力例
[ 4, 3, 2, 1 ]
[ 'd', 'c', 'b', 'a' ]

サンプル

テンプレート:Ref app.ts

非同期プログラミングと並行・並列処理

ObservableとPromise

比較

  • Observable は宣言型で、購読するまで処理が開始されない。Promise は作成時に直ちに実行される。
    • 結果が必要なときにいつでも実行できるレシピを定義するために、Observable が役立つ。
  • Observable は多くの値を提供します。Promise は1つです。
    • 時間の経過とともに複数の値を取得するのには Observable が有効
  • Observable は、連鎖とサブスクリプションを区別します。Promise は .then() 句しかありません。
    • Observableは、作業を実行させることなく、システムの他の部分で使用される複雑な変換レシピを作成するのに便利
  • Observable の subscribe() はエラーを処理します。Promise は子の Promise にエラーをプッシュします。
    • Observable は集中管理され、予測可能なエラー処理に役立

Promise

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

  • 非同期の作業を抽象化して、それらを組み立てたり順番に並べる方法
import { isNumber } from "util";

function getPromise(waittime:string): Promise<string> {
    return new Promise<string>((resolve, reject) => {
        if (isNaN(Number(waittime)) ) {
            reject("error");
        } else {
            const wt = parseInt(waittime);
            setTimeout(() => {
                resolve(`WAIT ${wt}`);
            }, wt);
        }
    });
}

function usePromise() {
    getPromise("2000")
        .then((v) => {
            console.log(v);
        })
        .catch((e) => {
            console.log(e);
        });
    getPromise("aaaaa")
        .then((v) => {
            console.log(v);
        })
        .catch((e) => {
            console.log(e);
        });
}

usePromise();
  • 結果

非同期で呼び出すため、呼び出し結果と出力順序が入れ替わる

error
WAIT 2000

async , await

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

  • Promiseはとてもポピュラーなパターンなので、JavaScript(TypeScript)では独自の構文が用意されている。
  • await は .then に対するシンタックスシュガーと言える
  • await を使ってPromiseを待機する場合、async ブロックの中で行う
import { isNumber } from "util";

function getPromise(waittime:string): Promise<string> {
    return new Promise<string>((resolve, reject) => {
        if (isNaN(Number(waittime)) ) {
            reject("error");
        } else {
            const wt = parseInt(waittime);
            setTimeout(() => {
                resolve(`WAIT ${wt}`);
            }, wt);
        }
    });
}

async function usePromise() {
    try {
        let result = await getPromise("2000")
        console.log(result);
        let result2 = await getPromise("aaaa")
        console.log(result2);
    } catch(e) {
        console.log(e);
    }
}

usePromise();
  • 結果

await で同期されるため、呼び出し順に出力される

WAIT 2000
error

Node.jsを使う

Node.js

環境構築

  • 作業ディレクトリで実行
$ npm init -y
Wrote to /Users/hirotoyagi/Workspaces/Node/sample/package.json:

{
  "name": "sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
  • package.jsonを編集
 "scripts": {
   "build": "tsc",
   "watch": "tsc --watch"
 },
  • TypeScriptのインストールと確認
$ npm install --save-dev typescript
$ node_modules/.bin/tsc --version
Version 3.9.7
  • 初期化
$ node_modules/.bin/tsc --init
message TS6071: Successfully created a tsconfig.json file.
  • Node.jsの型定義をインストール
$ npm install --save @types/node
  • tsをTypeScriptで記述しjsにコンパイル

app.ts

console.log("test");
$ npm run build
  • app.js が生成されるので、Nodeで実行

app.js

"use strict";
console.log("test");
$ node app

Tips

TypeDoc

インストール

> npm install typedoc -g

作成

> typedoc --out ./docs/ ./src/ --module commonjs