1.2.2. redux
为什么要使用redux,解决了什么痛点
随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state (状态)。 这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。
管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰
阐述redux三大原则
- 单一数据源
- 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
- state是只读的
- 唯一改变 state 的方法就是触发
action,action 是一个用于描述已发生事件的普通对象。
- 唯一改变 state 的方法就是触发
- 使用纯函数来执行修改
- 为了描述 action 如何改变 state tree ,你需要编写
reducers。 - Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。刚开始你可以只有一个 reducer,随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分,因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器
- 为了描述 action 如何改变 state tree ,你需要编写
阐述redux数据流过程
- 调用
store.dispatch(action)- Action 就是一个描述“发生了什么”的普通对象。比如:
{ type: 'LIKE_ARTICLE', articleId: 42 } { type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } } { type: 'ADD_TODO', text: 'Read the Redux docs.' }- 可以把 action 理解成新闻的摘要。如 “玛丽喜欢42号文章。” 或者 “任务列表里添加了'学习 Redux 文档'”。
- 你可以在任何地方调用
store.dispatch(action),包括组件中、XHR 回调中、甚至定时器中。
- Redux store 调用传入的 reducer 函数
- Store 会把两个参数传入 reducer: 当前的 state 树和 action。
// 当前应用的 state(todos 列表和选中的过滤器) let previousState = { visibleTodoFilter: 'SHOW_ALL', todos: [ { text: 'Read the docs.', complete: false } ] } // 将要执行的 action(添加一个 todo) let action = { type: 'ADD_TODO', text: 'Understand the flow.' } // reducer 返回处理后的应用状态 let nextState = todoApp(previousState, action)- 注意 reducer 是纯函数。它仅仅用于计算下一个 state。它应该是完全可预测的:多次传入相同的输入必须产生相同的输出。它不应做有副作用的操作,如 API 调用或路由跳转。这些应该在 dispatch action 前发生。
- 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
- 根 reducer 的结构完全由你决定。Redux 原生提供combineReducers()辅助函数,来把根 reducer 拆分成多个函数,用于分别处理 state 树的一个分支。
- 下面演示 combineReducers() 如何使用。假如你有两个 reducer:一个是 todo 列表,另一个是当前选择的过滤器设置:
function todos(state = [], action) { // 省略处理逻辑... return nextState } function visibleTodoFilter(state = 'SHOW_ALL', action) { // 省略处理逻辑... return nextState } let todoApp = combineReducers({ todos, visibleTodoFilter })- 当你触发 action 后,combineReducers 返回的 todoApp 会负责调用两个 reducer:
let nextTodos = todos(state.todos, action) let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action)- 然后会把两个结果集合并成一个 state 树:
return { todos: nextTodos, visibleTodoFilter: nextVisibleTodoFilter }- 虽然 combineReducers() 是一个很方便的辅助工具,你也可以选择不用;你可以自行实现自己的根 reducer!
- Redux store 保存了根 reducer 返回的完整 state 树。
- 这个新的树就是应用的下一个 state!所有订阅 store.subscribe(listener) 的监听器都将被调用;监听器里可以调用 store.getState() 获得当前 state。
- 现在,可以应用新的 state 来更新 UI。如果你使用了 React Redux 这类的绑定库,这时就应该调用 component.setState(newState) 来更新。
如果注册了两个一模一样的reducer,会产生什么问题
const reducer1 = function(state = 0, action){
switch(action.type) {
case "ADD":
state = state + 1
}
return state;
}
const reducer2 = function(state = 0, action) {
switch(action.type) {
case "ADD":
state = state + 1
}
return state;
}
const reducer = combineReducers({
A: reducer1,
B: reducer2
})
console.log(reducer)
const store = createStore(reducer)
console.log(store);
console.log(store.getState())// {A:0,B:0}
定义一个组件,加一个事件用来触发store.dispatch
export default class ReducerTest extends Component{
add = () => {
store.dispatch({
type: "ADD"
});
console.log(store.getState())
}
render() {
return <button onClick={this.add}>Add </button>
}
}
点击3次后: 打印:
{A:1,B:1}
{A:2,B:2}
{A:3,B:3}
阐述redux的middleware,其目的是什么
Redux middleware被用于解决不同的问题,但其中的概念是类似的。它提供的是位于 action 被发起之后,到达 reducer 之前的扩展点。 你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等
阐述combineReducers作用及其原理
combineReducers辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用createStore方法。- 合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由
combineReducers()返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给combineReducers()时对应的 key 进行命名。
rootReducer = combineReducers({potato: potatoReducer, tomato: tomatoReducer})
// rootReducer 将返回如下的 state 对象
{
potato: {
// ... potatoes, 和一些其他由 potatoReducer 管理的 state 对象 ...
},
tomato: {
// ... tomatoes, 和一些其他由 tomatoReducer 管理的 state 对象,比如说 sauce 属性 ...
}
}