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

效果很好!虽然这个设计似乎有几个问题。

问题:

当我们需要从另一个容器访问 actionsselectorssagas 时,这似乎是一种反模式。假设我们有一个全局 /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.jsactionCreators.jsreducers.jssagas.jsselectors.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)合并在一起.