TypeScript 入門日記 06

前回

blog.takunology.jp

今回はこれをやっていくぅっ!(きま○れクック)

docs.microsoft.com

ジェネリック

基本的にはどんな型でも受け付けるが、型チェック(指定した型以外は受け付けない)のためによく利用する。これはたぶん C# と同じかも?

型名は任意だが T とするのが一般的。

function getArray<T>(items : T[]) : T[] {
    return new Array<T>().concat(items);
}

// number 型を宣言して利用できる(型チェックによるエラーが発生)
let numberArray = getArray<number>([5, 10, 15, 20]);
numberArray.push(25);                      
numberArray.push('This is not a number');

// string 型を宣言して利用できる(型チェックによるエラーが発生)
let stringArray = getArray<string>(['Cats', 'Dogs', 'Birds']);
stringArray.push('Rabbits');              
stringArray.push(30);                      

ジェネリックは複数宣言できる。

function identity<T, U> (value: T, message: U) : T {
    console.log(message);
    return value
}

let returnNumber = identity<number, string>(100, 'Hello!');
let returnString = identity<string, string>('100', 'Hola!');
let returnBoolean = identity<boolean, string>(true, 'Bonjour!');

returnNumber = returnNumber * 100;   // OK
returnString = returnString * 100;   // エラー
returnBoolean = returnBoolean * 100; // エラー

ジェネリックのプロパティには具体的な値を入れることはできない。代入するならば必ず何かしらの型を宣言する必要がある。

function identity<T, U> (value: T, message: U) : T {
    let result: T = value + value;    // ジェネリックへの代入は不可
    console.log(message);
    return result
}

ジェネリック制約

どんな型も受け付けるが、特定の型は弾きたいときに制限できる。ValidTypes には stringnumber しか許可していないので、第一引数にこれら以外の型を入れるとエラーになる。C# だと where で制約できるので、間違えそう。

type ValidTypes = string | number;

function identity<T extends ValidTypes, U> (value: T, message: U) : T {
    let result: T = value + value;    // ジェネリックへは直接代入できない
    console.log(message);
    return result
}

let returnNumber = identity<number, string>(100, 'Hello!');      // OK
let returnString = identity<string, string>('100', 'Hola!');     // OK
let returnBoolean = identity<boolean, string>(true, 'Bonjour!'); // エラー

オブジェクトのプロパティ制約もできる。ちょっと混乱しそうだけど、第一引数にオブジェクトを代入した場合、ジェネリック K はそのオブジェクトのもつキーの型に制約される。つまり、そのオブジェクトのキーが string であれば、第二引数も string になり、number であれば第二引数が number になる。

第一引数に依存するような感じかな。

function getPets<T, K extends keyof T>(pet: T, key: K) {
  return pet[key];
}

let pets1 = { cats: 4, dogs: 3, parrots: 1, fish: 6 };
let pets2 = { 1: "cats", 2: "dogs", 3: "parrots", 4: "fish"}

console.log(getPets(pets1, "fish"));  // 6
console.log(getPets(pets2, "3"));     // エラー

型ガード

最初のほうにやった typeof キーワードを使えば型ガードができる。

type ValidTypes = string | number;
function identity<T extends ValidTypes, U> (value: T, message: U) {
    let result: ValidTypes = '';
    let typeValue: string = typeof value;

    if (typeof value === 'number') {           // number 型判定
        result = value + value;               
    } else if (typeof value === 'string') {    // string 型判定
        result = value + value;                
    }

    console.log(`The message is ${message} and the function returns a ${typeValue} value of ${result}`);

    return result
}

let numberValue = identity<number, string>(100, 'Hello');
let stringValue = identity<string, string>('100', 'Hello');

console.log(numberValue);       // 200
console.log(stringValue);       // 100100

ただし、プリミティブ型は typeof で使用が可能。クラスの場合は instanceof を使う。

ジェネリックインターフェイスと関数

ジェネリックを複数持つインターフェイスをつくり、そのプロパティを宣言。プロパティのジェネリックには value に対して number 型、message に対して string を割り当てているので、これらの型に合うようにして引数へ代入しないと型チェックでエラーになる。

interface ProcessIdentity<T, U> {
    (value: T, message: U): T;
}

function processIdentity<T, U> (value: T, message: U): T {
    console.log(message);
    return value;
}

let processor: ProcessIdentity<number, string> = processIdentity;
let returnNumber1 = processor(100, "Hello!");

ジェネリックインターフェイスとクラス

今度はクラスに使う。クラスにはインターフェイスを使用しているので、インターフェイスで実装したプロパティとメソッドの内部実装が必要になる。このとき、ジェネリックを使用しているので、それも使用する。(型名は異なっていても良い)

interface ProcessIdentity2<T, U> {
    value: T;
    message: U;
    process(): T;
}

class processIdentity2<X, Y> implements ProcessIdentity2<X, Y> {
    value: X;
    message: Y;
    constructor(val: X, msg: Y) {
        this.value = val;
        this.message = msg;
    }
    process(): X {
        console.log(this.message);
        return this.value;
    }
}

let processor2 = new processIdentity2<number, string>(100, "Hello");
processor2.process();

ジェネリック制約の利用例

自作のデータ構造(クラス)を持つような型も同様にジェネリックを利用できる。この場合は Car というクラスでジェネリック制約しているので、Car 以外の型は受け付けないようになっている。

class Car {
    make: string = "Generic Car";
    doors: number = 4;
}

class ElectricCar extends Car {
    make = "Electric Car";
    doors = 4;
}

class Truck extends Car {
    make = "Truck";
    doors = 2;
}

function acclerate<T extends Car> (car: T): T {
    console.log(`All ${car.doors} doors are closed.`);
    console.log(`The ${car.make} is now accelerating!`);
    return car;
}

let myElectricCar = new ElectricCar;
acclerate<ElectricCar>(myElectricCar);
let myTruck = new Truck;
acclerate<Truck>(myTruck);

課題

最後に MS Learn の課題をやっておわり。

class DataStore<T> {
    private _data: Array<T> = new Array(10);
    
    AddOrUpdate(index: number, item: T) {
        if(index >= 0 && index < 10) {
            this._data[index] = item;
        } else {
            console.log('Index is greater than 10');
        }
    }

    GetData(index: number) {
        if(index >= 0 && index < 10) {
            return this._data[index];
        } else {
            return
        }
    }
}

let cities = new DataStore();

cities.AddOrUpdate(0, "Mumbai");
cities.AddOrUpdate(1, "Chicago");
cities.AddOrUpdate(11, "London");

console.log(cities.GetData(1));
console.log(cities.GetData(12));

let empIDs = new DataStore<number>();
empIDs.AddOrUpdate(0, 50);
empIDs.AddOrUpdate(1, 65);
empIDs.AddOrUpdate(2, 89);
console.log(empIDs.GetData(0));

type Pets = {
    name: string;
    breed: string;
    age: number;
}

let pets = new DataStore<Pets>();
pets.AddOrUpdate(0, {name: "Rex", breed: "Golden Retriever", age: 5});
pets.AddOrUpdate(1, {name: "Sparky", breed: "Jack Russell Terrire", age: 3});
console.log(pets.GetData(1));

出力結果

Index is greater than 10
Chicago
undefined
50
{ name: 'Sparky', breed: 'Jack Russell Terrire', age: 3 }

イベントのお知らせ

2022年4月24日(日)に MS Tech Camp #14 を開催します。今回は私「たくのろじぃ」主催で、Blazor を使ったポートフォリオサイト製作と Azure Static Web Apps へのデプロイをやります。
興味がある方は(社会人、学生問わず)ぜひご参加ください!
Youtube での配信ですので、休日はごろごろしながらご覧いただければと思います。もちろん、一緒にやっていただいても結構です!

mspjp.connpass.com