TypeScript 入門日記 06
前回
今回はこれをやっていくぅっ!(きま○れクック)
ジェネリック
基本的にはどんな型でも受け付けるが、型チェック(指定した型以外は受け付けない)のためによく利用する。これはたぶん 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
には string
か number
しか許可していないので、第一引数にこれら以外の型を入れるとエラーになる。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 での配信ですので、休日はごろごろしながらご覧いただければと思います。もちろん、一緒にやっていただいても結構です!