在 JavaScript 中,我们知道函数不能有显式返回,并且函数的返回值应该是未定义的:
function fn() {
// TODO
}
console.log(fn()); // => undefined
需要注意的是,在 TypeScript 中,如果我们显式声明函数的返回值类型未定义,则会得到如下所示的错误消息。
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中的=>就是该函数的实现。
在对象(即socket类型)中,我们不仅可以使用这些声明句型typescript 参数类型,还可以使用类似于对象属性的缩写句型来声明函数类型的属性,例如:
interface Entity {
add: (a: number, b:) => number;
del(a: number, b: number): number;
}
const entity: Entity = {
add: (a,) => a + b,
del(a,) {
return a - b;
},
};
从某种意义上说,这两种方法是等效的。 而很多时候,我们为什么要或者不能显式指定返回值的类型,这就涉及到可以默认和推导的返回值类型的解释。
可默认和可推测的返回类型
幸运的是,TypeScript 中可以推断函数返回值的类型,即可以默认。
函数是一个相对独立的上下文,我们可以根据输入参数进行处理和估计值,并返回一个新值。 从类型的角度来看,我们还可以通过类型推断处理来估计输入参数的类型,并返回一个新的类型,如下例所示:
function computeTypes(one: string, two:) {
const nums = [two];
const strs = [one]
return {
nums,
strs
} // 返回 { nums: number[]; strs: string[] } 的类型
}
请记住:这是一个非常重要且有趣的功能。 函数返回值的类型推断结合类库可以实现非常复杂的类型推断,例如 ReduxModel 中State、Reducer、Effect 类型的关联。
通常,TypeScript 中函数的返回值类型是可以默认和推断的,有些特殊情况需要我们显式声明返回值类型,例如 Generator 函数的返回值。
Generator 函数的返回值
ES6 中新的 Generator 函数在 TypeScript 中也有相应的类型定义。
Generator 函数返回一个 Iterator 迭代器对象。 我们可以使用Generator的同名socket类库或者Iterator的同名socket类库来表示返回值的类型(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 之前的版本不支持指定 next、return的类型,因此在有一些历史的各个代码中,我们可能会看到 Generator 和 Iterator 类型的不同描述。
参数类型
关于JS中函数的参数,在es6之后,可以分为几个特点:可选参数、默认参数、剩余参数。
可选参数和默认参数
在实际工作中,我们可能经常会看到函数参数可传可不传。 事实上,TypeScript也支持这些函数类型的表达,如下代码所示:
function log(x?:) {
return x;
}
log(); // => undefined
log('hello world'); // => hello world
上面代码中,我们在类型指示中的:前添加?,表示log函数的参数x是可默认的。
也就是说参数x的类型可能是未定义的(第5行调用log时不传入形参)或者字符串类型(第6行调用log传入'helloworld'形参) ,这是否意味着默认值和类型是未定义的等价?
function log(x?:) {
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);
很明显:这里?:表示参数可以默认,也可以不传,但如果传的话,必须是字符串类型。 也就是说,在调用函数时,我们不能显式传入参数。 并且typescript 参数类型,如果我们声明参数类型为xxx|undefined,则意味着函数参数不能默认,类型必须为xxx或undefined。
因此,在上面的代码中,如果 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'
在上面的例子中,TypeScript 根据函数的默认参数 `hello' 推断出 x 的类型是 string|undefined。
事实上,对于默认参数,TypeScript 也可以显式声明参数的类型(通常我们只有在默认参数的类型是参数类型的子集时才需要这样做)。 但此时的default参数仅作为参数的默认值,如下代码所示:
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,并且由于默认参数 string 类型是联合类型number|string的子类型,因此 TypeScript 会也通过测试。
剩余参数
在ES6中,JavaScript支持函数参数的剩余参数,可以将多个参数收集到一个变量中。 同样,TypeScript 中也支持这样的参数类型定义,如下代码所示:
function sum(...nums: number[]) {
return nums.reduce((a,) => 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' 不是数字类型。
假设我们定义函数参数nums的聚合类型为(number|string)[],如下代码所示:
function sum(...nums: (number | string)[]): number {
return nums.reduce<number>((a,) => a + Number(b), 0);
}
sum(1, '2', 3); // 6
这样,函数的每个参数的类型都是联合类型number|string,所以sum(1,'2',3)的类型检测也通过了。
打字稿中的常见错误: