启示
Redux由Flux演变而来,但受Elm的启发,避开了Flux的复杂性。
不管你有没有使用过他们,只需几分钟就能上手Redux。
安装
安装稳定版:
npm install --save redux
多数情况下,你还需要使用React绑定库和开发者工具
npm install --save react-redux
npm install --save-dev redux-devtools
要点
应用中所有的state都以一个对象数的形式储存在一个单一的store中。
唯一改变state的办法是触发action,一个描述发生什么的对象。
为了描述action如何改变state树,你需要编写reducers。
就是这样:
/**
* Created by iqianjin-shisong on 16/5/30.
*/
import { createStore } from 'redux';
/**
* 这是一个 reducer,形式为 (state, action) => state 的纯函数.
* 描述了 action 如何把state转变成下一个 state.
*
* state 的形式取决于你, 可以使基本类型,数组,对象
* 甚至是 Immutable.js 生成的数据结构. 唯一的要点是
* 当state变化时需要返回全新的对象,而不是修改传入的参数.
*
* 下面的例子使用 'switch' 语句和字符串来做判断, 但你可以写帮助类(helper)
* 根据不同的约定(如方法映射)来做判断,只要使用你的项目即可
*
* @param state
* @param action
* @returns {number}
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
// 来创建 Redux store 来存放应用的状态
// API 是 { subscribe, dispatch, getState }
let store = createStore(counter);
// 可以手动订阅更新, 也可以事件绑定到视图层
store.subscribe(() =>
console.log(store.getState())
);
// 改变内部 state 唯一方法是 dispatch 一个 action
// action 可以本序列化, 用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({type: 'INCREMENT'})
// 1
store.dispatch({type: 'INCREMENT'})
// 2
store.dispatch({type: 'DECREMENT'})
// 1
你应该把要做的修改变成一个普通对象,这个对象被叫做 action,而不是直接修改state。然后编写专门的函数来决定每个 action 如何改变应用的state, 这个函数被叫做 reducer。
如果你以前使用 Flux,那么你只需要注意一个重要的区别。 Redux 没有Dispatcher 且不支持多个store。相反,只有一个单一的store和一个根级的 reducer 函数。随着应用不断变大,你应该把根级的 reducer拆成多个小的reducer, 分别独立的操作 state 树的不同部分,而不是添加新的 stores。这就像一个 React 应用只有一个根级的组件,这个根组件又由很多小组件构成。
用这个架构开发计数器有点杀鸡用牛刀,但它的美在于做复杂应用和庞大系统时邮箱的扩展能力。由于它可以用action 追溯应用的每一次修改,因此才有强大的开发工具。如录制用户会话并回放所有action来重现它。
三大原则
Redux 可以用这三个基本原则来描述:
单一数据源
整个应用的 state 被储存在一颗 object tree 中, 并且这个 object tree 只存在于唯一一个 store 中。
这让同构应用开发变得飞航容易。来自服务端的 state 可以在无需编写更多代码的情况下被序列化并注入到客户端中。由于是单一的 state tree, 调试也变得非常容易。 在开发中, 你可以把应用的 state 保存在本地,从而加快开发速度。此外,受益于单一的 state tree ,以前难以实现的如“撤销/重做”这类功能也变得轻而易举。
console.log(store.getState());
{
visibilityFilter: 'SHOW_ALL',
todos: [{
text: 'Consider using Redux',
completed: true,
}, {
text: 'Keep all state in a single tree',
completed: false
}]
}
State是只读的
唯一改变 state 的方法就是触发 state, action 是一个用于描述已发生时间的普通对象。
这确保了视图和网络请求都不能直接修改state,相反他们只能表达想要修改的意图,因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心 race condition 的出现。 Action 就是普通对象而已,因此他们可以被日志打印、序列化、储存、后期调试或测试时回放出来。
store.dispatch({
type: 'COMPLETE_TODO',
index: 1
});
store.dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: 'SHOW_COMPLETED'
});
使用纯函数来执行修改
为了描述 action 如何改变 state tree, 你需要编写 reducers。
Reducer只是一些纯函数,他接受先前的 state 和 action,并返回新的 state。 刚开始你可以只有一个reducer, 随着应用变大,你可以把它拆成多个小的 reducers, 分别独立地操作 state tree 的不同部分,因为 reducer 只是函数,你可以控制他们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。
function visibilityFilter(state = 'SHOW_ALL', action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default:
return state;
}
}
function todos(state = [], actioin) {
switch (actioin.type) {
case 'ADD_TODO':
return [...state, {
text: actioin.text,
completed: false
}];
case 'COMLETE_RODO':
return [...state.slice(0, action.index),
Object.assign({}, state[action.index], {
completed: true
}),
...state.slice(action.index + 1)
];
default:
return state;
}
}
import { combineReducers, createStore } from 'redux';
let reducer = combineReducers({ visibilityFilter, todos });
let store = createStore(reducer);
就是这样,现在应该明白Redux是怎么回事了。