理解Redux
从 React 说起
刚开始接触 React 的时候其实是感到非常惊艳的,在 React 的架构下可以像搭积木一样把前端的视图组件组合在一起,非常好玩,React 这种模块化的结构保证了组件内部的高度自治性,使得前端开发高度正交化。以至于刚刚使用 React 的时候简直觉得是在上帝之手的帮助之下写前端,触手丝滑,效率飞起,但是用了一段时间之后就很快发现了一些问题,比如父组件可以通过给子组件赋值 props,但是子组件向父组件传值却只能通过较为复杂的双向绑定,如果要向不是父子关系的组件传值怎么办呢?React 的文档会告诉你只能再 copy 一个组件过来了。EXM?组件之间传值这么常见的操作 React 却只能给出一个这么简单粗暴的办法?负分!差评!
但是知道了 Redux 这么一个神奇的存在之后,感觉 React 终于有救了,在 Redux 的框架之下,所有的状态被集中起来统一管理,使得应用内的数据完美地同步起来,再也不用担心组件之间的通讯问题了。
当然这里要提一下,Redux 本身是一个单独的框架,跟 React 没什么关系,但是由于在 React 的框架下视图和 state 和 view 是一一对应的,这样就使得 Redux 更适用于 React 环境下的开发。
Redux 的基本思想
Redux 是一个基于Flux思想实现的一个针对web应用的状态管理库,在Redux 里 Web 应用被视为一个有穷状态机,在这个状态机里所有状态的变化都是可以追溯甚至是可以撤销的,为了实现这样的机制,Redux 进行了以下三个约束:
- 所有的 state 构成一棵 object tree,这棵 object tree 只存在于唯一一个store 中。
- 所有state都是只读的,唯一改变 state 的方法是触发 action
- 使用纯函数来执行修改,以保证每次对状态修改的执行结果都是一致的
在 redux 中主要引入了 action、reducer、store 这三个概念,action 用于定义一个请求,reducer 用于根据 action 产生新状态,store 用于存储和管理 state,监听 action,将 action 自动分配给 reducer 并根据 reducer 的执行结果更新 state。
在这里你可以根据字面意思把 store 想象成一个仓库,这个仓库里的货物就是 state ,但是这个仓库的管理员实在是懒得跟自己的客户沟通,就请了好几个助手来帮自己,比如助手 A 专门负责某个手机厂商的请求,助手B专门负责电脑厂商的请求,他们计算好进出货量之后通知管理员,管理员如果接到手机厂商的请求就把他分配给 A,接到电脑厂商的请求就分配给B,得到 A 和 B 的结果之后管理员就可以马上更新仓库了,在这里这些助手就是 reducer,像手机和电脑厂商的请求就是 action。执行任务分配的这个过程就是 store.despatch(action)
函数 。
Redux 的基本概念
Action
action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。用来表明一个事件的发生,但并不对状态如何修改做任何描述。一个action 的结构是一个 javascript 普通对象,一个 action 的结构如下
{
//type字段用于标识action的类型,一般用一个字符串来表示
type: 'ADD_TODO',
//text是用户自定义的字段,一般用来传递和状态修改相关的参数
text: 'Build my first Redux app'
}
Reducer
action 只是描述了有事情发生了这一事实,但是并没有指名如何更新 state,reducer 就是对状态修改过程的描述,但是需要注意的有以下两点:
-
由于状态是只读的,reducer 本身并不能真正实现状态的修改,而是只把新状态作为返回值返回。
-
为了确保每次对状态修改的结果都是一致的,reducer 必须是一个纯函数,也就是说,只要是同样的输入,必定得到同样的输出。纯函数需要遵循以下约束:
不得改写参数 不能调用系统 I/O 的API 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
一个 reducer 的结构如下
/* 在这个 reducer 中,对于一个类型为 ADD_TODO 的 action,返回的新状态是在传入状态数组中追加了一个元素 */
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
default:
return state
}
}
Store
在定义了描述事件发生的 action 和描述状态修改方案的 reducer 之后,如何把它们联系到一起真正实现状态的改变呢?这就要牵扯到 store 啦,前面已经提过 redux 的原则之一就是所有的 state 构成的对象树都存到了 store 中,除此之外 store 还有以下功能:
- 维持应用的 state
- 提供
getState()
方法获取 state - 提供
dispatch(action)
方法更新 state; - 通过
subscribe(listener)
注册监听器; - 通过
subscribe(listener)
返回的函数注销监听器。
创建一个 store 其实很简单:
import { createStore } from 'redux'
import reducers from './reducers'
let store = createStore(reducers)
createStore()
的第二个参数是可选的, 用于设置 state 初始状态。这对开发同构应用时非常有用,服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。
let store = createStore(todoApp, window.STATE_FROM_SERVER)
接下来就可以简单实现一个创建处理 action 的过程了
import { createStore } from 'redux'
const ADD_TODO = 'ADD_TODO'
//创建一个 action
const simpleAction1 = {
type: 'ADD_TODO',
text: 'Build my first Redux app'
}
const simpleAction2 = {
type: 'ADD_TODO',
text: 'Build my second Redux app'
}
//创建一个 reducer, 功能为收到类型为 ADD_TODO 的 action 后为在 state 中添加一个条目
function reducer(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
default:
return state
}
}
//创建一个初始状态为空数组的 store
let store = createStore(reducer,[])
// 打印初始状态
console.log(store.getState())
// 每次 state 更新时,打印日志
// 注意 subscribe() 返回一个函数用来注销监听器
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
)
// 发起一个 action, 执行之后会更新状态,由于注册了监听器你可以看到每次更新都会打印当前状态
store.dispatch(simpleAction1)
store.dispatch(simpleAction1)
// 停止监听 state 更新
unsubscribe();