箭头函数
function greeter(fn: (a: string) => void) {
fn("Hello, World");
}
//单独定义函数的参数类型
type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
// ...
}
调用签名
Javascript中,函数除了可以调用之外还可以具有属性,但是,函数类型表达式语法不允许生命属性。如果我们想用属性描述可调用的东西,我们可以在对象类型中编写调用签名
type DescribableFunction = {
description:string;
(someArg:number):boolean;
}
function doSomething(fn:DescribableFunction){
console.log(fn.description + " returned " + fn(6));
}
function myFunc(someArg:number){
return someArg>3;
}
myFunc.description = "defult description";
doSomething(myFunc);
构造签名
Javascript函数也可以使用new运算符调用。Typescript将他们称为构造函数,因为他们通常会创建一个新对象,你可以通过在调用签名钱添加new关键字来编写构造签名
type SomeObject = any;
// ---cut---
type SomeConstructor = {
new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
return new ctor("hello");
}
一些对象,比如Javascript的Data对象,可以在有或没有new的情况下调用,你可以任意组合相同各类型的调用和构造签名:
interface CallOrConstruct {
new (s: string): Date;
(n?: number): string;
}
泛型
function firstElement<Type>(arr: Type[]): Type | undefined {
return arr[0];
}
// s is of type 'string'
const s = firstElement(["a", "b", "c"]);
// n is of type 'number'
const n = firstElement([1, 2, 3]);
// u is of type undefined
const u = firstElement([]);
约束条件
我们编写函数,可以处理任何类型的值,有时我们想关联两个值,但只能对某个值的子集进行操作,在这种情况下我们可以使用约束来限制类型参数可以接受的类型种类
下面编写一个返回两个值中较长者的函数,为此,我们需要一个length属性,他是一个数字,我们通过编写extends子句将类型参数限制为该类型:
// @errors: 2345 2322
function longest<Type extends { length: number }>(a: Type, b: Type) {
if (a.length >= b.length) {
return a;
} else {
return b;
}
}
// longerArray is of type 'number[]'
const longerArray = longest([1, 2], [1, 2, 3]);
// longerString is of type 'alice' | 'bob'
const longerString = longest("alice", "bob");
// Error! Numbers don't have a 'length' property
const notOK = longest(10, 100);
因为我们将Type限制为{length:number},所以我们可以访问a和b参数的.length属性。如果没有类型约束,我们将无法访问这些属性,因为这些值可能是没有长度属性的其他类型。
使用约束值
我们经常会通过约束值范一个常见的错误
function minimumLength<Type extends { length: number }>(
obj: Type,
minimum: number
): Type {
if (obj.length >= minimum) {
return obj;
} else {
return { length: minimum };
Type '{ length: number; }' is not assignable to type 'Type'.
'{ length: number; }' is assignable to the constraint of type 'Type', but 'Type' could be instantiated with a different subtype of constraint '{ length: number; }'.
}
}
看起来这个函数没问题 - Type 被约束为 { length: number },并且该函数返回 Type 或与该约束匹配的值。 问题是该函数 promise 返回与传入相同类型的对象,而不仅仅是与约束匹配的某个对象。 如果这段代码是合法的,你可以编写绝对行不通的代码:
// 'arr' gets value { length: 6 }
const arr = minimumLength([1, 2, 3], 6);
// and crashes here because arrays have
// a 'slice' method, but not the returned object!
console.log(arr.slice(0));
指定类型参数
Typescript通常可以在泛型调用中推断出预期的类型参数,但并非总是如此,例如:假设你编写了一个函数来组合两个数组
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2);
}
但是使用不匹配的数组调用此函数就会出错
我们使用手动指定Type来进行更改
const arr = combine<string | number>([1, 2, 3], ["hello"]);
推断
有些时候我们也可以不必指定Type,类型会自动推断例如map
// prettier-ignore
function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
return arr.map(func);
}
// Parameter 'n' is of type 'string'
// 'parsed' is of type 'number[]'
const parsed = map(["1", "2", "3"], (n) => parseInt(n));
可选参数
可选参数包括undefined
declare function f(x?: number): void;
// cut
// All OK
f();
f(10);
f(undefined);
回调中的可选参数
我们了解可选参数和函数类型表达式,在编写调用回调函数的时候就很容易犯错。
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
for (let i = 0; i < arr.length; i++) {
callback(arr[i], i);
}
}
在编写index?作为可选参数时通常的意图时他们希望这两个调用都是合法的
myForEach([1, 2, 3], (a) => console.log(a));
myForEach([1, 2, 3], (a, i) => console.log(a, i));
这实际上意味着callback可能会被一个参数调用,换句话说,函数定义表明实际可能如下所示
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
for (let i = 0; i < arr.length; i++) {
// I don't feel like providing the index today
callback(arr[i]);
}
}
反过来,Typescript将强制执行此含义并处罚实际上不可能的错误
myForEach([1, 2, 3], (a, i) => {
console.log(i.toFixed());
'i' is possibly 'undefined'.
});
在 JavaScript 中,如果你调用一个参数多于参数的函数,多余的参数将被忽略。 TypeScript 的行为方式相同。 具有较少参数(相同类型)的函数总是可以代替具有更多参数的函数。
规则:为回调编写函数类型时,切勿编写可选参数,除非你打算在不传递该参数的情况下调用该函数
函数重载
重载分为两部分组成(缺一不可)
- 声明
- 实现
//类型声明
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
//类型实现
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);
No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
//这里写了两个重载:一个接受一个参数,另一个接受三个参数,这里需要注意我们编写了一个具有兼容签名的函数实现。函数有一个实现签名,但是这个签名不能直接调用。即使我们在必须的参数之后编写了一个带有两个可选参数的函数,也不能用两个参数调用它。
重载签名和实现签名
如上面所说一个重载函数需要两部分组成
- 常见错误一
function fn(x: string): void;
function fn() {
// ...
}
// Expected to be able to call with zero arguments
fn();
Expected 1 arguments, but got 0.
一看上面的代码就有问题,出现在了我们的重载和实现不兼容,从外部看不到实现的签名,在编写重载函数时,你应该实现两个或多个签名例如
// @errors: 2554
function fn(x: string): void;
function fn():void;
function fn(add?:string){
console.log('9999')
}
// Expected to be able to call with zero arguments
fn();
- 常见错误二
function fn(x: boolean): void;
// Argument type isn't right
function fn(x: string): void;
This overload signature is not compatible with its implementation signature.
function fn(x: boolean) {}
//更改
function fn(x: boolean | string) {}
function fn(x: string): string;
// Return type isn't right
function fn(x: number): boolean;
This overload signature is not compatible with its implementation signature.
function fn(x: string | number) {
return "oops";
}
//更改
function fn(x: string | number):string|boolean {
if(typeof x == "string"){
return "oops";
}else{
return true
}
}
- 常见错误三
function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
return x.length;
}
我们可以用字符串或数组调用它,我们可以用字符串或数组调用它。 但是,我们不能使用可能是字符串或数组的值来调用它,因为 TypeScript 只能将函数调用解析为单个重载:
因为两个重载具有相同的参数计数和相同的返回类型。我们可一个更改为
function len(x: any[] | string) {
return x.length;
}
关于object
特殊类型 object 指的是任何非原始值(string、number、bigint、boolean、symbol、null 或 undefined)。 这与空对象类型 { } 不同,也与全局类型 Object 不同。 你很可能永远不会使用 Object
object不是Object。总是使用object!
关于unknow
unknow类型代表任何值,这类似于any,但更安全。因为使用unknown值做任何事情都是不合法的。
function f1(a: any) {
a.b(); // OK
}
function f2(a: unknown) {
a.b();
'a' is of type 'unknown'.
}
也可以描述返回类型值的函数
function safeParse(s: string): unknown {
return JSON.parse(s);
}
// Need to be careful with 'obj'!
const obj = safeParse(someRandomString);
never
- 函数不返回值可以设置never
function fail(msg: string): never {
throw new Error(msg);
}
- 当 TypeScript 确定联合中没有任何内容时,never 也会出现。
function fn(x: string | number) {
if (typeof x === "string") {
// do something
} else if (typeof x === "number") {
// do something else
} else {
x; // has type 'never'!
}
}
Function
全局类型 Function 描述了 bind、call、apply 等属性,以及 JavaScript 中所有函数值上的其他属性。 它还具有 Function 类型的值始终可以被调用的特殊属性; 这些调用返回 any:
function doSomething(f: Function) {
return f(1, 2, 3);
}
这是一个无类型的函数调用,通常最好避免,因为不安全的 any 返回类型。
如果你需要接受任意函数但不打算调用它,则类型 () => void 通常更安全。
参数解构
你可以使用参数解构来方便地将作为参数提供的对象解压缩到函数体中的一个或多个局部变量中。 在 JavaScript 中,它看起来像这样:
function sum({ a, b, c }) {
console.log(a + b + c);
}
sum({ a: 10, b: 3, c: 9 });
对象的类型注释遵循解构语法:
function sum({ a, b, c }: { a: number; b: number; c: number }) {
console.log(a + b + c);
}
将类型提取
// Same as prior example
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
console.log(a + b + c);
}