要了解函数式编程、高阶函数、纯函数、不可变数据,上手可能难一点。
一个独立的 js 库,可以用于 react、vue,安装相应的包就可
封装的很简单,就是要写很多重复的代码(模板)
与其他常用状态管理库的比较,
- vuex 只能在 vue 中使用
(后面两个还没写)
- recoil
- mobx
- createStore:
ƒ createStore(reducer, preloadedState, enhancer)
生成 store 对象 - compose:
ƒ compose()
组合多个函数,从右到左,比如:compose(f, g, h)
最终得到这个结果(...args) => f(g(h(...args))).
- applyMiddleware:
ƒ applyMiddleware()
函数是一个增强器,组合多个中间件,最终增强store.dispatch
函数,dispatch
时,可以串联执行所有中间件。 - combineReducers:
ƒ combineReducers(reducers)
组合多个reducers
,返回一个总的reducer
函数。 - bindActionCreators:
ƒ bindActionCreators(actionCreators, dispatch)
生成actions,主要用于其他库,比如
react-redux。
- dispatch(action) 派发动作,也就是把 subscribe 收集的函数,依次遍历执行
- subscribe(listener) 订阅收集函数存在数组中,等待触发 dispatch 依次执行。返回一个取消订阅的函数,可以取消订阅监听。
- getState() 获取存在 createStore 函数内部闭包的对象。
- replaceReducer(nextReducer) 主要用于 redux 开发者工具,对比当前和上一次操作的异同。有点类似时间穿梭功能。
- observable()
所以可以直接推测出 核心 API createStore 内部就是创建几个函数后返回一个对象包含这几个函数。类似这样:
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable,
};
这里面前三个在 mini 里实现了
一顿判断传入的 action 是否合法
然后把收集到的函数依次调用
判断是否正在 dispatch ,是就抛出错误,不是就返回 currentState
订阅监听函数,收集到一个数组nextListeners
里,dispatch 时调用的函数就来自这
最后返回一个取消监听的函数
- 传入要用的中间件链
- 返回一个两层函数
在 createStore 中作为第二个参数返回, createStore 是有对参数类型判断后进行调整的
像这样使用:
var store = Redux.createStore(
counter,
Redux.applyMiddleware(logger1, logger2, logger3)
);
这个 store 就是增强了 dispatch 方法的 store 对象了
我们看看这样进入 createStore 之后是怎么样
其实 applyMiddleware 就是一个 redux 唯一一个自带的 enhancer
那么 createStore 返回的就是
applyMiddleware(...middleware)(createStore)(reducer, preloadedState);
所以 applyMiddleware 要返回啥?
首先它接收 middlewares 之后,返回的第一层是一个接收 createStore 的函数,然后返回第二层函数,第二层函数接收 reducer...等参数,然后返回第三层。
第三层返回的也要和 没传入 enhancer 时的 crateStore 或者说基础版的 createStore 返回的一样,是一个 store。但是这个 store,是 dispatch 强化后的 store~
用强化后的 dispatch 覆盖掉原来的即可
return { ...store, dispatch };
首先强化后的 dispatch 肯定还是 dispatch,所以 compose 最后一个就是原来的 store.dispatch
如果传入的 middlewares 是[a,b,c]
,chain 就是加工后的 middleware,或者说就是已经传入 store 并调用的 middleware,那么强化后的 dispatch 就是 a2(b2(c2(store.dispatch)))
a 是长这样的:
function logger1({ getState }) {
return function (next) {
return function (action) {
//...
const returnValue = next(action);
//...
};
};
}
a2 是长这样的,接收 next:
function (next) {
return function (action) {
//...
const returnValue = next(action);
//...
};
};
然后 compose
const dispatch = compose(...chain)(store.dispatch);
在 compose 后 合起来就是大概这样
a2(b2(c2(store.dispatch)));
或者说store.dispatch
作为c2
的 next 参数传入,c2(store.dispatch)
作为b2
的next
参数传入...
所以在 a2 中调用 next,就会串联到下一个里面,执行完了原始的 dispatch 后,又一个个的回去 ——形成洋葱模型
首先,中间件要连成一串,需要一个 next 方法来保证串联 —— 或者说,首位相连就是通过这个保证的,对 next 进行一层层的包装,他自然需要独立一层。compose 完,要的就是强化的 dispatch,是 dispatch 就要接收 action,所以也要在最后一层接收 action 参数。中间件还要可以使用 store 中的东西,比如 getState 和 action,所以 store 要放在顶层
那大概就是这样
function middleware() {
return ({ getState, dispatch }) => {
return next => action => {
return next(action);
};
};
}
或者说,其实你也可以这么理解,触发 dispatch 之后,就触发了 middleware1、然后 next 到 middleware2...,最后到 dispatch —— 这个就是原始的,没强化的 store.dispatch
输出强化后的 store.dispatch 看看:
他确实就是 第一个中间件 中的第二层函数
function (action) {
//...
next(action)
};
这是一个各大流行库中有使用中间件就有的方法,可以看我 mini-koa 中也有,不过那里的实现方法和这里不太一样
function compose(...fns) {
if (fns.length === 0) return arg => arg;
if (fns.length === 1) return fns[0];
return fns.reduce(
(a, b) =>
(...args) =>
a(b(...args))
);
}
koa 中是递归 dispatch 遍历 + promise,仓库里的 mini-koa 只实现了简单版本,没有 promise
就是合 reducers 的,合 reducers,那最后返回的自然也是 reducer 函数 —— 接收 state 和 action
并且在里面依次调用传入的各个 reducer
将不同 reducer 和相关的状态按照键值对存储到 store 的 state 中
自己实现的 mini 版本,省略了很多边界的判断,大家可以直接从源码中学习相关的边界,非常细!
再带你手写一个周下载量 300w+ 的 redux 中间件(手动狗头)
其实就是判断传入 dispatch 的 action
- 如果是函数,就传入 dispatch 等参数执行它
- 如果不是,就正常使用,传入下一个中间件
如此就支持了 action 为函数,比如异步请求数据再更新 state
欢迎自己来调试一下,感受这 dispatch 传递的魅力
可以看到:
- action 是一级一级往下传的
- 第三个中输出的 next 就是原始的 dispatch,最后执行对应 action 的就是原始的 dispatch
整体就是洋葱模型:123 321
讲的有点乱,尤其 applyMiddleware 部分。文中用了很多 "或者说",其实就是我试图换一种表达看看能不能表达的更好,写作水平还望海涵
如果你没看懂,那是我的问题!
你还可以看看这几个:
- (学习 redux 源码整体架构,深入理解 redux 及其中间件原理)[https://github.com/lxchuan12/redux-analysis/#readme]
- (Redux 从设计到源码)[https://tech.meituan.com/2017/07/14/redux-design-code.html]