文章串列:
-
《一》大話 TypeScript 基本型別
-
《二》大話 Typescript 列舉
-
《三》大話 Typescript 介面
-
《四》大話 Typescript 泛型
-
《五》大話 Typescript 函式與類
-
《六》Typescript 最佳實踐
為了更好的閱讀體驗, 可以看.
一年前剛接觸 Typescript 的時候, 覺得它加大了代碼作業量. 寫一大堆東西.為了找某個型別東奔西跑, 引入第三庫還經常報錯.
然而現在的我想說: 真香.
我們經常吐槽別人代碼可維護性特別低, 總是希望別人能夠主動的寫注釋, 可是寫注釋卻沒有任何方式可以進行約束. 這下好了, 型別就是最好的注釋, 用 Typescript, 可以大大提高代碼的可維護性.
一. 如何處理第三方庫型別相關問題
Typescipt 所提供的第三方庫型別定義不僅約束我們的輸入呼叫, 還能為我們提供檔案. 現在, NPM 上的第三方型別定義種類繁多,很難保證型別定義是正確的. 也很難保證所有使用的第三方庫都有型別定義.
那么, 在這個充滿未知的程序中,如何才能正確使用TypeScript中的第三方庫呢?
下面列舉了四種常見的無法正常作業的場景以及對應的解決方法:
-
庫本身沒有自帶型別定義
-
庫本身沒有型別定義, 也沒有相關的@type
-
型別宣告庫有誤
-
型別宣告報錯
1. 庫本身沒有自帶型別定義
查找不到相關的庫型別. 舉個栗子
在初次將 react 改造支持 typescript 時, 想必很多人都會遇到 module.hot 報錯. 此時只需要安裝對應的型別庫即可.
安裝 @types/webpack-env
2. 庫本身沒有型別定義, 也沒有相關的@type
那只能自己宣告一個了. 隨便舉個栗子.
declare module "lodash"
3. 型別宣告庫有誤
-
推動解決官方型別定義的問題, 提issue, pr
-
Import 后通過 extends 或者 merge 能力對原型別進行擴展
-
忍受型別的丟失或不可靠性
-
使用 // @ts-ignore 忽略
4. 型別宣告報錯
-
在 compilerOptions 的添加"skipLibCheck": true, 曲線救國
二. 巧用型別收縮解決報錯下面列舉了幾種常見的解決方法:
-
型別斷言
-
型別守衛 typeof in instanceof 字面量型別保護
-
雙重斷言
1、 型別斷言
型別斷言可以明確的告訴 TypeScript 值的詳細型別,
在某些場景, 我們非常確認它的型別, 即使與 typescript 推斷出來的型別不一致. 那我們可以使用型別斷言.
語法如下:
<型別>值
值 as 型別 // 推薦使用這種語法. 因為<>容易跟泛型, react 中的語法起沖突
舉個例子, 如下代碼, padding 值可以是 string , 也可以是 number, 雖然在代碼里面寫了 Array(), 我們明確的知道, padding 會被parseint 轉換成 number 型別, 但型別定義依然會報錯.
function padLeft(value: string, padding: string | number) {
// 報錯: Operator '+' cannot be applied to
// types 'string | number' and 'number'
return Array(padding + 1).join(" ") + value;
}
解決方法, 使用型別斷言. 告訴 typescript 這里我確認它是 number 型別, 忽略報錯.
function padLeft(value: string, padding: string | number) {
// 正常
return Array(padding as number + 1).join(" ") + value;
}
但是如果有下面這種情況, 我們要寫很多個 as 么?
function padLeft(value: string, padding: string | number) {
console.log((padding as number) + 3);
console.log((padding as number) + 2);
console.log((padding as number) + 5);
return Array((padding as number) + 1).join(' ') + value;
}
2、 型別守衛
型別守衛有以下幾種方式, 簡單的概括以下
-
typeof: 用于判斷 "number","string","boolean"或 "symbol" 四種型別.
-
instanceof : 用于判斷一個實體是否屬于某個類
-
in: 用于判斷一個屬性/方法是否屬于某個物件
-
字面量型別保護
上面的例子中, 是 string | number 型別, 因此使用 typeof 來進行型別守衛. 例子如下:
function padLeft(value: string,padding: string | number) {
if (typeof padding === 'number') {
console.log(padding + 3); //正常
console.log(padding + 2); //正常
console.log(padding + 5); //正常
//正常
return Array(padding + 1).join(' ') value;
}
if (typeof padding === 'string') {
return padding + value;
}
}
相比較 型別斷言 as , 省去了大量代碼. 除了 typeof , 我們還有幾種方式, 下面一一舉例子.
-
instanceof :用于判斷一個實體是否屬于某個類
class Man {
handsome = 'handsome';
}
class Woman {
beautiful = 'beautiful';
}
function Human(arg: Man | Woman) {
if (arg instanceof Man) {
console.log(arg.handsome);
console.log(arg.beautiful); // error
} else {
// 這一塊中一定是 Woman
console.log(arg.beautiful);
}
}
-
in : 用于判斷一個屬性/方法是否屬于某個物件
interface B {
b: string;
}
interface A {
a: string;
}
function foo(x: A | B) {
if ('a' in x) {
return x.a;
}
return x.b;
}
-
字面量型別保護
有些場景, 使用 in, instanceof, typeof 太過麻煩. 這時候可以自己構造一個字面量型別.
type Man = {
handsome: 'handsome';
type: 'man';
};
type Woman = {
beautiful: 'beautiful';
type: 'woman';
};
function Human(arg: Man | Woman) {
if (arg.type === 'man') {
console.log(arg.handsome);
console.log(arg.beautiful); // error
} else {
// 這一塊中一定是 Woman
console.log(arg.beautiful);
}
}
3、雙重斷言
有些時候使用 as 也會報錯,因為 as 斷言的時候也不是毫無條件的. 它只有當S型別是T型別的子集,或者T型別是S型別的子集時,S能被成功斷言成T.
所以面對這種情況, 只想暴力解決問題的情況, 可以使用雙重斷言.
function handler(event: Event) {
const element = event as HTMLElement;
// Error: 'Event' 和 'HTMLElement'
中的任何一個都不能賦值給另外一個
}
如果你仍然想使用那個型別,你可以使用雙重斷言,首先斷言成兼容所有型別的any
function handler(event: Event) {
const element = (event as any) as HTMLElement;
// 正常
}
三. 巧用 typescript 支持的 js 最新特性優化代碼
1. 可選鏈 Optional Chaining
let x = foo?.bar.baz();
typescript 中的實作如下:
var _a;
let x = (_a = foo) === null ||
_a === void 0 ? void 0 : _a.bar.baz();
利用這個特性, 我們可以省去寫很多惡心的 a && a.b && a.b.c 這樣的代碼
2. 空值聯合 Nullish Coalescing
let x = foo ?? '22';
typescript 中的實作如下:
let x = (foo !== null && foo !== void 0 ?
foo : '22');
四. 巧用高級型別靈活處理資料typescript 提供了一些很不錯的工具函式. 如下圖

-
型別索引
為了實作上面的工具函式, 我們需要先了解以下幾個語法:
keyof : 獲取型別上的 key 值
extends : 泛型里面的約束
T[K] : 獲取物件 T 相應 K 的元素型別
type Partial<T> = {
[P in keyof T]?: T[P]
}
在使用 props 的時候, 有時候全部屬性都是可選的, 如果一個一個屬性寫 ? , 大量的重復動作. 這種時候可以直接使用 Partial<State>
Record 作為一個特別靈活的工具. 第一個泛型傳入物件的key值, 第二個傳入 物件的屬性值.
type Record<K extends string, T> = {
[P in K]: T;
}
我們看一下下面的這個物件, 你會怎么用 ts 宣告它?
const AnimalMap = {
cat: { name: '貓', title: 'cat' },
dog: { name: '狗', title: 'dog' },
frog: { name: '蛙', title: 'wa' },
};
此時用 Record 即可.
type AnimalType = 'cat' | 'dog' | 'frog';
interface AnimalDescription {
name: string, title: string
}
const AnimalMap:
Record<AnimalType, AnimalDescription> = {
cat: { name: '貓', title: 'cat' },
dog: { name: '狗', title: 'dog' },
frog: { name: '蛙', title: 'wa' },
};
-
never, 構造條件型別
除了上面的幾個語法. 我們還可以用 never , 構造條件型別來組合出更靈活的型別定義.
語法:
never: 從未出現的值的型別
// 如果 T 是 U 的子型別的話,那么就會回傳 X,否則回傳 Y
構造條件型別 : T extends U ? X : Y
type Exclude<T, U> = T extends U ? never : T;
// 相當于: type A = 'a'
type A = Exclude<'x' | 'a', 'x' | 'y' | 'z'>
-
更簡潔的修飾符: - 與 +
可以直接去除 ? 將所有物件屬性變成必傳內容.
type Required<T> = { [P in keyof T]-?: T[P] };
// Remove readonly
type MutableRequired<T> = {
-readonly [P in keyof T]: T[P]
};
-
infer: 在 extends 條件陳述句中待推斷的型別變數,
// 需要獲取到 Promise 型別里蘊含的值
type PromiseVal<P> =
P extends Promise<infer INNER> ? INNER : P;
type PStr = Promise<string>;
// Test === string
type Test = PromiseVal<PStr>;
五. 辨別 type & interface
在各大型別庫中, 會看到形形色色的 type 和 interface . 然而很多人在實際中卻不知道它們的區別.
官網的定義如下:
An interface can be named in an extends or implements clause, but a type alias for an object type literal cannot.
An interface can have multiple merged declarations, but a type alias for an object type literal cannot.
從一張圖看出它們兩的區別:

建議: 能用 interface 實作,就用 interface , 如果不能才用 type.
為了更好的閱讀體驗, 《typescrit 最佳實踐》
- 歡迎關注「前端加加」,認真學前端,做個有專業的技術人...
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/158910.html
標籤:JavaScript
上一篇:vue基礎用法
