用鸭子缩放Redux应用程序

预期课程开始时,“ React.js开发人员”准备了有用材料的翻译。





您的应用程序的前端如何扩展?六个月后如何使您的代码支持?

在2015年,Redux席卷了前端开发领域,并确立了自己的标准,超越了React。

对于最近完成重构大量的代码基础上的反应,我们实现的终极版,而不是我公司的工作回流

我们必须采取这一步骤,因为如果没有结构良好的应用程序和清晰的规则,就不可能继续前进。

代码库已经存在两年多了,从一开始就存在回流。我们不得不更改与React组件紧密相关的代码,一年以上没有人碰过。
根据所做工作的经验,我创建了这个存储库,这将有助于说明我们在redux上组织代码的方法。
当您了解有关redux,actionsreducers的更多信息时,您将从简单的示例开始。当今可用的许多教程并没有超出它们。但是,如果要在Redux上创建比任务列表更复杂的内容,则需要一种合理的方法来随着时间的推移扩展代码库。

曾经有人说过,在计算机科学中,没有比给不同的事物命名更困难的任务了。我不能不同意。在这种情况下,文件夹结构和文件组织将排在第二位。

让我们看看我们过去如何进行代码组织。

功能与功能


有两种公认的组织应用程序的方法:功能优先和功能优先。
在左侧的屏幕截图中,文件夹结构是按照功能优先的原则组织的,而右侧是功能优先的原则。



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

这根本无法扩展。随着应用程序的增长和新功能的出现,您将文件添加到相同的文件夹中。因此,您需要长时间滚动其中一个文件夹的内容才能找到所需的文件。

另一个问题是合并文件夹。您的应用程序线程之一可能需要访问所有文件夹中的文件。

这种方法的优点之一是,在我们的例子中,它可以将React与Redux隔离。因此,如果要更改状态管理库,将知道需要哪些文件夹。如果您需要更改视图库,则可以保留带redux的文件夹。

“功能优先”意味着顶层目录将根据应用程序的主要功能进行命名:产品,购物车,会话。

由于每个新功能都位于一个新文件夹中,因此该方法的伸缩性更好。但是,在Redux和React组件之间没有分隔。从长远来看,更改其中之一并不是一件容易的事。

此外,您将拥有不属于任何功能的文件。结果,所有这些都归于common或shared文件夹,因为您还想在应用程序的不同功能中使用代码。

结合两全其美


尽管这不是本文的主题,但我想说的是,UI文件中的状态管理文件需要单独存储。

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

的核心是隔离的原理React代码位于名为views的文件夹中,而redux代码位于另一个名为redux的文件夹中。 入门级的这种分离使我们可以灵活地以完全不同的方式组织应用程序的各个部分。views文件夹中



我们支持功能优先文件的组织。在React的上下文中,这看起来很自然:页面,布局,组件,增强器等。

为了不使一个文件夹中的文件数量疯狂,您可以在这些文件夹中使用功能优先的方法。

同时,在redux文件夹中...

介绍新鸭


每个应用程序功能必须对应于单独的动作和简化程序,以便应用功能优先的方法是有意义的。

原来的模块化方法鸭子可以很容易地与终极版,并提供工作结构化的方式,以新的功能添加到您的应用程序。

但是,您想了解在扩展应用程序时会发生什么。我们意识到,为每个功能组织一个文件的方法会使应用程序混乱,并使其支持成为问题。

于是出现了鸭子解决方案是将功能拆分为鸭子文件夹。

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

duck文件夹应:

  • 包含仅处理应用程序一个概念的所有逻辑,例如:产品,购物车,会话等。
  • 包含index.js文件,该文件根据鸭子规则导出。
  • 将代码存储在执行类似工作的单个文件中,例如化简器,选择器和操作。
  • 包含与鸭子相关的测试。

例如,在此示例中,我们没有使用基于redux构建的抽象。创建软件时,从最少的抽象量入手很重要。因此,您将看到抽象的价值不超过抽象的好处。

如果要确保抽象性不好,请观看此Cheng Lou视频

让我们看一下每个文件的内容。

种类


类型 文件包含您在应用程序中执行操作的名称作为一种好习惯,您应该尝试覆盖与它们所属的功能相对应的名称空间。这种方法在调试复杂的应用程序时会有所帮助。

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

export default {
    QUACK,
    SWIM
};

动作


该文件包含动作创建者的所有功能

import types from "./types";

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

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

export default {
    swim,
    quack
};

请注意,所有动作均由函数表示,即使未对其进行参数化也是如此。对于大型代码库,一致的方法是最高优先级。

运作方式


要表示操作链(Operations),您将需要redux 中间件,以改善dispatch的功能。流行的例子是redux-thunkredux-sagaredux-observable

在我们的例子中,使用redux-thunk。我们需要单独的thunk行动创造者,甚至在编写额外的代码的成本。因此,我们将操作定义为action的包装

如果该操作仅发送一个动作,也就是说,它实际上并没有使用redux-thunk,那么我们将发送动作创建者函数如果操作使用重,它可以发送很多操作并使用promise链接它们

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
};

可以根据需要将其称为“ 操作”,“ thunks”,“ sagas”,“史诗”。只需确定命名原则并坚持下去即可。

最后,我们将讨论索引并看到操作是Duck公共接口的一部分。动作被封装,操作可以从外部访问。

减速器


如果您具有更多层面的功能,则绝对应该使用几个化简器来处理复杂的状态结构。另外,不要害怕根据需要使用尽可能多的CombineReducer。这将允许更自由地使用状态对象结构。

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;

在大型应用程序中,状态树将至少包含三个级别。Reducer函数应尽可能小,并且仅处理简单的数据构造。创建一个灵活且可维护的状态结构所需的全部就是CombineReducers函数

查看完整的项目示例,并查看如何正确使用CombineReducers,尤其是在文件中reducers.js以及store.js在何处构建状态树。

选择器


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

选择器函数获取应用程序状态的一部分,并基于该状态返回一些数据。他们从不更改应用程序的状态。

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

export default {
    checkIfDuckIsInRange
};

指数


该文件指示将从duck文件夹中导出的内容。
是他:

  • 默认情况下,从duck 导出reducer函数
  • 将选择器和操作导出为注册的导出。
  • 如果需要,可以导出其他鸭子的类型。

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;

测验


将Redux与ducks框架一起使用的优势在于,您可以在要测试的代码之后立即编写测试。

在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 ) ;
        } );
    } );
} );

在此文件中,您可以编写用于reducer,操作,选择器等的测试
我可以写一整篇有关代码测试好处的文章,但是它们已经足够了,所以只需测试您的代码即可!

就这样


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

基于功能的分区方法可帮助您的应用程序随着其增长而保持灵活性和可扩展性。基于功能的分离方法在构建应用程序不同部分共有的小组件时会很好地工作。

您可以在此处查看完整的react-redux-example 代码库请记住,存储库正在正常运行。 您如何组织您的redux应用程序?我期待收到有关所描述方法的反馈。课程上见




All Articles