关于TypeScript学习笔记汇总
本文承接上面内容做后续总结
函数
1.参数类型注解(Parameter Type Annotations)
在声明函数时,可以在每个参数后面添加类型标注,以声明函数接受哪些类型的参数。当一个参数有一个类型注释时,该函数的参数将被检查。
// Parameter type annotation
function greet(name: string) {
console.log("Hello, " + name.toUpperCase() + "!!");
}
// Would be a runtime error if executed!
greet(42); // Argument of type 'number' is not assignable to parameter of type 'string'.
2.返回类型注解(Return Type Annotations)
function getFavoriteNumber(): number {
return 26;
}
2.1 没有返回值的函数定义返回值类型为 void
let foo = function (str:string):void {
console.log('str',str);
}
foo('hello world');
2.2 函数返回值如果不确定,使用unkown
比如:这里s是 string类型,返回不一定是string,用 unkown 可以让代码更安全。
function safeParse(s: string): unknown {
return JSON.parse(s);
}
let x = safeParse('123') // 123 number类型
2.3 有些函数充不返回值用 never
Never 类型表示从未观察到的值。在返回类型中,这意味着函数抛出异常或终止程序的执行。
function fail(msg: string): never {
throw new Error(msg);
}
3.匿名函数(Anonymous Functions)
匿名函数与函数声明略有不同。当一个函数出现在可以决定如何调用它的地方时,该函数的参数将自动给定类型。
// No type annotations here, but TypeScript can spot the bug
const names = ["Alice", "Bob", "Eve"];
// Contextual typing for function
names.forEach(function (s) {
console.log(s.toUppercase());
//Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
// Contextual typing also applies to arrow functions
names.forEach((s) => {
console.log(s.toUppercase());
// Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'?
});
尽管参数 s
没有类型注释,但 TypeScript
使用 forEach
函数的类型以及数组的推断类型来确定类型 s
将具有的类型。这个过程成为
contexture typing
(根据上下文猜测匿名函数参数的类型)。例子中会报错,应该是toUpperCase(C大写)。
4.参数
4.1 可选参数
可以将参数标记为可选:
function foo(bar: number, bas?: string): void {
// ..
}
foo(123);
foo(123, 'hello');
4.2 默认参数
当调用者没有提供该参数时,你可以提供一个默认值(在参数声明后使用 = someValue
):
function foo(bar: number, bas: string = 'hello') {
console.log(bar, bas);
}
foo(123); // 123, hello
foo(123, 'world'); // 123, world
4.3 剩余参数
如果参数可以输入任意个数的参数,那就无法挨个定义,ES6中的...
拓展运算符,可以将 arguments
这个类数组对象进行结构。比如下面这个例子:
function multiply(n: number, ...m: number[]) {
return m.map((x) => n * x);
}
const result = multiply(10, 1, 2, 3, 4); // [10, 20, 30, 40]
5.重载(Overloaded)
当我们需要根据参数的不同类型,返回不同类型的结果时,可以使用函数重载。为同一个函数提供多个函数类型定义来进行函数重载。编译器会根据这个列表去处理函数的调用。编译器会依次查找重载列表,找到匹配的函数定义。
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); // Error , No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
上面例子中,我们写了两个重载: 一个接受一个参数,另一个接受三个参数。前两个签名称为重载签名。然后,我们编写了一个具有兼容签名的函数实现。
注意: 这三个 makeDate 函数必须挨着写,中间不能加任何其他的东西,否则就会报错
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
console.log(111)
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
//...
}
6.函数中声明 this
JavaScript 规范规定不能有一个名为 this 的参数,因此 TypeScript 使用这个语法空间在函数体中声明这个参数的类型。
interface User {
id: number;
admin: boolean;
}
declare const getDB: () => DB;
// ---cut---
interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}
const db = getDB();
// 这样描述函数里有this可以用
const admins = db.filterUsers(function (this: User) {
return this.admin;
});
注意,这里不能使用箭头函数
const admins = db.filterUsers(() => this.admin); // Error
// The containing arrow function captures the global value of 'this'.
// Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.
7.函数可转让性
函数的 void
返回类型可以产生一些不寻常但是预期的行为。带有 void
返回类型(type vf = () = > void)
的上下文函数类型在实现时可以返回任何其他值,但它将被忽略。
因此,下面示例中都不会报错:
type voidFunc = () => void;
const f1: voidFunc = () => {
return true;
};
const f2: voidFunc = () => true;
const f3: voidFunc = function () {
return true;
};
const v1 = f1(); // 返回是void v1没办法用
const v2 = f2(); // 返回是void v2没办法用
const v3 = f3(); // 返回是void v3没办法用
注意一点: 我们不能强制定义函数返回值是void,还让函数返回其他类型,这样会报错
function f2(): void {
return true; // Error
}
8.构造函数的表达
在函数参数中传入一个构造函数,使用new
关键字来表示构造函数
type SomeConstructor = {
new (s: string): String
}
function fn(ctor: SomeConstructor) {
return new ctor("hello")
}
const str = fn(String)
console.log(str) // hello
9.泛型函数
通常编写一个函数,其中输入的类型与输出的类型相关,或者两个输入的类型以某种方式相关。让我们暂时考虑一个返回数组第一个元素的函数:
function firstElement(arr: any[]) {
return arr[0];
}
这个函数的返回类型是 any,我们希望的是如果返回数组类型会更好。所以可以用到泛型
function firstElement<Type>(arr: Type[]): Type | undefined {
return arr[0];
}
通过向这个函数添加类型参数 Type 并在两个地方使用它,我们在函数的输入(数组)和输出(返回值)之间创建了一个链接。现在,当我们调用它的时候,一个更具体的类型出现了:
//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([]);
10. 推导
我们可以不用在示例中指定Type
,ts会自动推断
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));
在这个示例中,TypeScript 可以根据函数表达式的返回值(数字)推断出 Input 类型参数的类型(来自给定的字符串数组) ,以及 Output 类型参数。
11. 泛型约束
有时候我们想要关联两个值,但是只能对值的某个子集进行操作。在这种情况下,我们可以使用约束来限制类型参数可以接受的类型种类。
下面示例是一个函数,要返回两个值中较长的一个。要做到这一点,我们需要一个length
属性,它是一个数字。我们通过写一个 extends
子句将类型参数约束为该类型,约束所有Type
都有 length
这个属性。
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); // Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
注意下面这个示例会出错:
function minimumLength<Type extends { length: number }>(
obj: Type,
minimum: number
): Type {
if (obj.length >= minimum) {
return obj;
} else {
return { length: minimum }; // Error
// 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; }'.
}
}
我们看到 返回{ length: minimum }
看起来好像是可以的,但是我们需要的返回值是Type类型,而这里泛型约束 Type 有length属性,但是不是所有有length 属性的都是 Type
12. 指定类型参数
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2);
}
// 如果直接用不匹配的数组调用这个函数会报错
const arr = combine([1, 2, 3], ["hello"]);
// Type 'string' is not assignable to type 'number'.
我们可以手动指定Type
const arr = combine<string | number>([1, 2, 3], ["hello"]);
13.函数编写规范
13.1 下推类型参数
有两种编写类似函数的方法,哪种更好呢?
function firstElement1<Type>(arr: Type[]) {
return arr[0];
}
function firstElement2<Type extends any[]>(arr: Type) {
return arr[0];
}
// a: number (good)
const a = firstElement1([1, 2, 3]);
// b: any (bad)
const b = firstElement2([1, 2, 3]);
乍看觉得似乎完全相同,但是 firstElement1 编写的更好,他的推断返回的类型是Type
,firstelement2 的推断返回类型是 any
,因为 TypeScript 必须使用约束类型解析 arr[0]
表达式。尽可能使用参数本身的类型。
13.2 使用更少的类型参数
下面是一对类似的函数:
function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
return arr.filter(func);
}
function filter2<Type, Func extends (arg: Type) => boolean>(
arr: Type[],
func: Func
): Type[] {
return arr.filter(func);
}
filter1
更好,泛型参数少,因为 func 依赖Type就可以进行定义,就不需要再额外定义专门的泛型参数或者参数类型。否则会让函数更难阅读和推理!
13.3 类型参数应该出现两次
我们再看下面这个例子:
function greet<Str extends string>(s: Str) {
console.log("Hello, " + s);
}
greet("world");
这个例子也不好,因为这里都没有必要使用泛型,我们可以更简化它:
function greet(s: string) {
console.log("Hello, " + s);
}
所以能不用泛型就不用,如果一个类型参数只出现在一个位置,请重新考虑是否真的需要它