Action
只描述了有事情发生了这一事实,而 reducer
要做的事就是如何更新 state
在 Redux
应用中,所有的 state
都被保存在一个单一对象树中,建议设计一个良好的数据组织结构。如何才能以最简的形式把应用的 state
用对象描述出来?
以酒店应用为例,需要保存两个不同的内容:
- 当前筛选后酒店列表的过滤条件
- 默认酒店列表
通常,这个 state
树还需要存放其它一些 UI
数据,但尽量把这些数据与 UI
相关的 state
分开。
{
visibilityFilter: 'SHOW_ALL',
hotels:[
{
text: '公寓',
isShow: true,
},
{
text: '客栈',
isShow: false
}]
}
开发复杂的应用时,不可避免会有一些数据相互引用。建议你尽可能地把
state
范式化,不存在嵌套。把有数据放到一个对象里,每个数据以 ID 为主键,不同数据相互引用时通过 ID 来查找。把应用的state
想像成数据库。例如,实际开发中,在 state 里同时存放hotelsById: { id -> hotel } 和 hotels: array<id>
是比较好的方式。
reducer
就是一个函数,接收旧的 state
和 action
,返回新的 state
(previousState,action) => newState
之所以称作 reducer
是因为它将被传递给Array.prototype.reduce(reducer,?initialValue)
方法。保持 reducer
纯净是非常重要的,永远不要在 reducer
里做这些操作:
- 修改传入参数
- 执行
API
请求和路由跳转等 - 调用非纯属函数,如
Date.now()
等
只需要谨记 reducer
一定要保持纯净,只要传入参数一样,返回必须一样。没有特殊情况,没有副作用,没有 API 请求、没有修改参数、单纯执行计算。
function hotelApp(state = initialState, action) {
// 这里暂不处理任何 action,
// 仅返回传入的 state。
return state;
}
现在可以处理 SET_VISIBILITY_FILTER
, 需要做的只是改变 state
中的 visibilityFilter
function hotelApp(state = initialState, action) {
switch(action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
});
default:
return state;
}
}
注意:
-
不要修改
state
, 使用 Object.assign() 新建一个副本(assign() 可以快速的复制一个或者多个对象到目标对象中
)。 不能Object.assign(state, {visibilityFilter: action.filter})
, 因为它会改变第一个参数的值。必须把第一个参数设置为空对象, 即:Object.assign({}, {visibilityFilter: action.filter})
-
在
default
情况下返回旧的state
。遇到未知的action
时, 一定要返回旧的state
还有两个 action
需要处理, 我们先处理 ADD_HOTEL
.
function hotelApp(state = initialState, action) {
switch(action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter,
});
case ADD_HOTEL:
return Object.assign({}, state, {
text: action.text,
isShow: false
});
default:
return state;
}
}
COMPLETE_TODO
也很好理解:
case COMPLETE_TODO:
return Object.assign({}, state, {
todos: [
...state.todos.slice(0, action.index), //比如: 0 ~ 5
Object.assign({}, state.todos[action.index], { //此处为6
completed: true
}),
...state.todos.slice(action.index + 1) //后面就是7
]
});
我们不能直接修改却要更新数组中指定的一项数据,要先把前面和后面都切开。时刻谨记永远不要在克隆 state
前修改它。
combineReducers
接收一个对象, 可以把所有顶级的 reducer
放到一个独立的文件中, 通过 export
暴露出每个 reducer
函数,然后使用 import * as reducer
得到一个以它们名字作为 key 的 object:
import { combineReducers } from 'redux';
import * as reducers from './reducers';
const hotelApp = combineReducers(reducers);
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };
var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1); // { a: 1, b: 2, c: 3 }, target object itself is changed.
var str = "Hello happy world!"
str.slice(6); //happy world!
var str = "Hello happy world!"
str.slice(6, 11) //happy
- QQ:2225226