Redux高階系列2 如何合理地設計State

2021-08-07 10:46:02 字數 3247 閱讀 8931

redux是乙個非常流行的狀態管理解決方案,redux應用執行過程中的任何乙個時刻,都是乙個狀態的反映。可以說,state 驅動了redux邏輯的運轉。設計乙個好的state並非易事,本文先從設計state時最容易犯的兩個錯誤開始介紹,然後引出如何合理地設計state。

以api為設計state的依據,往往是乙個api對應乙個子state,state的結構同api返回的資料結構保持一致(或接近一致)。例如,乙個部落格應用,/posts介面返回部落格列表,返回的資料結構如下:

[

}...

]

,

"content": "some really short blog content. "

}

[

...]

上面三個介面的資料分別作為3個子state,構成應用全域性的state:

},...

],"currentpost": ,

"content": "some really short blog content. "

}, "currentcomments": [

,...

]}

這個state中,posts和currentpost存在很多重複的資訊,而且posts、currentcomments是陣列型別的結構,不便於查詢,每次查詢某條記錄時,都需要遍歷整個陣列。這些問題本質上是因為api是基於服務端邏輯設計的,而不是基於應用的狀態設計的。比如,雖然獲取部落格列表時,已經獲取了每篇部落格的標題、作者等基本資訊,但對於獲取部落格詳情的api來說,根據api的設計原則,這個api依然應該包含部落格的這些基本資訊,而不能只是返回部落格的內容。再比如,posts、currentcomments之所以返回陣列結構,是考慮到資料的順序、分頁等因素。

既然不能依據api設計state,很多人又會走到另外乙個反面,基於頁面ui設計state。頁面ui需要什麼樣的資料和資料格式,state就設計成什麼樣。我們以todo應用為例,頁面會有三種狀態:顯示所有的事項,顯然所有的已辦事項和顯示所有的待辦事項。以頁面ui為設計state的依據,那麼state將是這樣的:

,

],"uncompleted": [

],"completed": [

]}

這個state對於展示ui的元件來說,使用起來非常方便,當前應用處於哪種狀態,就用對應狀態的陣列型別的資料渲染ui,不用做任何的中間資料轉換。但這種state存在的問題也很容易被發現,一是這種state依然存在資料重複的問題;二是當新增或修改一條記錄時,需要修改不止乙個地方。例如,當新增一條記錄時,all和uncompleted這兩個陣列都要新增這條新增記錄。這種型別的state,既會造成儲存的浪費,又會存在資料不一致的風險。

這兩種設計state的方式實際上是兩種極端的設計方式,實際專案中,完全按照這兩種方式設計state的開發者並不多,但絕大部分人都會受到這兩種設計方式的影響。請回憶一下,你是否有過把某個api返回的資料原封不動的作為state的一部分?又是否有過,為了元件渲染方便,專門為某個元件的ui定義乙個state?

下面我們來看一下應該如何合理地設計state。最重要最核心的原則是像設計資料庫一樣設計state。把state看做乙個資料庫,state中的每一部分狀態看做資料庫中的一張表,狀態中的每乙個字段對應表的乙個字段。設計乙個資料庫,應該遵循以下三個原則:

資料按照領域(domain)分類,儲存在不同的表中,不同的表中儲存的列資料不能重複。

表中每一列的資料都依賴於這張表的主鍵。

表中除了主鍵以外的其他列,互相之間不能有直接依賴關係。

這三個原則,可以翻譯出設計state時的原則:

把整個應用的狀態按照領域(domain)分成若干子state,子state之間不能儲存重複的資料。

state以鍵值對的結構儲存資料,以記錄的key/id作為記錄的索引,記錄中的其他欄位都依賴於索引。

state中不能儲存可以通過已有資料計算而來的資料,即state中的字段不互相依賴。

按照這三個原則,我們重新設計部落格應用的state。按領域劃分,state可以拆分為三個子state: posts、comments、authors,posts中的記錄以部落格的id為key值,包含title、create_time、author、comments,同樣的方式可以設計出comments、authors的結構,最終state的結構如下:

,

...},

"comments": ,

...},

"authors": ,

"81": ,

...}}

現在這個state看起來是不是很像有三張表的資料庫呢?但這個state還有不滿足應用需求的地方:鍵值對的儲存方式無法保證部落格列表資料的順序,但對於部落格列表,有序性顯然是需要的。解決這個問題,我們可以通過定義另外乙個狀態postids,以陣列格式儲存部落格的id:

,

...},

"postids": [1, ...],

"comments": ,

...},

"authors": ,

"81": ,

...}}

截至目前為止,我們的state都是根據後台api返回的領域資料進行設計的,但實際上,應用的state,不僅包含領域資料,還需要包含應用的ui邏輯資料,例如根據當前是否正在與伺服器通訊,處理頁面的載入效果;當應用執行出錯時,需要顯示錯誤資訊等。這時,state的結構如下:

,

"postids": [1, ...],

"comments": ,

"authors":

}

隨著應用業務邏輯的增加,state的第一層級的節點也會變得越來越多。這時候我們往往會考慮合併關聯性較強的節點資料,然後通過拆分reducer的方式,讓每乙個子reducer處理乙個節點的狀態邏輯。這個例子中,我們可以把posts、postids進行合併,同時狀態名做了調整,把isfetching、error作為全域性的ui邏輯狀態合併:

,

"posts":,

...},

"allids": [1, ...],

} "comments": ,

"authors":

}

總結一下,設計redux state的關鍵在於,像設計資料庫一樣設計state。把state看作應用在記憶體中的乙個資料庫,action、reducer等看作操作這個資料庫的sql語句。

如何合理地建立 Mysql 索引

前言 索引基礎知識 建立索引 alter table table name add index index name 刪除索引 alter table table name drop index index name 檢視表中的索引 show index from table name 如何優化sq...

如何合理地安排窗體的啟動順序?

public sub main 顯示登入對話方塊 dim frmsplash as frmsplashscreen new frmsplashscreen frmsplash.show threading.thread.sleep 3000 frmsplash.close 顯示使用者登入對話方塊 d...

如何合理地安排窗體的啟動順序?

在開發程式過程如何更好安排frmspash窗體,登入窗體frmlogin,及主窗體是新手經常遇到的煩腦,這是本人經常使用的乙個過程,供大家參考,這段代面要放到module中來執行。public sub main application.enablevisualstyles application.s...