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