基于 Redux + TypeScript 實現(xiàn)強類型檢查和對 Json 的數(shù)據(jù)清理
突然像是打通了任督二脈一樣就用了 generics 搞定了之前一直用 any 實現(xiàn)的類型……
關于 Redux 的部分,這里不多贅述,基本的實現(xiàn)都在這里:Redux Toolkit 調用 API 的四種方式 和 async thunk 解決 API 調用的依賴問題。
之前的實現(xiàn)方法是這個:TS 使用自動提示生成對象中的鍵,不過實現(xiàn)了一下還是稍微麻煩了一些,而且 CRUD 中的 Create 操作比較難(因為缺乏默認值),之后還是換了一種寫法。
雖然這里是用的是 react redux,不過因為不涉及到渲染部分,以及 redux 也可以在非 react 項目中使用,所以還是放到 TS 分類中了(x
下面就根據(jù)自己的經(jīng)驗和目前寫的項目講一下我自己琢磨出來的實現(xiàn)。
對象的類型定義
這種主要是通過 type
or interface
去實現(xiàn),具體沒什么差別啦,不過對于我們來說,數(shù)據(jù)類型是固定的,沒必要修改對應的數(shù)據(jù)類型,使用 type
就好了。具體用 type
還是 interface
,還是具體需求具體分析。
另外 type
的優(yōu)勢就是少打一些字……?
具體實現(xiàn)如下:
export type IPost = {
body: string;
// 因為網(wǎng)上數(shù)據(jù)和項目數(shù)據(jù)格式不一致,所以這里暫時注釋掉
// id: number;
title: string;
userId: number;
};
// 這個跟 redux 存儲狀態(tài)相關
// export interface IPostModel extends ISimpleDataFormat<IPost> {}
export type IRdmMarketModel = ISimpleDataFormat<IRdmMarket>;
隨后定義一個默認值:
export const defaultPost: IPost = {
body: '',
id: 0,
title: '',
userId: 0,
};
這樣就完成了最初的設定。
Redux 的類型配置
Redux 的配置就比較復雜了,這一個也是比較項目相關的部分,下面也會一步步地進行拆解進行實現(xiàn)。
API 傳來的數(shù)據(jù)類型
initialState 還是挺重要的,因為我們的 API 有十幾二十多個,每一個的存儲類型都是一致的,因此就不可能說將同樣的東西 cv 十幾二十遍。最終我設計的 Redux 存儲格式為:
我們的業(yè)務是,后端絕對會返回一個 data
的數(shù)組,其中的數(shù)據(jù)類型大致為:
{
"data": [
{
"type": "",
"id": 0,
"attributes": {},
// 并不一定存在
"relationships": {}
}
],
// include 也是外鏈關系,為relationship所包含的entity,數(shù)據(jù)類型與data一致
"includes": {}
}
其中 type 為當前 entity 的名稱,attributes 為當前 entity 所包含的屬性,relationship 為可能存在的外鏈,includes 通過 query parameters 調用,為 relationship 中,外鏈對象的具體數(shù)據(jù)。
在具體存儲的時候,我想把 id 放到 attributes 中,這樣轉化為數(shù)組給 UI 組件時會方便一些。于是,我定義了 ISimpleDataFormat
的類型:
export type IRelationship = {
data: {
type: string;
id: number;
};
};
// 其實這個用 { [key: string]: IRelationship } 的效果應該也是一樣的……?
export type IRelationships = Record<string, IRelationship>;
export type ISimpleDataFormat<T> = {
attributes: T & { id: string };
relationships?: IRelationships;
};
relationship 我保存了后端傳來的數(shù)據(jù)格式,一來沒有特別的復雜,二來對于 create/update 操作,有的時候需要添加 relationship 進行雙重驗證和建立外鏈。
Redux 狀態(tài)的設定
同樣,我也對 Redux 存儲的狀態(tài)進行了標準化。首先它需要有一個 loading 狀態(tài),這樣可以方便 UI 進行狀態(tài)的更改,同時提醒用戶正在拉去數(shù)據(jù)。其次需要錯誤的狀態(tài),同樣也是為了顯示在 UI 上,最后需要分別存儲 data 和 included。經(jīng)過討論,最終決定以 K-V 對的方法存儲數(shù)據(jù)。
最后的狀態(tài)定義如下:
// 也可以使用 Record,我之后應該會以 Record 為主,畢竟寫起來方便些
export type ReduxDataType<T> = { [key: string]: T };
export type ISliceStateType<T, U> = {
isLoading: boolean;
error: null | SerializedError;
data: ReduxDataType<T>;
included: ReduxDataType<U>;
};
export const initialSliceState = {
isLoading: false,
error: null,
data: {},
included: {},
};
其中 included 可能會存在多個不同的外鏈,不過目前我們只用到了一個,所以用 <T, U>
,如果之后 included 出現(xiàn)兩個以上的外鏈,那么……再改叭……
slice 的實現(xiàn)
這里主要講一個 fetch,其他的操作基本一致,沒什么特別大的區(qū)別。API 方面的話,就用網(wǎng)上的 dummy API 了:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { initialSliceState } from '../sliceType';
import { ISimpleDataFormat, ISliceStateType } from '../../../types';
import { pick } from 'lodash';
export type IPost = {
body: string;
title: string;
userId: number;
};
export interface IPostModel extends ISimpleDataFormat<IPost> {}
export const defaultPost: IPost = {
body: '',
title: '',
userId: 0,
};
export const fetchPost = createAsyncThunk('posts/fetch', async () => {
return fetch('https://jsonplaceholder.typicode.com/posts')
.then((response) => response.json())
.then((json) => json);
});
const postSlice = createSlice({
name: 'rdmMarket',
initialState: initialSliceState as ISliceStateType<IPostModel, unknown>,
reducers: {},
extraReducers(builder) {
builder.addCase(fetchPost.fulfilled, (state, action) => {
state.isLoading = false;
action.payload.forEach((data: IPost & { id: string }) => {
const id = String(data.id);
const pickedAttributes = pick(data, Object.keys(defaultPost)) as IPost;
const model: IPostModel = {
attributes: {
id,
...pickedAttributes,
},
};
state.data[id] = model;
});
});
},
});
export const postReducer = postSlice.reducer;
效果:
這里的數(shù)據(jù)相對而言比較簡單,因此說使用 pick 對 model 的操作好像有些多余。不過對我們項目來說:
- 后端會自動生成一堆前端用不到的 key,比如說創(chuàng)建時間、更新時間、創(chuàng)建對象、版本等,這些東西前段用不到,存儲在狀態(tài)里就是占用無謂的空間。我們項目數(shù)據(jù)量還比較大,有的時候會有十幾二十來萬的數(shù)據(jù)(2b 項目,非 2c),所以能做一點優(yōu)化就做一點優(yōu)化。
- 多余的數(shù)據(jù)傳到后端會被 rejected 掉,所以對數(shù)據(jù)的過濾是必須的
如果說一些屬性是不需要的,直接在 interface 中刪掉,TS 就會自動報錯了。
類型檢查主要還是防止 typo 以及寫代碼更方便一些,比如說:
我們項目的數(shù)據(jù)都是強定形的,所以對我們項目來說使用 TS 的優(yōu)勢絕對比使用 JS 多……尤其是有些 entity 會有一百多個 attributes,這時候如果沒有一些智能提示或者是類型檢查,報錯都得掉一堆頭發(fā)文章來源:http://www.zghlxwxcb.cn/news/detail-626778.html
是的,真的發(fā)生過,后來發(fā)現(xiàn)就因為是 typo 所以拿不到值……還有就是后端改了一些數(shù)據(jù)類型,然后前端沒有在所有的組件中全部更新,導致有些頁面出現(xiàn)問題有些事正常的。也就是那時候我們決定要落實 TS 的實現(xiàn)(雖然到最后只有我一個人在認真想怎么重構,豬隊友都在用 any……痛苦……文章來源地址http://www.zghlxwxcb.cn/news/detail-626778.html
到了這里,關于基于 Redux + TypeScript 實現(xiàn)強類型檢查和對 Json 的數(shù)據(jù)清理的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!