通过标准班级组织减速机

问候,今天,我将与您讨论Reducer的组织方式。 并告诉我我从哪里开始和来到哪里。


因此,组织减速器有一定的标准,它看起来像这样:


export default function someReducer(state = initialState, action) { switch (action.type) { case 'SOME_REDUCER_LABEL': return action.data || {}; default: return state; } } 

此处的一切都很简单明了,但是在进行了这样的构造后,我意识到这种方法有很多困难。


  • 标签需要以某种方式存储,因为它们开始沿着项目爬行,并且爬行到控制器之外。
  • 标签必须具有唯一性,因为否则可能会与其他减速器相交
  • 使用这种结构的大部分时间都花在组织代码而不是处理传入数据上
  • 当reducer中有很多标签时,代码变得草率且难以阅读,并且通用名称空间并没有公开取悦我。
    大约在同一时间,我们开始使用sagas处理副作用。 这使我们可以大大简化与服务器端的通信,而无需使用回调。

现在,我们不得不让传奇人物知道副作用产生后需要调用哪个reducer。


我发现最明智的选择是让动作创建者。


我们以前的代码开始看起来像这样:


  import { FetchSaga } from '../../helpers/sagasHelpers'; const SOME_REDUCER_LABEL = 'SOME_REDUCER_LABEL'; export const someReducerLabelActionCreator = FetchSaga.bind(this, SOME_REDUCER_LABEL); export default function someReducer(state = initialState, action) { switch (action.type) { case SOME_REDUCER_LABEL: return action.data || {}; default: return state; } } 

FetchSaga是用于传奇的动作生成器函数(以下称为动作创建器),该传奇从服务器请求数据并将其分派给reducer,其标签在初始化阶段传递给该函数(SOME_REDUCER_LABEL)。


现在,对于传奇和标准版,reduce的标签要么是从reducer导出的,要么是动作创建者是从reducer导出的。 而且,为每个标签创建了这样的处理程序。 这只会增加一个头痛,因为一旦我打开了reducer,我便计算了10个定义标签的常量,然后几次调用了sagas的各种动作创建者,然后又调用了一个用于处理reducer状态的函数,它看起来像这样


 import { FetchSaga } from '../../helpers/sagasHelpers'; const SOME_REDUCER_LABEL1 = 'SOME_REDUCER_LABEL1';  .... const SOME_REDUCER_LABEL10 = 'SOME_REDUCER_LABEL10'; export const someReducerLabelActionCreator1 = FetchSaga.bind(this, SOME_REDUCER_LABEL1);   ..... export const someReducerLabelActionCreator10 = FetchSaga.bind(this, SOME_REDUCER_LABEL10); export default function someReducer(state = initialState, action) { switch (action.type) { case SOME_REDUCER_LABEL: return action.data || {}; case SOME_REDUCER_LABEL1: return action.data || {}; case SOME_REDUCER_LABEL2: return action.data || {}; case SOME_REDUCER_LABEL3: return action.data || {}; .... default: return state; } } 

当将所有这些动作导入控制器时,该动作也被夸大了。 它阻碍了。


看了这么多reducers之后,我发现我们正在编写许多永不更改的实用程序代码。 另外,我们必须确保将克隆状态发送到组件。


然后我有了标准化减速器的想法。 他面前的任务并不困难。


  1. 检查传入操作,如果该操作不适用于当前reducer,则返回旧状态,或者自动克隆状态并将其传递给处理程序方法,该方法将更改状态并将其返回给组件。
  2. 您应该停止使用标签,相反,控制器应该收到一个对象,其中包含我们感兴趣的reducer的所有动作创建者。
    因此,一旦导入了这样一个集合,我就可以通过它创建任意数量的动作创建者,以将函数从reducer调度到控制器,而无需重新导入
  3. 与其使用笨拙的开关箱和linter使用的通用命名空间,我不希望每个动作都有一个单独的方法,克隆的reducer的状态和动作将进入该方法
  4. 能够从reducer继承一个新的reducer会很好。 如果逻辑重复,但例如针对一组不同的标签。

这个想法对我来说似乎可行,因此我决定尝试实施它。


这是普通减速器现在的样子


  //    ,        reducer' import stdReducerClass from '../../../helpers/reducer_helpers/stdReducer'; class SomeReducer extends stdReducerClass { constructor() { super(); /**   reducer'.   reducer    action,     */ this.prefix = 'SOME_REDUCER__'; } /**   ,     reducer - type - ,    . -        ,     action creator,  SOME_REDUCE__FETCH.   type        action creator  someReduceInstActions - method - ,      action,            - sagas -    ,   ,       .    ,   action creator  ,      SOME_REDUCE__FETCH,  ,   ,      reducer    . */ config = () => [ { type: 'fetch', method: this.fetch, saga: 'fetch' }, { type: 'update', method: this.update }, ]; //           action creators init = () => this.subscribeReduceOnActions(this.config()); //  ,      fetch = (clone, action) => { //       return clone; }; //  ,         update = (clone, action) => { //       return clone; }; } const someReducerInst = new SomeReducer(); someReducerInst.init(); //   action creators   config //    action creator      export const someReducerInstActions = someReducerInst.getActionCreators(); //    .   checkActionForState    Action  ,      reducer'   export default someReducerInst.checkActionForState; 

内部的stdReducerClass如下


 import { cloneDeep } from 'lodash'; //    lodash //        ,            import { FetchSaga } from '../helpers/sagasHelpers/actions'; export default class StdReducer { _actions = {}; actionCreators = {}; /** UNIQUE PREFIX BLOCK START */ /**      ,  .   ,   ,    ,  reducer    action       */ uniquePrefix = ''; set prefix(value) { const lowedValue = value ? value.toLowerCase() : ''; this.uniquePrefix = lowedValue; } get prefix() { return this.uniquePrefix; } /** INITIAL STATE BLOCK START */ /**   initialState      reducer'. */ initialStateValues = {}; set initialState(value) { this.initialStateValues = value; } get initialState() { return this.initialStateValues; } /** PUBLIC BLOCK START */ /** *        init() .   ,      Config, action creator   _subscribeAction * actionsConfig -     ,     {type, method, saga?}     ,     action creator          */ subscribeReducerOnActions = actionsConfig => actionsConfig.forEach(this._subscribeAction); /**      _subscribeAction,    ,        type.  , reducer  ,       action. */ _subscribeAction = (action) => { const type = action.type.toLowerCase(); this._actions[type] = action.method; //       this.actionCreators[type] = this._subscribeActionCreator(type, action.saga); //   action creator   } /** _subscribeActionCreator -   , action creator          -   saga    ,      -   fetch           ,             type    ,   ,    action creator,  ,     SOME_Reducer__,      FETCH,      SOME_Reducer__FETCH,     action creator */ _subscribeActionCreator = (type, creatorType) => { const label = (this.prefix + type).toUpperCase(); switch (creatorType) { case 'fetch': return this._getFetchSaga(label); default: return this._getActionCreator(label); } } /** _getFetchSaga -     ,          */ _getFetchSaga = label => FetchSaga.bind(this, label); /** _getActionCreator -  action creator,      ,   ,    . */ _getActionCreator = label => (params = {}) => ({ type: label, ...params }); /**    ,      playload.     action   ,    */ checkActionForState = (state = this.initialState || {}, action) => { if (!action.type) return state; const type = action.type.toLowerCase(); const prefix = this.prefix;       ,    ,   . const internalType = type.replace(prefix, ''); //        if (this._actions[internalType]) { //     -    const clone = cloneDeep(state); //  ,    ,  action  ,     //        return this._actions[internalType](clone, action); } //   ,   action   .     return state; } /**       action creator,    reducer */ getActionCreators = () => this.actionCreators; } 

控制器中的外观如何? 等等


 import { someReducerInstActions } from '../../../SomeReducer.js' const mapDispatchToProps = dispatch => ({ doSoAction: (params) => dispatch(someReducerInstActions.fetch(url, params)), doSoAction1: (value, block) => dispatch(someReducerInstActions.update({value, block})), }); 

因此,结果是:


  1. 摆脱了堆积标签
  2. 摆脱了控制器中的大量进口商品
  3. 拆下的开关盒
  4. 他们一次击败了萨加斯人,现在我们可以将他们的位置扩大到一个地方,确保所有继承人都将自动获得其他副作用处理程序
  5. 万一有相关的逻辑,我们有机会从化简器那里继承(目前这对我来说永远没有用=)
  6. 他们将克隆的责任从开发人员转移到了一定会记住的类上。
  7. 创建减速器时的例行程序较少
  8. 每个方法都有一个隔离的名称空间

我试图尽可能详细地描述一切=)对不起,如果感到困惑,楚科奇不是作家。 我希望我的经验对某人有用。


当前示例可以在这里看到


感谢您的阅读!


UPD:已修复的错误。 他在晚上写,读得不好。 感谢您如此细致地指出它们=)

Source: https://habr.com/ru/post/zh-CN439730/


All Articles