350 Views
September 05, 23
スライド概要
社内で行ったTypeScript勉強会のDay2です。Day2 ではジェネリクスをメインに取り扱います。
LIFULL HOME'Sを運営する株式会社LIFULLのアカウントです。 LIFULLが主催するエンジニア向けイベント「Ltech」等で公開されたスライド等をこちらで共有しております。
TypeScript ハンズオン Day2 Araki Yuki 1 © LIFULL Co.,Ltd. 本書の無断転載、複製を固く禁じます。
Genericsとは Generics
Genericsとは
function test<T>(x: T): T {
return x;
}
const y = test<string>('aaa');
Genericsとは 型の安全性とコードの共通化を両立するもの
Genericsとは 例えば?
Genericsとは 配列の最初の要素を返す関数 何も考えずに共通化したコード function getFirstElement(array: number[]):number { return array[0]; function getFirstElement(array: any[]):any { } return array[0]; function getFirstElement(array: string[]):string{ return array[0]; } }
Genericsとは これだと型安全に使えない
Generics 型変数 型変数
Generics 型変数
function printAndReturn<T>(value: T): T {
console.log(value);
return value;
Q.型変数のTって何?
A.慣習的に型変数にはT,U,Pが使われます。
Tでないとダメというわけではないです。
}
● Tが型変数と呼ばれる部分
○ 何かしらの型が代入されると思ってください
○ 3つのTは全て同一のものです
● <T>が型変数を定義している部分
● valueの型に使われているTと戻り値に書かれているTは定義された型変数を利用している部分
Generics 型引数 型引数
Generics 型引数
function printAndReturn(value: number): number {
console.log(value);
function printAndReturn<T>(value: T): T {
return value;
console.log(value);
return value;
}
}
function printAndReturn(value: string): string {
const value1 = printAndReturn<number>(123);
console.log(value);
const value2 = printAndReturn<string>('a');
return value;
}
● <number>が型引数 <T>にnumberを代入しているイメージ
● printAndReturnにnumberの型引数を渡しているので
○ (value: T)のTがnumberになり
○ `: T`の戻り値の型もnumberになる
Generics 型引数
function printAndReturn<T>(value: T): T {
console.log(value);
return value;
型引数は型の推論が行われます。
変数の123からTがnumberであることが
推測できるので、省略することが可能で
}
す。
const value = printAndReturn(123);
printAndReturn<number>(123);
123から推論できるので<number>の省略が可能
printAndReturn(123);
Generics A1
Genericsを使って共通化したコード
function test<T>(array: T[]):T {
return array[0];
使い方
const array1:string[] = ['a', 'b'];
const array2:number[] = [1,2,3];
}
const a = test(array1);
test<T>
Tは型変数の引数
array: T[]
受け取った型変数の
型の配列を受け取り
:T
受け取った型変数の
型を返す関数
const b = test(array2);
Generics A2
Genericsを使って共通化したコード
function myFilter<T>(arr: T[], predicate: (elm: T) => boolean): T[] {
const result = [];
for (const elm of arr) {
if (predicate(elm)) {
result.push(elm);
}
}
return result;
}
myFilter<T>
このTは型変数の定義箇所
array: T[]
Tの配列を受け取り
(elm:T) => boolean
Tをbooleanで判定する関数を受け取る型
: T[]
フィルタリングされたTの配列を返す
Generics A3
Genericsを使って共通化したコード
function giveId<T>(obj:T):T & {id:string}{
const id = "本当はランダムがいいけどここではただの文字列";
return {
...obj,
id
};
}
giveId<T>
このTは型変数の定義箇所
obj:T
任意のObjectTを受け取り
T & {id:string}
Tと{id:string}のインターセクション型を返す
Generics 型変数(複数)
function printAndReturn<T, U>(value1: T,
● 型引数は複数設定することが可能。
value2: U):(T | U)[] {
● <>に対してカンマ区切りで渡す。
console.log(value1);
console.log(value2);
return [value1, value2];
}
const value = printAndReturn<number,
string>(123, 'aaa');
Generics 型の制約 型の制約
Generics 型の制約
interface Animal {
readonly name: string;
型変数であるTが何者かが不明なので
animal.nameの様に呼び出すことができない
bark(): void;
}
function test<T>(
animal: T
) {
console.log(animal.name);
// ↑compile error
animal.bark();
// ↑compile error
}
ただし <T extends hoge> のようにextendsを使っ
て制約をかけることで、呼び出しが可能になる。
Generics 型の制約
interface Animal {
readonly name: string;
bark(): void;
}
TがAnimalと互換性のある型という保証があるの
で、animal.nameやanimal.bark();
の呼び出しが可能になる。
function test<T extends Animal>(
animal: T
また下記のような呼び出しは不可能になる
) {
console.log(animal.name);
const hoge = {
name: 'aaa'
// ↑compile ok
}
animal.bark();
};
// ↑compile ok
test(hoge);
// ↑ Compile Errorになる
Generics 型の制約
keyofを使った型の制約
const sample1 = {
name: 'Taro',
age: 10
};
const sample2 = {
Tの型に制限はなし
Uの型はTのObjectのkeyからなるunion型
id: 3,
text: 'hoge'
};
function getValue<T, U extends keyof T>(obj: T, key:U): T[U]{
Tにsample1が入る時
Uは 'name' | 'age'という型になる
return obj[key];
}
console.log(getValue(sample1, 'name')) // 出力:'Taro'
console.log(getValue(sample2, 'id')) // 出力:3
// ↓第2引数は 'id' | 'text'という型しか受け付けないのでエラーになる
Tにsample2が入る時
Uは 'id' | 'text'という型になる
console.log(getValue(sample2, 'name'))
解説用コード
Generics A4
Genericsを使って共通化したコード
function getRequestCondParameter<T extends keyof Cond>(body:RequestBody,
query:RequestQuery, key: T): Cond[T] {
const cond: Cond = {
...body.cond,
...query.cond
};
return cond[key];
}
<T extends keyof Cond>
key:T
Cond[T]
TはCondのkeyの文字列のみ受け取る
引数のkeyはkeyof
Condの制約を受けている
interface Cond {
stationId?: string;
}
stationIdを渡せばstring | undefined
lineId?: string;
lineIdを渡せばstring | undefined
money?: number;
moneyを渡せば number | undefined
の型になる
Generics classやinterface
class Queue<T> {
private data:T[] = [];
push(item: T) { this.data.push(item); }
pop(): T | undefined { return this.data.shift(); }
}
const queue = new Queue<number>();
queue.push(0);
queue.push('hoge');// error
const queue = new Queue<number>();
Tはnumberで固定される
queue.push(0);
itemの型はnumberになっているため
問題なし
queue.push('hoge');
itemの型はnumberになっているため
エラーとなる
Genericsを使う意味 結局何が嬉しいのか?
Genericsを使う意味 ● コードを型安全に共通化できる ● だから型を間違えているとコンパイルエラーが出る ● そして型を把握できてるので補完が効く
Genericsを使う意味 今日書いたコードで説明 anyを使ってみて違いを知る Sample1 Sample2