[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)