redux的state樹應該如何設計?


如果你想要基於實體分類來做,可以考慮跳出redux,採用數據管道的思路。

比如說,我們有一種東西叫todo,那麼,究竟什麼東西能夠表達:

結合了對這個todo列表所有增刪改查,始終表達它當前狀態的這麼一個東西呢?

如果你把todo列表的獲取當成一個數據管道,然後,把查詢結果當做初始狀態,後續的所有變更用reducer的理念,疊加到初始狀態上,那最後得到的東西就是你想要的。

我用RxJS簡要寫個示例代碼,TypeScript寫的,可以用ts-node運行:

import { Subject, Observable } from "rxjs"

interface Todo {
id: number
title: string
}

interface ITodoOperation extends Function {
(todos: Todo[]): Todo[]
}

const update$: Subject& = new Subject&()

const result$: Observable& = update$
.scan((todos: Todo[], operation: ITodoOperation) =&> {
return operation(todos)
}, [])

result$.subscribe(todos =&> console.log(todos))

const addTodo = (todo) =&> {
update$.next((todos) =&> {
todos.push(todo)
return todos
})
}

const removeTodo = (id) =&> {
update$.next((todos) =&> {
return todos.filter(todo =&> todo.id !== id)
})
}

const updateTodo = (newTodo: Todo) =&> {
update$.next((todos) =&> {
todos.forEach(todo =&> {
if (todo.id === newTodo.id) {
todo.title = newTodo.title
}
})
return todos
})
}

const arr = [0, 1, 2, 3, 4]
arr.map(num =&> ({id: num, title: `item${num}`}))
.forEach(todo =&> addTodo(todo))

removeTodo(3)
updateTodo({id: 2, title: "dasdasd"})

其中,result$就是你想要的這麼一個東西。只要視圖在這個上面訂閱,類似上面代碼裡面的console.log那個地方,就可以在每次有變化的時候,一直得到它當前狀態的。

然後,注意一下從update$到result$的那句轉化,也就是scan,這個其實就是類似redux的理念了。

那麼,得到這樣的數據管道,可以用來幹什麼呢?並不一定要訂閱了使用,而是直接拿來跟其它實體組合,然後得到一個組合的自動更新的數據管道,視圖再訂閱那個。

這時候,你在回頭看「組件之間的通信」這段話,其實除了初始賦值,大部分都是偽命題。為什麼你要讓組件之間進行通信?如果每個組件都是在跟一個通用數據層打交道,調用它們的方法去操作數據,比如剛才封裝的addTodo,removeTodo,updateTodo等,然後其它的視圖訂閱對應數據管道的組合結果,每當產生變更的時候,自然就會得到最新結果。

所以這種理念,大局上是基於實體的數據管道,每條管道裡面跑的是類似redux的東西,更容易組合出複合業務。

更詳細的,可以參見我這篇專欄文章:知乎專欄


瀉藥,可是我只是來噴業界毒瘤redux,和安利 reactive-react/react-most (中文) 的,只要是需要設計,都不是什麼好東西。

只有一個store跟資料庫確實沒什麼區別,乾脆不如用graphQL什麼的,BFF,邏輯後移

真正好的代碼,不應該需要靠設計,產品的需求變化不是簡單的一開始設計好就能hold得住的,永遠不會有完美的設計

真正好的設計,store應該分散而且更貼近dumb component(View),簡單的TDD就搞得清楚,重構一邊不會影響到另一邊

不知道redux怎麼想的,要去reduce一個公共的state?component不應該知道有其他component的存在,誰關心別人什麼state,為什麼要share
store,你考慮過寫測試時的感受嗎,為什麼component要受到公共store的污染

這就是我為什麼要寫 reactive-react/react-most (Show HN: React-Most a€「 Declarative Monadic Reactive State Container for React)的原因。

reactive-react/react-most 的思想是只 lift 控制到HoC,component自包含,唯一需要共享的不應該是redux推的store,而只應該是action,因此react-most只提供一個可以共用的action 流。每個component只應該關心對流中的某些action做出什麼響應,來reduce自己的state。我們已經在產品上使用react-most,不需要任何設計,每一個component都可以TDD出來。
想聊細節歡迎到 https://gitter.im/jcouyang/react-most
關於most的問題可以到 https://gitter.im/cujojs/most (英文)

-------

更新: react-most 2.0 現在更名為 xreact 用TypeScipt重新,不默認依賴於mostjs,一些api的調整。

ref

- reactive-react/react-most

- React Most FRP best practice

- Show HN: React-Most a€「 Declarative Monadic Reactive State Container for React

- Some problems with React/Redux

- cujojs/most

- Cycle.js


更新:不用redux了

rxjs多好啊 用什麼redux,代碼寫亂了,邏輯也亂了

MVVM什麼的最棒了,直接放Subject和Observable在Service和component中,邏輯清晰代碼整潔層次分明,多利用下rxjs的操作符,什麼都可以做到

一個文件夾下幾十個js/ts什麼的看著就不爽,閱讀代碼也費力,要去尋找對應的action和reducer,浪費了大好青春

一些未必對的經驗:

沒寫過成熟的react+redux項目,只說一下寫ng2項目時對狀態管理的一些想法和思路

最開始用的就是 徐飛 的方案,用rxjs來偏手動的管理,好處很明顯,rx強大的operator和類promise的調用風格,適應後會非常順手

缺點不多,但是對我來說比較難以忍受的就是管理非常不方便, 對於每個observable,都要有個subject,分開管理,並且當需要共享一些數據時,就要額外把一些observable整理到額外的service中,這裡就有些不舒服,並且享受不到redux的各種優點了

但是總體來說,rxjs是非常值得使用的方案

不過就偏題了 題目問的是redux的state樹的管理

這個ng2的項目走到中期,嘗試了類redux的 ngrx/store ,大概類似於提供了rxjs支持的redux(最開始覺得rxjs式的redux完全是多此一舉毫無意義,嘗試了一段時間後直接開分支打回重寫,卻寫不下去了,就又回到了ngrx/store的方案)

----------------------------------------------------------------------

回到題主提的幾點,說一下我的想法

按實體管理

這種是我最不願意接受的,我寫前端代碼三個多月大概,實在沒看出前端有任何」實體「,前端的一切都是為 呈現數據 而服務的,前端的職責就是把獲取到的接近資料庫實體的數據,重新打散整理,更好地呈現在前台,如果前端的state里存儲的是數據的原來模樣,那這個redux還有什麼意義呢?這樣的redux只管理到了」怎樣獲得數據「,而對於整個前端的狀態管理,沒有什麼幫助

按模塊管理

有相當一段長時間,採用的是這種設計,總的來說是可靠的,可靠程度和模塊化程度相關,但是感覺這樣的話數據會過重,並且嵌套太深,當模塊底層的數據需要共享時,就不再整齊了,別且有些非常適合劃分出來的state,並不適合或者無法封裝成模塊

所以我的選擇是,按照特性區域劃分,也可以說是按照頁面劃分,也就是說結構是以前台所呈現的頁面為基礎的

頁面結構

app-&>用戶區域-&> 主題列表

-&> 主題 -&> 評論區

-&>管理後台-&> 論壇管理

-&> 用戶管理

在此基礎上

1. 把所有頁面state扁平化

2. 多個子區域需要共享的數據,放到父區域對應的state中存儲;多個區域共享的數據,放到appState中存儲

3. 多區域共享的功能組件的狀態數據,放到新的功能state中(比如站內聊天,就可以加一個imState,比如通用的api請求,就可以放在apiState中)

state結構

root -&> app -&> 全局數據, 如全局toast消息, 當前用戶,一些全局標記等,比如獲取省市列表,分類列表等數據,存儲在這裡

-&> auth -&> 這裡用於處理所有認證授權相關操作的數據,因為認證功能的特殊性,獨立出來會更方便管理

-&> yard -&> 當前頁面區域數據,如導航欄,通知消息等

-&> api -&> api請求的狀態管理數據,比如請求狀態,用於重試的失敗的api請求actionList等

-&> 主題列表

-&> 主題

-&> 用戶管理

遠程數據和本地數據

這個依然按照 以特性區域 劃分的原則

翻頁數據 和 帖子列表 都應該屬於主題列表的state,不過翻頁數據,除非需要持久化,或者外部同步,或者會被動更改,否則這種 主動點擊更改的 過會就丟的 數據應該沒必要放進store

放進store的應該是 帖子過濾選項 這種數據

對於前端的一些設計,成熟的方案文章不多,也都大多不怎麼滿意,這個方案是我迭代幾次重寫幾次,到現在用的比較久的,也許將來遇到新的需求新的情況,更大規模項目,這個方案會不合適,但是目前這樣的設計,用起來還是比較順手的

另外 最簡單的也是最強大的,還是rxjs,在redux上糾纏設計,最終只會得到心裡的滿足感,但是rxjs,是真的簡單,暴力,牛逼,拋去個人習慣和喜好,rxjs是最好的方案,沒有之一

當然redux+rxjs也是極好的 大概是拿了個90分的rxjs當狗糧餵了一個70分的redux,把戰鬥力提升到了95,但是攻速降低了40%


個人看法,什麼東西都放 Store 是個錯誤的做法

這樣不僅僅會讓你的 Store 變得很大,而且維護起來非常麻煩。拿個數據都要 dispatch 一下,再 connect 一下去取,完全沒有這樣的必要。

其實要解決這個問題要思考一下 Redux 到底是解決問題,前端應用狀態的管理。為什麼前端應用狀態需要做管理,我們思考一下問題。

如果頁面是純靜態頁面,那還需要 Redux 嗎?明顯不需要,後端伺服器從資料庫拿數據用模版渲染直接吐出來就行了。

大部分網站的數據都是變化的,能夠響應用戶的操作而變化,然後渲染頁面。因為數據變化,所以要渲染頁面,會帶來數據管理的複雜性,所以需要 Redux 這樣的工具來幫助我們管理

想到這裡一般人就已經不會再往下想了。這樣用起 Redux 就很懵逼了。再問一個問題,數據變化 -&> 渲染頁面,這個過程到底怎麼帶來了數據管理的問題,Redux 為什麼能(要)解決這個問題。

那麼再做一個思想實驗:如果現在頁面上僅僅只有兩個組件,它們能夠響應用戶的變化改變狀態,並且修改自己的顯示行為。舉個例子,頁面上只有一個輸入框,你 focus 的時候它會變長;另外一個組件是個下拉菜單,滑鼠放上去會顯示?這兩個組件都需要依賴一定的狀態改變自己的行為,但是需要 Redux 來進行管理嗎?好像沒什麼用。

再拓展一下這個例子,如果頁面上有 100 個這樣的組件,所依賴的狀態雖然會變化但是互不干擾,你需要 Redux 嗎?——嗯,想想好像不需要

如果你能認同這個想法,那麼你會發現 因為數據變化,所以要渲染頁面,會帶來數據管理的複雜性,所以需要 Redux 這樣的工具來幫助我們管理 這個想法有點膚淺。

我們來簡單定義一下什麼是組件:

  1. 有自己特定的顯示形態
  2. 可能響應用戶操作的

我們來定義一下什麼是狀態:

  1. 內存上個某個數據,可能會發生變化

我們來定義一下什麼是組件對狀態的依賴或者影響:

  1. 依賴:業務上,組件的顯示形態由該狀態決定
  2. 影響:業務上,用戶對組件的操作行為會改變該狀態

如果在業務上某個可改變狀態,對於多個組件可能會有多少依賴或影響情況發生?我們來討論一下:

情況1:

  • 這個狀態被 0 個組件依賴和影響。

那麼我們可以認為這個狀態不存在,你把它放 Redux 是沒有用的。

情況2:

  • 這個狀態只被 1 個組件依賴或(和)影響

想想一下。對於這種情況,其實如果你把這個狀態放在這個組件內部,其它組件根本不會在乎。因為它們不需要,不需要改變它。所以這時候我們可以認為這個狀態 屬於 這個組件,你把它放在組件內部不會帶來什麼管理的複雜性。我們姑且先這麼干。

情況3:

  • 這個狀態被多個組件依賴或(和)影響。

這種情況會比上面兩種情況複雜多了。你想像一下,如果我們按照情況 2 解決方案來處理這種情況會發生什麼問題。某個狀態被某個組件所擁有,如果另外一個組件需要需要依賴或者影響這個狀態的時候,就需要依賴那個組件!舉個例子,例如網站的按鈕,點擊它的時候需要發送 Ajax 請求並且 disabled 掉,並且彈出 loading modal。那麼這個 loading 的狀態不管放在按鈕組件還是 loading 組件都不合適,這樣做必然會導致另外一個組件的依賴。一旦這種公共狀態多起來,組件之間的依賴會極其混亂。

解決辦法就是把這種狀態抽離出來作為公共部分

這就是 Redux 這一類的狀態管理工具所解決的問題:管理被多個組件所依賴或者影響的狀態。

講得比較抽象,我總結一下結論:對於某個可變化的狀態,首先要看它在組件之間的依賴和影響狀況,如果它被多個組件所依賴或者影響(不光要記住依賴這個詞,也要記住影響這個詞,想想如果一個狀態被1個組件依賴,被另外一個組件影響的情況),那麼才抽出來放到 Store 裡面

如果一個狀態只被一個組件依賴和影響,那麼其實你可以直接放到這個組件內部,因為別的組件根本不在乎。

如果你不分析情況直接全放 Store,那麼看起來你好像很規範。但其實在另外一個層面增加了代碼的複雜性,應用大起來了 Store 就會很臃腫,管理起來很麻煩。

所以這裡給出的個人建議是:如果某個狀態只被一個組件所依賴和影響,那麼這個狀態就由這個組件自身來管理,包括數據初始化和變更等等。

其實蠻建議 https://github.com/heroku/react-refetch 和 Redux 合起來管理狀態,不過好像很多人都不知道這個玩意。

其實這個是思維方式的問題,我在這回答裡面提到過:前端新人的迷茫? - 鬍子大哈的回答 - 知乎

其實這裡還沒有把事情說完整,例如狀態改變有哪幾種情況(定時器、HTTP請求、用戶操作?),跟上面的情況結合起來怎麼考慮?後續再更新例子吧。

知乎上很多答案都很好,但是我覺得有時候大家把問題的根源思考清楚的話,討論起來會更好些。隨手拿起個工具就開始解決問題,有時候你會發現,這個問題根本就不是這個工具,或者是單獨由這個工具就能解決的。

拋磚引玉。


其實這個問題完全可以拋開redux來分析。

一個model設計,我提倡的原則是熵最小,也就是精簡到只包含必要的數據。

前端由於其gui編程的特殊性,數據將會涉及到ui狀態和數據邏輯狀態。 redux應該專註於數據邏輯部分。

關於數據結構設計,我建議題主選取扁平化設計的方式,不要過度嵌套表結構。所有的子數據表按照reducer的namespace拆分。

最後,建議題主寫model處理的時候,以點帶面,而不是大而全的考慮問題


還在思考中,沒有想清楚答案,大概了解到這些。

如果只是單頁面應用,不需要從服務端同步數據,store 作為 ssot 也就是唯一數據源,確實應當模仿資料庫,形成表結構等等,然後在 root component 當中做一些數據的關聯拼接等等傳給子組件,應該比較順。

但是如果後端還有一個真實的資料庫,store 作為緩存要跟後端同步的情況下,這就複雜了。沒有想清楚。懷疑沒有完美的 store 方案。


恰好看到這個問題,題主和我之前的糾結很類似,感覺遇到了同道。如果我沒有理解錯題主的意思的話,之前我也提問過類似的問題,可是沒啥人鳥我(碎碎念……),問題鏈接:

http://www.zhihu.com/question/53196876

目前我的結論是,redux無法像維護資料庫一樣去設計state

因為redux之所以出現,就是為了管理組件的state的,而組件的state本身可以看作是這個組件的數據模型,也就是說redux的store避免不了的會包含一大堆頁面數據模型,想做成純粹面向業務的數據模型也不是不可能,但肯定違背了redux的初衷,有點緣木求魚的感覺

如果想保持一個純粹的業務數據模型,我目前的想法是在redux體系外部維護一個既是觀察者又是被觀察者的業務數據對象,每當有業務數據變更時就只更新這個對象,而這個對象本身被redux的store觀察,把這個業務數據對象作為唯一的業務數據源來更新store自身的數據

然而這種做法簡直沒有任何可行性……

首先是增加了問題本身的複雜度,簡直是為了解決一個問題引入了一堆問題

其次,這完全背離了redux的哲學體系,產生了redux完全控制不住的副作用,增加了測試難度

最後,如果非要這麼做的話,不如拋棄redux,直接使用其他observable的解決方案

在確定了把redux的store作為頁面模型倉庫管理後,還會遇到一些問題:

1、業務數據的分散會不會導致數據同步的難度增加?

2、業務數據分散在各個頁面模型中,會不會造成業務數據和UI的耦合度過高?

3、除了UI控制項的組件(例如:日曆,級聯樹,上傳……)外,container類型的組件要不要維護自己的state?例如,這個頁面需要對非同步的業務介面彈出特定的modal,這個是否彈出modal的狀態是放在組件的state還是redux的state?如果放在組件的state裡面就需要在willRecieveProps裡面添加條件判斷更新組件的state,這樣的業務多了以後willRecieveProps方法的大量判斷會不會導致組件越來越臃腫?而如果放在store裡面,什麼時機再去更新這個狀態呢,關閉modal時?container組件卸載時?任何其他需要的時機?

上面的問題我也在糾結中,內心沒有一個很好的方案或者理由可以說服另一個想法,回答的問題沒有提的問題多,我這種回答者也是沒sei了~


Redux的store設計,應該盡量遵循遵循這樣的原則

1. 範式化,也就是像設計關係資料庫一樣,減少冗餘數據,不要像NoSQL一樣用空間換時間,沒有這個必要,後面會具體介紹;

2. 扁平化,state對象的層級結構不要太深,其實,做到了範式化,扁平化也就是很自然的事情,基本上每個sub-state對應一個關係型資料庫的表就對了。

按照題主所說的例子,現在數據包含article、comments、users這樣一個類似博客的數據結構,state數據類似於這樣。

{
articles: [
{
id: 1,
title: "React Intro",
content: "about react"
},
{
id: 2,
title: "Redux intro",
content: "about redux"
}
],
users: [
{
id: "morgan",
nick: "Morgan"
}
],
comments: [
{
id: 1,
userId: "user_1",
articleId: 1,
content: "Good article"
}
]
}

可以看到,state上有三個sub-state,分別是articles、users和comments,其中每個元素都有id作為唯一標識,當要展示某個article的內容,根據article的id,可以在comments這個sub-state里的articleId匹配找到所有的相關的comments,然後根據userId在users這個sub-state裡面找到user信息,最後,就可以顯示一篇文章所有的內容還有評論和評論者名字了。

範式化的好處,就是沒有數據冗餘,當需要修改某一個數據的時候,只需要修改一處;如果用去範式化(de-normalized)的方法,那就是每篇文章把相關comment以及用戶信息都記載一個object裡面,好處是拿來就可以用,比如像下面這樣。

{
articles: [
{
id: 1,
title: "React Intro",
content: "about react",
comments: [
content: "Good article"
user: {
id: "morgan",
nick: "Morgan"
}
]
}
]
}

現在如果現在發現用戶morgan的nick發生了改變,就不得不修改所有morgan評論過的article中的nick欄位,很麻煩的。

可以看見,去模式化之後,state的層次也變深了,不再偏平了,這樣寫代碼的時候也更加麻煩。

const nick = getState().articles[0].comments[0].user.nick;

上面這樣的code是有問題的,因為不能保准articles[0]里一定有comments,comments欄位也不保准有user,user也不保准有nick,所以最後code就會寫成這樣。

const article = getState().articles[0] || {};
const comment = article.comments[0] || {};
const user = comments.user;
const nick = user.nick;

因為層級深了,各種檢查下一層欄位是否有值,代碼也就麻煩了。

當然,了解關係型資料庫的朋友都知道,範式化存儲的缺點就是join數據需要消耗CPU,不過這對Redux不是問題。

首先,在前端頁面中的數據和後端資料庫中的數據不是一個量級的,前端工程師不大可能遭遇後端資料庫訪問那樣的性能問題。

其次,如果要追求極致的性能,還有reactjs/reselect 這個神器,使用 reselect,雖然還是需要做數據join操作,但是可以充分利用緩存結果,避免重複運算。

(今天忙,先更新到這裡,回頭再說)


最近在啟動一個新的react + redux的項目,希望比上一次改進一些東西,參考了各位的答案,還參考了好幾篇文章。

我其實糾結的一個核心問題是:

State樹是否應該直接和前端視圖對應?

如果,那視圖之間的數據共享怎麼做?

如果不是,即按實體劃分,那麼UI數據又應該存哪裡?對應reducer、actions又應該怎麼組織?

最後思考了很久我得到了下面這個模式,可以解決我的問題(Keynote畫的,有點丑)

對於設計State樹來說:按照實體(數據模型)+UI狀態來設計。例如一個TodoList的數據應該放到實體里,某個模態框的顯隱應該放到UI狀態里。

對於寫ReducerAction來說:Reducer和Action都直接服務兩種State,不考慮任何視圖相關的問題。

對於Containers來說:使用Selector從State里挑(計算)出來的數據進行按需渲染,使用Complex Action為它提供的語義化Action來生成新的應用State。

關於Complex ActionSelector:他們倆其實就類似mapDispatchToProps和mapStateToProps, 只不過Selector是mapStateToProps的最佳實踐(避免重複計算等),Complex Action作為中間層將噁心的業務邏輯包掉(非同步、UI Actions與Actions的組合等)。

個人思考的一點結論,還請多指教。

參考的文章:

https://medium.com/@yiquanzhou/how-to-structure-real-world-redux-react-application-d61e66a7dd36#.o1s069sp8

Three Rules For Structuring (Redux) Applications

The Anatomy Of A React Redux Module (Applying The Three Rules)


全劇仍在store里,不是全局自己控制項維護


既用 redux 的 store,又用組件私有狀態,同時又要做到:

記錄所有狀態的變化;

查看所有組件當前的狀態;

重現過去某一時刻的狀態;

方便地暴露某個私有狀態給其他組件;

你告訴我怎麼實現?

文本框 focus 與否不需要關心?產品腦子一熱,說用戶點這個輸入框時,旁邊顯示一個什麼什麼。你怎麼實現?忽然又說不顯示了,你是不是又要把 focus 狀態改回私有的?


同樣同意不把所有狀態放 store,Redux 歸根到底是數據流管理,首先就應該分清哪些是我們關係的業務數據,顯然一個 spinner 當前的旋轉角度不是我們操心的業務數據,一個文本框 focus 與否也不是,假如這些狀態都需要我們去管理的話,我們是不是應該操心一下瀏覽器或操作系統里的一些狀態了呢?這也是為什麼 React 要分 props 和 state,組件私有的狀態 state 完全夠用。另外 Redux 很重要的功能是跨組件的狀態共享和動作派發,我們在設計 reducer 的時候盡量以頁面或者功能點為單位,比如 todos 和 filter 分開,因為一個 Todos 組件只負責渲染 todo 列表,他並不操心現在的過濾器狀態,或者說它並不需要顯示其相關屬性,而 Filter 組件也是如此。對於 todo 列表要基於 filter 狀態來顯示這就應該是 Container 做的事情了,它來生成所有 computed values,用 props 的方式暴露給 React 組件。一個好的 state 樹需要能恢復應用的狀態,也就是說你把 state 序列化一下,下次運行的時候反序列化,應用根據這個狀態樹能渲染出上次退出時的樣子,就很合格,這一點對於路由和 history 跳轉尤為重要。


作者:二月

鏈接:redux的state樹應該如何設計? - 二月的回答 - 知乎

來源:知乎

著作權歸作者所有,轉載請聯繫作者獲得授權。

樓主會mongodb嗎?把state當成mongodb資料庫的格式去設計就可以了。

state是json對象,比如本地數據和伺服器返回的數據保存的問題,還有初始化數據和更新之後數據的保存。

const state = {
loading: true or false (本地初始數據,表示loading組件是否顯示),
serverData: {} or [] (伺服器數據,可能是對象,也可能是數組,初始化可以為空,或者自定義一串初始化的數據,)
}

那麼前端頁面很多的情況下,肯定會有很多的state要定義,這個時候,就需要採用工程化的思想去維護每個reducer,但是只有一個store原則是不變的,怎樣把store裡面的state合理的拆分成多個reducer去維護呢?

1 &> 全局state,這些state保存到名為global的reducer上,比如全局loading,全局登陸狀態,全局彈框和隱藏彈框等。(按全局劃分)

2 &> 頁面state,每個route對應一個page,一個page有自己單獨的state,這些satte可以放到一起管理,這樣的話代碼結構就不容易亂七八糟。(按頁面劃分)

本地state和伺服器返回數據的state不需要拆開,可以放到同一個reducer裡面,就像我上面寫的例子一樣。

=======================================================================

2017.1.6 更新一下redux數據存儲的坑總結

坑:store是非持久性存儲

大部分業務的數據都是從服務端請求,存儲在服務端資料庫,前端strore緩存當前訪問時候的數據,但其實很多數據都是要實時更新的,比如訪問一個路由,進入這個路由之後,就要去從伺服器請求新的數據回來(刷新瀏覽器也是類似),所以store並不能持久性存儲。

坑1解決辦法以及使用場景:

在單頁應用中,假設有這樣一個路由,一個列表頁面,根據返回的listId不同,載入不同的數據顯示,你也可以把他看成是一個分頁,通常在商品展示列表頁面就可以用這樣的方式實現。

&

假設你是這樣設計store的數據結構,把後端返回的列表數據直接丟到數組裡面,有的人會提到用id來索引,不過我通常把listData這個參數看成是一個key,直接索引這個key就能渲染對應的數據。

現在還有一種存儲辦法是在store只存儲id,然後把數據保存在瀏覽器緩存中,通過這個id來索引緩存中的數據,有個插件可以實現(不記得叫啥了)。

const initState = {
listData: []
}

說到這裡,你可能會覺得為什麼不直接存儲在瀏覽器緩存,還要先保存到store,再緩存到瀏覽器呢?其實我們不就是看中了虛擬dom的性能嗎,你想要在數據模型發生改變的時候即使更新,state會幫你做好這些事情,不需要自己去監聽。

繼續說道商城列表實現的例子,在componentWillMount的時候請求伺服器數據,請求指定的介面方法,將數據保存到store裡面。listData初始化是個空數組,返回新的數據的時候,就會發生新數據的渲染。這個時候你就能看到頁面數據載入出來了。

componentWillMount() {
dispatch(actionFetch(listId))
}

在卸載組件的時候,可以清除listData的數據,也可以不清除,如果下次訪問這個頁面還是返回相同的數據模型,就不需要更新dom,在切換過程中,加一個好看的loading。

我們看到很多手機網站網頁切換的時候,頭部的信息通常是靜態的,對於這些靜態數據,我們可以在store中初始化,如果需要更新(通常是手動觸發靜態數據的更新),就覆蓋掉。

最後提一點,前面做了這麼多數據操作,然後用戶不開心,把瀏覽器刷新了,store全部被初始化了,所以對於實時動態刷新的數據,要在componentWillMount做非同步請求處理,對於不需要實時更新的動態數據,可以保存到瀏覽器緩存(通常我是不會這麼乾的)。


好久不玩知乎了,上來看到這個話題分享點自己的經驗。我從14年中旬用react+flux 然後15年開始用redux,重寫了以前的業務。然後從今年開始做前端微服務。隨便說點感受。

目前主要是兩種觀點把:

一種把webapp的app部分增強,那麼redux確實是這個思想,我的store里會有一個資料庫,每一個子表對應遠程資料庫的entity,幾乎就是遠程資料庫的子集,通過normalizr搭配她的schema扁平到store里,然後通過pagination的util方法把數據對應的id和結果做緩存。一旦數據發生改變,我會拿出來store里變化的對應表,然後連接需要查看的表 然後在mapStateToProps實時計算再次map成ui控制項要的data,利用reselect做記憶避免重複選擇。這樣更新數據。當我觸發action,同樣先查表,看看在不在本地緩存,如果在,那麼看策略是否是強制更新,還是按需更新還是不更新。這樣保證數據總是最小更新,那怕我有1000個list item,我每次只更新我需要更新的內容。同時app的狀態放在自己module里的reducer,共享的放共享的子store,每次觸發action,比如用redux-saga搭配immutablejs,那麼也能做判斷當前是否需要載入等等。就是完全一個思考周到的app的設計。webapp就變成了很重的精心設計的一個app。這樣有這樣的好處,如果你是一個人數比較多的開發團隊,我可以ui組寫ui 邏輯組寫函數式數據處理,這樣也很能保證大家協同工作,網路層和後端一起寫網路action以及相應封裝。甚至後端介面協議不一樣,數據返回格式都不一樣,作為前端app我都可以map成為我前端為組件也業務涉及的資料庫,這就是redux適合重型開發的原因,然後利用webpack的code split做拆離。這種在大型團隊很適合,尤其跨部門合作的時候。由於架構的複雜度是不會隨著業務複雜度變化而變化的,大家只要按照規則寫feature,我覺得是很好的,唯一concern的是執行效率問題,其實也有很多方法優化。

另一種想把app複雜度降下來,讓其他模塊承受更多。我想大家應該已經感受到第一種點問題了,那就是重,管理麻煩。如果你沒有很好的doc和每一部分的測試,以及開發規範,以及定期的優化。那麼redux會讓你覺得是罪惡根源,全局污染,action調用混亂,debug追溯太難等等問題。我們想為什麼以前後端大家喜歡用java重型框架設計清楚想清楚所有的事,而現在大家又更喜歡微服務了?因為內部問題可以轉化為外部問題,多個系統可以合作,單系統更可靠可測試可拓展。如果是這樣的想法,那麼你就是屬於第二種角度思考問題的朋友。如果業務很複雜,我們可以做前端微服務,把複雜業務拆開。其實在用redux的朋友到後面我們也會用webpack去做split code,那我們是不是更激進一點,自己拆,拆完的業務和業務之間的依賴只是一些數據驅動的依賴不牽涉太多的交互聯動,如果有很少量的可以暴露一些api就好。微服務的特點大家知道要有持久層,有app間互相的通信,前端比如就放nodeserver搭配localstorage或者自己寫的util,這個server的話其實就是整合各個小的前端app,讓插件能夠跑起來。並且能把數據處理交給其他模塊,比如數據層的交給nodeserver,讓他先幫你做這個reselect,給你組件需要的數據,配合前端的graphql,喜歡的函數式朋友也可以用elm之類保證數據無副作用,更清爽。前端action-view需要更輕量維護,比如mobx這種就很不錯,搭配一下rxjs。然後app和app之間數據可以通過本地資料庫傳遞,也可以通過nodeserver緩存。這樣下來我們發現,前端變成插件化的應用,每個可拆分業務就是一個小前端,很好維護。核心就是一些util,然後router在server上載入一個一個業務,業務之間也可插拔,利用這樣的前後端搭配同構微服務,也能比較好的解決這類問題。如果業務很簡單,那就直接mobx+rxjava搭配一點elm的思想,react甚至能用精簡版的inferno這類更高效。


redux會將所有組件的state全部都扔進一個大的state中去維護,每次reducer只會更新一小部分。這要求開發者要自行構建良好的state組織結構,否則當頁面組件越來越多時,很容易被一個超級大的state繞暈。

我們現在在項目中使用了redux-arena,它可以將redux與react打包成一個模塊載入,如果react組件被卸載,那麼react組件在redux中的state/reducer/saga都會被自動卸載,徹底解決state樹和reducer過於龐大的問題。

一張gif圖說明redux-arena做了什麼,如下圖所示,當切換pageA和asyncPageB的掛載和卸載,會觸發redux中全局state樹的改變,所有與當前頁面展示無關的state和reducer徹底不復存在,你看到的state一定是當前被掛載的組件的state,極大地提升了state的可讀性。

項目的github地址:hapood/redux-arena

還有一篇相關介紹在這裡:https://zhuanlan.zhihu.com/p/28690716


贊同 @歐陽繼超 的【業界毒瘤 redux】說法,安利 [URL 是單頁應用的精華](https://kenberkeley.github.io/vue-demo/dist/docs/zh-cn/development/URL-is-soul-of-SPA.html)


按照官方的說法(Async Actions · Redux),在實際場景中,有嵌套對象的情況下,使用你所說的方案1會更好一些。參考如下:

If you have nested entities, or if you let users edit received entities, you should keep them separately in the state as if it was a database. In pagination information, you would only refer to them by their IDs. This lets you always keep them up to date. The real world example shows this approach, together with normalizr to normalize the nested API responses. With this approach, your state might look like this...


推薦閱讀:

為什麼瀏覽器要限制跨域訪問?
我們為什麼需要 React?
已經確定的設計,功能開發測試完了都已經上線了,覺得不滿意,要重新設計開發,在互聯網公司是普遍現象嗎?
CSS+JS如何實現這樣的顏色動態切換?
Node.js模塊里exports與module.exports的區別?

TAG:前端開發 | Redux |