摘要:例如下拉框的顯示與關(guān)閉。如何設(shè)計結(jié)構(gòu)在使用的過程中,我們都會使用的方式,將我們的拆分到不同的文件當(dāng)中,通常會遵循高內(nèi)聚方便使用的原則,按某個功能模塊頁面來劃分。
為什么使用redux
使用react構(gòu)建大型應(yīng)用,勢必會面臨狀態(tài)管理的問題,redux是常用的一種狀態(tài)管理庫,我們會因為各種原因而需要使用它。
不同的組件可能會使用相同的數(shù)據(jù),使用redux能更好的復(fù)用數(shù)據(jù)和保持?jǐn)?shù)據(jù)的同步
react中子組件訪問父組件的數(shù)據(jù)只能通過props層層傳遞,使用redux可以輕松的訪問到想要的數(shù)據(jù)
全局的state可以很容易的進(jìn)行數(shù)據(jù)持久化,方便下次啟動app時獲得初始state
dev tools提供狀態(tài)快照回溯的功能,方便問題的排查
但并不是所有的state都要交給redux管理,當(dāng)某個狀態(tài)數(shù)據(jù)只被一個組件依賴或影響,且在切換路由再次返回到當(dāng)前頁面不需要保留操作狀態(tài)時,我們是沒有必要使用redux的,用組件內(nèi)部state足以。例如下拉框的顯示與關(guān)閉。
常見的狀態(tài)類型react應(yīng)用中我們會定義很多state,state最終也都是為頁面展示服務(wù)的,根據(jù)數(shù)據(jù)的來源、影響的范圍大致可以將前端state歸為以下三類:
Domain data: 一般可以理解為從服務(wù)器端獲取的數(shù)據(jù),比如帖子列表數(shù)據(jù)、評論數(shù)據(jù)等。它們可能被應(yīng)用的多個地方用到,前端需要關(guān)注的是與后端的數(shù)據(jù)同步、提交等等。如何設(shè)計state結(jié)構(gòu)UI state: 決定當(dāng)前UI如何展示的狀態(tài),比如一個彈窗的開閉,下拉菜單是否打開,往往聚焦于某個組件內(nèi)部,狀態(tài)之間可以相互獨立,也可能多個狀態(tài)共同決定一個UI展示,這也是UI state管理的難點。
App state: App級的狀態(tài),例如當(dāng)前是否有請求正在loading、某個聯(lián)系人被選中、當(dāng)前的路由信息等可能被多個組件共同使用到狀態(tài)。
在使用redux的過程中,我們都會使用modules的方式,將我們的reducers拆分到不同的文件當(dāng)中,通常會遵循高內(nèi)聚、方便使用的原則,按某個功能模塊、頁面來劃分。那對于某個reducer文件,如何設(shè)計state結(jié)構(gòu)能更方便我們管理數(shù)據(jù)呢,下面列出幾種常見的方式:
1.將api返回的數(shù)據(jù)直接放入state這種方式大多會出現(xiàn)在列表的展示上,如帖子列表頁,因為后臺接口返回的數(shù)據(jù)通常與列表的展示結(jié)構(gòu)基本一致,可以直接使用。
2.以頁面UI來設(shè)計state結(jié)構(gòu)如下面的頁面,分為三個section,對應(yīng)開戶中、即將流失、已提交審核三種不同的數(shù)據(jù)類型。
因為頁面是展示性的沒有太多的交互,所以我們完全可以根據(jù)頁面UI來設(shè)計如下的結(jié)構(gòu):
tabData: { opening: [{ userId: "6332", mobile: "1858849****", name: "test1", ... }, ...], missing: [], commit: [{ userId: "6333", mobile: "1858849****", name: "test2", ... }, ... ] }
這樣設(shè)計比較方便我們將state映射到頁面,拉取更多數(shù)據(jù)只需要將新數(shù)據(jù)簡單contact進(jìn)對應(yīng)的數(shù)組即可。對于簡單頁面,這樣是可行的。
3.State范式化(normalize)很多情況下,處理的數(shù)據(jù)都是嵌套或互相關(guān)聯(lián)的。例如,一個群列表,由很多群組成,每個群又包含很多個用戶,一個用戶可以加入多個不同的群。這種類型的數(shù)據(jù),我們可以方便用如下結(jié)構(gòu)表示:
const Groups = [ { id: "group1", groupName: "連線電商", groupMembers: [ { id: "user1", name: "張三", dept: "電商部" }, { id: "user2", name: "李四", dept: "電商部" }, ] }, { id: "group2", groupName: "連線資管", groupMembers: [ { id: "user1", name: "張三", dept: "電商部" }, { id: "user3", name: "王五", dept: "電商部" }, ] } ]
這種方式,對界面展示很友好,展示群列表,我們只需遍歷Groups數(shù)組,展示某個群成員列表,只需遍歷相應(yīng)索引的數(shù)據(jù)Groups[index],展示某個群成員的數(shù)據(jù),繼續(xù)索引到對應(yīng)的成員數(shù)據(jù)GroupsgroupIndex即可。
但是這種方式有一些問題:
存在很多重復(fù)數(shù)據(jù),當(dāng)某個群成員信息更新的時候,想要在不同的群之間進(jìn)行同步比較麻煩。
嵌套過深,導(dǎo)致reducer邏輯復(fù)雜,修改深層的屬性會導(dǎo)致代碼臃腫,空指針的問題
redux中需要遵循不可變更新模式,更新屬性往往需要更新組件樹的祖先,產(chǎn)生新的引用,這會導(dǎo)致跟修改數(shù)據(jù)無關(guān)的組件也要重新render。
為了避免上面的問題,我們可以借鑒數(shù)據(jù)庫存儲數(shù)據(jù)的方式,設(shè)計出類似的范式化的state,范式化的數(shù)據(jù)遵循下面幾個原則:
不同類型的數(shù)據(jù),都以“數(shù)據(jù)表”的形式存儲在state中
“數(shù)據(jù)表” 中的每一項條目都以對象的形式存儲,對象以唯一性的ID作為key,條目本身作為value。
任何對單個條目的引用都應(yīng)該根據(jù)存儲條目的 ID 來索引完成。
數(shù)據(jù)的順序通過ID數(shù)組表示。
上面的示例范式化之后如下:
{ groups: { byIds: { group1: { id: "group1", groupName: "連線電商", groupMembers: ["user1", "user2"] }, group2: { id: "group2", groupName: "連線資管", groupMembers: ["user1", "user3"] } }, allIds: ["group1", "group2"] }, members: { byIds: { user1: { id: "user1", name: "張三", dept: "電商部" }, user2: { id: "user2", name: "李四", dept: "電商部" }, user3: { id: "user3", name: "王五", dept: "電商部" } }, allIds: [] } }
與原來的數(shù)據(jù)相比有如下改進(jìn):
因為數(shù)據(jù)是扁平的,且只被定義在一個地方,更方便數(shù)據(jù)更新
檢索或者更新給定數(shù)據(jù)項的邏輯變得簡單與一致。給定一個數(shù)據(jù)項的 type 和 ID,不必嵌套引用其他對象而是通過幾個簡單的步驟就能查找到它。
每個數(shù)據(jù)類型都是唯一的,像用戶信息這樣的更新僅僅需要狀態(tài)樹中 “members > byId > user” 這部分的復(fù)制。這也就意味著在 UI 中只有數(shù)據(jù)發(fā)生變化的一部分才會發(fā)生更新。與之前的不同的是,之前嵌套形式的結(jié)構(gòu)需要更新整個 groupMembers數(shù)組,以及整個 groups數(shù)組。這樣就會讓不必要的組件也再次重新渲染。
通常我們接口返回的數(shù)據(jù)都是嵌套形式的,要將數(shù)據(jù)范式化,我們可以使用Normalizr這個庫來輔助。
當(dāng)然這樣做之前我們最好問自己,我是否需要頻繁的遍歷數(shù)據(jù),是否需要快速的訪問某一項數(shù)據(jù),是否需要頻繁更新同步數(shù)據(jù)。
對于這些關(guān)系數(shù)據(jù),我們可以統(tǒng)一放到entities中進(jìn)行管理,這樣root state,看起來像這樣:
{ simpleDomainData1: {....}, simpleDomainData2: {....} entities : { entityType1 : {byId: {}, allIds}, entityType2 : {....} } ui : { uiSection1 : {....}, uiSection2 : {....} } }
其實上面的entities并不夠純粹,因為其中包含了關(guān)聯(lián)關(guān)系(group里面包含了groupMembers的信息),也包含了列表的順序信息(如每個實體的allIds屬性)。更進(jìn)一步,我們可以將這些信息剝離出來,讓我們的entities更加簡單,扁平。
{ entities: { groups: { group1: { id: "group1", groupName: "連線電商", }, group2: { id: "group2", groupName: "連線資管", } }, members: { user1: { id: "user1", name: "張三", dept: "電商部" }, user2: { id: "user2", name: "李四", dept: "電商部" }, user3: { id: "user3", name: "王五", dept: "電商部" } } }, groups: { gourpIds: ["group1", "group2"], groupMembers: { group1: ["user1", "user2"], group2: ["user2", "user3"] } } }
這樣我們在更新entity信息的時候,只需操作對應(yīng)entity就可以了,添加新的entity時則需要在對應(yīng)的對象如entities[group]中添加group對象,在groups[groupIds]中添加對應(yīng)的關(guān)聯(lián)關(guān)系。
enetities.js
const ADD_GROUP = "entities/addGroup"; const UPDATE_GROUP = "entities/updateGroup"; const ADD_MEMBER = "entites/addMember"; const UPDATE_MEMBER = "entites/updateMember"; export const addGroup = entity => ({ type: ADD_GROUP, payload: {[entity.id]: entity} }) export const updateGroup = entity => ({ type: UPDATE_GROUP, payload: {[entity.id]: entity} }) export const addMember = member => ({ type: ADD_MEMBER, payload: {[member.id]: member} }) export const updateMember = member => ({ type: UPDATE_MEMBER, payload: {[member.id]: member} }) _addGroup(state, action) { return state.set("groups", state.groups.merge(action.payload)); } _addMember(state, action) { return state.set("members", state.members.merge(action.payload)); } _updateGroup(state, action) { return state.set("groups", state.groups.merge(action.payload, {deep: true})); } _updateMember(state, action) { return state.set("members", state.members.merge(action.payload, {deep: true})) } const initialState = Immutable({ groups: {}, members: {} }) export default function entities(state = initialState, action) { let type = action.type; switch (type) { case ADD_GROUP: return _addGroup(state, action); case UPDATE_GROUP: return _updateGroup(state, action); case ADD_MEMBER: return _addMember(state, action); case UPDATE_MEMBER: return _updateMember(state, action); default: return state; } }
可以看到,因為entity的結(jié)構(gòu)大致相同,所以更新起來很多邏輯是差不多的,所以這里可以進(jìn)一步提取公用函數(shù),在payload里面加入要更新的key值。
export const addGroup = entity => ({ type: ADD_GROUP, payload: {data: {[entity.id]: entity}, key: "groups"} }) export const updateGroup = entity => ({ type: UPDATE_GROUP, payload: {data: {[entity.id]: entity}, key: "groups"} }) export const addMember = member => ({ type: ADD_MEMBER, payload: {data: {[member.id]: member}, key: "members"} }) export const updateMember = member => ({ type: UPDATE_MEMBER, payload: {data: {[member.id]: member}, key: "members"} }) function normalAddReducer(state, action) { let payload = action.payload; if (payload && payload.key) { let {key, data} = payload; return state.set(key, state[key].merge(data)); } return state; } function normalUpdateReducer(state, action) { if (payload && payload.key) { let {key, data} = payload; return state.set(key, state[key].merge(data, {deep: true})); } } export default function entities(state = initialState, action) { let type = action.type; switch (type) { case ADD_GROUP: case ADD_MEMBER: return normalAddReducer(state, action); case UPDATE_GROUP: case UPDATE_MEMBER: return normalUpdateReducer(state, action); default: return state; } }將loading狀態(tài)抽離到根reducer中,統(tǒng)一管理
在請求接口時,通常會dispatch loading狀態(tài),通常我們會在某個接口請求的reducer里面來處理響應(yīng)的loading狀態(tài),這會使loading邏輯到處都是。其實我們可以將loading狀態(tài)作為根reducer的一部分,多帶帶管理,這樣就可以復(fù)用響應(yīng)的邏輯。
const SET_LOADING = "SET_LOADING"; export const LOADINGMAP = { groupsLoading: "groupsLoading", memberLoading: "memberLoading" } const initialLoadingState = Immutable({ [LOADINGMAP.groupsLoading]: false, [LOADINGMAP.memberLoading]: false, }); const loadingReducer = (state = initialLoadingState, action) => { const { type, payload } = action; if (type === SET_LOADING) { return state.set(key, payload.loading); } else { return state; } } const setLoading = (scope, loading) => { return { type: SET_LOADING, payload: { key: scope, loading, }, }; } // 使用的時候 store.dispatch(setLoading(LOADINGMAP.groupsLoading, true));
這樣當(dāng)需要添加新的loading狀態(tài)的時候,只需要在LOADINGMAP和initialLoadingState添加相應(yīng)的loading type即可。
也可以參考dva的實現(xiàn)方式,它也是將loading存儲在根reducer,并且是根據(jù)model的namespace作為區(qū)分,
它方便的地方在于將更新loading狀態(tài)的邏輯被提取到plugin中,用戶不需要手動編寫更新loading的邏輯,只需要在用到時候使用state即可。plugin的代碼也很簡單,就是在鉤子函數(shù)中攔截副作用。
function onEffect(effect, { put }, model, actionType) { const { namespace } = model; return function*(...args) { yield put({ type: SHOW, payload: { namespace, actionType } }); yield effect(...args); yield put({ type: HIDE, payload: { namespace, actionType } }); }; }其他
對于web端應(yīng)用,我們無法控制用戶的操作路徑,很可能用戶在直接訪問某個頁面的時候,我們store中并沒有準(zhǔn)備好數(shù)據(jù),這可能會導(dǎo)致一些問題,所以有人建議以page為單位劃分store,舍棄掉部分多頁面共享state的好處,具體可以參考這篇文章,其中提到在視圖之間共享state要謹(jǐn)慎,其實這也反映出我們在思考是否要共享某個state時,思考如下幾個問題:
有多少頁面會使用到該數(shù)據(jù)
每個頁面是否需要多帶帶的數(shù)據(jù)副本
改動數(shù)據(jù)的頻率怎么樣
參考文章https://www.zhihu.com/questio...
https://segmentfault.com/a/11...
https://hackernoon.com/shape-...
https://medium.com/@dan_abram...
https://medium.com/@fastphras...
https://juejin.im/post/59a16e...
http://cn.redux.js.org/docs/r...
https://redux.js.org/recipes/...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/109428.html
摘要:設(shè)計一個好的并非易事,本文先從設(shè)計時最容易犯的兩個錯誤開始介紹,然后引出如何合理地設(shè)計。錯誤以為設(shè)計的依據(jù)以為設(shè)計的依據(jù),往往是一個對應(yīng)一個子,的結(jié)構(gòu)同返回的數(shù)據(jù)結(jié)構(gòu)保持一致或接近一致。至此,的結(jié)構(gòu)設(shè)計完成。 Redux是一個非常流行的狀態(tài)管理解決方案,Redux應(yīng)用執(zhí)行過程中的任何一個時刻,都是一個狀態(tài)的反映。可以說,State 驅(qū)動了Redux邏輯的運轉(zhuǎn)。設(shè)計一個好的State并非...
摘要:就是應(yīng)用程序領(lǐng)域的狀態(tài),它是類型中的模型的設(shè)計的概念,這設(shè)計是由架構(gòu)而來的,在原本的架構(gòu)中是允許多個的結(jié)構(gòu),簡化為只有單一個。的設(shè)計中是與中的相比,它們之間有一些類似的設(shè)計。 Redux里的強(qiáng)硬規(guī)則與設(shè)計不少,大部份都會與FP(函數(shù)式程序開發(fā))、改進(jìn)原本的Flux架構(gòu)設(shè)計有關(guān)。Redux官網(wǎng)文檔上的三大基本原則,主要是因為有可能怕初學(xué)者不理解Redux中的一些限制或設(shè)計,所以先寫出來說...
摘要:作者小滬江前端開發(fā)工程師本文為原創(chuàng)文章,有不當(dāng)之處歡迎指出。于是,單一數(shù)據(jù)源規(guī)則實施起來,是規(guī)定用的頂層容器組件的來存儲單一對象樹,同時交給來管理。顧名思義,當(dāng)更新時,的回調(diào)函數(shù)會更新視圖層,以達(dá)到訂閱的效果。 作者:小boy (滬江web前端開發(fā)工程師)本文為原創(chuàng)文章,有不當(dāng)之處歡迎指出。轉(zhuǎn)載請注明出處。文章示例代碼:https://github.com/ikcamp/rea... ...
摘要:這對復(fù)雜問題定位是有好處的。同時,也是純函數(shù),與的是純函數(shù)呼應(yīng)。強(qiáng)約束約定,增加了內(nèi)聚合性。通過約定和全局的理解,可以減少的一些缺點。約定大于配置也是框架的主要發(fā)展方向。 React+Redux非常精煉,良好運用將發(fā)揮出極強(qiáng)勁的生產(chǎn)力。但最大的挑戰(zhàn)來自于函數(shù)式編程(FP)范式。在工程化過程中,架構(gòu)(頂層)設(shè)計將是一個巨大的挑戰(zhàn)。要不然做出來的東西可能是一團(tuán)亂麻。說到底,傳統(tǒng)框架與rea...
摘要:個人看來,一個狀態(tài)管理的應(yīng)用,無論是使用,還是,最困難的部分是在的設(shè)計。中,并沒有移除,而是改為用于觸發(fā)。也是一個對象,用于注冊,每個都是一個用于返回一部分的。接受一個數(shù)組或?qū)ο螅鶕?jù)相應(yīng)的值將對應(yīng)的綁定到組件上。 系列文章: Vue 2.0 升(cai)級(keng)之旅 Vuex — The core of Vue application (本文) 從單頁應(yīng)用(SPA)到服務(wù)器...
閱讀 1376·2021-11-04 16:11
閱讀 3036·2021-10-12 10:11
閱讀 2969·2021-09-29 09:47
閱讀 1608·2021-09-22 15:40
閱讀 1007·2019-08-29 15:43
閱讀 2798·2019-08-29 13:50
閱讀 1572·2019-08-29 13:28
閱讀 2685·2019-08-29 12:54