需要注意的是,这里的=>与 ES6 中箭头函数的=>有所不同。TypeScript 函数类型中的=>用来表示函数的定义,其左侧是函数的参数类型,右侧是函数的返回值类型;而 ES6 中的=>是函数的实现。
type Adder = (a: number, b: number) => number; // TypeScript 函数类型定义const add: Adder = (a, b) => a + b; // 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;},};
请记住:这是一个很重要也很有意思的特性,函数返回值的类型推断结合泛型(我们会在 10 讲中详细介绍)可以实现特别复杂的类型计算(本质是复杂的类型推断,这里称之为计算是为了表明其复杂性),比如 Redux Model 中 State、Reducer、Effect 类型的关联。
function computeTypes(one: string, two: number) {const nums = [two]const strs = [one]return {nums,strs} // 返回 { nums: number[]; strs: string[] } 的类型}
Generator 函数的返回值
Generator 函数返回的是一个 Iterator 迭代器对象,我们可以使用 Generator 的同名接口泛型或者 Iterator 的同名接口泛型(在 10 讲会介绍)表示返回值的类型(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 类型}
可选参数和默认参数
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 0log1(undefined);
剩余参数
function sum(...nums: number[]) {return nums.reduce((a, b) => a + b, 0);}sum(1, 2); // => 3sum(1, 2, 3); // => 6sum(1, '2'); // ts(2345) Argument of type 'string' is not assignable to parameter of type 'number'
function sum(...nums: (number | string)[]): number {return nums.reduce<number>((a, b) => a + Number(b), 0);}sum(1, '2', 3); // 6
this
为了说明在typescript中this的声明方法,我们首先需要更改tsconfig.json的配置:
"compilerOptions": {..."noImplicitThis": true}
function say(this: Window, name: string) {console.log(this.name);}window.say = say;window.say('hi');const obj = {say};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没有在函数第一个参数中声明
function test(b: number, this: { a: number }) {const {a} = this;console.log(a);}test.call({ a: 1 });// error A 'this' parameter must be the first parameter. TS2680
错误的调用了this声明的函数
function test(this: { a: number }) {const {a} = this;console.log(a);}test({ a: 1 }); // 直接以传参数的形式调用test,Expected 0 arguments, but got 1. TS2554
函数重载
JavaScript 是一门动态语言,针对同一个函数,它可以有多种不同类型的参数与返回值,这就是函数的多态。
function convert(x: string | number | null): string | number | -1 {if (typeof x === 'string') {return Number(x);}if (typeof x === 'number') {return String(x);}return -1;}const x1 = convert('1'); // => string | numberconst x2 = convert(1); // => string | numberconst x3 = convert(null); // => string | number
函数重载(Function Overload),如下示例中 1~3 行定义了三种各不相同的函数类型列表,并描述了不同的参数类型对应不同的返回值类型,而从第 4 行开始才是函数的实现。
function convert(x: string): number;function convert(x: number): string;function convert(x: null): -1;function convert(x: string | number | null): any {if (typeof x === 'string') {return Number(x);}if (typeof x === 'number') {return String(x);}return -1;}const x1 = convert('1'); // => numberconst x2 = convert(1); // => stringconst x3 = convert(null); // -1
TypeScript 会从上到下查找函数重载列表中与入参类型匹配的类型,并优先使用第一个匹配的重载定义。因此,我们需要把最精确的函数重载放到前面。
例如下边,因为 P2 继承自 P1,所以类型为 P2 的参数会和类型为 P1 的参数一样匹配到第一个函数重载,此时 x1、x2 的返回值都是 number。
function convert(x: P1): number;function convert(x: P2): string;function convert(x: string | number | null | P1 | P2): string | number | -1 {if (typeof x === "string") {return Number(x);}if (typeof x === "number") {return String(x);}return -1;}interface P1 {name: string;}interface P2 extends P1 {age: number;}const x1 = convert({ name: '' } as P1); // => numberconst x2 = convert({ name: '', age: 18 } as P2); // => number
我们只需要将函数重载列表的顺序调换一下,类型为 P2 和 P1 的参数就可以分别匹配到正确的函数重载了
function convert(x: P2): string;function convert(x: P1): number;function convert(x: string | number | null | P1 | P2): string | number | -1 {if (typeof x === "string") {return Number(x);}if (typeof x === "number") {return String(x);}return -1;}interface P1 {name: string;}interface P2 extends P1 {age: number;}const x1 = convert({ name: '' } as P1); // => numberconst x2 = convert({ name: '', age: 18 } as P2); // => string
类型谓词(is)
function isString(s): s is string { // 类型谓词return typeof s === 'string';}function isNumber(n: number) {return typeof n === 'number';}function operator(x: unknown) {if(isString(x)) { // ok x 类型缩小为 string}if (isNumber(x)) { // ts(2345) unknown 不能赋值给 number}}
