需要注意的是,这里的=>与 ES6 中箭头函数的=>有所不同。TypeScript 函数类型中的=>用来表示函数的定义,其左侧是函数的参数类型,右侧是函数的返回值类型;而 ES6 中的=>是函数的实现。

  1. type Adder = (a: number, b: number) => number; // TypeScript 函数类型定义
  2. const add: Adder = (a, b) => a + b; // ES6 箭头函数
  1. interface Entity {
  2. add: (a: number, b: number) => number;
  3. del(a: number, b: number): number;
  4. }
  5. const entity: Entity = {
  6. add: (a, b) => a + b,
  7. del(a, b) {
  8. return a - b;
  9. },
  10. };

请记住:这是一个很重要也很有意思的特性,函数返回值的类型推断结合泛型(我们会在 10 讲中详细介绍)可以实现特别复杂的类型计算(本质是复杂的类型推断,这里称之为计算是为了表明其复杂性),比如 Redux Model 中 State、Reducer、Effect 类型的关联。

  1. function computeTypes(one: string, two: number) {
  2. const nums = [two]
  3. const strs = [one]
  4. return {
  5. nums,
  6. strs
  7. } // 返回 { nums: number[]; strs: string[] } 的类型
  8. }

Generator 函数的返回值

Generator 函数返回的是一个 Iterator 迭代器对象,我们可以使用 Generator 的同名接口泛型或者 Iterator 的同名接口泛型(在 10 讲会介绍)表示返回值的类型(Generator 类型继承了 Iterator 类型),示例如下:

  1. type AnyType = boolean;
  2. type AnyReturnType = string;
  3. type AnyNextType = number;
  4. function *gen(): Generator<AnyType, AnyReturnType, AnyNextType> {
  5. const nextValue = yield true; // nextValue 类型是 number,yield 后必须是 boolean 类型
  6. return `${nextValue}`; // 必须返回 string 类型
  7. }

可选参数和默认参数

  1. function log(x?: string) {
  2. console.log(x);
  3. }
  4. function log1(x: string | undefined) {
  5. console.log(x);
  6. }
  7. log();
  8. log(undefined);
  9. log1(); // ts(2554) Expected 1 arguments, but got 0
  10. log1(undefined);

剩余参数

  1. function sum(...nums: number[]) {
  2. return nums.reduce((a, b) => a + b, 0);
  3. }
  4. sum(1, 2); // => 3
  5. sum(1, 2, 3); // => 6
  6. sum(1, '2'); // ts(2345) Argument of type 'string' is not assignable to parameter of type 'number'
  1. function sum(...nums: (number | string)[]): number {
  2. return nums.reduce<number>((a, b) => a + Number(b), 0);
  3. }
  4. sum(1, '2', 3); // 6

this

为了说明在typescript中this的声明方法,我们首先需要更改tsconfig.json的配置:

  1. "compilerOptions": {
  2. ...
  3. "noImplicitThis": true
  4. }
  1. function say(this: Window, name: string) {
  2. console.log(this.name);
  3. }
  4. window.say = say;
  5. window.say('hi');
  6. const obj = {
  7. say
  8. };
  9. obj.say('hi'); // ts(2684) The 'this' context of type '{ say: (this: Window, name: string) => void; }' is not assignable to method's 'this' of type 'Window'.

需要注意的是,如果我们直接调用 say(),this 实际上应该指向全局变量 window,但是因为 TypeScript 无法确定 say 函数被谁调用,所以将 this 的指向默认为 void,也就提示了一个 ts(2684) 错误。

注意:显式注解函数中的 this 类型,它表面上占据了第一个形参的位置,但并不意味着函数真的多了一个参数,因为 TypeScript 转译为 JavaScript 后,“伪形参” this 会被抹掉,这算是 TypeScript 为数不多的特有语法。

  • this没有在函数第一个参数中声明

    1. function test(b: number, this: { a: number }) {
    2. const {a} = this;
    3. console.log(a);
    4. }
    5. test.call({ a: 1 });// error A 'this' parameter must be the first parameter. TS2680
  • 错误的调用了this声明的函数

    1. function test(this: { a: number }) {
    2. const {a} = this;
    3. console.log(a);
    4. }
    5. test({ a: 1 }); // 直接以传参数的形式调用test,Expected 0 arguments, but got 1. TS2554

函数重载

JavaScript 是一门动态语言,针对同一个函数,它可以有多种不同类型的参数与返回值,这就是函数的多态。

  1. function convert(x: string | number | null): string | number | -1 {
  2. if (typeof x === 'string') {
  3. return Number(x);
  4. }
  5. if (typeof x === 'number') {
  6. return String(x);
  7. }
  8. return -1;
  9. }
  10. const x1 = convert('1'); // => string | number
  11. const x2 = convert(1); // => string | number
  12. const x3 = convert(null); // => string | number

函数重载(Function Overload),如下示例中 1~3 行定义了三种各不相同的函数类型列表,并描述了不同的参数类型对应不同的返回值类型,而从第 4 行开始才是函数的实现。

  1. function convert(x: string): number;
  2. function convert(x: number): string;
  3. function convert(x: null): -1;
  4. function convert(x: string | number | null): any {
  5. if (typeof x === 'string') {
  6. return Number(x);
  7. }
  8. if (typeof x === 'number') {
  9. return String(x);
  10. }
  11. return -1;
  12. }
  13. const x1 = convert('1'); // => number
  14. const x2 = convert(1); // => string
  15. const x3 = convert(null); // -1

TypeScript 会从上到下查找函数重载列表中与入参类型匹配的类型,并优先使用第一个匹配的重载定义。因此,我们需要把最精确的函数重载放到前面。

例如下边,因为 P2 继承自 P1,所以类型为 P2 的参数会和类型为 P1 的参数一样匹配到第一个函数重载,此时 x1、x2 的返回值都是 number。

  1. function convert(x: P1): number;
  2. function convert(x: P2): string;
  3. function convert(x: string | number | null | P1 | P2): string | number | -1 {
  4. if (typeof x === "string") {
  5. return Number(x);
  6. }
  7. if (typeof x === "number") {
  8. return String(x);
  9. }
  10. return -1;
  11. }
  12. interface P1 {
  13. name: string;
  14. }
  15. interface P2 extends P1 {
  16. age: number;
  17. }
  18. const x1 = convert({ name: '' } as P1); // => number
  19. const x2 = convert({ name: '', age: 18 } as P2); // => number

我们只需要将函数重载列表的顺序调换一下,类型为 P2 和 P1 的参数就可以分别匹配到正确的函数重载了

  1. function convert(x: P2): string;
  2. function convert(x: P1): number;
  3. function convert(x: string | number | null | P1 | P2): string | number | -1 {
  4. if (typeof x === "string") {
  5. return Number(x);
  6. }
  7. if (typeof x === "number") {
  8. return String(x);
  9. }
  10. return -1;
  11. }
  12. interface P1 {
  13. name: string;
  14. }
  15. interface P2 extends P1 {
  16. age: number;
  17. }
  18. const x1 = convert({ name: '' } as P1); // => number
  19. const x2 = convert({ name: '', age: 18 } as P2); // => string

类型谓词(is)

  1. function isString(s): s is string { // 类型谓词
  2. return typeof s === 'string';
  3. }
  4. function isNumber(n: number) {
  5. return typeof n === 'number';
  6. }
  7. function operator(x: unknown) {
  8. if(isString(x)) { // ok x 类型缩小为 string
  9. }
  10. if (isNumber(x)) { // ts(2345) unknown 不能赋值给 number
  11. }
  12. }