咱們在處理數據時,經常會遇到某項數據,或者屬性是 undefined
,從而引發中斷性錯誤,形成數據處理失敗。解決這一問題最直接的辦法就是在使用前判斷是否 undefined
,可是若是每個數據使用前都進行判斷,很是繁瑣,並且容易遺漏。因此這裏給你們介紹兩個辦法:前端
undefined
,避免繁瑣的判斷過程;爲了更直觀的描述問題和解決辦法,咱們設計了這樣一個需求:json
通常來講,如今的前端組件對錶格數據的要求,都是:後端
除數據行以外,還包含一些附加信息用於分頁,好比數組
那麼,從後端返回的數據可能會是這樣(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,就須要爲兩個數據結構定義對應的類型了。
咱們有兩個數據結構 res
和 tableData
,在 TypeScript 裏能夠直接把它們定義爲 any
類型,這是最簡單的操做,可是沒什麼用 —— 由於 TypeScript 不檢查 any
類型。
因此先根據咱們以前的約定,把 res
和 tableData
的類型定義出來:
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 是會提示錯誤的:
解決的辦法是使用解構重命名:
const { message: msg } = res;
或者,若是咱們忘了處理 undefined
,好比忘了給解構的 rows
賦予初始值,那也會獲得錯誤提示,由於
rows?: T[]
表示它可省略,便可能是 undefined
data: T[]
表示它必定不會是 undefined
解決的辦法是,賦予初始值,使其不可能爲 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);
回到 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 的強類型檢查,簡直是完美!
咱們講了最簡單的數據轉換:直接按源數據屬性取值。雖然簡單,可是有坑,也有處理的方法和技巧:
undefined
、null
,甚至 NaN
等須要特殊處理的數據undefined
屬性的處理這裏講的數據處理比較基礎,但其中的坑也比較容易被忽略。後面在專欄或訂閱號中,咱們還會繼續探討更復雜一些的數據處理分析方法和處理技巧,請關注!