TypeScript 的函数类型

83 阅读3分钟

简介

如果函数的类型定义很冗长,或者多个函数使用同一种类型,写法二用起来就很麻烦。因此,往往用type命令为函数类型定义一个别名,便于指定给其他变量。

type MyFunc = (txt:string) => void;

const hello:MyFunc = function (txt) {
  console.log('hello ' + txt);
};

如果一个变量要套用另一个函数类型,有一个小技巧,就是使用typeof运算符。

function add(
  x:number,
  y:number
) {
  return x + y;
}

const myAdd:typeof add = function (x, y) {
  return x + y;
}

函数类型还可以采用对象的写法。

let add:{
  (x:number, y:number):number
};
 
add = function (x, y) {
  return x + y;
};

箭头函数

类型写在箭头函数的定义里面,与使用箭头函数表示函数类型,写法有所不同。

type Person = { name: string };

const people = ['alice', 'bob', 'jan'].map(
  (name):Person => ({name})
);
// 错误
(name:Person) => ({name})

// 错误
name:Person => ({name})

可选参数

参数的类型实际上是原始类型|undefined 类型显式设为undefined的参数,就不能省略。

function f(x:number|undefined) {
  return x;
}

f() // 报错

参数默认值

可选参数与默认值不能同时使用。

rest 参数

rest 参数甚至可以嵌套。

function f(...args:[boolean, ...string[]]) {
  // ...
}

rest 参数可以与变量解构结合使用。

function repeat(
  ...[str, times]: [string, number]
):string {
  return str.repeat(times);
}

// 等同于
function repeat(
  str: string,
  times: number
):string {
  return str.repeat(times);
}

void 类型

void 类型允许返回undefinednull 如果打开了strictNullChecks编译选项,那么 void 类型只允许返回undefined 需要特别注意的是,如果变量、对象方法、函数参数的类型是 void 类型的函数,那么并不代表不能赋值为有返回值的函数。恰恰相反,该变量、对象方法和函数参数可以接受返回任意值的函数,这时并不会报错。

type voidFunc = () => void;

const f:voidFunc = () => {
  return 123;
};

never 类型

它用在函数的返回值,就表示某个函数肯定不会返回值,即函数不会正常执行结束。 (1)抛出错误的函数。

function fail(msg:string):never {
  throw new Error(msg);
}

(2)无限执行的函数。

const sing = function():never {
  while (true) {
    console.log('sing');
  }
};

函数重载

有些函数可以接受不同类型或不同个数的参数,并且根据参数的不同,会有不同的函数行为。这种根据参数类型不同,执行不同逻辑的行为,称为函数重载(function overload)。 TypeScript 对于“函数重载”的类型声明方法是,逐一定义每一种情况的类型。

function add(
  x:number,
  y:number
):number;
function add(
  x:any[],
  y:any[]
):any[];
function add(
  x:number|any[],
  y:number|any[]
):number|any[] {
  if (typeof x === 'number' && typeof y === 'number') {
    return x + y;
  } else if (Array.isArray(x) && Array.isArray(y)) {
    return [...x, ...y];
  }

  throw new Error('wrong parameters');
}

函数重载的每个类型声明之间,以及类型声明与函数实现的类型之间,不能有冲突。

// 报错
function fn(x:boolean):void;
function fn(x:string):void;
function fn(x:number|string) {
  console.log(x);
}

类型最宽的声明应该放在最后面,防止覆盖其他类型声明。

function f(x:any):number;
function f(x:string): 0|1;
function f(x:any):any {
  // ...
}

const a:0|1 = f('hi'); // 报错

对象的方法也可以使用重载。

class StringBuilder {
  #data = '';

  add(num:number): this;
  add(bool:boolean): this;
  add(str:string): this;
  add(value:any): this {
    this.#data += String(value);
    return this;
  }

  toString() {
    return this.#data;
  }
}
type CreateElement = {
  (tag:'a'): HTMLAnchorElement;
  (tag:'canvas'): HTMLCanvasElement;
  (tag:'table'): HTMLTableElement;
  (tag:string): HTMLElement;
}

应该优先使用联合类型替代函数重载。

// 写法一
function len(s:string):number;
function len(arr:any[]):number;
function len(x:any):number {
  return x.length;
}

// 写法二
function len(x:any[]|string):number {
  return x.length;
}

构造函数

构造函数的类型写法,就是在参数列表前面加上new命令

class Animal {
  numLegs:number = 4;
}

type AnimalConstructor = new () => Animal;

function create(c:AnimalConstructor):Animal {
  return new c();
}

const a = create(Animal);

构造函数还有另一种类型写法,就是采用对象形式。

type F = {
  new (s:string): object;
};