:『Type Script中国語入門教程』9、汎用型
13867 ワード
著作権
記事の転載先:https://github.com/zhongsp
上記のWebサイトに直接ジャンプして最新バージョンを表示することをお勧めします.
紹介する
ソフトウェアエンジニアリングでは,一貫した定義の良好なAPIを作成するだけでなく,再利用性も考慮する.コンポーネントは、現在のデータ型だけでなく、将来のデータ型もサポートします.これにより、大規模なシステムを作成する際に非常に柔軟な機能を提供します.
C#やJavaのような言語では、
汎用ハローワールド
次に、汎用関数を使用する最初の例:identity関数を作成します.この関数は、入力された値を返します.この関数を
汎用型を使用しない場合、この関数は次のようになります.
または、
したがって、戻り値のタイプを入力パラメータのタイプと同じように使用する方法が必要です.ここでは、値ではなくタイプを表す特殊な変数であるタイプ変数を使用します.
タイプ変数
このバージョンの
汎用関数を定義すると,2つの方法で使用できる.1つ目は、タイプパラメータを含むすべてのパラメータを入力することです.
ここでは、
第2の方法はもっと普遍的だ.タイプ推論を使用すると、コンパイラは入力されたパラメータに基づいてTのタイプを自動的に決定するのに役立ちます.
私たちは
汎用変数の使用
汎用を使用して
前の例
そうすれば、コンパイラは
次に,
上記の例を実現することもできます.
他の言語を使ったことがあるなら、この文法に詳しいかもしれません.次のセクションでは、
汎用タイプ
前節ではidentity汎用関数を作成し、異なるタイプに適用できます.この節では,関数自体のタイプと,汎用インタフェースの作成方法について検討する.
汎用関数のタイプは、非汎用関数のタイプと変わらないが、関数宣言のように、最も前のタイプパラメータがあるだけだ.
数量的にも使用方法的にも対応できれば,異なる汎用パラメータ名を用いることもできる.
また、呼び出し署名付きオブジェクトの字面量を使用して、汎用関数を定義することもできます.
これは私たちに最初の汎用インタフェースを書くように導いた.上記の例のオブジェクトの字面量をインタフェースとして取り出します.
似たような例では,汎用パラメータをインタフェース全体のパラメータとしたいかもしれない.これにより、どの汎用タイプ(例えば、
注意、私たちの例は少し変更されました.汎用関数を記述するのではなく,非汎用関数署名を汎用タイプの一部とする.
汎用インタフェースに加えて、汎用クラスを作成することもできます.列挙汎用とネーミングスペース汎用は作成できません.
汎用クラス
汎用クラスは汎用インタフェースとあまり差がないように見えます.汎用クラスは(
インタフェースと同様に、汎用タイプをクラスの後ろに直接置くと、クラスのすべての属性が同じタイプを使用していることを確認するのに役立ちます.
クラスのセクションで述べたように、クラスには静的部分とインスタンス部分の2つの部分があります.汎用クラスとはインスタンス部分のタイプを指すので、クラスの静的プロパティではこの汎用タイプは使用できません.
汎用拘束
前の例を覚えておくと、あるタイプの値のセットを操作したい場合があり、このセットの値がどのような属性を持っているかを知っています.
操作anyのすべてのタイプに比べて、
このため、コンストレイント条件を記述するインタフェースを定義します.
この汎用関数はコンストレイントを定義したので、任意のタイプには適用されません.
コンストレイントタイプに一致する値を入力する必要があります.必要なプロパティを含める必要があります.
汎用コンストレイントでのタイプパラメータの使用
タイプパラメータを使用して別のタイプパラメータを制約する必要がある場合があります.たとえば、
上のコードを書き換えるには、次の方法で実現できます.
注意:上記の2つの書き方は完全に同等ではありません.1段目のプログラムの戻り値はUである可能性がありますが、2段目のプログラムにはこの制限はありません.
汎用型でクラスタイプを使用する
TypeScriptで汎用を使用してファクトリ関数を作成する場合は、コンストラクション関数のクラスタイプを参照する必要があります.たとえば、
より高度な例では、プロトタイププロパティを使用してコンストラクション関数とクラスインスタンスの関係を推定し、制約します.
記事の転載先:https://github.com/zhongsp
上記のWebサイトに直接ジャンプして最新バージョンを表示することをお勧めします.
紹介する
ソフトウェアエンジニアリングでは,一貫した定義の良好なAPIを作成するだけでなく,再利用性も考慮する.コンポーネントは、現在のデータ型だけでなく、将来のデータ型もサポートします.これにより、大規模なシステムを作成する際に非常に柔軟な機能を提供します.
C#やJavaのような言語では、
を使用して再利用可能なコンポーネントを作成することができ、1つのコンポーネントは複数のタイプのデータをサポートすることができます.これにより、ユーザーは独自のデータ型でコンポーネントを使用することができます.汎用ハローワールド
次に、汎用関数を使用する最初の例:identity関数を作成します.この関数は、入力された値を返します.この関数を
echo
コマンドと見なすことができます.汎用型を使用しない場合、この関数は次のようになります.
function identity(arg: number): number { return arg; }
または、
any
タイプを使用して関数を定義します.function identity(arg: any): any { return arg; }
any
タイプを使用すると、この関数はargパラメータの任意のタイプを受信できますが、受信タイプと返されるタイプが同じであるべきであるという情報が失われます.数値を入力すると、どのタイプの値も返される可能性があることしか知りません.したがって、戻り値のタイプを入力パラメータのタイプと同じように使用する方法が必要です.ここでは、値ではなくタイプを表す特殊な変数であるタイプ変数を使用します.
function identity<T>(arg: T): T { return arg; }
タイプ変数
T
をidentityに追加しました.T
は、ユーザーが入力したタイプ(例えば、number
)をキャプチャするのに役立ち、その後、このタイプを使用することができます.その後、再びT
を戻り値タイプとして使用しました.パラメータタイプと戻り値タイプが同じであることがわかりました.これにより、関数で使用されているタイプの情報を追跡できます.このバージョンの
identity
関数を汎用と呼びます.複数のタイプに適用できるからです.any
を使用するのとは異なり、最初の例のように正確性を維持し、数値タイプを入力し、数値タイプを返します.汎用関数を定義すると,2つの方法で使用できる.1つ目は、タイプパラメータを含むすべてのパラメータを入力することです.
var output = identity<string>("myString"); // type of output will be 'string'
ここでは、
T
が文字列タイプであることを明確に指定し、<>
ではなく()
で囲まれたパラメータとして関数に渡す.第2の方法はもっと普遍的だ.タイプ推論を使用すると、コンパイラは入力されたパラメータに基づいてTのタイプを自動的に決定するのに役立ちます.
var output = identity("myString"); // type of output will be 'string'
私たちは
<>
で明確な指定タイプを使用していないことに注意してください.コンパイラはmyString
を見て、T
をこのタイプに設定します.タイプ推論は、コードの簡素化と高可読性を維持するのに役立ちます.コンパイラがタイプを自動的に推定できない場合は,上記のようにTに伝達されるタイプしか明確ではなく,複雑な場合には可能である.汎用変数の使用
汎用を使用して
identity
のような汎用関数を作成する場合、コンパイラは関数体でこの汎用タイプを正しく使用する必要があります.言い換えれば、これらのパラメータを任意またはすべてのタイプとしなければなりません.前の例
identity
を見てみましょう.function identity<T>(arg: T): T { return arg; }
arg
の長さを同時に印刷したい場合.私たちはそうする可能性があります.function loggingIdentity<T>(arg: T): T { console.log(arg.length); // Error: T doesn't have .length return arg; }
そうすれば、コンパイラは
arg
の.length
属性を使用したと誤報しますが、arg
がこの属性を持っていることを示す場所はありません.これらのタイプ変数は任意のタイプを表すので、この関数を使用する人は数値を入力する可能性がありますが、数値には.length
属性がありません.次に,
T
タイプの配列を直接T
ではなく操作したいと仮定する.配列を操作するので、.length
属性は存在するはずです.この配列は、他の配列を作成するように作成できます.function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
loggingIdentity
のタイプ:汎用関数loggingIdentity
と、受信タイプパラメータT
と、要素タイプarg
の配列であり、要素タイプT
の配列を返します.数値配列を入力すると、T
のタイプがT
であるため、数値配列が返されます.これにより、汎用変数Tをタイプ全体ではなくタイプの一部として使用することができ、柔軟性が向上します.上記の例を実現することもできます.
function loggingIdentity<T>(arg: Array<T>): Array<T> { console.log(arg.length); // Array has a .length, so no more error return arg; }
他の言語を使ったことがあるなら、この文法に詳しいかもしれません.次のセクションでは、
number
のようにカスタム汎用を作成する方法について説明します.汎用タイプ
前節ではidentity汎用関数を作成し、異なるタイプに適用できます.この節では,関数自体のタイプと,汎用インタフェースの作成方法について検討する.
汎用関数のタイプは、非汎用関数のタイプと変わらないが、関数宣言のように、最も前のタイプパラメータがあるだけだ.
function identity<T>(arg: T): T { return arg; } var myIdentity: <T>(arg: T) => T = identity;
数量的にも使用方法的にも対応できれば,異なる汎用パラメータ名を用いることもできる.
function identity<T>(arg: T): T { return arg; } var myIdentity: <U>(arg: U) => U = identity;
また、呼び出し署名付きオブジェクトの字面量を使用して、汎用関数を定義することもできます.
function identity<T>(arg: T): T { return arg; } var myIdentity: {<T>(arg: T): T} = identity;
これは私たちに最初の汎用インタフェースを書くように導いた.上記の例のオブジェクトの字面量をインタフェースとして取り出します.
interface GenericIdentityFn { <T>(arg: T): T; } function identity<T>(arg: T): T { return arg; } var myIdentity: GenericIdentityFn = identity;
似たような例では,汎用パラメータをインタフェース全体のパラメータとしたいかもしれない.これにより、どの汎用タイプ(例えば、
Array<T>
)が使用されているかが明らかになり、インタフェースの他のメンバーもこのパラメータのタイプを知ることができます.interface GenericIdentityFn<T> { (arg: T): T; } function identity<T>(arg: T): T { return arg; } var myIdentity: GenericIdentityFn<number> = identity;
注意、私たちの例は少し変更されました.汎用関数を記述するのではなく,非汎用関数署名を汎用タイプの一部とする.
Dictionary<string> Dictionary
を使用する場合、汎用タイプ(ここではGenericIdentityFn
)を指定するためにタイプパラメータを入力し、その後のコードで使用されるタイプをロックします.呼び出し署名にパラメータを置くタイミングと、インタフェースにいつ置くかを理解することは、どのタイプが汎用部分に属するかを説明するのに役立ちます.汎用インタフェースに加えて、汎用クラスを作成することもできます.列挙汎用とネーミングスペース汎用は作成できません.
汎用クラス
汎用クラスは汎用インタフェースとあまり差がないように見えます.汎用クラスは(
number
)を使用して汎用タイプを囲み、クラス名の後に続く.class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; } var myGenericNumber = new GenericNumber<number>(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function(x, y) { return x + y; };
<>
クラスの使用は直感的であり、数値タイプのみを使用することを制限していないことに注意してください.文字列またはその他のより複雑なタイプを使用することもできます.var stringNumeric = new GenericNumber<string>(); stringNumeric.zeroValue = ""; stringNumeric.add = function(x, y) { return x + y; }; alert(stringNumeric.add(stringNumeric.zeroValue, "test"));
インタフェースと同様に、汎用タイプをクラスの後ろに直接置くと、クラスのすべての属性が同じタイプを使用していることを確認するのに役立ちます.
クラスのセクションで述べたように、クラスには静的部分とインスタンス部分の2つの部分があります.汎用クラスとはインスタンス部分のタイプを指すので、クラスの静的プロパティではこの汎用タイプは使用できません.
汎用拘束
前の例を覚えておくと、あるタイプの値のセットを操作したい場合があり、このセットの値がどのような属性を持っているかを知っています.
GenericNumber
の例では、loggingIdentity
のarg
プロパティにアクセスしたいのですが、コンパイラは各タイプにlength
プロパティがあることを証明することができず、エラーを報告しました.function loggingIdentity<T>(arg: T): T { console.log(arg.length); // Error: T doesn't have .length return arg; }
操作anyのすべてのタイプに比べて、
length
属性を持つすべてのタイプを処理するように関数を制限したい.入力されたタイプにこの属性がある限り、少なくともこの属性が含まれていることを許可します.そのためには,Tに対する制約要件をリストする必要がある.このため、コンストレイント条件を記述するインタフェースを定義します.
.length
プロパティを含むインタフェースを作成し、このインタフェースと.length
キーワードを使用して制約を実装します.interface Lengthwise { length: number; } function loggingIdentity<T extends Lengthwise>(arg: T): T { console.log(arg.length); // Now we know it has a .length property, so no more error return arg; }
この汎用関数はコンストレイントを定義したので、任意のタイプには適用されません.
loggingIdentity(3); // Error, number doesn't have a .length property
コンストレイントタイプに一致する値を入力する必要があります.必要なプロパティを含める必要があります.
loggingIdentity({length: 10, value: 3});
汎用コンストレイントでのタイプパラメータの使用
タイプパラメータを使用して別のタイプパラメータを制約する必要がある場合があります.たとえば、
function find<T, U extends Findable<T>>(n: T, s: U) { // errors because type parameter used in constraint // ... } find (giraffe, myAnimals);
上のコードを書き換えるには、次の方法で実現できます.
function find<T>(n: T, s: Findable<T>) { // ... } find(giraffe, myAnimals);
注意:上記の2つの書き方は完全に同等ではありません.1段目のプログラムの戻り値はUである可能性がありますが、2段目のプログラムにはこの制限はありません.
汎用型でクラスタイプを使用する
TypeScriptで汎用を使用してファクトリ関数を作成する場合は、コンストラクション関数のクラスタイプを参照する必要があります.たとえば、
function create<T>(c: {new(): T; }): T {
return new c();
}
より高度な例では、プロトタイププロパティを使用してコンストラクション関数とクラスインスタンスの関係を推定し、制約します.
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
function findKeeper<A extends Animal, K> (a: {new(): A;
prototype: {keeper: K}}): K {
return a.prototype.keeper;
}
findKeeper(Lion).nametag; // typechecks!