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  阅读(126)  评论(0编辑  收藏  举报