ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] # 忽略错误 有时候不想处理不相干的错误 1. 单行忽略 `// @ts-ignore` 2. 忽略全文 `// @ts-nocheck` 3. 取消忽略全文 `// @ts-check` # `unknown` 在 TypeScript 中,任何类型都可以被归为`any`类型。这让`any`类型成为了类型系统的 [**顶级类型**](https://en.wikipedia.org/wiki/Top_type) (也被称作 **全局超级类型**)。 使用 `any` 类型,可以很容易地编写类型正确但是执行异常的代码。如果我们使用 `any` 类型,就无法享受 TypeScript 大量的保护机制。 但**如果能有顶级类型也能默认保持安全**呢? `unknown` 就是一种可以保证安全的顶级类型: ``` let value: unknown; value.foo.bar; // Error value.trim(); // Error value(); // Error new value(); // Error value[0][1]; // Error ``` 以上代码就会被认为不是安全的,而被报错! TypeScript 不允许我们对类型为 `unknown` 的值执行任意操作。相反,我们必须首先执行某种类型检查以缩小我们正在使用的值的类型范围。 1. TypeScript 的[基于控制流的类型分析](https://mariusschulz.com/blog/typescript-2-0-control-flow-based-type-analysis) 可以让我们进行范围缩小的安全操作: ```js ... if (typeof value === "function") { ... if (value instanceof Date) { ... function isNumberArray(value: unknown): value is number[] { ... ``` 2. 对 `unknown` 类型使用类型断言 ```js const value: unknown = 42; const someString: string = value as string; const otherString = someString.toUpperCase(); // BOOM ``` ## 其他特点 所有类型的值都可以被定义为 `unknown` 类型,也包括了 `string` 类型,因此,`unknown | string` 就是和 `unknown` 类型本身相同的值集。 ```js type UnionType3 = unknown | string; // unknown ``` `unknown & null` 类型表示可以被同时赋值给 `unknown` 和 `null` 类型的值。所以在交叉类型中将只剩下 `null` 类型。 ```js type IntersectionType1 = unknown & null; // null ``` 类型为 `unknown` 的值上使用的运算符,**只有等和不等**: * `===` * `==` * `!==` * `!=` # `Never` TypeScript 中的底层类型。`never`表示的是**那些永不存在的值的类型**,比如在函数中抛出异常或者无限循环(Xee: 它们是永远不可能有确切的值): * 一个从来不会有返回值的函数(如:如果函数内含有`while(true) {}`); * 一个总是会抛出错误的函数(如:`function foo() { throw new Error('Not Implemented') }`,`foo`的返回类型是`never`); * `never`类型只能被赋值给另外一个`never` ``` function foo(x: string | number): boolean { if (typeof x === 'string') { return true; } else if (typeof x === 'number') { return false; } // 如果不是一个 never 类型,这会报错: // - 不是所有条件都有返回值 (严格模式下) // - 或者检查到无法访问的代码 // 但是由于 TypeScript 理解 `fail` 函数返回为 `never` 类型 // 它可以让你调用它,因为你可能会在运行时用它来做安全或者详细的检查。 return fail('Unexhaustive'); } function fail(message: string): never { throw new Error(message); } ``` # `new()`构造函数类型 在 typescript 中,要实现工厂模式是很容易的,我们只需要先声明一个构造函数的类型参数,它构造出来的是 T 类型 `new():T`,然后在函数体里面返回 `c` 这个类构造出来的对象即可。 ```js function create<T>(c: { new(): T }): T { return new c() } class Test { constructor() { console.log("hello test class") } } create(Test) ``` 在 TypeScript 语言规范中这样定义构造函数类型: 1. 构造函数类型字面量: ```js new ( p1, p2, ... ) => R ``` 2. 构造签名的对象类型字面量: ```js { new ( p1, p2, ... ) : R } ``` 两者是等价的。 > [TS 的构造签名和构造函数类型是啥?傻傻分不清楚](http://www.semlinker.com/ts-constructor-type/) > [what is new() in Typescript?](https://stackoverflow.com/questions/39622778/what-is-new-in-typescript) # 条件类型及 infer 条件类型会以一个条件表达式进行类型关系检测,从而在两种类型中选择其一: ``` T extends U ? X : Y ``` 以上表达式的意思是:若`T`能够赋值给`U`,那么类型是`X`,否则为`Y`。 在条件类型表达式中,我们可以用`infer`声明一个类型变量并且对它进行使用。 ```js async function stringPromise() { return "Hello, Semlinker!"; } interface Person { name: string; age: number; } async function personPromise() { return { name: "Semlinker", age: 30 } as Person; } type PromiseType<T> = (args: any[]) => Promise<T>; type UnPromisify<T> = T extends PromiseType<infer U> ? U : never; type extractStringPromise = UnPromisify<typeof stringPromise>; // string type extractPersonPromise = UnPromisify<typeof personPromise>; // Person ``` 其实这里的 infer R 就是声明一个**变量来承载传入函数签名的返回值类型**,简单说就是用它取到函数返回值的类型方便之后使用. # 类型保护 类型保护我的理解是,通过 if 等条件语句的判断告诉编译器,**我知道它的类型了,可以放心调用这种类型的方法**,常用的类型保护有`typeof`类型保护,`instanceof`类型保护和`自定义`类型保护 示例: ```js let x: any; if (typeof x === 'symbol') { // any类型,typeof 类型保护只适用 number、string、boolean 或 symbol } let x: Date | RegExp; if (x instanceof RegExp) { // 正确 instanceof 类型保护,自动对应到 RegExp 实例类型 x.test(''); } type Foo = { kind: 'foo'; // 字面量类型 foo: number; }; type Bar = { kind: 'bar'; // 字面量类型 bar: number; }; function doStuff(arg: Foo | Bar) { if (arg.kind === 'foo') { // 在这个块中,TypeScript 知道 `arg` 的类型必须是 `Foo` console.log(arg.foo); // ok console.log(arg.bar); // Error } else { // 一定是 Bar console.log(arg.foo); // Error console.log(arg.bar); // ok } } ``` 自定义类型保护 ``` interface RequestParams { url: string, onSuccess?: () => void, onFailure?: () => void } // 断言告诉编译器通过 isValidRequestParams,判断 request 就是 RequestParams 类型的参数 function isValidRequestParams(request: any): request is RequestParams { return request && request.url } let request // 检测客户端发送过来的参数 if (isValidRequestParams(request)){ request.url } ``` # 任意属性 有时候我们希望一个接口允许有任意的属性,可以使用如下方式: ~~~ interface Person { name: string; age?: number; [propName: string]: any; } let xcatliu: Person = { name: 'Xcat Liu', website: 'http://xcatliu.com', }; ~~~ 使用 `[propName: string]` 定义了任意属性取 `string` 类型的值。 需要注意的是,**一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性:** ```js interface Person { name: string; age?: number; [propName: string]: string; } let xcatliu: Person = { name: 'Xcat Liu', age: 25, website: 'http://xcatliu.com', }; // index.ts(3,3): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'. // index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; website: string; }' is not assignable to type 'Person'. // Index signatures are incompatible. // Type 'string | number' is not assignable to type 'string'. // Type 'number' is not assignable to type 'string'. ``` 上例中,任意属性的值允许是 `string`,但是可选属性 `age` 的值却是 `number`,`number` 不是 `string` 的子属性,所以报错了。 # 联合类型 `A | B` 要么是 A 要么是 B,因此只有所有源类型的公共成员(“交集”)才能访问。 ``` interface A { name: string, age: number, sayName: (name: string) => void } interface B { name: string, gender: string, sayGender: (gender: string) => void } let a: A | B // 可以访问 a.name // 不可以访问 a.age a.sayGender() ``` 上面的例子中只能访问公共的属性,因为编译器不知道到底是 A 类型还是 B 类型,所以只能访问公共的属性。 # 交叉类型 组合多个类型组成新的类型,**新类型包含了原类型的所有类型属性**。 ```js interface ObjectConstructor { assign<T, U>(target: T, source: U): T & U; } ``` 上面的是 ts 的源码,可以看到这里将 U 拷贝到 T 类型,返回了 T 和 U 的交叉类型 再举一个简单的例子: ```js interface A { name: string, age: number, sayName: (name: string) => void } interface B { name: string, gender: string, sayGender: (gender: string) => void } let a: A & B // 都是合法的 a.age a.sayName ``` 这里面的 a 对象就继承了接口定义的所有属性,相当于同时实现了两个接口 # 可索引类型 与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如`a[10]`或`ageMap["daniel"]`。 可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型 ```js interface StringArray { [index: number]: string; } let myArray: StringArray; myArray = ["Bob", "Fred"]; let myStr: string = myArray[0]; // 打印Bob ``` 定义了 StringArray 接口,它具有索引签名。 这个索引签名表示了当用 `number` 去索引 StringArray 时会得到 `string` 类型的返回值 1. TypeScript 支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 `number` 来索引时,JavaScript 会将它转换成 `string` 然后再去索引对象 (Xee:这里我觉得应该是 **`string` 属于 `number`的子类型** )。 也就是说用 100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。 2. 索引签名的名称(如:`{ [index: string]: { message: string } }`里的`index`)**除了可读性外,并没有任何意义**。例如:如果有一个用户名,你可以使用`{ username: string}: { message: string }`,这有利于下一个开发者理解你的代码。 # 类型别名 [TypeScript 强大的类型别名](https://juejin.im/post/5c2f87ce5188252593122c98#heading-1) 还可以这么玩?超实用 Typescript 内置类型与自定义类型 [https://juejin.im/post/5cf7d87cf265da1ba328b405#heading-2](https://juejin.im/post/5cf7d87cf265da1ba328b405#heading-2) # 参考学习库 [react UI 组件库](https://github.com/jsrdxzw/r-recruit-ui) [Ajax 网络请求库](https://github.com/jsrdxzw/ts-axios) # 参考 [TypeScript学习笔记之对象类型](https://juejin.im/post/5c45511af265da617265c489) [深入理解 TypeScript 中文翻译版](https://jkchao.github.io/typescript-book-chinese/) [https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Namespaces.html](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Namespaces.html) [https://github.com/whxaxes/blog/issues/19](https://github.com/whxaxes/blog/issues/19)