TypeScript 中文教程之函数上----翻译自ts官方
在ts中表达一个函数等方法有多种:
🔯 函数类型表达式
function greeter(fn: (a: string) => void) {
fn("Hello World");
}
function printToConsole(s: string) {
console.log(s);
}
greeter(printToConsole);
这种类似于箭头函数的表示是最简单的。其中加粗部分表示一个以字符串类型的a为入参,并且没有任何返回值的函数。
另一种写法:使用类的别名去定义一个函数类:
type GreetFunction = (a: string) => void; // 定义一个参数为字符串并且没有返回值的函数类
function greeter(fn: GreetFunction) { ... }
🔯 调用签名
在JavaScript中,函数除了可以调用外,还可以具有属性。但是,函数类型表达式语法不允许声明属性。如果我们想用属性描述可调用的东西,我们可以在对象类型中编写调用签名:
type DescribableFunction = {
description : string,
(someArg: number): boolean;
};
function doSomething(fn: DescribableFunction) {
console.log(fn.description + "returned" + fn(3));
}
函数的签名和函数类型表达式有稍微的不同:在参数列表和返回值之间使用冒号: ,而上面的函数类型表达式使用箭头 => 。
🔯 结构签名
js中函数也可以使用 new 来创建一个实例。TS中你也可以编写一个结构签名并在new后面使用。
type SomeConstructor = {
new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
return new ctor('Hello');
}
比如js中的Date对象,在调用时可以使用new也可以忽略new,你可以将条用签名和结构签名随意结合。
例如:
interface CallOrContruct {
new (s: string): Date;
(n?: number): number;
}
🔯 泛型函数
通常一个函数的入参和返回值是有关联的,或者两个参数间有着某种关联。让我们试想一下一个函数返回数组中的第一个元素:
function firstElement(arr: any[]) {
return arr[0];
}
这个函数虽然可以正常工作,但是返回值是一个 any 类型。最理想的是返回数组元素的类型。
在TypeScript中,当我们想要描述两个值之间的对应关系时,使用泛型。为此,我们在函数签名中声明一个类型参数:
function firstElement<Type>(arr: Type[]): Type | undefined {
return arr[0];
}
通过添加一个Type的参数,并在参数和返回值两个位置上使用,这样我们就创建了一个输入值和返回值有关联的函数,尝试调用他们:
const s = firstElement(['a', 'b', 'c']); // s 是 string 类型
const n = firstElement([1, 2, 3, 4]); // n 是 number 类型
上面这个例子,使用了单一的类型参数 Type。实际应用中我们定义多个类型参数,例如:
function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
return arr.map(func);
}
const parsed = map(['1', '2', '3', '4'], (n) => parseInt(n));
🔯 约束
我们已经编写了一些泛型函数,可以处理任何类型的值。有时,我们希望将两个值关联起来,但只能对值的某个子集进行操作。在这种情况下,我们可以使用约束来限制类型参数可以接受的类型。
让我们编写一个返回两个值中较长值的函数。为此,我们需要一个number类型的长度属性。我们通过编写extends子句将类型参数约束到该类型:
function longest<Type extends {length: number}>(a: Type, b: Type) {
if (a.length >= b.length) {
return a;
} else {
return b;
}
}
const longestArray = longest([1, 2], [3, 4, 5]);
const longestString = longest('Bowennan', 'JamesBond');
const notOk = longest(10, 300) // 🤢
由于数字10,300都没有 length 这个属性,所以报错了。
🔯 指定参数类型
TypeScript通常可以推断泛型调用中的预期类型参数,但也有意外情况:
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2);
}
当你这样调用时就会报错:
const arr = combine([1, 2, 1], ['hello']); // 🤢 第一个参数已经决定了类型为 number,所以当你传入第二个 string 参数时就报错了
我们可以在调用时指定泛型的类型:
const arr = combine<string | number>([1, 2, 1], ['hello']);
🔯 如何编写好的泛型函数呢?
1. 遵从类型推导,减少不必要的定义
function firstElement1<Type>(arr: Type[]) {
return arr[0]
}
function firstElement2<Type extends any[]>(arr: Type) {
return arr[0];
}
😊 // a: number
const a = firstElement1([1,2,23])
🤢 // b: any
const b = firstElement2([1,1,23])
第一个函数返回值指向了Type,第二个返回值需要遵从一个没有必要的约束。如果可能,使用类型参数本身要好于约束。
2. 使用尽量少的类型参数
function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
return arr.filter(func);
}
function filter2<Type, Func extends (arg: Type) => boolean>(arr: Type[], func: Func): Type[] {
return arr.filter(func);
}
第二个函数中我们创建了一个单一关联的Func类型参数,指定了参数类型,只是关联到一个参数 func。这种做法只会提升代码的阅读难度,并没有任何优势,尽可能减少类型参数。
3. 至少出现两次,否则没有必要使用泛型
function greet<Str extends string>(s: Str) {
console.log('Hello' + s);
}
只有一个参数,没有必要使用类型参数。
function greet(s: string) {...}
posted on 2022-01-15 11:49 Deflect-o-Bot 阅读(140) 评论(0) 编辑 收藏 举报