摘要:設計一個好的并非易事,本文先從設計時最容易犯的兩個錯誤開始介紹,然后引出如何合理地設計。錯誤以為設計的依據(jù)以為設計的依據(jù),往往是一個對應一個子,的結(jié)構(gòu)同返回的數(shù)據(jù)結(jié)構(gòu)保持一致或接近一致。至此,的結(jié)構(gòu)設計完成。
Redux是一個非常流行的狀態(tài)管理解決方案,Redux應用執(zhí)行過程中的任何一個時刻,都是一個狀態(tài)的反映。可以說,State 驅(qū)動了Redux邏輯的運轉(zhuǎn)。設計一個好的State并非易事,本文先從設計State時最容易犯的兩個錯誤開始介紹,然后引出如何合理地設計State。
錯誤1:以API為設計State的依據(jù)以API為設計State的依據(jù),往往是一個API對應一個子State,State的結(jié)構(gòu)同API返回的數(shù)據(jù)結(jié)構(gòu)保持一致(或接近一致)。例如,一個博客應用,/posts接口返回博客列表,返回的數(shù)據(jù)結(jié)構(gòu)如下:
[ { "id": 1, "title": "Blog Title", "create_time": "2017-01-10T23:07:43.248Z", "author": { "id": 81, "name": "Mr Shelby" } } ... ]
我們還需要查看一篇博客的詳情,假設通過接口/posts/{id}獲取博客詳情,通過接口/posts/{id}/comments獲取博客的評論,返回的數(shù)據(jù)結(jié)構(gòu)如下:
{ "id": 1, "title": "Blog Title", "create_time": "2017-01-10T23:07:43.248Z", "author": { "id": 81, "name": "Mr Shelby" }, "content": "Some really short blog content. " }
[ { "id": 41, "author": "Jack", "create_time": "2017-01-11T23:07:43.248Z", "content": "Good article!" } ... ]
上面三個接口的數(shù)據(jù)分別作為3個子State,構(gòu)成應用全局的State:
{ "posts": [ { "id": 1, "title": "Blog Title", "create_time": "2017-01-10T23:07:43.248Z", "author": { "id": 81, "name": "Mr Shelby" } }, ... ], "currentPost": { "id": 1, "title": "Blog Title", "create_time": "2017-01-10T23:07:43.248Z", "author": { "id": 81, "name": "Mr Shelby" }, "content": "Some really short blog content. " }, "currentComments": [ { "id": 1, "author": "Jack", "create_time": "2017-01-11T23:07:43.248Z", "content": "Good article!" }, ... ] }
這個State中,posts和currentPost存在很多重復的信息,而且posts、currentComments是數(shù)組類型的結(jié)構(gòu),不便于查找,每次查找某條記錄時,都需要遍歷整個數(shù)組。這些問題本質(zhì)上是因為API是基于服務端邏輯設計的,而不是基于應用的狀態(tài)設計的。比如,雖然獲取博客列表時,已經(jīng)獲取了每篇博客的標題、作者等基本信息,但對于獲取博客詳情的API來說,根據(jù)API的設計原則,這個API依然應該包含博客的這些基本信息,而不能只是返回博客的內(nèi)容。再比如,posts、currentComments之所以返回數(shù)組結(jié)構(gòu),是考慮到數(shù)據(jù)的順序、分頁等因素。
錯誤2:以頁面UI為設計State的依據(jù)既然不能依據(jù)API設計State,很多人又會走到另外一個反面,基于頁面UI設計State。頁面UI需要什么樣的數(shù)據(jù)和數(shù)據(jù)格式,State就設計成什么樣。我們以todo應用為例,頁面會有三種狀態(tài):顯示所有的事項,顯然所有的已辦事項和顯示所有的待辦事項。以頁面UI為設計State的依據(jù),那么State將是這樣的:
{ "all": [ { "id": 1, "text": "todo 1", "completed": false }, { "id": 2, "text": "todo 2", "completed": true } ], "uncompleted": [ { "id": 1, "text": "todo 1", "completed": false } ], "completed": [ { "id": 2, "text": "todo 2", "completed": false } ] }
這個State對于展示UI的組件來說,使用起來非常方便,當前應用處于哪種狀態(tài),就用對應狀態(tài)的數(shù)組類型的數(shù)據(jù)渲染UI,不用做任何的中間數(shù)據(jù)轉(zhuǎn)換。但這種State存在的問題也很容易被發(fā)現(xiàn),一是這種State依然存在數(shù)據(jù)重復的問題;二是當新增或修改一條記錄時,需要修改不止一個地方。例如,當新增一條記錄時,all和uncompleted這兩個數(shù)組都要添加這條新增記錄。這種類型的State,既會造成存儲的浪費,又會存在數(shù)據(jù)不一致的風險。
這兩種設計State的方式實際上是兩種極端的設計方式,實際項目中,完全按照這兩種方式設計State的開發(fā)者并不多,但絕大部分人都會受到這兩種設計方式的影響。請回憶一下,你是否有過把某個API返回的數(shù)據(jù)原封不動的作為State的一部分?又是否有過,為了組件渲染方便,專門為某個組件的UI定義一個State?
合理設計State下面我們來看一下應該如何合理地設計State。最重要最核心的原則是像設計數(shù)據(jù)庫一樣設計State。把State看做一個數(shù)據(jù)庫,State中的每一部分狀態(tài)看做數(shù)據(jù)庫中的一張表,狀態(tài)中的每一個字段對應表的一個字段。設計一個數(shù)據(jù)庫,應該遵循以下三個原則:
數(shù)據(jù)按照領域(Domain)分類,存儲在不同的表中,不同的表中存儲的列數(shù)據(jù)不能重復。
表中每一列的數(shù)據(jù)都依賴于這張表的主鍵。
表中除了主鍵以外的其他列,互相之間不能有直接依賴關系。
這三個原則,可以翻譯出設計State時的原則:
把整個應用的狀態(tài)按照領域(Domain)分成若干子State,子State之間不能保存重復的數(shù)據(jù)。
State以鍵值對的結(jié)構(gòu)存儲數(shù)據(jù),以記錄的key/ID作為記錄的索引,記錄中的其他字段都依賴于索引。
State中不能保存可以通過已有數(shù)據(jù)計算而來的數(shù)據(jù),即State中的字段不互相依賴。
按照這三個原則,我們重新設計博客應用的State。按領域劃分,State可以拆分為三個子State: posts、comments、authors,posts中的記錄以博客的id為key值,包含title、create_time、author、comments,同樣的方式可以設計出comments、authors的結(jié)構(gòu),最終State的結(jié)構(gòu)如下:
{ "posts": { "1": { "id": 1, "title": "Blog Title", "content": "Some really short blog content.", "created_at": "2016-01-11T23:07:43.248Z", "author": 81, "comments": [ 352 ] }, ... }, "comments": { "352": { "id": 352, "content": "Good article!", "author": 41 }, ... }, "authors": { "41": { "id": 41, "name": "Jack" }, "81": { "id": 81, "name": "Mr Shelby" }, ... } }
現(xiàn)在這個State看起來是不是很像有三張表的數(shù)據(jù)庫呢?但這個State還有不滿足應用需求的地方:鍵值對的存儲方式無法保證博客列表數(shù)據(jù)的順序,但對于博客列表,有序性顯然是需要的。解決這個問題,我們可以通過定義另外一個狀態(tài)postIds,以數(shù)組格式存儲博客的id:
{ "posts": { "1": { "id": 1, "title": "Blog Title", "content": "Some really short blog content.", "created_at": "2016-01-11T23:07:43.248Z", "author": 81, "comments": [ 352 ] }, ... }, "postIds": [1, ...], "comments": { "352": { "id": 352, "content": "Good article!", "author": 41 }, ... }, "authors": { "41": { "id": 41, "name": "Jack" }, "81": { "id": 81, "name": "Mr Shelby" }, ... } }
這樣,當顯示博客列表時,根據(jù)postIds獲取列表順序,然后根據(jù)博客id從posts中獲取博客的信息。這個地方有些同學可能有疑惑,認為posts和postIds都保存了id數(shù)據(jù),違反了不同State間不能有重復數(shù)據(jù)的原則。但其實這并不是重復數(shù)據(jù),postIds保存的數(shù)據(jù)是博客列表的順序,只不過“順序”這個數(shù)據(jù)是通過博客id來體現(xiàn)的。這和一張表的主鍵同時可以用作另外一張表的外鍵,是同樣的道理。同樣需要注意的是,當新增加一條博客時,posts和postId這兩個狀態(tài)都要進行修改。這看似變得麻煩,不如直接使用一個數(shù)組類型的狀態(tài)操作簡單,但是當需要修改某一篇博客的數(shù)據(jù)時,這種結(jié)構(gòu)就有了明顯的優(yōu)勢,而且直接使用數(shù)組保存狀態(tài),會存在對象嵌套層級過深的問題,想象下訪問評論的內(nèi)容,需要通過類似posts[0].comments[0].content三層結(jié)構(gòu)才能獲取到,當業(yè)務越復雜,這個問題越突出。扁平化的State,才具有更好的靈活性和擴展性。
截至目前為止,我們的State都是根據(jù)后臺API返回的領域數(shù)據(jù)進行設計的,但實際上,應用的State,不僅包含領域數(shù)據(jù),還需要包含應用的UI邏輯數(shù)據(jù),例如根據(jù)當前是否正在與服務器通信,處理頁面的加載效果;當應用運行出錯時,需要顯示錯誤信息等。這時,State的結(jié)構(gòu)如下:
{ "isFetching": false, "error": "", "posts": { ... }, "postIds": [1, ...], "comments": { ... }, "authors": { ... } }
隨著應用業(yè)務邏輯的增加,State的第一層級的節(jié)點也會變得越來越多。這時候我們往往會考慮合并關聯(lián)性較強的節(jié)點數(shù)據(jù),然后通過拆分reducer的方式,讓每一個子reducer處理一個節(jié)點的狀態(tài)邏輯。這個例子中,我們可以把posts、postIds進行合并,同時狀態(tài)名做了調(diào)整,把isFetching、error作為全局的UI邏輯狀態(tài)合并:
{ "app":{ "isFetching": false, "error": "", }, "posts":{ "byId": { "1": { ... }, ... }, "allIds": [1, ...], } "comments": { ... }, "authors": { ... } }
這樣,我們就可以定義appReducer、postsReducer、commentsReducer、authorsReducer四個reducer分別處理4個子狀態(tài)。至此,State的結(jié)構(gòu)設計完成。
總結(jié)一下,設計Redux State的關鍵在于,像設計數(shù)據(jù)庫一樣設計State。把State看作應用在內(nèi)存中的一個數(shù)據(jù)庫,action、reducer等看作操作這個數(shù)據(jù)庫的SQL語句。
歡迎關注我的公眾號:老干部的大前端,領取21本大前端精選書籍!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/84943.html
簡介:簡單實現(xiàn)react-redux基礎api react-redux api回顧 把store放在context里,所有子組件可以直接拿到store數(shù)據(jù) 使組件層級中的 connect() 方法都能夠獲得 Redux store 根組件應該嵌套在 中 ReactDOM.render( , rootEl ) ReactDOM.render( ...
摘要:沿著管道有兩組偵聽器中間件和訂閱。中間件是可以偵聽傳入的動作的函數(shù),支持諸如,或偵聽器之類的工具。將視為一個帶有更新前更新后鉤子的全局對象,以及能夠以簡單的方式合成新狀態(tài)。應將兩者視為一體,并且不再需要文件導出類型的字符串。 難道現(xiàn)在狀態(tài)管理不是一個可以解決的問題嗎?直觀地說,開發(fā)人員似乎知道一個隱藏的事實:狀態(tài)管理的使用似乎比需要的更困難。在本文中,我們將探討一些你可能一直在問自己的...
閱讀 2342·2021-11-24 09:39
閱讀 3788·2021-11-19 09:40
閱讀 2156·2021-09-27 13:36
閱讀 1902·2019-08-30 15:44
閱讀 397·2019-08-30 13:52
閱讀 2716·2019-08-30 11:13
閱讀 2187·2019-08-29 16:18
閱讀 1763·2019-08-29 15:43