Redux 常見問題:程式碼結構
目錄
- 我的檔案結構應該是什麼樣子?我應該如何將專案中的動作建立者和 reducer 分組?我的選取器應該放在哪裡?
- 我應該如何在 reducer 和動作建立者之間分割我的邏輯?我的「商業邏輯」應該放在哪裡?
- 我為什麼應該使用動作建立者?
- WebSocket 和其他持續連線應該放在哪裡?
- 我可以在非元件檔案中使用 Redux 儲存嗎?
我的檔案結構應該是什麼樣子?我應該如何將我的專案中的動作建立器和 reducer 分組?我的選取器應該放在哪裡?
由於 Redux 僅是一個資料儲存庫程式庫,因此它對於專案的結構沒有直接的意見。然而,有一些常見模式是大多數 Redux 開發人員傾向使用的
- Rails 風格:為「動作」、「常數」、「reducer」、「容器」和「元件」分開資料夾
- 「功能資料夾」/「網域」風格:每個功能或網域分開資料夾,可能每個檔案類型都有子資料夾
- 「鴨子/切片」:類似於網域風格,但明確地將動作和 reducer 綁定在一起,通常透過在同一個檔案中定義它們
通常建議在 reducer 旁邊定義選取器並匯出,然後在其他地方重複使用(例如在 mapStateToProps
函數、非同步動作建立器或 sagas 中),以將所有知道狀態樹實際形狀的程式碼放在 reducer 檔案中。
我們特別建議將邏輯組織到「功能資料夾」中,將特定功能的所有 Redux 邏輯放在單一的「切片/鴨子」檔案中.
請參閱此部分以取得範例
詳細說明:範例資料夾結構
範例資料夾結構可能如下所示/src
index.tsx
:呈現 React 元件樹的進入點檔案/app
store.ts
:儲存設定rootReducer.ts
:根 reducer(選用)App.tsx
:根 React 元件
/common
:hook、通用元件、工具程式等/features
:包含所有「功能資料夾」/todos
:單一功能資料夾todosSlice.ts
:Redux reducer 邏輯和關聯動作Todos.tsx
:React 元件
/app
包含應用程式範圍的設定和佈局,取決於所有其他資料夾。
/common
包含真正通用且可重複使用的工具程式和元件。
/features
具有包含與特定功能相關的所有功能的資料夾。在此範例中,todosSlice.ts
是「鴨子」風格的檔案,其中包含對 RTK 的 createSlice()
函數的呼叫,並匯出切片 reducer 和動作建立器。
儘管最終而言,您如何配置磁碟上的程式碼並不重要,但請務必記住,不應孤立地考慮動作和簡化器。完全有可能(且建議)在一個資料夾中定義的簡化器回應在另一個資料夾中定義的動作。
進一步資訊
文件
文章
- 如何擴充 React 應用程式(隨附說明:擴充 React 應用程式)
- Redux 最佳實務
- 建構(Redux)應用程式的規則
- React/Redux 應用程式的更佳檔案結構
- 組織程式碼的四種策略
- 封裝 Redux 狀態樹
- Redux 簡化器/選擇器不對稱
- 模組化簡化器和選擇器
- 我邁向可維護的 React/Redux 專案結構的歷程
- React/Redux 連結:架構 - 專案檔案結構
討論
- #839:強調與簡化器並列定義選擇器
- #943:簡化器查詢
- React Boilerplate #27:應用程式結構
- Stack Overflow:如何建構 Redux 元件/容器
- Twitter:Redux 沒有終極檔案結構
我應如何將邏輯拆分為簡化器和動作建立器?我的「商業邏輯」應放在哪裡?
對於哪些邏輯部分應放入簡化器或動作建立器,沒有單一的明確答案。有些開發人員偏好「肥大」的動作建立器,搭配「精簡」的簡化器,後者僅擷取動作中的資料,並將其盲目合併至對應的狀態。其他人則試著強調讓動作盡可能精簡,並將動作建立器中 getState()
的使用降至最低。(針對此問題,其他非同步方法,例如 sagas 和 observables,屬於「動作建立器」類別。)
將更多邏輯放入簡化器中有多項潛在優點。動作類型很可能會更具語意性且更有意義(例如 "USER_UPDATED"
,而非 "SET_STATE"
)。此外,在簡化器中加入更多邏輯表示更多功能會受到時間旅行除錯的影響。
此則留言很好地總結了兩難處境
現在,問題在於要將什麼放入動作建立器,以及什麼放入簡化器,也就是在肥大與精簡動作物件之間做出選擇。如果您將所有邏輯放入動作建立器,您最後會得到肥大的動作物件,基本上宣告狀態的更新。簡化器會變成純粹、愚蠢的,加上這個、移除那個、更新這些功能。它們將很容易組合。但您的商業邏輯並不會太多。如果您在簡化器中放入更多邏輯,您最後會得到精簡、漂亮的動作物件,將大部分資料邏輯放在一個地方,但您的簡化器更難組合,因為您可能需要來自其他分支的資訊。您最後會得到大型簡化器,或從狀態中較高層級取得其他引數的簡化器。
我們建議盡可能將邏輯放入 reducer 中。有時你可能需要一些邏輯來協助準備要放入 action 中的內容,但 reducer 應該執行大部分的工作。
進一步資訊
文件
文章
討論
- 在 action creator 中放入過多邏輯會如何影響除錯
- #384:reducer 中的內容越多,你可以透過時光旅行重播的內容就越多
- #1165:商業邏輯/驗證應放置在哪裡?
- #1171:關於 action-creator、reducer 和 selector 的最佳實務建議
- Stack Overflow:在 action creator 中存取 Redux 狀態?
- #2796:釐清「商業邏輯」
- Twitter:遠離不明確的術語...
我為什麼應該使用 action creator?
Redux 不需要 action creator。你可以自由地以最適合你的方式建立 action,包括直接傳遞物件文字給 dispatch
。Action creator 源自於 Flux 架構,並已被 Redux 社群採用,因為它提供了多項好處。
Action creator 的可維護性較高。可以集中在一個地方更新 action,並套用至各處。所有 action 的執行個體保證具有相同的形式和預設值。
Action creator 可進行測試。必須手動驗證內嵌 action 的正確性。如同任何函式,action creator 的測試可以寫一次並自動執行。
Action creator 較容易撰寫文件。action creator 的參數會列舉 action 的相依性。而且集中 action 定義提供了一個方便的文件說明註解放置處。當 action 是內嵌撰寫時,較難擷取和傳達這些資訊。
Action creator 是一種更強大的抽象。建立 action 通常涉及轉換資料或發出 AJAX 要求。Action creator 提供了一個統一的介面來處理這些不同的邏輯。這種抽象讓組件可以派遣 action,而不會受到建立該 action 的細節影響。
進一步資訊
文章
討論
WebSocket 和其他持續連線應該放在哪裡?
在 Redux 應用程式中,中介軟體是放置 WebSocket 等持續連線的適當位置,原因有幾個
- 中介軟體存在於應用程式的生命週期中
- 如同儲存體本身,你可能只需要一個特定連線的單一執行個體,供整個應用程式使用
- 中介軟體可以看到所有已發送的 action,並自行發送 action。這表示中介軟體可以接收已發送的 action,並將其轉換為透過 WebSocket 傳送的訊息,以及在透過 WebSocket 收到訊息時發送新的 action。
- WebSocket 連線執行個體無法序列化,因此 不應放在儲存體狀態中
請參閱 這個範例,它說明了 socket 中介軟體如何分派和回應 Redux 動作。
有許多現有的中介軟體可用於 Websocket 和其他類似連線 - 請參閱下方的連結。
函式庫
如何在非元件檔案中使用 Redux 儲存?
每個應用程式應該只有一個 Redux 儲存。這使得它在應用程式架構中有效地成為單例。與 React 搭配使用時,儲存會在執行階段注入到元件中,方法是在根元件 <App>
周圍呈現一個 <Provider store={store}>
,因此只有應用程式設定邏輯需要直接匯入儲存。
但是,有時程式碼庫的其他部分也需要與儲存互動。
您應該避免將儲存直接匯入到其他程式碼庫檔案中。雖然在某些情況下它可能有效,但這通常會導致循環匯入相依性錯誤。
一些可能的解決方案是
- 將您的儲存依賴邏輯寫成 thunk,然後從元件分派該 thunk
- 將對
dispatch
的參考從元件傳遞為相關函式的引數 - 將邏輯寫成中介軟體並在設定時將它們新增到儲存
- 在建立應用程式時將儲存實例注入到相關檔案中。
一個常見的用例是在 Axios 攔截器內部從 Redux 狀態中讀取 API 授權資訊,例如令牌。攔截器檔案需要參考 store.getState()
,但也需要匯入到 API 層級檔案中,這會導致循環匯入。
您可以從攔截器檔案公開一個 injectStore
函式
let store
export const injectStore = _store => {
store = _store
}
axiosInstance.interceptors.request.use(config => {
config.headers.authorization = store.getState().auth.token
return config
})
然後,在您的進入點檔案中,將儲存注入到 API 設定檔案中
import store from './app/store'
import { injectStore } from './common/api'
injectStore(store)
這樣一來,應用程式設定就是唯一必須匯入儲存的程式碼,而檔案相依性圖表會避免循環相依性。