我在坑底的typescript手记之返回值类型和参数类型

Posted 恪愚

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我在坑底的typescript手记之返回值类型和参数类型相关的知识,希望对你有一定的参考价值。

返回值类型

javascript 中,我们知道一个函数可以没有显式 return,此时函数的返回值应该是 undefined

function fn() 
  // TODO

console.log(fn()); // => undefined

需要注意的是,在 TypeScript 中,如果我们显式声明函数的返回值类型为 undfined,将会得到如下所示的错误提醒。

function fn(): undefined  // ts(2355) A function whose declared type is neither 'void' nor 'any' must return a value
  // TODO

此时,正确的做法是使用 void 类型来表示函数没有返回值的类型(这是“废柴” void 类型唯一有用的场景),示例如下:

function fn1(): void 

fn1().doSomething(); // ts(2339) Property 'doSomething' does not exist on type 'void'

我们可以使用类似定义箭头函数的语法来表示函数类型的参数和返回值类型,此时=> 类型仅仅用来定义一个函数类型而不用实现这个函数。

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

在对象(即接口类型)中,除了使用这种声明语法,我们还可以使用类似对象属性的简写语法来声明函数类型的属性,如:

interface Entity 
    add: (a: number, b: number) => number;
    del(a: number, b: number): number;

const entity: Entity = 
    add: (a, b) => a + b,
    del(a, b) 
      return a - b;
    ,
;

在某种意义上来说,这两种形式都是等价的。但是很多时候,我们不必或者不能显式地指明返回值的类型,这就涉及可缺省和可推断的返回值类型的讲解。

可缺省和可推断的返回值类型

幸运的是,函数返回值的类型可以在 TypeScript 中被推断出来,即可缺省。

函数内是一个相对独立的上下文环境,我们可以根据入参对值加工计算,并返回新的值。从类型层面看,我们也可以通过类型推断加工计算入参的类型,并返回新的类型,示例如下:

function computeTypes(one: string, two: number) 
  const nums = [two];
  const strs = [one]
  return 
    nums,
    strs
   // 返回  nums: number[]; strs: string[]  的类型 

请记住:这是一个很重要也很有意思的特性,函数返回值的类型推断结合泛型可以实现特别复杂的类型推断,比如 Redux Model 中 StateReducerEffect 类型的关联。

一般情况下,TypeScript 中的函数返回值类型是可以缺省和推断出来的,但是有些特例需要我们显式声明返回值类型,比如 Generator 函数的返回值。

Generator 函数的返回值

ES6 中新增的 Generator 函数在 TypeScript 中也有对应的类型定义。

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

type AnyType = boolean;
type AnyReturnType = string;
type AnyNextType = number;
function *gen(): Generator<AnyType, AnyReturnType, AnyNextType> 
  const nextValue = yield true; // nextValue 类型是 number,yield 后必须是 boolean 类型
  return `$nextValue`; // 必须返回 string 类型

注意:TypeScript 3.6 之前的版本不支持指定 nextreturn 的类型,所以在某些有点历史的代码中,我们可能会看到 Generator 和 Iterator 类型不一样的表述。

参数类型

关于 JS 中函数的参数,在es6之后,统共可以分为:可选参数、默认参数、剩余参数 几个特性。

可选参数和默认参数

在实际工作中,我们可能经常碰到函数参数可传可不传的情况,当然 TypeScript 也支持这种函数类型表达,如下代码所示:

function log(x?: string) 
  return x;

log(); // => undefined
log('hello world'); // => hello world

在上述代码中,我们在类型标注的:前添加?表示 log 函数的参数 x 就是可缺省的。

也就是说参数 x 的类型可能是 undefined(第 5 行调用 log 时不传入实参)类型或者是 string 类型(第 6 行调用 log 传入 'hello world' 实参),那是不是意味着可缺省和类型是 undefined 等价呢?

function log(x?: string) 
  console.log(x);

function log1(x: string | undefined) 
  console.log(x);

log();
log(undefined);
log1(); // ts(2554) Expected 1 arguments, but got 0
log1(undefined);

显而易见:这里的 ?: 表示参数可以缺省、可以不传,但如果传则一定是string类型。也就是说调用函数时,我们可以不显式传入参数。但是,如果我们声明了参数类型为 xxx | undefined,就表示函数参数是不可缺省且类型必须是 xxx 或者 undfined。

因此,在上述代码中,log1 函数如果不显示传入函数的参数,TypeScript 就会报一个 ts(2554) 的错误,即函数需要 1 个参数,但是我们只传入了 0 个参数。

在 ES6 中支持函数默认参数的功能。而 TypeScript 会根据函数的默认参数的类型来推断函数参数的类型,示例如下:

function log(x = 'hello') 
    console.log(x);

log(); // => 'hello'
log('hi'); // => 'hi'
log(1); // ts(2345) Argument of type '1' is not assignable to parameter of type 'string | undefined'

在上述示例中,根据函数的默认参数 'hello' ,TypeScript 推断出了 x 的类型为 string | undefined

当然,对于默认参数,TypeScript 也可以显式声明参数的类型(一般默认参数的类型是参数类型的子集时,我们才需要这么做)。不过,此时的默认参数只起到参数默认值的作用,如下代码所示:

function log1(x: string = 'hello') 
    console.log(x);

// ts(2322) Type 'string' is not assignable to type 'number'
function log2(x: number = 'hello') 
    console.log(x);

log2();
log2(1);
log2('1'); // ts(2345) Argument of type '"1"' is not assignable to parameter of type 'number | undefined'

上例函数 log2 中,我们显式声明了函数参数 x 的类型为 number,表示函数参数 x 的类型可以不传或者是 number 类型。因此,如果我们将默认值设置为字符串类型,编译器就会抛出一个 ts(2322) 的错误。

同理,如果我们将函数的参数传入了字符串类型,编译器也会抛出一个 ts(2345) 的错误。

请再次注意:函数的默认参数类型必须是参数类型的子类型

function log3(x: number | string = 'hello') 
    console.log(x);

在上述代码中,函数 log3 的函数参数 x 的类型为可选的联合类型 number | string,但是因为默认参数字符串类型是联合类型 number | string 的子类型,所以 TypeScript 也会检查通过。

剩余参数

在 ES6 中,JavaScript 支持函数参数的剩余参数,它可以把多个参数收集到一个变量中。同样,在TypeScript 中也支持这样的参数类型定义,如下代码所示:

function sum(...nums: number[]) 
    return nums.reduce((a, b) => a + b, 0);

sum(1, 2); // => 3
sum(1, 2, 3); // => 6
sum(1, '2'); // ts(2345) Argument of type 'string' is not assignable to parameter of type 'number'

在上述代码中,sum 是一个求和的函数,...nums将函数的所有参数收集到了变量 nums 中,而 nums 的类型应该是 number[],表示所有被求和的参数是数字类型。因此,sum(1, '2') 抛出了一个 ts(2345) 的错误,因为参数 '2' 并不是 number 类型。

如果我们将函数参数 nums 聚合的类型定义为 (number | string)[],如下代码所示:

function sum(...nums: (number | string)[]): number 
    return nums.reduce<number>((a, b) => a + Number(b), 0);

sum(1, '2', 3); // 6

那么,函数的每一个参数的类型就是联合类型 number | string,因此 sum(1, '2', 3) 的类型检查也就通过了。


typescript中的常见报错:

  • ts(2322)是一个静态类型检查的错误,在注解的类型和赋值的类型不同的时候就会抛出这个错误
  • ts(2355) :简单来说就是:函数必须有一个返回值(报错翻译:声明类型既不是void也不是any的函数必须返回一个值)
  • ts(2339): 在TypeScript中如果按JS的方式去获取对象属性时,可能会遇到。它表示Typescript在执行代码检查时在该对象没有定义相应属性。解决方法有 —— 直接设置对象为any、对象断言为any、将属性.获取方式改为数组式获取三种
  • ts(2345):参数类型与预期不符
  • ts(2554):参数个数与预期不符

以上是关于我在坑底的typescript手记之返回值类型和参数类型的主要内容,如果未能解决你的问题,请参考以下文章

我在坑底的typescript手记之几种特殊的变量类型

我在坑底的typescript手记之几种特殊的变量类型

TypeScript 之 函数

CocosCreator手记03——简单配置VSCode的TypeScript环境

TypeScript之函数

在工厂函数之外公开本地 TypeScript 类的类型