默認值+TS類型約束提升數據處理成功率

咱們在處理數據時,經常會遇到某項數據,或者屬性是 undefined,從而引發中斷性錯誤,形成數據處理失敗。解決這一問題最直接的辦法就是在使用前判斷是否 undefined ,可是若是每個數據使用前都進行判斷,很是繁瑣,並且容易遺漏。因此這裏給你們介紹兩個辦法:前端

  • 利用默認值解決 undefined,避免繁瑣的判斷過程;
  • 利用 TypeScript 的類型系統對數據進行檢查,避免遺漏。

爲了更直觀的描述問題和解決辦法,咱們設計了這樣一個需求:json

需求:後端響應數據轉換成 UI 所須要的數據

通常來講,如今的前端組件對錶格數據的要求,都是:後端

  • 以數組表示數據行,即當前頁數據
  • 每行是一個數據對象,其屬性對應於表格列
  • 除數據行以外,還包含一些附加信息用於分頁,好比數組

    • 庫存數據總數(不是當前數據行的總數)
    • 每頁數據呈現條數(通常由前端傳給後端,後端返回數據時帶出來)
    • 當前頁數(通常由前端傳給後端,後端返回數據時帶出來)

那麼,從後端返回的數據可能會是這樣(JSON 示例)網絡

{
  "code": 0,
  "message": "查詢成功",
  "data": {
    "total": 12345,
    "page": 3,
    "rows": [
      {
        "id": 34,
        "title": "第 34 號數據",
        "stamp": "2020-06-25T20:18:19Z"
      },
      ...
    ]
  }
}

前端呈現若是使用 Layui 的數據表格,它所要求的數據格式是這樣的(來自官方文檔):數據結構

{
  "code": 0,
  "msg": "",
  "count": 1000,
  "data": [{}, {}]
}

遠程響應數據結構和表格須要的數據結構之間,對應關係很是明顯。工具

咱們用 res 來表示後端 JSON 轉成的 JavaScript 對象,那麼爲 Layui 準備的數據會這樣取值:fetch

const tableData = {
    code: res.code,
    msg: res.message,
    count: res.data.total,
    data: res.data.rows
}

這個處理轉換很是簡單,只是作了一個屬性名稱的變化。ui

可是遠端返回的數據,可能沒有 code,或者 message,甚至沒有 data,那麼上面的處理結果就可能包括 undefined。前端組件不但願有這樣的值,因此須要添加默認值處理。spa

利用解構變量能夠賦予默認值這一特性

爲了演示默認值處理,咱們假設,後端返回的數據規範比較靈活,爲了節約網絡資源,有一個默認值約定:

  • 若是請求正常完成,省略 "code": 0
  • 若是沒有特殊消息,省略 "message": ""
  • 若是沒有數據,即 "total": 0 的狀況,省略 "data": {}
  • 若是當前頁沒有數據,省略 "rows": []

這種狀況下,在進行數據轉換時就須要充分考慮到某項數據可能不存在,避免 undefined 錯誤。

Object.assign() 或者 Spread 運算符能夠部分解決這個問題,可是

  • 只能解決單層(直接)屬性,不能解決深層屬性默認值
  • 有坑,它們對 「undefiend 屬性」和「不存在的屬性」處理行爲不一樣

不過咱們能夠利用解構變量可以賦予默認值的特性來進行處理。下面這段代碼就巧妙地利用了這一特性來避免 undefined 錯誤。

function getData(res) {
    const { code = 0, message: msg, data = {} } = res;
    const { total: count = 0, rows = [] } = data;

    return {
        code,
        msg,
        count,
        data: rows
    }
}

const tableData = getData(res);

解構確實是能夠解決問題,可是若是遺漏或者寫錯屬性,調試起來恐怕不易。好比上面

const { message: msg } = res;

就很容易錯寫成

const { msg } = res

這是 JS 的硬傷,即便用 ESLint 這樣強悍的靜態檢查工具也不能解決。可是若是引入強類型定義,使用 TypeScript 就好辦了。

使用 TypeScript 和類型檢查轉換過程

既然用 TypeScript,就須要爲兩個數據結構定義對應的類型了。

咱們有兩個數據結構 restableData,在 TypeScript 裏能夠直接把它們定義爲 any 類型,這是最簡單的操做,可是沒什麼用 —— 由於 TypeScript 不檢查 any 類型。

因此先根據咱們以前的約定,把 restableData 的類型定義出來:

interface FetchData<T> {
    total: number;
    page?: number;
    rows?: T[];
}

interface FetchResult<T> {
    code?: number;
    message?: string;
    data?: FetchData<T>;
}

interface LayuiTalbeData<T> {
    code: number;
    msg?: string;
    count: number;
    data: T[];
}

而後要把 res 聲明成 FetchResult<T> 類型。這裏咱們暫時不關心具體每行數據的結構,因此直接用 any 代替好了

const res: FetchResult<any> = await fetch();
// 或者
// const res = await fetch() as FetchResult<any>;

這種狀況下,假如咱們不當心寫錯了屬性名,好比解構時把源屬性 message 錯寫成了目錄屬性名 msg,即 const { msg } = res,VSCode 是會提示錯誤的:

image.png

解決的辦法是使用解構重命名:

const { message: msg } = res;

或者,若是咱們忘了處理 undefined,好比忘了給解構的 rows 賦予初始值,那也會獲得錯誤提示,由於

  • 源數據定義中 rows?: T[] 表示它可省略,便可能是 undefined
  • 目標數據定義中 data: T[] 表示它必定不會是 undefined

image.png

解決的辦法是,賦予初始值,使其不可能爲 undefined

const { rows = [] } = data;

完整的 TypeScript 代碼以下(類型定義參考前面的定義):

function getData<T>(res: FetchResult<T>): LayuiTalbeData<T> {
    const { code = 0, message: msg, data = {} as FetchData<T> } = res;
    const { total: count = 0, rows = [] } = data;

    return {
        code,
        msg,
        count,
        data: rows
    }
}

// 這裏 TypeScript 能夠推導出 tableData 類型是 LayuiTalbeData<any>
const tableData = getData(res);

使用 Optional Chain 和 Nullish Coalescing

回到 JavaScript,其實還有一個辦法能夠處理默認值的問題:

const tableData = {
    code: res.code || 0,
    msg: res.message || "",
    count: (res.data && res.data.total) || 0,
    data: (res.data && res.data.rows) || []
}

這也是一個很是常見的辦法。這個辦法在 TypeScript 中配置類型定義一樣可行。只是對多層屬性的處理仍然顯得有點麻煩。不過 JavaScript 最近引入了「Optional Chain」和「Nullish Coalescing」特性,這個代碼能夠更簡潔:

const tableData = {
    code: res.code ?? 0,
    msg: res.message,
    count: res.data?.total ?? 0,
    data: res.data?.rows ?? []
};

放在 TypeScript 中是這樣寫的:

const tableData: LayuiTalbeData<any> = {
    code: res.code ?? 0,
    msg: res.message,
    count: res.data?.total ?? 0,
    data: res.data?.rows ?? []
}

上面的 TypeScript 代碼中,若是錯寫了 res.msg 或者忘了加 ?? [] 等,TSC 或者 VSCode 都會有錯誤提示,以保證你能修正代碼。

看,新特性配合 TypeScript 的強類型檢查,簡直是完美!

小結

咱們講了最簡單的數據轉換:直接按源數據屬性取值。雖然簡單,可是有坑,也有處理的方法和技巧:

  • 注意可能出現的 undefinednull,甚至 NaN 等須要特殊處理的數據
  • 使用解構將屬性提取出來,並根據數據結構的須要適當賦予初始值
  • 使用 Optional Chain 和 Nullish Coalescing 簡化對可能爲 undefined 屬性的處理
  • 使用 TypeScript 在開發期檢查錯誤

這裏講的數據處理比較基礎,但其中的坑也比較容易被忽略。後面在專欄或訂閱號中,咱們還會繼續探討更復雜一些的數據處理分析方法和處理技巧,請關注!

相關文章
相關標籤/搜索