[译]Redux减速器数组
By robot-v1.0
本文链接 https://www.kyfws.com/applications/array-of-redux-reducers-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 8 分钟阅读 - 3906 个词 阅读量 0[译]Redux减速器数组
原文地址:https://www.codeproject.com/Articles/1177598/Array-of-Redux-Reducers
原文作者:Modesty Zhang
译文由本站 robot-v1.0 翻译
前言
A simplified approach to enable an array of reducers in a React/Redux web application with existing reducers and selector pattern. 一种简化的方法,可在具有现有reducers和选择器模式的React/Redux Web应用程序中启用一系列reducer.
介绍(Introduction)
反应(React) 和(and) Redux(Redux) 为现代Web应用程序提供了强大而灵活的体系结构.它强制(offers a powerful and flexible architecture for modern web applications. It enforces) 单向数据流(unidirectional data flow) 启动,管理应用程序状态突变并对其做出反应,还提供(to initiate, manage and react to application state mutations, also provides) 工具(tools) 和(and) 公用事业(utilities) 构建大规模的复杂应用程序. Redux构造,例如(to build large scale complex applications. Redux constructs, like) 减速器(reducers) 和(and) 组合减速器(combined reducers) ,启用逻辑分区中的状态管理,就好像不同(, enables managing state in a logical divisions as if different) 助焊剂商店(Flux stores) 对于不同的州.但是,没有标准模式可以处理同一个reducer的多个实例,当打开多个域对象实例时,我们需要一种实用且侵入性较小的解决方案.(for different states. However, there is no standard pattern to handle multiple instances of the same reducer, when opening up multiple instances of domain objects, we need a practical and less intrusive solution.)
例如,您已经拥有带有组合化合器的Redux商店与一个项目(域对象)一起工作,如何同时处理多个实例,例如一组条目或项目?标准的拆分/合并化简器在这里无济于事,因为状态将作为具有突变的props数据传递给组件,而(For example, you’ve already got Redux store with combined reducers work with one item (domain object), how to handle multiple instances at the same time, like an array of entries or items? The standard splitting / combining reducers doesn’t help here, because state will pass to components as props data with mutations, while) 减速器(reducers) 本质上是一个处理功能(are essentially a function that handles) 行动(actions) 管理和改变状态.(to manage and mutate state.) 此外,由于目前(Additionally, since current) 组合减速器(combined reducers) ,(,) 行动(actions) 并且组件已经针对单个实例进行了工作和测试,我们最好将现有代码放入集合中以重用它们.(and components have been working and tested for single instance, we’d better to reuse existing code when putting them into a collection.) 我们通过两个步骤解决了这个问题.首先,将所有反应成分从商店形状中分离出来,使它们仅在"成分道具"的逻辑边界内运行.其次,管理阵列中的状态集合,并将同一组合的reducer应用于更高级别的reducer.(We’ve solved the problem with two steps. First, decoupling all react components from store shape, make them only operates within a logical boundary of “component props”. Second, managing a collection of states in an array and apply the same combined reducer in a higher level reducer.) 解耦步骤有助于在重新架构商店形状的同时保持组件的完整性,而收集化步骤则是在不进行实质性更改的情况下,利用现有的异化器和动作达到特定选定实例的状态.(The decoupling step helps to keep components intact while re-architecture the store shape, and the collection-ization step is to utilize existing reducers and actions to a specific selected instance’s state without substantial changes.) 这两个步骤的好处是变更范围包含在更高级别的减速器中,所有组件都无法存储形状.当发展相当复杂且大型的应用程序架构时,这两个好处对于灵活性和可维护性,最终的项目进度和质量至关重要.(The benefit of this two steps approach is the scope of changes are contained in higher level reducers, all components become agnostic to store shapes. When evolving a fairly complex and large application architecture, these two benefits are essential to flexibility and maintainability, ultimately project schedules and qualities.)
背景(Background)
在我们的项目中相当晚才需要多个实例.构建该应用程序时没有同时打开/编辑多个域对象的概念.已经为单个域对象开发了所有组件,操作,化简器,并且对象状态由组合化简器管理.现在,我们需要在其自己的选项卡中打开不同的域对象,以便用户可以轻松地在它们之间切换.(The requirement for multiple instances comes in a fairly late in our project. The application has been built without the notion of multiple domain objects opened / edited at the same time. All components, actions, reducers have been developed for single domain object, and object state is managed by a combined reducer. Now we need to open different domain objects in its own tab, so that user can easily switch between them.) 我们找到(We found) Redux选择器模式(Redux Selector Pattern) 是解耦步骤的很好的帮助,它在与reducer并置的选择器中封装了有关状态形状的知识,因此组件不依赖于状态结构,在重新成形数据存储时不需要更改组件代码.(is a great helper to the decoupling step, it encapsulates the knowledge about state shape in selectors that are collocated with reducer, so that component doesn’t depend upon state structure, component code doesn’t need to change when re-shaping the data store.) 当当前选定实例发生更改时,动作和组件将自动切换到选定状态,无需运行重复且不断变化的逻辑来获取呈现状态,因为选择器实质上消除了对商店形状的依赖性.(When current selected instance is changed, actions and components will automatically switch to selected state, no need to run repetitive and ever-changing logic to get the state for rendering, since the selectors essentially eliminate the dependencies on store shape.) 有了选择器后,我们可以简单地从根状态中删除单个实例的组合reducer.当打开新的域对象时,在创建选项卡之后,组合的化合器及其初始状态将与选项卡状态相关联.并且,当分派动作时,我们可以使用当前选定的选项卡状态手动调用同一组合的reducer.然后组件渲染,React虚拟DOM使用选项卡数组中的选定对象数据更新DOM.(With selectors in place, we can simply remove the single instance’s combined reducer from root state. When a new domain object is opened, after a tab created, the combined reducer and its initial state will be associated with the tab state. And when action is dispatched, we can manually invoke the same combined reducer with current selected tab state. Then components render and React virtual DOM updates the DOM with the selected object data in the tab array.) 让我们看一些代码.(Let’s see some code.)
使用状态选择器(Using state selectors)
假设(Assuming) AEditor
是我们域对象的组件,单个编辑器组件代码如下所示,没有选择器:(is the component for our domain object, a single editor component code looks like this without selectors:)
// Domain object component without selector
//
const AEditor = React.createClass({
componentWillMount() {
...
},
componentWillReceiveProps(nextProps) {
...
},
render() {
return ( <lowerlevelcomponent {...this.props.editorstate} />);
}
});
export default connect(
state => ( {editorState: state.editorContainer.editorState} ),
dispatch => { (actions: bindActionCreators(actions, dispatch)} )
)(AEditor);
注意(Notice the) editorState
直接从根状态检索,但是该组件仅需要(is retrieved directly from root state, but the component only need) editorState
渲染.即使不需要多个(to render. Even without the need for multiple) AEditor
实例,最好消除对状态形状的依赖,这是选择器可以提供帮助的地方.(instances, it’d be better to eliminate the dependency on state shape, this is where selector comes to help.)
这是带有选择器的相同组件代码:(Here is the same component code with selectors:)
// Domain object component with selector
//
const AEditor = ... ; //same as above
export default connect(
state => ( {editorState: getEditorState(state) } ),
dispatch => { (actions: bindActionCreators(actions, dispatch)} )
)(AEditor);
getEditorState
是选择器,它所做的就是将根状态作为输入,并返回(is the selector, all it does is to take the root state as input, and returns the) editorState
.只有选择器知道存储形状,当当前选定的编辑器更改时,它将返回选定的编辑器状态.这就是我们可以保持的方式(. Only the selector knows the store shape, when current selected editor changes, it’ll return the selected editor state back. This is how we can keep) AEditor
组件和相关操作(当它具有对象数组时).(component and related actions as is when it has an array of objects.)
这是执行(Here is the implementation of) getEditorState
在根减速器中:(in root reducer:)
// selectors for single instance, editorContainer is a combined reducer.
export const getEditorContainer = (state) => state.editorContainer;
export const getEditorState = (state) => fromEditor.getEditor(getEditorContainer(state));
这里没有什么特别的,这些选择器功能的工作是为组件"选择"正确的状态.对于状态的多个实例,它们对于使当前操作与选定的对象状态一起使用至关重要.(Nothing really special here, these selector functions job is to “select” the correct state to the components. For multiple instances of states, they’re critical to make current Actions work with selected object state.) 现在该组件已与商店形状分离,我们可以继续创建编辑器状态数组.(Now that component is decoupled from store shape, we can move on to creating array of editor states.)
创建初始状态(Creating initial states)
的(The) editorContainer
实际上是组合减速器:(is actually a combined reducer:)
export const editorContainer = combineReducers({
editorState,
propertiesState,
outlineState,
categoryState,
dataPaneState
});
每个在哪里(where each) xxxState
是减速器.例如,这是(is a reducer. For example, here is the) reducer.editor.state.js
实现的文件(file that implements) editorState
:(:)
// sub-reducer that each has its own initialState
const initialState = {
actId: '',
actProps: undefined,
actSteps: undefined,
deleted: false
};
export const editorState = (state = initialState, action = {}) => {
switch (action.type) {
case types.NAVIGATE_WITH_ACTION_ID: return onNavigateWithActionId(state, action);
...
default: return state;
}
};
在应用组合式减速器之前(Before we can apply the combined reducer) editorContainer
到一个集合,我们需要一个初始状态的选择器(to a collection, we need a selector of initial state) :
// initial state selector for editorContainer
export const getInitialEditorContainerState = () => ({
editorState: initialEditor,
propertiesState: initialProperties,
outlineState: initialOutline,
categoryState: initialCategory,
dataPaneState: initialDataPane
});
我们需要做的就是从子约简器中导出每个initialState,然后将它们导入到初始状态选择器中:(All we need to do is to export each initialState from the sub reducers, then import them for the initial state selector:)
// updated reducer.editor.state.js with exported initialState
export const initialState = {
actId: '',
... // other initial props
};
...
// seletor.initialStaet.js: import initialState from each reducer file:
import { initialState as initialEditor } from "../editor/reducer.editor.state";
export const getInitialEditorContainerState = () => ({
editorState: initialEditor,
... // other imported initialStaet
});
一旦我们有了组合减速器的初始状态,其余的就很简单了.(Once we have the initial state for the combined reducer, all the rest is pretty straight forward.)
应用组合减速器(Applying combined reducer)
我们的目标是使用户能够打开/编辑多个(Our goal is to enable user opening / editing multiple) AEditor
实例在其自己的标签中.以来(instances in its own tab. Since) tabsState
已经有一个数组(already has an array of) tab
,在创建新标签时,我们可以设置(, when creating a new tab, we can set the) initialEditorContainerState
作为选项卡的(as the tab’s) contentState
,同时设置(, also set the) editorContainer
组合减速器为其(combined reducer as its) contentReducer
.(.)
这是之后调用的代码(Here is the code invoked after) newTab
在…中创建(is created in) tabsState
减速器:(reducer:)
// setting contentState and contentReducer for newly created tab
function _setTabContent(newTab) {
if (newTab.tabType === AppConst.AEDITOR) {
if (!newTab.contentState || typeof(newTab.contentState) !== "object") {
newTab.contentState = getInitialEditorContainerState();
}
if (!newTab.contentReducer || typeof(newTab.contentReducer) !== "function") {
newTab.contentReducer = editorContainer;
}
}
}
接下来,我们需要调用(Next, we need to invoke) contentReducer
更新(to update) contentState
在(in) tabsState
减速器:(reducer:)
// for all non-tab related actions, relay to contentReducer with contentState, then update the tab
function onContentActions(state, action) {
let updatedState = state;
let curTab = state.tabs[state.selectedIndex];
if (curTab && curTab.contentReducer) {
let updatedTabs = state.tabs.slice();
updatedTabs.splice(state.selectedIndex, 1, {...curTab, contentState: curTab.contentReducer(curTab.contentState, action)});
updatedState = {...state, tabs: updatedTabs};
}
return updatedState;
}
export default function tabsState(state = _initialTabsState, action) {
switch (action.type) {
case AppConst.OPEN_OR_CREATE_TAB: return onOpenOrCreateTab(state, action);
case AppConst.CLOSE_ONE_TAB: return onCloseOneTab(state, action);
case AppConst.UPDATE_ONE_TAB: return onUpdateOneTab(state, action);
default: return onContentActions(state, action);
}
}
注意(Notice) onContentActions
通过调用更新当前选项卡(update the current tab by calling) curTab.contentReducer(curTab.contentState, action)
,这是确保所有当前的"动作和组件"与当前选定的标签内容一起使用的关键.(, this is the key to make sure all current Actions and Components work with current selected tab content.)
将标签状态与(Associating tab state with) contentState
和(and) contentReducer
当选项卡扩展为承载不同类型的组件时,将为我们提供灵活性.当您的用例不在选项卡中时,相同的想法和技术仍然适用.(gives us flexibility when tabs extended to host different type of components. When your use case is not in tabs, same idea and technique still applies.)
包起来(Wrap up)
尽管没有标准模式可将reducer应用于集合,但选择器模式和从数组中选择状态调用单个实例的reducer是一种实用而有效的解决方案.(Although no standard pattern to apply reducers to a collection, the selector pattern and invoking single instance’s reducer with selected state from an array is a practical and efficient solution.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
HTML HTML5 ECMAScript ECMAScript6 Javascript Architect ReactJS 新闻 翻译