Redux
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
安装
npm install --save redux
要点
应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。 惟一改变 state 的办法是触发 action,一个描述发生什么的对象。为了描述 action 如何改变 state 树,你需要编写 reducers。
例子
import {
createStore } from 'redux';
/*
这是一个reducer,形式为 (state, action) => state 的纯函数
描述了 action 如何把 state 转变为下一个state
state 的形式,可以是基本类型/数组/对象 等等
唯一的要点是 当 state 变化时需要返回全新的对象,而不是修改传入的参数
*/
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
核心概念
当使用普通对象来描述应用的 state 时。例如,todo 应用的 state 可能长这样:
{
todos: [{
text: 'Eat food',
completed: true
},
{
text: 'Exercise',
completed: false
}],
visibilityFilter: 'SHOW_COMPLETED'
}
这个对象就像 “Model”,区别是它并没有 setter(修改器方法)。因此其它的代码不能随意修改它,造成难以复现的 bug。
要想更新 state 中的数据,你需要发起一个 action。Action 就是一个普通 JavaScript 对象(注意到没,这儿没有任何魔法?)用来描述发生了什么。下面是一些 action 的示例:
{
type: 'ADD_TODO', text: 'Go to swimming pool' }
{
type: 'TOGGLE_TODO', index: 1 }
{
type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
强制使用 action 来描述所有变化带来的好处是可以清晰地知道应用中到底发生了什么。如果一些东西改变了,就可以知道为什么变。action 就像是描述发生了什么的指示器。最终,为了把 action 和 state 串起来,开发一些函数,这就是 reducer。再次地,没有任何魔法,reducer 只是一个接收 state 和 action,并返回新的 state 的函数。
对于大的应用来说,不大可能仅仅只写一个这样的函数,所以我们编写很多小函数来分别管理 state 的一部分:
function visibilityFilter(state = 'SHOW_ALL', action) {
if(action.type === 'SET_VISIBILITY_FILTER') {
return action.filter;
} else {
return state;
}
}
function todos(state = [], action) {
switch(action.type) {
case 'ADD_TODO':
return state.concat([{
text: action.text, completed: false }])
case 'TOGGLE_TODO':
return state.map((todo, index) =>
action.index === index ?
{
text: todo.text, completed: !todo.completed } : todo
)
default:
return state;
}
}
再开发一个 reducer 调用这两个 reducer,进而来管理整个应用的 state:
function todoApp(state = {
}, action) {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
}
}
三大原则
单一数据源
整个应用的state
被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个store
中。
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 的方法就是触发action
,action 是一个用于描述已发生事件的普通对象。
这样确保了视图和网络请求都不能直接修改 state,相反它们只能表达想要修改的意图。因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行。 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 只是函数,你可以控制它们被调用的顺序,传入附加数据。
function visibilityFilter(state = 'SHOW_ALL', action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter
default:
return state
}
}
function todos(state = [], action) {
switch(action.type) {
case 'ADD_TODO':
return [
...state,
{
text: action.text,
completed: false
}
]
case 'COMPLETE_TODO':
return state.map((todo, index) => {
if(index === action.index) {
return Object.assign({
}, todo, {
completed: true
})
}
return todo
})
default:
return state
}
}
import {
combineReducers, createStore } from 'redux'
let reducer = combineReducers({
visibilityFilter, todos })
let store = createStore(reducer)