Skip to main content

从Redux源码中的HOC模式探索

本篇博客记录Redux源码的学习过程, 探索逻辑复用的一种处理方案HOCRedux中的体现

1.React-Redux中HOC模式的探索

1.1 Redux工作流

View binding library --> React Redux

从 React-Redux 解析HOC模式, 对比Custom Hooks的逻辑复用思路

2. Redux 工作思路

2.1 三大理念

  • State只读: Immutable state

  • 单一数据源: Single datasource

  • 纯函数修改state: Pure function

遵循React的immutable state, 唯一修改state的方式是dispatch action


// 1. 创建store容器,传入reducer
let store = createStore(counterReducer);


// 2. 编写reducer pure function
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case "counter/incremented":
return { value: state.value + 1 };
case "counter/decremented":
return { value: state.value - 1 };
default:
return state;
}
}

// 3.订阅变化, 修改DOM, 实际在开发中可以使用React - Redux进行修改 - view binding library
store.subscribe(() => {
const dom = document.getElementById("store_div");
dom.innerHTML = `store Value: ${store.getState().value}`;
});

源码也很简单.

2.2 容器组件和展示组件分离

容器组件展示组件分离是从HOC模式开始的

如何将ReactRedux绑定在一起 :View binding library --> React-Redux

React实现的展示组件和Redux实现的容器组件不产生依赖, 低耦合

使用React-Redux这种View binding library进行props映射,将容器组件中的state映射到展示组件的props中

Redux-toolkit帮助我们处理的这些事情,不需要进行connect()进行绑定

3. Redux-thunk

in order to handle async request

3.1 Redux-thunks 工作原理

Redux-thunks源码的index.js文件中做了个简单的判断:

如果patch的类型是function, 就交给redux-thunks处理

异步处理结构:

(functionProps) => (dispatch) => (async fetch) => operation

这里是redux-thunk中间件的链式调用

Redux-thunks的index中进行验证,如果dispatch的值是函数,那么交给Redux-thunks处理,所以这里没有使用createAsyncThunk的API 答案: 当 action 创建函数返回函数时,这个函数会被 Redux Thunk middleware 执行。这个函数并不需要保持纯净;它还可以带有副作用 包括执行异步 API 请求。这个函数还可以 dispatch action,就像 dispatch 前面定义的同步 action 一样。 在这里的redux-thunk中使用了dispatch

example:


export const login = (form: AuthForm) => (dispatch: AppDispatch) =>
auth.login(form).then((user) => dispatch(setUser(user)));

3.2 Redux如何引入Redux-thunk的中间件

使用applyMiddleware()


const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware, // 允许我们 dispatch() 函数
loggerMiddleware // 一个很便捷的 middleware,用来打印 action 日志
)
)

注意: 在Redux-toolkit中已经默认支持Redux-thunk,不需要配置

thunk的优点是可以再次被dispatch, 这样我们可以很方便的处理异步请求

比如loading, error, isFetching状态, 所以Redux-thunk是处理异步请求比较好的方案

当然你也可以使用react-query等data-fetching之类的库去进行处理

4. 理解 middleware 中间件

4.1 特性

最优秀的特征: 链式引用, 多个中间件组合


function logger({ getState }) {
return (next) => (action) => {
console.log('will dispatch', action)

// 调用 middleware 链中下一个 middleware 的 dispatch。
let returnValue = next(action)

console.log('state after dispatch', getState())

// 一般会是 action 本身,除非
// 后面的 middleware 修改了它。
return returnValue
}
}

let store = createStore(
todos,
[ 'Use Redux' ],
applyMiddleware(logger)
)

applyMiddleware接受一个middleware的数组

4.2 源码部分:


export default function applyMiddleware(
// 结构中间件, 链式调用
// createStore第二个或者第三个参数
...middlewares: Middleware[]
): StoreEnhancer<any> {
return (createStore: StoreEnhancerStoreCreator) => <S, A extends AnyAction>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => {
const store = createStore(reducer, preloadedState)
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}

const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
// 把中间件的参数middlewareAPI传递给所有的中间件函数
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 调用了compose方法, 组合不同的函数
// 组合函数, funcs = [f,g,h] => f(g(h(...args))),把所有中间件串联,上个中间件的结果将传递给下一个中间件
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

// 返回createStore, 用新的dispatch覆盖之前的dispatch实现中间件的链式调用
// 返回一个getState和变更的dispatch方法给中间件函数, 实现对dispatch的增强
return {
...store,
dispatch
}
}
}

middleware的结果将作为下一个middleware参数传入

所以redux的middleware是这样的结构


function middlewara() {
// 可以看到这里对应源码中的middlewaraAPI
return ({ getState, dispatch }) => next => action => {
return next(action)
}
}

所以redux-thunk的关键代码是这样的:


function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}

return next(action);
};
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;