M.Hiroi's Home Page

JavaScript Programming

お気楽 TypeScript 超入門

[ Home | Light | JavaScript | TypeScript ]

TypeScript の基礎知識

●Union Types

リスト : Type Alias の使用例

// 以下のプログラムは --strictNullChecks を指定するとコンパイルエラーになる

// 簡単な連結リスト 
type LinkedList<T> = { item: T; next: LinkedList<T> };

const ls0: LinkedList<number> = { item: 0, next: null };
const ls1 = { item: 1, next: ls0 };
const ls2 = { item: 2, next: ls1 };

console.log(ls2.item);
console.log(ls2.next.item);
console.log(ls2.next.next.item);
console.log(ls2.next.next.next);
console.log(ls2);
2
1
0
null
{ item: 2, next: { item: 1, next: { item: 0, next: null } } }
リスト : Union Types の使用例

// 以下のプログラムは --strictNullChecks を指定してもコンパイルできる

// 空リスト (本来ならばシングルトンにする)
class Nil {
    get first(): never { throw new Error("empty list"); };
    get rest(): never { throw new Error("empty list"); }
}

// コンスセル
class Cons<T> {
    constructor(private _first: T, private _rest: List<T>) {}
    get first(): T { return this._first; }
    get rest(): List<T> { return this._rest; }
}

// 連結リスト
type List<T> = Nil | Cons<T>;

// 終端
const nil = new Nil();

// 生成関数
function cons<T>(x: T, xs: List<T>): List<T> {
    return new Cons<T>(x, xs);
}

const xs0 = cons<number>(0, nil);
const xs1 = cons(1, xs0);
const xs2 = cons(2, xs1);

console.log(xs2.first);
console.log(xs2.rest.first);
console.log(xs2.rest.rest.first);
console.log(xs2);
2
1
0
Cons {
  _first: 2,
  _rest: Cons { _first: 1, _rest: Cons { _first: 0, _rest: Nil {} } } }
リスト : 抽象クラスを使った連結リストの実装

abstract class List<T> {
    abstract get first(): T;
    abstract get rest(): List<T>;
}

// 空リスト (本来ならばシングルトンにする)
class Nil extends List<never> {
    get first(): never { throw new Error("empty list"); };
    get rest(): never { throw new Error("empty list"); }
}

// コンスセル
class Cons<T> extends List<T> {
    constructor(private _first: T, private _rest: List<T>) { super(); }
    get first(): T { return this._first; }
    get rest(): List<T> { return this._rest; }
}

// 終端
const nil = new Nil();

// 生成関数
function cons<T>(x: T, xs: List<T>): List<T> {
    return new Cons<T>(x, xs);
}

const xs0 = cons<number>(0, nil);
const xs1 = cons(1, xs0);
const xs2 = cons(2, xs1);

console.log(xs2.first);
console.log(xs2.rest.first);
console.log(xs2.rest.rest.first);
console.log(xs2);
リスト : never 型

class Foo { foo: 1; }
class Bar extends Foo { bar: 2 }
class Baz extends Bar { baz: 3 }

function foo<T extend Bar>(): void { console.log("oops"); }

// foo<Foo>();    コンパイルエラー
foo<Bar>();
foo<Baz>();
// foo<number>(); コンパイルエラー
foo<any>();
foo<never>();

●Type Assertions

リスト : Type Assertions の使用例

class FooX {
    constructor(private _x: number) { }
    get x(): number { return this._x; }
}

class BarX extends FooX {
    constructor(x: number, private _y: number) {
        super(x);
    }
    get y(): number { return this._y; }
}

class BazX extends FooX {
    constructor(x: number, private _z: string) {
        super(x);
    }
    get z(): string { return this._z; }
}

// アップキャスト
const objFoo0: FooX = new BarX(123, 456);
const objFoo1: FooX = new BazX(789, "hello, world");

// console.log(objFoo0.y); コンパイルエラー
// console.log(objFoo1.z); コンパイルエラー

// ダウンキャスト (正常に実行)
console.log((<BarX>objFoo0).y);  // 456
console.log((<BazX>objFoo1).z);  // hello, world

// コンパイルできるが実行結果は undefined
// objFoo0 は BazX のオブジェクトではないから
console.log((<BazX>objFoo0).z);  // undefined
456
hello, world
undefined

●Type Guards

リスト : Type Guards の使用例

function isFoo(x: {foo: string}): x is {foo: string} {
    if (!x) return false;
    return typeof x.foo === 'string';
}

function typeSample(obj: number | string | Array<number> | {foo: string} | undefined): void {
    if (typeof obj === 'number') {
        // obj は数値
        console.log(obj + 123);
    } else if (typeof obj === 'string') {
        // obj は文字列
        console.log("hello, " + obj);
    } else if (obj instanceof Array) {
        // obj は配列
        console.log(obj.reduce((a, x) => a + x));
    } else if (isFoo(obj)) {
        // obj は {foo: string} と互換性あり
        console.log(`foo: ${obj.foo}`);
    } else {
        // 残りは undefined
        console.log("oops!!");
    }
}

typeSample(1000);
typeSample("bar");
typeSample([1,2,3,4,5]);
typeSample({foo: 'oops!!'});
typeSample(undefined);
1123
hello, bar
15
foo: oops!!
oops!!

●Intersection Types

リスト : Intersection Types の使用例

type FooI = { foo: number };
type BarI = { bar: string };
type BazI = FooI & BarI;

const objX: FooI = { foo: 123 };
const objY: BarI = { bar: "hello, world" };

let objZ: BazI;
// objZ = objX;  コンパイルエラー
// objZ = objY;  コンパイルエラー
objZ = { foo: 456, bar: "oops!!" };  // OK
console.log(objX);
console.log(objY);
console.log(objZ);
{ foo: 123 }
{ bar: 'hello, world' }
{ foo: 456, bar: 'oops!!' }

●String Literal Types

リスト : String Literal Types の使用例

// 果物
type Fruit = "Apple" | "Grape" | "Orange" | "Banana";

// 果物の価格
class FruitPrice {
    constructor(private _kind: Fruit, private _price: number) { }
    get kind(): Fruit { return this._kind; }
    get price(): number { return this._price; }
}

// 価格表
const priceTable: FruitPrice[] = [
    new FruitPrice("Apple", 100),
    new FruitPrice("Grape", 150),
    new FruitPrice("Orange", 80)
];

// 価格を求める
function getPrice(x: Fruit, xs: FruitPrice[]): number {
    for (let fruit of xs) {
        if (fruit.kind == x) return fruit.price;
    }
    return 0;
}

console.log(getPrice("Apple", priceTable));
console.log(getPrice("Grape", priceTable));
console.log(getPrice("Orange", priceTable));
console.log(getPrice("Banana", priceTable));

// 簡単な Option 型
type Option<T> = "None" | { some: T };

function isNone<T>(x: Option<T>): x is "None" { return typeof x === 'string'; }
function isSome<T>(x: Option<T>): x is {some: T} { return typeof x !== 'string'; }

// Option から値を求める
function getValue<T>(x: Option<T>, y?: T) {
    if (isSome<T>(x)) {
        return x.some;
    } else if (y !== undefined) {
        return y;
    }
    throw new Error("getValue: value is none");
}

// 探索
function findIf<T>(pred: (x: T) => boolean, xs: T[]): Option<T> {
    for (let y of xs) {
        if (pred(y)) return { some: y };
    }
    return "None";
}

let result = findIf(x => x.kind == 'Apple', priceTable);
if (isSome(result))
    console.log(getValue(result).price);
else
    console.log("sold out");

result = findIf(x => x.kind == 'Banana', priceTable);
if (isSome(result))
    console.log(getValue(result).price);
else
    console.log("sold out");
100
150
80
0
100
sold out

●関数の多重定義

リスト : 関数の多重定義

// 関数宣言
function foo(): void;
function foo(x: number): void;
function foo(x: string): void;

// 実装
function foo(x?: any): void {
    if (x === undefined) {
        console.log("hello, foo");
    } else if (typeof x === 'number') {
        while (x-- > 0) console.log("hello, foo!");
    } else {
        console.log(`hello, ${x}!!`);
    }
}

foo();
foo(5);
foo("oops");
hello, foo
hello, foo!
hello, foo!
hello, foo!
hello, foo!
hello, foo!
hello, oops!!

●モジュール

リスト : モジュール foo.ts

class Foo {
    static foo(): string { return "foo"; }
    static bar(): string { return "bar"; }
    static baz(): string { return "baz"; }
}

export = Foo;
リスト : モジュールの使用例 (test.ts)

import foo = require("./foo");

console.log(foo.foo());
console.log(foo.bar());
console.log(foo.baz());
C>tsc -t es2015 -m commonjs test.ts

C>node test.js
foo
bar
baz

Copyright (C) 2017 Makoto Hiroi
All rights reserved.

[ Home | Light | JavaScript | TypeScript ]