问候,今天,我将与您讨论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之后,我发现我们正在编写许多永不更改的实用程序代码。 另外,我们必须确保将克隆状态发送到组件。
然后我有了标准化减速器的想法。 他面前的任务并不困难。
- 检查传入操作,如果该操作不适用于当前reducer,则返回旧状态,或者自动克隆状态并将其传递给处理程序方法,该方法将更改状态并将其返回给组件。
- 您应该停止使用标签,相反,控制器应该收到一个对象,其中包含我们感兴趣的reducer的所有动作创建者。
因此,一旦导入了这样一个集合,我就可以通过它创建任意数量的动作创建者,以将函数从reducer调度到控制器,而无需重新导入 - 与其使用笨拙的开关箱和linter使用的通用命名空间,我不希望每个动作都有一个单独的方法,克隆的reducer的状态和动作将进入该方法
- 能够从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})), });
因此,结果是:
- 摆脱了堆积标签
- 摆脱了控制器中的大量进口商品
- 拆下的开关盒
- 他们一次击败了萨加斯人,现在我们可以将他们的位置扩大到一个地方,确保所有继承人都将自动获得其他副作用处理程序
- 万一有相关的逻辑,我们有机会从化简器那里继承(目前这对我来说永远没有用=)
- 他们将克隆的责任从开发人员转移到了一定会记住的类上。
- 创建减速器时的例行程序较少
- 每个方法都有一个隔离的名称空间
我试图尽可能详细地描述一切=)对不起,如果感到困惑,楚科奇不是作家。 我希望我的经验对某人有用。
→ 当前示例可以在这里看到
感谢您的阅读!
UPD:已修复的错误。 他在晚上写,读得不好。 感谢您如此细致地指出它们=)