## redux
接下来要使用redux了,首先如果没有基础的话,需要对redux有一个大概的了解,推荐阅读阮一峰大神的[Redux 入门教程(一):基本用法](http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html)。
如果要对`redux`有一个非常详细的认识,我推荐阅读[中文文档](http://cn.redux.js.org/index.html),写的非常好。读了这个教程,有一个非常深刻的感觉,`redux`并没有任何魔法。但是对第一次接触状态管理框架的童鞋来说可能有一些概念难以接受,但是随着慢慢试用就会理解了,不要被`reducers`,`middleware`,`store`吓到了,其实它们就字面意思不容易理解而已。
## 核心概念(来自Redux中文文档)
一个应用会有一个`state`,它通常是一个普通对象,例如一个todo应用的`state`可能是这样的:
~~~
{
todos: [
{ text: 'Eat Food', completed: true },
{ text: 'Run', completed: false }
],
visibilityFilter: 'show_all'
}
~~~
这个对象就像是'Model',区别是它并没有 `setter`(修改器方法)。因此其它的代码不能随意修改它,造成难以复现的 bug。
想要更新`state`中的数据,你需要发起一个`action`。Action(动作)就是一个普通的`JavaScript`对象,这个对象用来描述发生了什么事情。例如:
~~~
{ type: 'ADD_TODO', text: 'Go to swimming pool' } // 添加一个todo事项,内容是去游泳池。
{ type: 'TOGGLE_TODO', index: 1} // 切换需要完成的todo。
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' } // 设置过滤条件。
~~~
强制使用 `action` 来描述所有变化带来的好处是可以清晰地知道应用中到底发生了什么。如果一些东西改变了,就可以知道为什么变。`action` 就像是描述发生了什么的面包屑。最终,**为了把 `action` 和 `state` 串起来(通过`action`改变`state`),开发一些函数,这就是 `reducer`**。再次地,没有任何魔法,**`reducer` 只是一个接收 `state` 和 `action`,并返回新的 `state` 的函数。** 对于大的应用来说,可能很难开发这样的函数,所以我们编写很多小函数来分别管理 state 的一部分:
~~~
function visibilityFilter(state = 'SHOW_ALL', action) {
// 通过判断 Action 的 类型操作 state
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)
}
}
~~~
这差不多就是 Redux 思想的全部。注意到没我们还没有使用任何 Redux 的 API。Redux 里有一些工具来简化这种模式,但是主要的想法是如何根据这些 action 对象来更新 state,而且 90% 的代码都是纯 JavaScript,没用 Redux、Redux API 和其它魔法。
## 三大原则:单一数据源、State是只读的、使用纯函数来执行修改任务
1. 单一数据源
整个应用的`state`被储存在一棵`object tree`中,并且这个`object tree`只存在于唯一一个`store`中。
2. State 是只读的
唯一改变`state`的办法就是触发一个`action`,`action`是一个用于描述已发生事件的普通对象。
这样确保了视图和网络请求都不能直接修改 state,相反它们只能表达想要修改的意图。
因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心 race condition 的出现。
Action 就是普通对象而已,因此它们可以被日志打印、序列化、储存、后期调试或测试时回放出来。
3. 使用纯函数来执行修改
为了描述 `action` 如何改变 `state tree` ,你需要编写 `reducers`。
Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。
刚开始你可以只有一个 reducer,随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分。
因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。
现在开始安装`redux`。
`npm install --save redux`
~~~
cd src
mkdir redux
cd redux
mkdir actions // 创建一个放action的目录
mkdir reducers // 创建一个放reducer的目录
touch reducers.js // 合并所有reducer,管理一个个reducer
touch store.js // 管理整个redux的仓库
touch actions/counter.js // 创建一个action
touch reducers/counter.js // 创建一个reducer
~~~
已经建好了基础目录,下一节来创建一个`action`。
不过在此之前,既然已经建好了目录,顺便把路径别名给改了吧:
~~~
// 静态文件服务器配置
devServer: {
// ... 省略devServer配置代码
},
resolve: {
alias: {
'@': resolve('src'),
// ...省略一些代码
actions: resolve('src/redux/actions'),
reducers: resolve('src/redux/reducers')
}
}
~~~