Redux:组织容器、组件、动作和缩减器
Redux: organising containers, components, actions and reducers
问题:
What is the most maintainable and recommended best practice for organising containers, components, actions and reducers in a large
React/Redux application?
我的看法:
当前的趋势似乎是围绕相关的容器组件来组织 redux 附属物(actions、reducers、sagas...)。例如
/src
/components
/...
/contianers
/BookList
actions.js
constants.js
reducer.js
selectors.js
sagas.js
index.js
/BookSingle
actions.js
constants.js
reducer.js
selectors.js
sagas.js
index.js
app.js
routes.js
效果很好!虽然这个设计似乎有几个问题。
问题:
当我们需要从另一个容器访问 actions
、selectors
或 sagas
时,这似乎是一种反模式。假设我们有一个全局 /App
容器和一个 reducer/state 存储我们在整个应用程序中使用的信息,例如类别和枚举。继续上面的示例,使用状态树:
{
app: {
taxonomies: {
genres: [genre, genre, genre],
year: [year, year, year],
subject: [subject,subject,subject],
}
}
books: {
entities: {
books: [book, book, book, book],
chapters: [chapter, chapter, chapter],
authors: [author,author,author],
}
},
book: {
entities: {
book: book,
chapters: [chapter, chapter, chapter],
author: author,
}
},
}
如果我们想在 /BookList
容器中使用 /App
容器中的 selector
,我们需要在 /BookList/selectors.js
中重新创建它(肯定是错误的?)或者从 /App/selectors
导入它(它总是完全相同的选择器..?不。)。这两种方法对我来说都不是最佳选择。
此用例的主要示例是身份验证(啊...我们确实爱恨你的身份验证),因为它是一个 非常 常见的 "side-effect" 模型。我们经常需要在整个应用程序中访问 /Auth
故事、动作和选择器。我们可能有容器 /PasswordRecover
、/PasswordReset
、/Login
、/Signup
....实际上在我们的应用程序中,我们的 /Auth
容器根本没有实际组件!
/src
/contianers
/Auth
actions.js
constants.js
reducer.js
selectors.js
sagas.js
简单地包含上面提到的各种通常不相关的 auth 容器的所有 Redux 附属品。
我个人使用ducks-modular-redux提案。
这不是 "official" 推荐的方法,但对我来说效果很好。每个 "duck" 包含一个 actionTypes.js
、actionCreators.js
、reducers.js
、sagas.js
和 selectors.js
文件。这些文件中没有对其他鸭子的依赖,以避免循环依赖或duck circle,每个"duck"只包含它必须管理的逻辑。
然后,在根目录下我有一个 components
和一个 containers
文件夹以及一些根文件 :
components/
文件夹包含我的应用程序的所有纯组件
containers/
文件夹包含由上述纯组件创建的容器。当一个容器需要一个涉及许多 "ducks" 的特定 selector
时,我将它写在我编写 <Container/>
组件的同一个文件中,因为它是相对于这个特定容器的。如果 selector
在多个容器中共享,我会在单独的文件中创建它(或在提供这些道具的 HoC 中)。
rootReducers.js
:通过组合所有 reducer
简单地公开根 reducer
rootSelectors.js
为每个状态片段公开根选择器,例如在您的情况下您可能有类似的东西:
/* let's consider this state shape
state = {
books: {
items: { // id ordered book items
...
}
},
taxonomies: {
items: { // id ordered taxonomy items
...
}
}
}
*/
export const getBooksRoot = (state) => state.books
export const getTaxonomiesRoot = (state) => state.taxonomies
它让我们 "hide" 每个鸭子 selectors.js
文件中的状态形状。由于每个 selector
在你的鸭子中接收到整个状态,你只需要在你的 selector.js
文件中导入相应的 rootSelector
。
rootSagas.js
在你的鸭子中编写所有的 sagas 并且 管理涉及许多 "ducks" 的复杂流程。
所以在你的情况下,结构可以是:
components/
containers/
ducks/
Books/
actionTypes.js
actionCreators.js
reducers.js
selectors.js
sagas.js
Taxonomies/
actionTypes.js
actionCreators.js
reducers.js
selectors.js
sagas.js
rootSelectors.js
rootReducers.js
rootSagas.js
当我的"ducks"够小的时候,我经常会跳过文件夹的创建,直接写一个ducks/Books.js
或者一个ducks/Taxonomies.js
文件,里面有这5个文件(actionTypes.js
, actionCreators.js
, reducers.js
, selectors.js
, sagas.js
)合并在一起.
问题:
What is the most maintainable and recommended best practice for organising containers, components, actions and reducers in a large React/Redux application?
我的看法:
当前的趋势似乎是围绕相关的容器组件来组织 redux 附属物(actions、reducers、sagas...)。例如
/src
/components
/...
/contianers
/BookList
actions.js
constants.js
reducer.js
selectors.js
sagas.js
index.js
/BookSingle
actions.js
constants.js
reducer.js
selectors.js
sagas.js
index.js
app.js
routes.js
效果很好!虽然这个设计似乎有几个问题。
问题:
当我们需要从另一个容器访问 actions
、selectors
或 sagas
时,这似乎是一种反模式。假设我们有一个全局 /App
容器和一个 reducer/state 存储我们在整个应用程序中使用的信息,例如类别和枚举。继续上面的示例,使用状态树:
{
app: {
taxonomies: {
genres: [genre, genre, genre],
year: [year, year, year],
subject: [subject,subject,subject],
}
}
books: {
entities: {
books: [book, book, book, book],
chapters: [chapter, chapter, chapter],
authors: [author,author,author],
}
},
book: {
entities: {
book: book,
chapters: [chapter, chapter, chapter],
author: author,
}
},
}
如果我们想在 /BookList
容器中使用 /App
容器中的 selector
,我们需要在 /BookList/selectors.js
中重新创建它(肯定是错误的?)或者从 /App/selectors
导入它(它总是完全相同的选择器..?不。)。这两种方法对我来说都不是最佳选择。
此用例的主要示例是身份验证(啊...我们确实爱恨你的身份验证),因为它是一个 非常 常见的 "side-effect" 模型。我们经常需要在整个应用程序中访问 /Auth
故事、动作和选择器。我们可能有容器 /PasswordRecover
、/PasswordReset
、/Login
、/Signup
....实际上在我们的应用程序中,我们的 /Auth
容器根本没有实际组件!
/src
/contianers
/Auth
actions.js
constants.js
reducer.js
selectors.js
sagas.js
简单地包含上面提到的各种通常不相关的 auth 容器的所有 Redux 附属品。
我个人使用ducks-modular-redux提案。
这不是 "official" 推荐的方法,但对我来说效果很好。每个 "duck" 包含一个 actionTypes.js
、actionCreators.js
、reducers.js
、sagas.js
和 selectors.js
文件。这些文件中没有对其他鸭子的依赖,以避免循环依赖或duck circle,每个"duck"只包含它必须管理的逻辑。
然后,在根目录下我有一个 components
和一个 containers
文件夹以及一些根文件 :
components/
文件夹包含我的应用程序的所有纯组件
containers/
文件夹包含由上述纯组件创建的容器。当一个容器需要一个涉及许多 "ducks" 的特定 selector
时,我将它写在我编写 <Container/>
组件的同一个文件中,因为它是相对于这个特定容器的。如果 selector
在多个容器中共享,我会在单独的文件中创建它(或在提供这些道具的 HoC 中)。
rootReducers.js
:通过组合所有 reducer
rootSelectors.js
为每个状态片段公开根选择器,例如在您的情况下您可能有类似的东西:
/* let's consider this state shape
state = {
books: {
items: { // id ordered book items
...
}
},
taxonomies: {
items: { // id ordered taxonomy items
...
}
}
}
*/
export const getBooksRoot = (state) => state.books
export const getTaxonomiesRoot = (state) => state.taxonomies
它让我们 "hide" 每个鸭子 selectors.js
文件中的状态形状。由于每个 selector
在你的鸭子中接收到整个状态,你只需要在你的 selector.js
文件中导入相应的 rootSelector
。
rootSagas.js
在你的鸭子中编写所有的 sagas 并且 管理涉及许多 "ducks" 的复杂流程。
所以在你的情况下,结构可以是:
components/
containers/
ducks/
Books/
actionTypes.js
actionCreators.js
reducers.js
selectors.js
sagas.js
Taxonomies/
actionTypes.js
actionCreators.js
reducers.js
selectors.js
sagas.js
rootSelectors.js
rootReducers.js
rootSagas.js
当我的"ducks"够小的时候,我经常会跳过文件夹的创建,直接写一个ducks/Books.js
或者一个ducks/Taxonomies.js
文件,里面有这5个文件(actionTypes.js
, actionCreators.js
, reducers.js
, selectors.js
, sagas.js
)合并在一起.