[Redux 源码] 来聊聊 Redux
如同世界上的很多事情一样,Redux 也从昔日的 React 的大网红,逐渐退出前端的舞台。这很正常。Redux 也算完成了它的使命,作为前端开发人员还是要充满感激,准备在未来的某一天挥手告别 Redux
2022 年应不应该学习 Redux React Redux
这个问题和以下问题一样无聊,哈哈哈!
2022 年前端还有没有前途? 2021 年前端还有没有前途? 2020 年前端还有没有前途? 这个问题,笔者在知乎发现, 从2015年开始,就有人在问。
笔者的答案是肯定,可以说掌握了 Redux, 其他的一些状态管理库,比如( zustand,Recoil,Unstated Next…… ) 再去看源码就非常好理解了。
为什么一开始是 Redux 而不是 Context 优势 和 区别 在哪?
Context 不支持局部订阅,这也是被大家诟病的重要原因。这也就意味着,只要引用类型中的单个属性/成员 发生变化,其余没有发生变化的成员也要全部 rerender。这里其实有解决方案就是下面代码所表示的 HOC 高阶组件,将 useContext 从组件当中解耦出来,然后由高阶组件去分发简单值状态给子组件传递 props。这样子组件可以通过 React.memo 优化 props / useMemo 包裹的一些个组件。但是终究不是办法。
其他被大家诟病的原因还有,Context 状态管理太分散,Context 嵌套,没有异步相关业务的处理方案。 当然,这些都是针对有全局状态管理需求的,像一些中后台场景下,绝大多数页面的数据流转都是在当前页完成,在页面挂载的时候请求后端接口获取并消费,这种场景下并不需要复杂的数据流方案。
const Hoc =
(Component) =>
() => {
const { userLists, flipLight } = useContext(Context);
// 组件局部订阅 userLists[index]
return (
<Component user={userLists[index]} />
);
};
const HocMemo = Hoc
React.memo(({ user }) => {
console.log('render', index);
return (
<div></div>
);
})
);
Redux 可以轻松实现支持局部订阅,而且不用我们去想怎么优化,为什么这么说? 因为只要开发者有意识的,正确的使用 useSelector 我们就可以实现UI和数据之间的局部订阅。
当然,Redux 还支持各种中间件的级联,和异步业务的处理。
function ReduxCpn({ index }) {
const isLit = useSelector((state) => state.userList[index]); //局部订阅
const dispatch = useDispatch();
console.log('render room', index);
return (
<div>
<button onClick={() => dispatch(flipLight(index))}>Flip</button>
</div>
);
}
为什么后来又不是 Redux
第一:Hook 时代来了
第二:React Query / SWR / useRequest 异步状态管理工具出现了
第三:开发者逐渐意识到模板代码太重,逻辑大量冗余,大项目不好维护。尤其是针对于网络请求,这 一块的解决方案很难用。
Redux 源码
简单使用
class Counter1 extends React.Component {
state = { number: store.getState().number };
componentDidMount() {
/* 返回取消监听函数 */
this.unsubscribe = store.subscribe(() => {
this.setState({ number: store.getStore().number });
});
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
return (
<div>
{" "}
<div>{this.state.number}</div>{" "}
<button onClick={store.dispatch({ type: "ADD" })}></button>{" "}
</div>
);
}
}
createStore 实现
💡 createStore 的作用:通过 reducer / combineReducer 组合出来的总的 reducer,创建并初始化 store,返回 dispatch 发布方法, subscribe 订阅方法,getStore 获取仓库方法。
const createStore = (reducer, preloadedState, enhancer) => {
let listeners = [];
function getState() {
return state;
}
// 收到 action,通过 reducer 函数重新计算新的 state
function dispatch(action) {
state = reducer(state, action);
listeners.forEach((l) => l());
return action;
}
/// 订阅函数 传入 listener, dispatch 的时候去调用每一个 listener
function subscribe(listener) {
listeners.push(listener);
return () => {
listeners = listeners.filter((l) => l !== listener);
};
}
dispatch({ type: "@@REDUX/INIT" });
return {
getState,
dispatch,
subscribe,
};
};
实现 combineReducers
💡 作用和实现:第一:combineReducer 组合多个 reducer 返回一个全局的 reducer 第二: 通过调用所有的 reducer 组合成一个 全局的 store。
function combineReducers(reducers) {
return function (state = {}, action) {
let nextState = {};
for (let key in reducers) {
nextState[key] = reducers[key](state[key], action);
}
return nextState;
};
}
实现 applyMiddle
💡 作用和实现:第一:实现中间件的注册 ,第二: 实现中间件的级联。
function applyMiddleware(...middlewares) {
return function (createStore) {
return function (reducer, preloadedState) {
//是创建计算改造后的store.dispatch的过程
let store = createStore(reducer, preloadedState); //先创建一个仓库
let dispatch;
let middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action),
};
//chain=[promise,thunk,logger]
let chain = middlewares.map((middleware) => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch,
};
};
};
}
// 效果可以理解为这样:add3(add2(add1(args))) 内层函数的调用结果作为外层函数的参数。
function compose(...funcs) {
return funcs.reduce(
(a, b) =>
(...args) =>
a(b(...args))
);
}
下面的一句代码实现了中间件的级联,如何实现的? 如果有三个中间件 middlewares=[promise,thunk,logger]
那么compose(...middlewares)
会变成像这样 (dispatch) ⇒ logger(thunk(promise(dispatch)))
所以,compose(...middileware)(store.dispatch)
返回的是第一个 logger 改造后的中间件,当我们在 UI 组件当中 dispatch 任何一个 action 实际上通过了所有中间件,直至到达目标中间件 (比如 thunk 中间件判断 action 是函数之后就停止),如果不是该中间件 通过 闭包 next(action) 调用下一个中间件。 至此,实现了中间件的级联。
dispatch = compose(...middlewares)(store.dispatch);
实现 Redux 中间件
中间件的原理:核心原理就是劫持原来的 dispatch 方法,在原来的 dispatch 方法之前,之后做一些事情。而对于 redux 中间件由于要实现级联和实现中间件的compse,所以要按照一定的格式,外层函数接受两个参数 一个是 getState, dispatch , 返回两层嵌套函数, 第一层函数接受一个next 作为级联时使用的函数,第二层内层函数接收一个action,作为我们改造后的新的 dispatch 方法。
Thunk
function thunk({ getState, dispatch }) {
return function (next) {
//为了实现中间件的级联,调用下一个中间件
return function (action) {
//这才就是我们改造后的dispatch方法了
if (typeof action === "function") {
return action(dispatch, getState);
}
return next(action);
};
};
}
Promise
function promise({ getState, dispatch }) {
return function (next) {
//为了实现中间件的级联,调用下一个中间件
//调用store.dispatch(action)
return function (action) {
//这才就是我们改造后的dispatch方法了
if (action.then && typeof action.then === "function") {
action.then(dispatch).catch(dispatch);
} else if (action.payload && typeof action.payload.then === "function") {
action.payload
.then((result) => dispatch({ ...action, payload: result }))
.catch((error) => {
dispatch({ ...action, payload: error, error: true });
return Promise.reject(error); //返回失败的promise
});
} else {
next(action);
}
};
};
}