Redux,用于管理应用程序状态,它的特点包括:
1、单向数据流
2、不可变数据
3、状态变化由 action 触发,导致 reducer 函数返回一个新状态
以下的几点讨论应该可以帮助到任何在大型、数据密集型应用中使用 Redux 的开发者:
第一点: 在存储和访问状态时使用索引和选择器
第二点: 把数据对象,对数据对象的修改以及其它 UI 状态区分开
第三点: 在单页应用的不同页面间共享数据,以及何时不该这么做
第四点: 在状态中的不同节点复用通用的 reducer 函数
第五点: 连接 React 组件与 Redux 状态的最佳实践
使用索引(index)保存数据,使用选择器(selector)读取数据
选择正确的数据结构可以对程序的结构和性能产生很大影响!
例如来自 /users 服务的数据。假设直接将这个普通数组原封不动地存储在状态中,当我们需要获取一个特定用户对象时,就需要遍历状态中的所有用户。如果用户很多,这可能会是一个代价高昂的操作。如果我们想跟踪用户的一小部分,例如选中和未选中的用户呢?我们要么需要把数据保存在两个数组中,要么就要记录这些选中和未选中用户在主数组中的索引。
在这种情况下,重构代码,改用索引的方式存储数据将会是更明智的选择,在 reducer 中,将数据结构改为:
{
"usersById": {
123: {
id: 123,
name: "Jane Doe",
email: "jdoe@example.com",
phone: "555-555-5555",
...
},
...
}
}
如果数据结构被重构成了这样,那么如何通过这种数据结构来展示一个简单的用户列表呢。为此,我们需要使用一个选择器,它是一个接收状态并返回所需数据的函数。一个简单的例子是一个返回状态中所有用户的函数:
const getUsers = ({ usersById }) => {
return Object.keys(usersById).map((id) => usersById[id]); // 返回了一个用户数据数组
}
将标准状态与视图状态、编辑状态分隔开
首先,标准状态指的是从服务返回的数据。除了标准状态,store 中还包含很多其他类型的数据,例如用户界面组件的状态等等。当首次从 API 读取到标准状态时,我们可能会想将其与页面的其他状态保存在同一个 reducer 文件中。这种方式可能很省事,但当你需要从不同数据源获取多种数据时,它就会变得难以扩展。
相反,我们会把标准状态保存在它单独的 reducer 文件中。这会促使你编写组织更加良好、更加模块化的代码。
垂直扩展 reducer(增加代码行数)比水平扩展 reducer(在 combineReducers 调用中引入更多的 reducer)的可维护性要差。将 reducers 拆分到各自的文件中有利于复用这些 reducer。
合理地在视图之间共享状态
在考虑共享状态时,请思考以下几个问题:
1、有多少视图或者其他 reducer 依赖此部分数据?
2、每个页面是否都需要这些数据的副本?
3、这些数据的改动有多频繁?
在状态之间复用 reducer 函数
在编写了一些 reducer 函数之后,我们可能想要在状态中的不同节点间复用 reducer 逻辑。
在 Redux 中复用 reducer 逻辑可能会有点棘手。默认情况下,当触发一个 action 时所有的 reducer 都会被执行。如果我们在多个 reducer 函数中共享一个 reducer 函数,那么当触发一个 action 时所有这些 reducer 都会被调用。然而这并不是我们想要的结果。当我们读取用户得到总数是 500 时,我们不想域名的 count 也变成 500。
我们推荐两种不同的方式来解决此问题,利用特殊作用域(scope)或是类型前缀(prefix)。第一种方式涉及到在 action 传递的数据中增加一个类型信息。
React 集成与包装
使用 react-redux 库,可以将状态中的数据映射到组件的 props 中。
const ConnectedComponent = connect(
(state) => { // mapStateToProps
return {
users: selectors.getCurrentUsers(state),
editingUser: selectors.getEditingUser(state),
... // 其它来自状态的 props
};
}),
(dispatch) => { // mapDispatchToProps
const actions = {
...actionCreators, // other normal actions
setPagination: actionCreatorFactories.setPaginationFor('USERS_'),
};
return bindActionCreators(actions, dispatch);
}
)(UsersComponent);