使用React-hooks实现一版简易Redux, 主要实现Redux中间件机制,看看Redux作者如何把闭包玩转…
首先这里提一下闭包: (我个人对闭包的认识)可能有的人认为函数返回函数就是闭包,但实则不然,简单来说,函数返回函数会形成闭包,但它不算是闭包。 如何理解这句话呢? 闭包其实和词法作用域相关,函数返回函数在函数被执行后,内层返回的函数在查找变量时走的顺序是从函数定义位置开始往上层查找,所以其定义处开始实则就是到了当前函数的外层函数的函数体内,所以,而这个过程是在程序运行时产生的,所以,闭包是js运行时产生的东西,而不是在编译时产生的东西,也就是只有这个函数被执行时才会形成闭包,也就是为什么说函数返回函数可以形成闭包,但实际不是闭包的原因…
先给出最终实现的Redux的实际调用格式和参数介绍:
- applyMiddleware是实现的一个方法, 从实现的redux库中导出
- createStore也是从实现的redux库中导出的一个方法
- thunk, promise, logger: 是实现的三个中间件,和redux中常用的中间件类似,用于构建redux中间件系统
- reducer: 对状态进行处理的reducer, 和redux中需要的reducer一样
- initialState Store仓库的初始state, 因为这是简易版,使用react-hooks实现,所以这个初始状态就从外部传递,而不是在reducer中指定
- connect 用于组件连接store仓库,用法和react-redux中的connect方法一样
- Provider 和react-redux中的Provider组件的使一样
// 整个方法的调用和redux的中间件调用基本类似,除了部分参数的区别之外
const { connect, Provider } = applyMiddleware(thunk, promise, logger)(createStore)(reducer,initialState);
基本实现createStore
- 对于createStore方法的实现,主要是为了导出connect方法和Provider组件,同时在其中创建store. 而对于store和reducer这一套流程的处理,将会使用react的useReducer hook进行处理
import React from "react"
const ReduxContext = React.createContext();
export function createStore(reducer, initialState){
let store = {};
const Provider = props => {
const [state, dispatch] = React.useReducer(reducer, initialState);
store.getState = () => state; // 获取store中的state的函数
if(!store._dispatch){
store._dispatch = dispatch; // 实际派发动作的函数,而之后加入中间件后会对其进行包装,所以这里加了 _dispatch,目的是为了和之后包装的dispatch方法进行区分
}
return (
<ReduxContext.Provider value={state}>
{/* 这里存在一个坑: 因为Provider在每次更新state中的内容时,Provider的props并不会发生改变,这就会导致其不会进行重新渲染,所以需要使用cloneELement进行一份克隆 */}
{React.cloneElement(props.children)}
</ReduxContext.Provider>
)
}
function connect(mapStateToProps, mapDispatchToProps){
return function(Component){ // 这是一个HOC,对组件进行包装,将store中的state和actions方法传递到组件的props中
let state = initialState;
let actions = {};
return props => {
if(store.getState){
state = mapStateToProps(store.getState())
}
actions = mapDispatchToProps(store.dispatch) // 这里的dispatch会是在middleware中间件机制中处理后的dispatch
return (<Component {...props} {...state} {...actions} dispatch={store.dispatch} />) // 将state和diaptch映射进去
}
}
}
return {store, connect, Provider}
}
核心: 实现中间件机制
- 为了大家能够更好的理解之后的递归串联中间件的流程,给出三个中间件(logger, thunk, promise), 和一个大致的调用流程图:
// 三个中间件:
let logger = store => next => action => {
console.log("logger", next, action)
console.log('%c prev state', `color: #A3A3A3, font-weight: bold`,store.getState());
console.log('%c action', `color: #7FBEDF, font-weight: bold`, action);
next(action);
console.log('%c next state', `color: #9CD69B, font-weight: bold`,store.getState());
}
let promise = store => next => action => {
console.log('promise', next, action)
if(typeof action?.then === "function"){
return action.then(store.dispatch)
}
next(action)
}
let thunk = store => next => action => {
console.log("thunk", next, action)
if(typeof action === "function"){
return action(store.dispatch, store.getState)
}
next(action)
}
调用流程图:
简单分析: 中间件传递到applyMiddleware函数之后,会在内部先将每个中间件执行一次,将store作为参数传递给每个中间件,此时中间件都被剥去最外层函数,形成第一层的闭包;之后开始执行上图中的compose: 也就是对中间件函数进行串联,串联的流程实质是将一个中间件作为另一个中间件的next函数,如此,在一个中间件中调用next函数,实际就是调用到了另一个中间件继续执行,而对于最初的一个中间件的next则回被赋值为调用实际的_dispatch方法的函数,如此,则执行完compose之后,便回将所有的中间件函数串联为一个大的函数,这个函数实际就是我们包装后的dispatch方法,而在compose的后,也就均执行了中间件的第二层函数,形成了第二层的闭包,最后,当组件开始调用dispatch方法,将action派发进入到中间件函数中,每个中间件函数调用next函数(实际就是另一个中间件的最内层的那个接收action的函数),将action依次往下传递,直到最后一个中间件时,其next函数就是最终去执行action派发的 _dispatch函数,完成最终的派发流程,而后依次返回
实现:
export function applyMiddleware(...middlewares){
return function(createStore){
return function(reducer, initialState){
let {store, connect, Provider} = createStore(reducer, initialState)
let dispatch; // 增强dispatch方法
const middlewareAPI = {
getState: () => store.getState(), // 在实际调用开始之前,store.getState个dispatch会是一个undefined, 等后面的执行后才会依次为他们附上值,所以这里需要包装成一个方法
dispatch: (...args) => dispatch(...args)
}
//1. 先执行中间件的一层,将store对象的两个个api传递给每个中间件
let chain = middlewares.map(middleware => middleware(middlewareAPI));
// 3. compose返回一个中间件串联好之后的函数,然后为这个函数传递一action函数,这个action函数就是每个中间件接收到的第三层,执行实际的派发操作
dispatch = compose(...chain)((...args) => store._dispatch(...args))
store.dispatch = dispatch;
return {
store,
connect,
Provider
}
}
}
}
function compose(...fns){
// 2. 递归执行中间件,每个中间件最内层的那个方法将会被作为中间件函数第二层接收到的next函数
// 这样就是在每个中间件中继续递归调用后面的中间件,直到最后一个中间件被调用后返回
if(fns.length === 0) return args => args;
if(fns.length === 1) return fns[0];
return fns.reduce((a, b) => (...args) => a(b(...args)));
}
- 以上的compose方法合并中间件之后将回形成如下形式的一个函数:
// 这里的args0就是在compose方法执行后再次至此该返回函数传递进来的(...args) => store._dispatch(...args)方法,也就是实际的派发action的方法
(...args0) => (...args1) => 中间件1(中间件2(...args1))(中间件3(...args0))
整个compose中间件应该就算是整个核心了,理清了这个流程基本也就搞清楚了redux的中间件核心机制,在redux的中间件机制的流程中回形成两层闭包,当然这还不包括在外层的哪些函数中形成的闭包…