redux扩展工具_用鸭子扩展您的Redux App

redux扩展工具

How does your front-end application scale? How do you make sure that the code you’re writing is maintainable 6 months from now?

您的前端应用程序如何扩展? 您如何确定您正在编写的代码从现在起6个月内可维护?

Redux took the world of front-end development by storm in 2015 and established itself as a standard — even beyond the scope of React.

Redux在2015年席卷了前端开发领域,并确立了自己的标准-甚至超出了React的范围。

At the company where I work, we recently finished refactoring a fairly large React codebase, adding redux instead of reflux.

在公司,我的工作,我们最近完成重构一个相当大的React的代码库,将终极版,而不是回流

We did it because moving forward would have been impossible without a well structured application and a good set of rules.

我们这样做是因为没有结构良好的应用程序和良好的规则,前进将是不可能的。

The codebase is more than two years old, and reflux was there from the beginning. We had to change code that wasn’t touched in more than a year and was pretty tangled with the React components.

该代码库已有两年多的历史了,从一开始就存在回流 。 我们不得不更改了一年多没有碰到的代码,并且与React组件纠缠不清。

Based on the work we did on the project, I put together this repo, explaining our approach in organizing our redux code.

基于我们在项目上所做的工作,我整理了此仓库 ,解释了组织redux代码的方法。

When you learn about redux and the roles of actions and reducers, you start with very simple examples. Most tutorials available today don’t go to the next level. But if you’re building something with Redux that’s more complicated than a todo list, you’ll need a smarter way of scaling your codebase over time.

当您了解redux以及动作和reduce的角色时,您将从非常简单的示例开始。 今天可用的大多数教程都不会再上一个台阶。 但是,如果您要使用Redux构建比待办事项列表更复杂的东西,则需要一种更智能的方式来随着时间扩展代码库。

Someone once said that naming things is one of the hardest jobs in computer science. I couldn’t agree more. But structuring folders and organizing files is a close second.

曾经有人说, 命名事物是计算机科学中最困难的工作之一。 我完全同意。 但是结构化文件夹和组织文件紧随其后。

Let’s explore how we approached code organization in the past.

让我们探讨一下过去如何进行代码组织。

功能与功能 (Function vs Feature)

There are two established approaches of structuring applications: function-first and feature-first.

有两种建立应用程序结构的方法:“ 功能优先”和“ 功能优先”

One the left below you can see a function-first folder structure. On the right you can see a feature-first approach.

您可以在下面的左侧看到功能优先的文件夹结构。 在右侧,您可以看到功能优先的方法。

Function-first means that your top-level directories are named after the purpose of the files inside. So you have: containers, components, actions, reducers, etc.

“功能优先”意味着您的顶级目录是根据内部文件的目的命名的。 因此,您拥有: 容器组件动作缩减器等等。

This doesn’t scale at all. As your app grows and you add more features, you add files into the same folders. So you end up with having to scroll inside a single folder to find your file.

这根本无法扩展。 随着应用程序的增长和添加更多功能,您将文件添加到相同的文件夹中。 因此,您最终不得不在单个文件夹中滚动才能找到文件。

The problem is also about coupling the folders together. A single flow through your app will probably require files from all folders.

问题还在于将文件夹耦合在一起。 单次通过您的应用程序流可能需要所有文件夹中的文件。

One advantage of this approach is that it isolates — in our case — React from redux. So if you want to change the state management library, you know which folders you need to touch. If you change the view library, you can keep your redux folders intact.

这种方法的一个优势是,在我们的案例中,它使React与Redux隔离开来。 因此,如果您想更改状态管理库,则知道需要触摸哪些文件夹。 如果更改视图库,则可以保持redux文件夹完整。

Feature-first means that the top-level directories are named after the main features of the app: product, cart, session.

Feature-first意味着顶级目录是根据应用程序的主要功能命名的: productcartsession

This approach scales much better, because each new feature comes with a new folder. But, you have no separation between the React components and redux. Changing one of them on the long run is a very tricky job.

这种方法的扩展性更好,因为每个新功能都带有一个新文件夹。 但是,React组件和redux之间没有分隔。 从长远来看,更改其中之一是一项非常棘手的工作。

Additionally you have files that do not belong to any feature. You end up with a folder common or shared, because you want to reuse code across many features in your app.

此外,您有不属于任何功能的文件。 由于要在应用程序中的许多功能之间重用代码因此最终得到一个公用共享文件夹。

两全其美 (The best of two worlds)

Although not in the scope of this article, I want to touch this single idea: always separate State Management files from UI files.

尽管不在本文讨论范围之内,但我还是想提出一个想法: 始终将状态管理文件与UI文件分开。

Think about your application on the long run. Imagine what happens with the codebase when you switch from React to another library. Or think how your codebase would use ReactNative in parallel with the web version.

从长远来看您的应用程序。 想象一下,当您从React切换到另一个库时,代码库会发生什么。 或者考虑您的代码库如何将ReactNative与Web版本并行使用。

Our approach starts from the need to isolate the React code into a single folder — called views — and the redux code into a separate folder — called redux.

我们的方法首先需要将React代码隔离到一个单独的文件夹(称为视图)中,并将redux代码隔离到一个单独的文件夹中,称为redux。

This first level split gives us the flexibility to organize the two separate parts of the app completely different.

第一级拆分使我们可以灵活地组织完全不同的应用程序的两个独立部分。

Inside the views folder, we prefer a function-first approach in structuring files. This feels very natural in the context of React: pages, layouts, components, enhancers etc.

在views文件夹中,我们更喜欢在结构化文件时采用功能优先的方法。 在React的上下文中,这感觉很自然: 页面布局组件,增强器等。

To not go crazy with the number of files in a folder, we may have a feature based split inside each of these folders.

为了避免困扰一个文件夹中的文件数量,我们可能在每个文件夹中都有基于功能的拆分。

Then, inside the redux folder…

然后,在redux文件夹中…

输入重新鸭子 (Enter re-ducks)

Each feature of the application should map to separate actions and reducers, so it makes sense to go for a feature-first approach.

应用程序的每个功能都应映射到单独的操作和简化程序,因此采用功能优先的方法是有意义的。

The original ducks modular approach is a nice simplification for redux and offers a structured way of adding each new feature in your app.

原始的ducks模块化方法是redux的一个很好的简化,并提供了一种结构化的方式来在您的应用程序中添加每个新功能。

Yet, we wanted to explore a bit what happens when the app scales. We realized that a single file for a feature becomes too cluttered and hard to maintain on the long run.

但是,我们想探索一下应用扩展时会发生什么。 我们意识到,功能的单个文件变得过于混乱,从长远来看很难维护。

This is how re-ducks was born. The solution was to split each feature into a duck folder.

这就是再鸭的诞生 。 解决方案是将每个功能拆分到一个鸭子文件夹中。

duck/
├── actions.js
├── index.js
├── operations.js
├── reducers.js
├── selectors.js
├── tests.js
├── types.js
├── utils.js

A duck folder MUST:

鸭子文件夹必须:

  • contain the entire logic for handling only ONE concept in your app, ex: product, cart, session, etc.

    包含用于仅在您的应用中处理一个概念的完整逻辑,例如: productcartsession等。

  • have an index.js file that exports according to the original duck rules.

    有一个index.js文件,该文件根据原始的鸭子规则导出。

  • keep code with similar purpose in the same file, such as reducers, selectors, and actions

    将目的相似的代码保存在同一文件中,例如reducersselectorsaction

  • contain the tests related to the duck.

    包含与鸭子有关的测试

For this example, we haven’t used any abstraction built on top of redux. When building software, it’s important to start with the least amount of abstractions. This way, you make sure that the cost of your abstractions doesn’t outweigh the benefits.

对于此示例,我们没有使用基于redux构建的任何抽象。 在构建软件时,从最少的抽象量开始很重要。 这样,您可以确保抽象的成本不会超过收益。

If you need to convince yourself that abstractions can be bad, watch this awesome talk by Cheng Lou.

如果您需要使自己相信抽象可能是不好的,请观看Cheng Lou的精彩演讲

Let’s see what goes into each file.

让我们看看每个文件中包含的内容。

种类 (Types)

The types file contains the names of the actions that you are dispatching in your application. As a good practice, you should try to scope the names based on the feature they belong to. This helps when debugging more complex applications.

类型文件包含要在应用程序中分派的动作的名称。 作为一种好习惯,您应该尝试根据名称所属的功能来确定名称的范围。 这有助于调试更复杂的应用程序。

const QUACK = "app/duck/QUACK";
const SWIM = "app/duck/SWIM";

export default {
    QUACK,
    SWIM
};
动作 (Actions)

This file contains all the action creator functions.

该文件包含所有动作创建器功能。

import types from "./types";

const quack = ( ) => ( {
    type: types.QUACK
} );

const swim = ( distance ) => ( {
    type: types.SWIM,
    payload: {
        distance
    }
} );

export default {
    swim,
    quack
};

Notice how all the actions are represented by functions, even if they are not parametrized. A consistent approach is more than needed in a large codebase.

请注意,即使未对参数进行参数设置,所有动作如何由函数表示。 在大型代码库中,一致的方法已远远超过了所需。

运作方式 (Operations)

To represent chained operations you need a redux middleware to enhance the dispatch function. Some popular examples are: redux-thunk, redux-saga or redux-observable.

为了表示链式操作,您需要一个Redux 中间件来增强调度功能。 一些流行的示例是: redux-thunkredux-sagaredux-observable

In our case, we use redux-thunk. We want to separate the thunks from the action creators, even with the cost of writing extra code. So we define an operation as a wrapper over actions.

在我们的例子中,我们使用redux-thunk 。 我们希望将动作与动作创建者区分开来,即使付出编写额外代码的代价。 因此,我们将操作定义为动作的包装。

If the operation only dispatches a single action — doesn’t actually use redux-thunk — we forward the action creator function. If the operation uses a thunk, it can dispatch many actions and chain them with promises.

如果该操作仅调度单个操作-实际上未使用redux-thunk-我们将转发操作创建者函数。 如果操作使用重击,则可以分派许多操作并将它们与承诺链接在一起。

import actions from "./actions";

// This is a link to an action defined in actions.js.
const simpleQuack = actions.quack;

// This is a thunk which dispatches multiple actions from actions.js
const complexQuack = ( distance ) => ( dispatch ) => {
    dispatch( actions.quack( ) ).then( ( ) => {
        dispatch( actions.swim( distance ) );
        dispatch( /* any action */ );
    } );
}

export default {
    simpleQuack,
    complexQuack
};

Call them operations, thunks, sagas, epics, it’s your choice. Just find a naming convention and stick with it.

称它们为操作,重击,萨加斯,史诗,这是您的选择。 只要找到一个命名约定并坚持下去即可。

At the end, when we discuss the index, we’ll see that the operations are part of the public interface of the duck. Actions are encapsulated, operations are exposed.

最后,当我们讨论index时 ,我们将看到操作是Duck的公共接口的一部分。 动作被封装,操作被公开。

减速器 (Reducers)

If a feature has more facets, you should definitely use multiple reducers to handle different parts of the state shape. Additionally, don’t be afraid to use combineReducers as much as needed. This gives you a lot of flexibility when working with a complex state shape.

如果特征具有更多的构面,则绝对应使用多个化简器来处理状态形状的不同部分。 此外,不要害怕根据需要使用CombineReducers 。 在处理复杂的状态形状时,这为您提供了很大的灵活性。

import { combineReducers } from "redux";
import types from "./types";

/* State Shape
{
    quacking: bool,
    distance: number
}
*/

const quackReducer = ( state = false, action ) => {
    switch( action.type ) {
        case types.QUACK: return true;
        /* ... */
        default: return state;
    }
}

const distanceReducer = ( state = 0, action ) => {
    switch( action.type ) {
        case types.SWIM: return state + action.payload.distance;
        /* ... */
        default: return state;
    }
}

const reducer = combineReducers( {
    quacking: quackReducer,
    distance: distanceReducer
} );

export default reducer;

In a large scale application, your state tree will be at least 3 level deep. Reducer functions should be as small as possible and handle only simple data constructs. The combineReducers utility function is all you need to build a flexible and maintainable state shape.

在大型应用程序中,您的状态树将至少深3层。 Reducer函数应尽可能小,并且仅处理简单的数据构造。 CombineReducers实用程序功能是构建灵活且可维护的状态形状所需的全部。

Check out the complete example project and look how combineReducers is used. Once in the reducers.js files and then in the store.js file, where we put together the entire state tree.

查看完整的示例项目,并查看如何使用CombineReducers 。 一次进入reducers.js文件,然后进入store.js文件,在这里我们将整个状态树放在一起。

选择器 (Selectors)

Together with the operations, the selectors are part of the public interface of a duck. The split between operations and selectors resembles the CQRS pattern.

选择器与操作一起是鸭子公共接口的一部分。 操作和选择器之间的划分类似于CQRS模式

Selector functions take a slice of the application state and return some data based on that. They never introduce any changes to the application state.

选择器函数获取应用程序状态的一部分,并根据该状态返回一些数据。 他们从不对应用程序状态进行任何更改。

function checkIfDuckIsInRange( duck ) {
    return duck.distance > 1000;
}

export default {
    checkIfDuckIsInRange
};
指数 (Index)

This file specifies what gets exported from the duck folder. It will:

此文件指定从duck文件夹导出的内容。 它会:

  • export as default the reducer function of the duck.

    默认情况下导出鸭子的reduce功能。
  • export as named exports the selectors and the operations.

    导出为命名,导出选择器和操作。
  • export the types if they are needed in other ducks.

    如果其他鸭子需要它们,则将其导出。
import reducer from "./reducers";

export { default as duckSelectors } from "./selectors";
export { default as duckOperations } from "./operations";
export { default as duckTypes } from "./types";

export default reducer;
测验 (Tests)

A benefit of using Redux and the ducks structure is that you can write your tests next to the code you are testing.

使用Redux和ducks结构的好处是您可以在要测试的代码旁边编写测试。

Testing your Redux code is fairly straight-forward:

测试您的Redux代码非常简单:

import expect from "expect.js";
import reducer from "./reducers";
import actions from "./actions";

describe( "duck reducer", function( ) {
    describe( "quack", function( ) {
        const quack = actions.quack( );
        const initialState = false;

        const result = reducer( initialState, quack );

        it( "should quack", function( ) {
            expect( result ).to.be( true ) ;
        } );
    } );
} );

Inside this file you can write tests for reducers, operations, selectors, etc.

在此文件中,您可以编写用于化简,操作,选择器等的测试。

I could write a whole different article about the benefits of testing your code, there are so many of them. Just do it!

我可以写一篇关于测试代码的好处的不同文章,其中有很多。 去做就对了!

就是这样 (So there it is)

The nice part about re-ducks is that you get to use the same pattern for all your redux code.

关于重新鸭子的好处是,您可以对所有redux代码使用相同的模式。

The feature-based split for the redux code is much more flexible and scalable as your application codebase grows. And the function-based split for views works when you build small components that are shared across the application.

随着您的应用程序代码库的增长,redux代码的基于功能的拆分更加灵活和可扩展。 当您构建在应用程序之间共享的小型组件时,基于视图的基于功能的拆分将起作用。

You can check out a full react-redux-example codebase here. Just keep in mind that the repo is still under active development.

您可以在此处查看完整的react-redux-example代码库。 请记住,回购仍在积极开发中。

How do you structure your redux apps? I’m looking forward to hearing some feedback on this approach I’ve presented.

您如何构造您的redux应用程序? 我期待听到有关我提出的这种方法的一些反馈。

If you found this article useful, click on the green heart below and I will know my efforts are not in vain.

如果您发现本文有用,请单击下面的绿色心脏,我将知道我的努力没有白费。

翻译自: https://www.freecodecamp.org/news/scaling-your-redux-app-with-ducks-6115955638be/

redux扩展工具

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值