redux是一个状态管理库。核心概念,包括:action、reducer和store。
- action: 是把数据从应用传到store的有效载荷。
- reducer: 指定了应用状态的变化如何相应action,并发送到store。
- store: 把action和reducer联系到一起的对象。
但是单单了解这三个概念和它们的内在联系还是不足够的。我们还需要了解state的由来,以及如何获取state、更新state;state和store又是什么区别。
下面,我们一步一步来深入学习这三个概念。
在文章中,我们会以”待办事项”为例子展开讲解redux。待办事项的需求如下:
- 一个待办事项的列表(用todos数组存储)
todos数组中的元素为待办事项内容,包含两个字段:{ text: '待办事项1', // string completed: false,// boolean 是否完成的状态 }
- 一个过滤条件filter,filter是一个字符串,标明目前需要将哪些待办事项展现出来
SHOW_ALL: 展示全部待办事项 SHOW_COMPLETED: 展示已完成的待办事项 SHOW_ACTIVE: 展示未完成的待办事项
1、 Action
Action 是把数据从应用传到store的有效载荷。Action本质上是一个普通的javascript对象。Action有一个约定,它必须包含 “type” 属性。
{
type: 'ADD_TODO',
text: 'something',
}
在实际应用中,我们会编写生成Action的方法,即 Action 创建函数,方便传递Action。
// actions.js
// 添加待办事项
export function addTodo(text) {
return { type: 'ADD_TODO', text }
}
// 对index对应待办事项的状态进行取反操作
export function toggleTodo(index) {
return { type: 'TOGGLE_TODO', index }
}
// 更改过滤条件
export function setVisibilityFilter(filter) {
return { type: 'SET_VISIBILITY_FILTER', filter }
}
在actions.js中,定义了三个Action创建函数,addTodo将待办事项添加到应用状态中,toggleTodo函数会更改index对应的事项的状态(未完成/已完成),setVisibilityFilter设置过滤条件。
2、 Reducer
reducer会响应接收到的action,然后改变对应的应用状态。reducer实际上是一个函数,这个函数包含两个参数,第一个参数为state,第二个参数为action。
// reducers.js
// 待办事项的过滤条件
function visibilityFilter(state = 'SHOW_ALL', action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter
default:
return state
}
}
在这里,参数state=[]的写法为es6语法,表明将state初始化为一个空数组。reducer的返回值组成了应用的状态(即”总state”,下面写到”应用的状态”的时候指的就是”总state”)。每一次调用reducer,参数state(即”子state”,”子state”构成了”总state”)的值为对应reducer对应状态的上一个值,该reducer的返回值又将更新到应用的状态中。如果是第一次调用reducer,参数state则为初始值。参数action则是上面介绍的一个带有type属性的普通javascript对象。
我们来细看visibilityFilter函数的逻辑,它使用switch语句,根据action.type来区分处理不同的action。如果action.type等于’SET_VISIBILITY_FILTER’,它将action.filter返回,作为这个”子state”的新值;如果action.type不等于’SET_VISIBILITY_FILTER’,则将state原样返回,不做处理。
如果仅有一个reducer,那么这个reducer的返回值就是应用的状态。不过在一般的应用中,为了便于阅读和维护,都会有多个reducer。然后再通过redux提供的全局方法 combineReducers 将reducer组合起来,构成最终的应用状态。combineReducers函数接收一个Object对象参数,该对象的key值可以设置任意多个,key值对应的value值为一个reducer。
// reducers.js
import { combineReducers } from 'redux'
// 待办事项的过滤条件
function visibilityFilter(state = 'SHOW_ALL', action) {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter
default:
return state
}
}
// 待办事项列表reducer
function todos(state = [], action) {
switch (action.type) {
// 添加待办事项
case 'ADD_TODO':
return [...state, {
text: action.text,
completed: false,
}]
break
// 对index对应待办事项的状态进行取反操作
case 'TOGGLE_TODO':
const data = state.map((todo, index) => {
return {
...todo,
completed: index === action.index ? !todo.completed : todo.completed
}
})
return data
break
default:
return state
}
}
const rootReducer = combineReducers({
visibilityFilter,
todos
})
export default rootReducer
reducers.js中,最终导出的是一个合并的reducer(rootReducer)。rootReducer最终的结构(即应用状态的结构)如下:
{
visibilityFilter: 'SHOW_ALL',
todos: [],
}
应用状态由reducers的返回值构成,但是在代码中我们不会直观地看到上面这个结构。这个结构需要我们结合各个reducer,根据它的第一个参数state或者返回值分析出来。在我们学习更高级框架(如dva)的时候,会看到如果应用的状态能够直接配置,那么我们对应用状态的维护将会变得更加简便和直观。
reducer的触发时机,就是当使用redux提供的dispatch方法派发一个action时触发。但是dispatch方法不是redux的全局方法,它是store实例的方法,接下来我们就介绍store。
3、 Store
store是把action和reducer联系到一起的对象。这里我们要注意和state区别开来,state由reducer的返回值组成,是整个应用的状态。store则是由redux提供的createStore方法创建的一个实例,createStore的参数就是reducers。
// index.js
import { createStore } from 'redux'
import rootReducer from './reducers.js'
const store = createStore(rootReducer)
store创建之后,获取应用状态信息、更新应用状态等操作都是通过store实例的方法来实现。
- 获取应用状态:提供 getState() 方法获取 state;
- 更新应用状态:提供 dispatch(action) 方法更新 state;
- 订阅:通过 subscribe(listener) 注册监听器。
以上,便介绍完了redux的几个重要概念。接下来,我们来验证一下。redux是一个状态管理库,它本身是不涉及UI,所以验证工作我们就通过console.log方法打印出state来验证。这可以通过subscribe方法来实现,当state方法变化时,subscribe方法的回调函数参数就会被调用。
在index.js中补充验证代码,如下:
// index.js
import { createStore } from 'redux'
import { addTodo, toggleTodo, setVisibilityFilter } from './actions.js'
import rootReducer from './reducers.js'
const store = createStore(rootReducer)
// 验证
store.subscribe(() => {
console.log(store.getState())
})
// 打印初始状态
console.log('初始状态', store.getState())
store.dispatch(addTodo('Learn Action'))
store.dispatch(addTodo('Learn reducer'))
store.dispatch(addTodo('Learn state'))
store.dispatch(addTodo('Learn store'))
store.dispatch(toggleTodo(0))
store.dispatch(toggleTodo(1))
store.dispatch(setVisibilityFilter('SHOW_COMPLETED'))
4、把代码跑起来
上面的样例代码包含3个文件: actions.js、reducers.js以及index.js。样例代码依赖于redux npm包,并使用了一些es6语法,想要把代码跑起来,我们使用webpack对这几个文件进行打包处理,让它成为浏览器可加载解析的文件。具体请参考redux-01,里面对构建步骤进行了详细的说明。