目次
1. 優れたジェネリック関数を作成するためのガイドライン
ジェネリック関数を書くのは楽しいですが、型パラメーターに夢中になってしまいがちです。型パラメーターが多すぎたり、必要のない制約を使用したりすると、推論が成功しにくくなり、関数の呼び出し元がイライラしてしまいます。
1.1 プッシュダウン型パラメータ
似たような関数を記述する 2 つの方法を次に示します。
function catArr<Type>(params: Type[]) {
return params;
}
function dogArr<Type extends any[]>(params: Type){
return params;
}
catArr(['波斯猫', '橘猫', '拿破仑'])
dogArr(['哈士奇', '金毛', '博美'])
一見すると、これらは同じように見えますが、この関数を記述するには catArr の方が適しています。その推論される戻り値の型は type ですが、dogArr の推論される戻り値の型は any です。これは、TypeScript が呼び出し中に要素の解決を「待機」するのではなく、制約された型で arr[0] 式を解決する必要があるためです。
注: 可能であれば、型パラメータを制約するのではなく、それ自体を使用してください。
1.2 使用する型パラメータを減らす
上記のコードを参照して、書き直してみましょう。
function catArr<Type>(params: Type[], func: (arg: Type) => Type): Type[] {
return params.filter(func);
}
function dogArr<Type, Func extends (arg: Type) => Type>
(params: Type[], func: Func): Type[] {
return params.filter(func);
}
2 つの値に関連しない型パラメーター Func を作成しました。これは、型パラメーターを指定したい呼び出し元が理由もなく追加の型パラメーターを手動で指定する必要があることを意味するため、常に危険信号です。Func は、関数を読みにくく、推論しにくくするだけです。
注: 常に可能な限り少ない数の型パラメータを使用します
1.3 型パラメータは 2 回出現する必要がある
関数がジェネリックである必要がないことを忘れてしまうことがあります。
function cat<Name extends string>(N: Name): string {
console.log('猫的名字是:: ' + N );
return N;
}
cat('拿破仑')
// 猫的名字是:: 拿破仑
より単純なバージョンも簡単に作成できます。
function cat(N: string): string {
console.log('猫的名字是:: ' + N );
return N;
}
cat('拿破仑')
// 猫的名字是:: 拿破仑
型パラメーターは、複数の値の型を関連付けるために使用されることに注意してください。型パラメーターが関数シグネチャ内で 1 回だけ使用される場合、その型パラメーターは何にも関連付けられません。これには、推論された戻り値の型が含まれます。たとえば、Name が cat の推論された戻り値の型の一部である場合、パラメーターと戻り値の型が関連付けられるため、作成したコード内に 1 回しか現れないにもかかわらず、2 回使用されることになります。
注: type パラメータが 1 か所のみに表示される場合は、それが本当に必要かどうかを再考してください。
2. オプションのパラメータ
JavaScript の関数は通常、可変の数の引数を受け取ります。たとえば、数値を 2、8、および 16 進数に変換します。
function N(n: number) {
console.log(n.toString()); // 17
console.log(n.toString(2)); // 0001
console.log(n.toString(8)); // 21
console.log(n.toString(16)); // 11
}
N(17)
TypeScript でこのメソッドを改良して、パラメーターをオプションとしてマークすることができます。?
function N(x:number, n?: number) {
console.log(x.toString(n)); // 17
}
N(17)
パラメーターのデフォルト値を指定することもできます。たとえば、16 進数のパラメーターを渡さない場合、デフォルトの 16 進数が使用されます。
function N(x:number, n = 16) {
console.log(x.toString(n)); // 11
}
N(17)
次の f 関数では、未定義のパラメータはすべて 10 に置き換えられるため、n は数値型です。パラメータがオプションの場合、呼び出し元はいつでも未定義のパラメータを渡すことができることに注意してください。これは「間違った」パラメータをシミュレートしているだけであるためです。
function N(x : number, n? : number) {
console.log(x.toString(n)); // 11
}
N(17) // 17
N(17, 10) // 17
N(17, undefined) // 17
3. コールバックのオプションのパラメータ
オプションのパラメーターと関数の型式について理解していると、コールバックを呼び出す関数を作成するときに次の間違いを犯しやすくなります。
function catArr(arr: any[], callback: (arg: any, index?: number) => void){
let count = arr.length;
for(let i =0 ; i< count; i++){
callback(arr[i], i)
}
}
catArr(['波斯猫', '橘猫', '拿破仑'], (v) =>{
console.log(v);
})
catArr(['波斯猫', '橘猫', '拿破仑'], (v, i) =>{
console.log(v, i);
})
コールバック関数のオプションのパラメーターを使用してプロトタイプ メソッドが呼び出された場合、エラーが発生する可能性があります。
catArr(['波斯猫', '橘猫', '拿破仑'], (v, i) =>{
console.log(v, i.toString()); // “i”可能为“未定义”。
})
JavaScript では、引数よりも多くの引数を指定して関数が呼び出された場合、余分な引数は無視されます。TypeScript も同様に動作します。(同じ型の) パラメーターが少ない関数は、常にパラメーターが多い関数を置き換えることができます。
注: コールバックの関数タイプを記述するときは、パラメータを渡さずに対応するメソッドを呼び出す場合を除き、オプションのパラメータを決して記述しないでください。
4. 関数のオーバーロード
一部の JavaScript 関数は、さまざまなパラメーター数とタイプで呼び出すことができます。たとえば、タイムスタンプ (引数 1 つ) または月/日/年の指定 (引数 3 つ) を受け取る日付を生成する関数を作成できます。
TypeScript では、オーバーロードされたシグネチャを記述することで、さまざまな方法で呼び出すことができる関数を指定できます。これを行うには、一定数の関数シグネチャ (通常は 2 つ以上) を記述し、その後に関数の本体を記述します。
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3); // 没有需要 2 参数的重载,但存在需要 1 或 3 参数的重载。
この例では、2 つのオーバーロードを作成しました。1 つは 1 つの引数を取るもので、もう 1 つは 3 つの引数を取るものです。最初の 2 つのシグネチャはオーバーロード シグネチャと呼ばれます。
次に、互換性のあるシグネチャを使用して関数実装を作成しました。関数には実装シグネチャがありますが、このシグネチャを直接呼び出すことはできません。必須引数の後にオプションの引数を 2 つ指定して関数を作成したとしても、引数を 2 つ指定して呼び出すことはできません。
5. オーバーロード署名と実装署名
これはよくある混乱の原因です。多くの場合、次のようなコードを作成しても、なぜエラーが発生するのか理解できません。
function fn(x: string): void;
function fn() {
// ...
}
fn(); // 应有 1 个参数,但获得 0 个。
同様に、関数本体の記述に使用される署名は、外部から「見る」ことはできません。
実装の署名は外部からは見えません。オーバーロードされた関数を作成する場合は、関数の実装の上に常に 2 つ以上のシグネチャを含める必要があります。
実装署名は、オーバーロードされた署名と互換性がある必要もあります。たとえば、次の関数は実装シグネチャがオーバーロードと一致しないため、バグが多くなります。
function fn(x: boolean): void;
function fn(x: string): void; // 此重载签名与其实现签名不兼容。
function fn(x: boolean) {}
function fn(x: string): string;
function fn(x: number): boolean; // 此重载签名与其实现签名不兼容。
function fn(x: string | number) {
return "oops";
}
6. 適切なオーバーロードを作成する
ジェネリックの場合と同様、関数のオーバーロードを使用するときに従う必要があるガイドラインがいくつかあります。これらの原則に従うと、関数の呼び出し、理解、実装が容易になります。
文字列または配列の長さを返す関数を考えてみましょう。
function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
return x.length;
}
この関数は問題なく、文字列または配列を使用して呼び出すことができます。ただし、TypeScript は関数呼び出しを単一のオーバーロードにのみ解決できるため、文字列または配列の値を使用して呼び出すことはできません。
len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]);
// 没有与此调用匹配的重载。
// 第 1 个重载(共 2 个),“(s: string): number”,出现以下错误。
// 类型“number[] | "hello"”的参数不能赋给类型“string”的参数。
// 不能将类型“number[]”分配给类型“string”。
// 第 2 个重载(共 2 个),“(arr: any[]): number”,出现以下错误。
// 类型“number[] | "hello"”的参数不能赋给类型“any[]”的参数。
// 不能将类型“string”分配给类型“any[]”。
両方のオーバーロードのパラメーター数と戻り値の型が同じであるため、代わりにオーバーロードされていないバージョンの関数を作成できます。
function len(x: any[] | string) {
return x.length;
}
これはとても良いです!呼び出し元はどちらの値でも呼び出すことができ、さらに、正しい実装署名を理解する必要もありません。
注: 可能な限り、オーバーロードよりも共用体型のパラメーターを優先してください。