Redux简介
Redux从设计之初就不是为了编写最短、最快的代码,他是为了解决 “当有确定的状态发生改变时,数据从哪里来” 这种可预测行为的问题的,所以当你使用Redux传值时,代码或许会很复杂,这时候请你不要去怀疑是不是不该使用Redux。
Redux和Vuex一样 是 JavaScript 状态容器,提供可预测化的状态管理。实际学习起这两种状态管理器来,我自个感觉Redux相对难学一点,Redux不止可以用于React的状态管理,它也可以用于Vue,只是当他用于React的时候,还必须依 React 绑定库和开发者工具。
npm install --save react-redux
npm install --save-dev redux-devtools
它和Vuex一样有着store 理念,且action都不能直接修改state上面的状态,Readux必须借助于reducers,所不同的是,Vuex里面是由action发起,然后再调用mutation修改(此处直接表明修改state上的何种属性);Redux则直接由action修改,但必须由reducers去根据action.type去判断修改的是state上面的哪个属性。
Redux也不支持多个Store,它只有一个单一的 store 和一个根级的 reduce 函数(reducer),随着应用不断变大,你应该把根级的 reducer 拆成多个小的 reducers,分别独立地操作 state 树的不同部分,而不是添加新的 stores。这就像一个 React 应用只有一个根级的组件,这个根组件又由很多小组件构成。
为什么要用状态管理
当你一个state变化需要引起第一个view视图层改变,接着第一个的改变引起第二个,以此类推,如果这个过程涉及到的组件很多,那么交给你的任务将很庞大,太繁琐,往深了说如果这个过程是可逆的,那将是你的噩梦,而Redux的出现就是来解决这个问题的。
核心概念
state
可以把它理解成一个普通的对象,由键值对构成,并且对键值对不做任何类型限制;
state是只读的,唯一改变 state 的方法就是触发 action
{ key : value , key1 : value1 }
action
它也是一个普通的JS对象,只不过他有固定的键type
强制使用 action 来描述所有变化带来的好处是可以清晰地知道应用中到底发生了什么。如果一些东西改变了,就可以知道为什么变。action 就像是描述发生了什么的指示器
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
reducer
最终,为了把 action 和 state 串起来,开发一些函数,就需要用到 reducer。
//其一
function userInfo (state = [],action){
switch (action.type){
case actionType.USERINFO_LOGIN:
return action.data;
case actionType.TENANTID:
return action.data;
case actionType.USERINFO_MENU:
return action.data;
default:
return state;
}
}
//其二
function visibilityFilter(state = 'SHOW_ALL', action) {
if (action.type === 'SET_VISIBILITY_FILTER') {
return action.filter;
} else {
return state;
}
}
//汇总
function todoApp(state = {}, action) {
return {
userInfo : userInfo (state.userInfo , action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
};
}
另一种汇总的写法
import { combineReducers, createStore } from 'redux'
let reducer = combineReducers({ visibilityFilter, userInfo })
let store = createStore(reducer)
纯函数:对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不会依赖外部环境的状态。
注意 : 永远不要在 reducer 里做这些操作:
- 修改传入参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如 Date.now() 或 Math.random()。
方法
bindActionCreators(actionCreators, dispatch)
把一个 value 为不同 action creator 的对象,转成拥有同名 key 的对象。同时使用 dispatch
对每个 action creator 进行包装,以便可以直接调用它们。
一般情况下你可以直接在 Store
实例上调用 dispatch
。如果你在 React 中使用 Redux,react-redux 会提供 dispatch
函数让你直接调用它 。
惟一会使用到 bindActionCreators
的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatch
或 Redux store 传给它。
为方便起见,你也可以传入一个函数作为第一个参数,它会返回一个函数。
参数
actionCreators
(Function or Object): 一个 action creator,或者一个 value 是 action creator 的对象。
返回值
(Function or Object): 一个与原对象类似的对象,只不过这个对象的 value 都是会直接 dispatch 原 action creator 返回的结果的函数。如果传入一个单独的函数作为 actionCreators
,那么返回的结果也是一个单独的函数。
combineReducers(reducers)
combineReducers
辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore
方法。
技巧
- 使用 ES7 提案的 对象展开运算符
{ ...state, visibilityFilter: action.filter }
- 缩减样板代码
1.将每个 action type 定义为 string 常量:const ADD_TODO = 'ADD_TODO';const REMOVE_TODO = 'REMOVE_TODO';
这样做的优势是:
- 帮助维护命名一致性,因为所有的 action type 汇总在同一位置。
- 有时,在开发一个新功能之前你想看到所有现存的 actions 。而你的团队里可能已经有人添加了你所需要的action,而你并不知道。
- Action types 列表在 Pull Request 中能查到所有添加,删除,修改的记录。这能帮助团队中的所有人及时追踪新功能的范围与实现。
- 如果你在 import 一个 Action 常量的时候拼写错了,你会得到 undefined 。在 dispatch 这个 action 的时候,Redux 会立即抛出这个错误,你也会马上发现错误。
2.通过创建函数生成 action 对象
export function addTodo(text) {
return {
type: 'ADD_TODO',
text
}
}
export function editTodo(id, text) {
return {
type: 'EDIT_TODO',
id,
text
}
}
也可以用于生成 action creator 的函数
function makeActionCreator(type, ...argNames) {
return function(...args) {
let action = { type }
argNames.forEach((arg, index) => {
action[argNames[index]] = args[index]
})
return action
}
}
const ADD_TODO = 'ADD_TODO'
export const addTodo = makeActionCreator(ADD_TODO, 'todo')
问题
Redux 只能搭配 React 使用?
不是,他可以Angular、 Angular 2、 Vue、 Mithril 等框架使用。
Redux 需要特殊的编译工具支持吗?
Redux 写法遵循 ES6 语法,但在发布时被 Webpack 和 Babel 编译成了 ES5,所以在使用时可以忽略 JavaScript 的编译过程。
将怎样的数据放入 Redux 的经验法则:
- 应用的其他部分是否关心这个数据?
- 是否需要根据需要在原始数据的基础上创建衍生数据?
- 相同的数据是否被用作驱动多个组件?
- 能否将状态恢复到特定时间点(在时光旅行调试的时候)?
- 是否要缓存数据(比如:数据存在的情况下直接去使用它而不是重复去请求他)?
如何在 reducer 之间共享 state? combineReducers 是必须的吗?
要共享state,建议将state再次拆分成小模块;
combineReducers 不是 必须的,它仅仅是通过简单的 JavaScript 对象作为数据,让 state 层能与 reducer 一一关联的函数而已。
如何组织State
设计规范化的State
范式化的数据包含下面几个概念:
- 任何类型的数据在 state 中都有自己的 “表”。
- 任何 “数据表” 应将各个项目存储在对象中,其中每个项目的 ID 作为 key,项目本身作为 value。
- 对单个项目的引用都应该根据存储项目的 ID 来完成。
- ID 数组应该用于排序
一个项目允许出现多个store吗?
Flux 原始模型中一个应用有多个 “store”,每个都维护了不同维度的数据。这样导致了类似于一个 store “等待” 另一 store 操作的问题。Redux 项目中只有一个store,而且通过将 reducer 分解成多个小而美的 reducer,进而切分数据域,隐含的实现了多个store,但又避免了上述情况的发生。
为何 type 必须是字符串,或者至少可以被序列化? 为什么 action 类型应该作为常量?
无法强制序列化 action,所以 Redux 只会校验 action 是否是普通对象,以及 type 是否定义。其它的都交由你决定,但是确保数据是可序列化将对调试以及问题的重现有很大帮助
虽然可以在任何地方手动创建 action 对象、手动指定 type 值,定义常量的方式使得代码的维护更为方便。
是否存在 reducer 和 action 之间的一对一映射?
不存在。建议的方式是编写独立且很小的 reducer 方法去更新指定的 state 部分,这种模式被称为 “reducer 合成”。一个指定的 action 也许被它们中的全部、部分、甚至没有一个处理到。这种方式把组件从实际的数据变更中解耦,一个 action 可能影响到 state 树的不同部分,对组件而言再也不必知道这些了。有些用户选择将它们紧密绑定在一起,就像 “ducks” 文件结构,显然是没有默认的一对一映射。所以当你想在多个 reducer 中处理同一个 action 时,应当避免此类结构。
为何组件没有被重新渲染、或者 mapStateToProps 没有运行?
如果直接返回同一对象,即使你改变了数据内容,Redux 也会认为没有变化。
为何不在被连接的组件中使用 this.props.dispatch?
connect() 方法有两个主要的参数,而且都是可选的。第一个参数 mapStateToProps 是个函数,让你在数据变化时从 store 获取数据,并作为 props 传到组件中。第二个参数 mapDispatchToProps 依然是函数,让你可以使用 store 的 dispatch 方法,通常都是创建 action 创建函数并预先绑定,那么在调用时就能直接分发 action。
如果在执行 connect() 时没有指定 mapDispatchToProps 方法,React Redux 默认将 dispatch 作为 prop 传入。所以当你指定方法时, dispatch 将 不 会自动注入。如果你还想让其作为 prop,需要在 mapDispatchToProps 实现的返回值中明确指出。
store 里能直接通过 store.dispatch()
调用 dispatch()
方法,但是多数情况下你会使用 react-redux 提供的 connect()
帮助器来调用。bindActionCreators()
可以自动把多个 action 创建函数 绑定到 dispatch()
方法上。