前提:公司兼容了两种技术栈
Vue
和React
,Vue
研究的比较多一些,反观React
还停留在查官方文档阶段
,最近恰好维护了一个React项目
,项目中用到Redux
,借此从新复习Redux
javascript
一句话介绍
Redux
:Redux是一个可预测化的JavaScript状态管理容器。
html
理解
Redux
离不开这三大原则java
整个应用的 state
被储存在一棵 object tree
中,而且这个 object tree
只存在于惟一一个 store
中。git
惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
github
store.dispatch({
type: 'COMPLETE_TODO',
index: 1
})
复制代码
为了描述 action 如何改变 state tree ,你须要编写 reducers
。action是描述修改操做
,而真正去操做修改state是reducers
web
function reducer(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
}
}
复制代码
经过上面👆的分析咱们获得三个关键的信息点:state
,action
,reducers
redux
在复习工做流以前咱们先来搞清楚几个概念,如下的概念解释都出自于词汇表 , 为了方便阅读,我就
摘抄
过来了promise
State
(也称为 state tree) 是一个宽泛的概念,可是在 Redux API 中,一般是指一个惟一的 state 值,由 store 管理且由 getState() 方法得到。它表示了 Redux 应用的所有状态,一般为一个多层嵌套的对象。网络
顶层 state 或为一个对象,或像 Map 那样的键-值集合,也能够是任意的数据类型。然而你应尽量确保 state 能够被序列化,并且不要把什么数据都放进去,致使没法轻松地把 state 转换成 JSON。app
Action 是一个普通对象,用来表示即将改变 state 的意图。它是将数据放入 store 的惟一途径。不管是从 UI 事件、网络回调,仍是其余诸如 WebSocket 之类的数据源所得到的数据,最终都会被 dispatch 成 action。
action 必须拥有一个 type 域
,它指明了须要被执行的 action type。Type 能够被定义为常量,而后从其余 module 导入。比起用 Symbols 表示 type,使用 String 是更好的方法,由于 string 能够被序列化。
Reducer (也称为 reducing function) 函数接受两个参数:以前累积运算的结果和当前被累积的值,返回的是一个新的累积结果。该函数把一个集合归并成一个单值。
在 Redux 中,累计运算的结果是 state 对象,而被累积的值是 action。Reducer 由上次累积的结果 state 与当前被累积的 action 计算获得一个新 state。这些 Reducer 必须是纯函数,并且当输入相同时返回的结果也会相同。它们不该该产生任何反作用。正因如此,才使得诸如热重载和时间旅行这些很棒的功能成为可能。
dispatching function
(或简言之 dispatch function) 是一个接收 action 或者异步 action的函数,该函数要么往 store 分发一个或多个 action
,要么不分发任何 action。
Action Creator
很简单,就是一个建立 action 的函数。不要混淆 action 和 action creator 这两个概念。Action 是一个信息的负载,而 action creator 是一个建立 action 的工厂
。
异步 action 是一个发给 dispatching 函数的值
,可是这个值还不能被 reducer 消费
。在发往 base dispatch() function 以前,middleware 会把异步 action 转换成一个或一组 action。异步 action 能够有多种 type
,这取决于你所使用的 middleware
。它一般是 Promise 或者 thunk 之类的异步原生数据类型
,虽然不会当即把数据传递给 reducer,可是一旦操做完成就会触发 action 的分发事件
。
Middleware 是一个组合 dispatch function 的高阶函数
,返回一个新的 dispatch function
,一般将异步 actions 转换成 action。
❗️❗️这也是接下来咱们要重点分析
最后放出一张记忆脑图
了解了
三大原则
以及概念
之后,来看看Redux的工做流吧
对比下图就能轻松理解了
从图中咱们知道Redux是单向数据流
,那么根据上面所学的知识咱们来设计下咱们的Redux目录结构
吧
如上图,大部分公司的Redux目录结构应该相似
这样,咱们须要actionCreators
文件来建立咱们的action
,action对象
必须拥有一个 type 域
,而后reducer根据不一样的type触发对应的操做
,因此建立actionTypes
文件,接下来就是处理reducer了
来修改咱们的state
,因此建立reducer
文件📃,因此明白上述概念,有助于咱们对目录结构的理解
,而不是傻乎乎的跟着别人的目录结构照猫画虎的建立
,起码要明白为何这样划分
再看中间件原理时,咱们来实现下
compose函数
,理解它对于理解咱们中间件原理有很大帮助
dispatch=fn1(fn2(fn3))
dispatch=compose(fn1,fn2,fn3)
复制代码
咱们期待有一个聚合的方法compose,能够这样使用,参数从右至左,将第一个参数fn3做为第二个参数fn2的参数,并将运行结果做为第三个参数fn1的参数,依次递推,最终返回一个新的函数
,这个新函数在基础函数f3
的基础上,获得了全部的高阶函数的能力
,🤔思考:假设这个f3参数
是换成是dispatch函数
呢?,不着急,咱们接着往下分析compose函数的实现
// compose聚合函数的顺序是从右到左 from right to left
const compose = function (...funcs) {
return funcs.reduce((a, b) => {
return (...args) => {
return a(b(...args))
}
})
}
复制代码
这些串联函数不优雅。ES6 的箭头函数简写
,从而看起来更舒服一些
function compose(...funcs) {
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
复制代码
因此compose(fn1, fn2, fn3) (...args) 至关于 fn1(fn2(fn3(...args)))
讲清楚了上述👆基本内容后,到了本问的关键点了,那就是Redux中间件是用来干吗的,原理是什么❓
前面分析Redux工做流
,Action
发出之后,Reducer
当即算出 State,是个同步流程
,那么想一想如何支持异步操做
,不仅仅支持异步操做
,还要支持错误处理、日志监控
,那么是在Redux工做流
哪一个环节进行拦截操做呢❓,答案是dispatch过程
,在分发action
进行拦截处理
在Redux中,与中间件的实现相关联的方法是applyMiddleware
,因此咱们来分析下这个方法吧(这里笔者提供一份调试中间件代码
,点击进入仓库
// 调用applyMiddleware
applyMiddleware(thunk, logger)
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => {
return dispatch(...args)
}
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
复制代码
能够看到dispatch = compose(...chain)(store.dispatch)
这行关键代码,Redux
为了支持中间件
,内部重写了原来的dispatch方法
,同时最后传入原来的store.dispatch
方法,也就是将原来的disptach方法
也当作中间件处理了
分析到这里,咱们能够知道传入的中间件
被compose函数聚合
后改写dispatch方法,因此咱们也能够理解成中间件是对dispatch方法的加强
,好比说:加强dispatch函数对action是函数、Promise的处理
举个例子🌰
仓库内修改为这段代码
function logger(store) {
return function wrapDispatchToAddLogging(next) {
return function dispatchAndLog(action) {
let result = next(action)
return result
}
}
}
function thunk({ dispatch, getState }) {
return function wrapDispatchToThunk(next) {
return function dispatchThunk(action) {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
}
}
}
applyMiddleware(thunk, logger) // 至关于 wrapDispatchToThunk(wrapDispatchToAddLogging(dispatch))
复制代码
打印dispatch
方法
dispatchThunk(action) {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
}
复制代码
转换next
// 转换next
dispatchThunk(action) {
if (typeof action === 'function') {
return action(dispatch, getState);
}
// next(action)
// 这里的next由来是执行logger方法返回了dispatchAndLog函数
return (function dispatchAndLog(action) {
let result = next(action)
return result
})(action)
}
复制代码
继续转换next
看到
dispatchAndLog
函数里还有个next
,咱们继续转换
// 转换next
dispatchThunk(action) {
if (typeof action === 'function') {
return action(dispatch, getState);
}
// next(action)
// 这里的next由来是执行logger方法返回了dispatchAndLog函数
return (function dispatchAndLog(action) {
// let result = next(action)
// 这里的dispatch是原来的dispacth
let result=(function(action){dispatch(action)})(action)
return result
})(action)
}
复制代码
看到这里咱们已经知道,Redux实现中间件的原理核心是加强原来dispatch函数的能力
,然函数拥有某种能力
天然而然想到高阶函数
的处理方式,compose 方法将新的 middlewares 和 store.dispatch 结合起来,生成一个新的 dispatch 方法,另外经过改写后的dispatch
方法,能够肯定Redux中间件
也是基于洋葱模型
执行顺序听从
洋葱模型
applyMiddleware(
logger,
thunk
)
复制代码
工做中咱们明白了Redux工做流
的状况下,其实干扰最多的可能就是Redux中间件了
,经常使用的有redux-thunk、redux-soga、redux-promise
等等,因此掌握中间件原理仍是很重要的,那么如何去写一个中间件❓,咱们经过继续分析applyMiddleware
方法
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => {
return dispatch(...args)
}
}
// 这里执行了一层中间件接收了{store.getState,dispatch}参数
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// compose(...chain)(store.dispatch) 至关于fn1(fn2(fn3(store.dispatch)))
// 又执行了一层中间件 这一层接收next参数 也就是下一个中间件参数
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
复制代码
经过上面👆的分析可知,一个Redux中间件
的基本形式(结构)以下
// 中间件逻辑代码须要通过三次柯里化
store => next => action => {
// 中间件逻辑代码
}
复制代码
dispacth函数
,让其能解析action为函数形式
,从而让Redux支持异步操做dispacth函数
,让其能解析action为Promise对象形式
,从而让Redux支持异步操做根据这个中间件结构
咱们来分析redux-thunk
中间件的源码
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
// 若是是函数 thunk来处理
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// 其它处理不了,交给下一个中间件处理
return next(action);
};
}
const thunk = createThunkMiddleware();
复制代码
嗯嗯…,redux-thunk
就这么几行源代码就实现了支持Redux异步操做
用法
const testRes = () => {
return new Promise((res, rej) => {
res({
type: 'TEST_RESOLVE'
})
});
}
store.dispatch(testRes());
复制代码
// redux-promise简易版源码
const vanillaPromise = store => next => action => {
// 判断不是Promise对象,交给下个中间件处理
if (typeof action.then !== 'function') {
return next(action)
}
// action为Promise对象,promise中间件能作处理
// 最后异步执行完触发执行store.dispatch ---> (...args) => dispatch(...args)
return Promise.resolve(action).then(store.dispatch)
}
复制代码
一句话Redux
用起来太笨重了
,去年实习期开始写React
项目时候,跟着别人的风格来写而已,直到维护同事的React
项目,才猛然意识到项目中糟糕Redux
写法,滥用Redux
,致使了这个项目是灾难级别的
后面意识到能不能二次封装下Redux
,简化写法
好比:
import { createModel } from "../../../model.js";
const model = {
namespace: 'counter',
state: {
count: 10
},
reducer: {
add(state: any, action: any) { // counter/add
state.count += 1
},
minus(state: any, action: any) {
state.count--
},
}
}
export default createModel(model)
复制代码
enennene….就有了以上对话,也感谢大佬的帮助,最后选用了remacth方案,这个库也挺好友好,兼容了老的写法
,具体能够细看这个它的文档,这里就不作分析了