Typescript 最佳實踐

一年前剛接觸 typescript 的時候, 以爲它加大了代碼工做量. 寫一大堆東西.爲了找某個類型東奔西跑, 引入第三庫還常常報錯. 然而如今的我想說: 真香. 咱們常常吐槽別人代碼可維護性特別低, 老是但願別人可以主動的寫註釋, 但是寫註釋卻沒有任何方式能夠進行約束. 這下好了, 類型就是最好的註釋, 用 typescript, 能夠大大提升代碼的可維護性.前端

一. 如何處理第三方庫類型相關問題

Typescipt 所提供的第三方庫類型定義不只約束咱們的輸入調用, 還能爲咱們提供文檔. 如今, NPM 上的第三方類型定義種類繁多,很難保證類型定義是正確的. 也很難保證全部使用的第三方庫都有類型定義. 那麼, 在這個充滿未知的過程當中,如何才能正確使用TypeScript中的第三方庫呢? 下面列舉了四種常見的沒法正常工做的場景以及對應的解決方法:react

  • 庫自己沒有自帶類型定義
  • 庫自己沒有類型定義, 也沒有相關的@type
  • 類型聲明庫有誤
  • 類型聲明報錯
  1. 庫自己沒有自帶類型定義 查找不到相關的庫類型. 舉個栗子 webpack

    在初次將 react 改造支持 typescript 時, 想必不少人都會遇到 module.hot 報錯. 此時只須要安裝對應的類型庫便可. 安裝 @types/webpack-env

  2. 庫自己沒有類型定義, 也沒有相關的@type 那隻能本身聲明一個了. 隨便舉個栗子.web

declare module 「lodash」
複製代碼
  1. 類型聲明庫有誤
  • 推進解決官方類型定義的問題, 提issue, pr
  • Import 後經過 extends 或者 merge 能力對原類型進行擴展
  • 忍受類型的丟失或不可靠性
  • // @ts-ignore 忽略
  1. 類型聲明報錯
  • 在 compilerOptions 的添加"skipLibCheck": true, 曲線救國

二. 巧用類型收縮解決報錯

下面列舉了幾種常見的解決方法:typescript

  • 類型斷言
  • 類型守衛 typeof in instanceof 字面量類型保護
  • 雙重斷言

一、 類型斷言 類型斷言能夠明確的告訴 TypeScript 值的詳細類型, 在某些場景, 咱們很是確認它的類型, 即便與 typescript 推斷出來的類型不一致. 那咱們可使用類型斷言. 語法以下:bash

<類型>值
值 as 類型 // 推薦使用這種語法. 由於<>容易跟泛型, react 中的語法起衝突
複製代碼

舉個例子, 以下代碼, padding 值能夠是 string , 也能夠是 number, 雖然在代碼裏面寫了 Array(), 咱們明確的知道, padding 會被parseint 轉換成 number 類型, 但類型定義依然會報錯.app

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;
}
複製代碼

二、 類型守衛 類型守衛有如下幾種方式, 簡單的歸納如下post

  • 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);
    }
}
複製代碼

三、雙重斷言 有些時候使用 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 Chining
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 這樣的代碼

  1. 空值聯合 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

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 extendsPromise<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.

六. 常量枚舉

對於敏感的數據, 可以使用常量枚舉的方式.

const enum learn { 
    math,
    language,
    sports
}
複製代碼

在編譯以後, 空空如也:

當咱們不須要一個對象, 但須要值的話, 就可使用這個方法 , 能夠減小運行時的代碼 ,以下:

往期精彩文章:

歡迎關注「前端加加」,認真學前端,作個有專業的技術人...

相關文章
相關標籤/搜索