应用场景: 为什么要用redux?
直接从需求入手开始
需求 1.0:在控制台上记录用户的每个动作
如果后端开发的话,一般会设计一个统计用户记录日志的中间件middleware
例如,在Express 中实现一个加单的logger,如下:
|
这样每次访问的时候,都会在控制台中留下类似下面的日志便于追踪调试:
|
如果把场景移动到前端,第一反应应该是这样实现,如下
|
上述 jQuery 与 MV* 的写法并没有本质上的区别
记录用户行为代码的侵入性极强,可维护性与扩展性十分堪忧
需求2.0升级
上线过了1个月觉得暴露信息太少了,额外我们需要统计用户操作的时间,如果按照前端的思想去改的话,当然得一个一个改,或者编辑器全局替换,
|
但是后端的同学,只需要在中间件出修改一下即可,很明显就可以对比出孰优孰劣
|
需求3.0
正式上线之后,我想在console中去掉Logger中的输出。注意,只是去掉Logger中的,其他的必须保留。开发环境下仍然要输出Logger,
这时候前端要实现的话,可能得在每一个绑定事件里都要加一个if判断。代码极其臃肿丑陋!
后端同样只需要在中间处加一下判断即可
需求 终极版
我们需要实现线上自动收集bug,并且可以还原当时场景。
如果只是bug收集的话,可以通过window.error事件,根据source map定位到源码查看(基本上毫无卵用)
但如果是还原场景的话,可以说基本不可能实现。因为仅仅报错信息,是无法知道用户到底是怎么一步步操作导致的。
即使通过各种奇淫技巧拿到了用户操作顺序。但在自己(开发环境)电脑上,也许永远是通过的……
相对地,后端的报错的收集、定位以及还原却是相当简单。只要一个 API 有 bug,那无论用什么设备访问,都会得到这个 bug
还原 bug 也是相当简单:把数据库备份导入到另一台机器,部署同样的运行环境与代码。如无意外,bug 肯定可以完美重现
实际上 jQuery / MV* 中也能实现用户动作的跟踪,用一个数组往里面
push
用户动作即可
但这样操作的意义不大,因为仅仅只有动作,无法反映动作前后,应用状态的变动情况
小结
为何前后端对于这类需求的处理竟然大相径庭?后端为何可以如此优雅?
原因在于,后端具有统一的入口与统一的状态管理(数据库),因此可以引入中间件机制来统一实现某些功能
多年来,前端工程师忍辱负重,操着卖白粉的心,赚着买白菜的钱,一直处于程序员鄙视链的底层
于是有大牛就把后端 MVC 的开发思维搬到前端,将应用中所有的动作与状态都统一管理,让一切有据可循
使用redux 配合 Redux DevTools 可以实现出“华丽如时光旅行一般的调试效果”
实际上就是开发调试过程中可以撤销与重做,并且支持应用状态的导入和导出(就像是数据库的备份)
而且,由于可以使用日志完整记录下每个动作,因此做到像 Git 般,随时随地恢复到之前的状态
由于可以导出和导入应用的状态(包括路由状态),因此还可以实现前后端同构(服务端渲染)
当然,既然有了动作日志以及动作前后的状态备份,那么还原用户报错场景还会是一个难题吗?
redux 怎么用?
Store
首先要区分 store
和 state
state
是应用的状态,一般本质上是一个普通对象
例如,我们有一个 Web APP,包含 计数器 和 待办事项 两大功能
那么我们可以为该应用设计出对应的存储数据结构(应用初始状态):
|
store
是应用状态 state
的管理者,包含下列四个函数:
getState() # 获取整个 state
dispatch(action) # ※ 触发 state 改变的【唯一途径】※
subscribe(listener) # 您可以理解成是 DOM 中的 addEventListener
replaceReducer(nextReducer) # 一般在 Webpack Code-Splitting 按需加载的时候用
二者的关系是:state = store.getState()
Redux 规定,一个应用只应有一个单一的 store
,其管理着唯一的应用状态 state
Redux 还规定,不能直接修改应用的状态 state
,也就是说,下面的行为是不允许的:
|
若要改变 state
,必须 dispatch
一个 action
,这是修改应用状态的不二法门
现在您只需要记住
action
只是一个包含type
属性的普通对象即可
例如{ type: 'INCREMENT' }
上面提到,state
是通过 store.getState()
获取,那么 store
又是怎么来的呢?
想生成一个 store
,我们需要调用 Redux 的 createStore
:
|
现在您只需要记住
reducer
是一个 函数,负责更新并返回一个新的state
而initialState
主要用于前后端同构的数据同步(详情请关注 React 服务端渲染)
Action
上面提到,action
(动作)实质上是包含 type
属性的普通对象,这个 type
是我们实现用户行为追踪的关键
例如,增加一个待办事项 的 action
可能是像下面一样:
|
当然,action
的形式是多种多样的,唯一的约束仅仅就是包含一个 type
属性罢了
也就是说,下面这些 action
都是合法的:
|
虽说没有约束,但最好还是遵循规范
如果需要新增一个代办事项,实际上就是将 code-2
中的 payload
“写入” 到 state.todos
数组中(如何“写入”?在此留个悬念):
|
刨根问底,action
是谁生成的呢?
Action Creator
Action Creator 可以是同步的,也可以是异步的
顾名思义,Action Creator 是 action
的创造者,本质上就是一个函数,返回值是一个 action
(对象)
例如下面就是一个 “新增一个待办事项” 的 Action Creator:
|
将该函数应用到一个表单(假设 store
为全局变量,并引入了 jQuery ):
|
在输入框中输入 “待办事项2” 后,点击一下提交按钮,我们的 state
就变成了:
|
通俗点讲,Action Creator 用于绑定到用户的操作(点击按钮等),其返回值
action
用于之后的dispatch(action)
刚刚提到过,action
明明就没有强制的规范,为什么 store.dispatch(action)
之后,
Redux 会明确知道是提取 action.payload
,并且是对应写入到 state.todos
数组中?
又是谁负责“写入”的呢?悬念即将揭晓…
Reducer
Reducer 必须是同步的纯函数
用户每次 dispatch(action)
后,都会触发 reducer
的执行reducer
的实质是一个函数,根据 action.type
来更新 state
并返回 nextState
最后会用 reducer
的返回值 nextState
完全替换掉原来的 state
注意:上面的这个 “更新” 并不是指
reducer
可以直接对state
进行修改
Redux 规定,须先复制一份state
,在副本nextState
上进行修改操作
例如,可以使用 lodash 的cloneDeep
,也可以使用Object.assign / map / filter/ ...
等返回副本的函数
在上面 Action Creator 中提到的 待办事项的 reducer
大概是长这个样子 (为了容易理解,在此不使用 ES6 / Immutable.js):
|
通俗点讲,就是
reducer
返回啥,state
就被替换成啥
小结
store
由 Redux 的createStore(reducer)
生成state
通过store.getState()
获取,本质上一般是一个存储着整个应用状态的对象action
本质上是一个包含type
属性的普通对象,由 Action Creator (函数) 产生- 改变
state
必须dispatch
一个action
reducer
本质上是根据action.type
来更新state
并返回nextState
的函数reducer
必须返回值,否则nextState
即为undefined
- 实际上,
state
就是所有reducer
返回值的汇总(本教程只有一个reducer
,主要是应用场景比较简单)
Action Creator =>
action
=>store.dispatch(action)
=>reducer(state, action)
=>原 state
state = nextState
终极问题:Redux 本质是什么,思想的来源在哪?
Redux 可以看做是后端MVC思想的前端实现和应用, 下面这张表完美地解释了一切。
| store
| 数据库实例 |
| state
| 数据库中存储的数据 |
| dispatch(action)
| 用户发起请求 |
| action: { type, payload }
| type
表示请求的 URL,payload
表示请求的数据 |
| reducer
| 路由 + 控制器(handler) |
| reducer
中的 switch-case
分支 | 路由,根据 action.type
路由到对应的控制器 |
| reducer
内部对 state
的处理 | 控制器对数据库进行增删改操作 |
| reducer
返回 nextState
| 将修改后的记录写回数据库 |
Redux 中 state 中的状态 就是对应的数据库中的数据。Redux 本质上就是一个封装好的状态管理工具。