上节主要讲解了 react-redux 在类组件和函数组件中的用法,本节主要分析一下如何自己实现一个 react-redux
我们发现 react-redux 的 Provider 其实和 react 中的 Context 用法很像,其实 react-redux 就是基于 Context 实现的。
Provider 组件的实现很简单:
// 1. 创建 context 对象
const Context = React.createContext();
// 2. 实现 Provider 组件,原理就是使用 Context 的 Provider
export function Provider(props) {
const { store, children } = props;
return <Context.Provider value={store}>{children}</Context.Provider>;
}
这样 Provider 组件的子组件就都可以接收到 store 对象了。
回顾一下 connect 函数的使用:
const ConnectCounter = connect(mapStateToProps, mapDispatchToProps)(Counter);
connect 函数接收两个参数:mapStateToProps
和 mapDispatchToProps
其中 mapStateToProps
是一个对象, mapDispatchToProps
可以是一个函数或者对象。
connect 函数返回一个高阶组件,即参数为一个组件,返回值也是一个组件。
源码实现:
// connect 是一个函数,接收两个参数,返回一个函数 wrap
// wrap 函数接收一个组件作为参数, 返回一个新的函数组件 comp, 参数就是props
export const connect = (mapStateToProps, mapDispatchToProps) => {
const wrap = (WrapComponent) => {
// 这里的 NewComp 就是一个函数组件
const NewComp = (props) => {
// 获取跨层级传递的 store
const store = useContext(Context);
// 将 state 作为参数传给 mapStateToProps,返回值就是一个props对象
const stateProps = mapStateToProps(store.getState());
// 获取 store 中的 dispatch 方法
let dispatchProps = { dispatch: store.dispatch };
// 如果 mapDispatchToProps 是一个函数
if (typeof mapDispatchToProps === "function") {
// 将 dispatch 作为参数传递,返回值也是一个props对象,其中的属性都是一些函数
dispatchProps = mapDispatchToProps(store.dispatch);
}
// mapDispatchToProps 还可以是一个对象
// {
// onAdd: () => ({ type: "ADD" }),
// onMinus: () => ({ type: "MINUS" })
// }
if (typeof mapDispatchToProps === "object") {
// 这就需要我们遍历对象,对每个 action 进行 dispatch
// redux 提供了这个 api: bindActionCreators
// 我们也可以自己实现
dispatchProps = myBindActionCreators(
mapDispatchToProps,
store.dispatch
);
}
// 自定义hooks, 获取强制渲染组件的方法
const forceUpdate = useForceUpdate();
// store 订阅更新
// 在 state 发生改变后,需要触发组件的更新渲染
useLayoutEffect(() => {
console.log("useLayoutEffect");
const unsubscribe = store.subscribe(() => {
// 类组件中有 forceUpdate, 那么如何让函数组件强制更新呢
// 1. 可以使用 useState, 因为 setState 可以触发组件重新渲染
forceUpdate();
console.log("state change");
});
return () => {
unsubscribe();
};
}, [forceUpdate, store]);
// 这样就通过 mapStateToProps,mapDispatchToProps 两个函数将 state 和 dispatch
// 通过 props 的形式传递给了使用 context 强化的组件
return (
<WrapComponent
{...props}
{...stateProps}
{...dispatchProps}
></WrapComponent>
);
};
return NewComp;
};
return wrap;
};
上述代码中,我们使用自定义hooks实现了一个强制渲染函数组件的方法:
function useForceUpdate() {
const [state, setState] = useState(true);
const forceUpdate = useCallback(() => {
setState(!state);
}, [state]);
return forceUpdate;
}
这里借用了setState 可以触发组件更新的特性。
我们还实现了一个 myBindActionCreators 函数,用来处理 mapDispatchToProps 为对象时的情况。
function myBindActionCreators(actionsObj, dispatch) {
const dispatchObj = {};
// 遍历 actionsObj
for (const key in actionsObj) {
if (actionsObj.hasOwnProperty(key)) {
const action = actionsObj[key];
console.log(key, action, action());
dispatchObj[key] = () => dispatch(action());
}
}
console.log(dispatchObj);
return dispatchObj;
}
mapDispatchToProps 对象:
{
onAdd: () => ({ type: "ADD" }),
onMinus: () => ({ type: "MINUS" })
}
这里最终返回的 dispatchObj 其实就是这样:
{
onAdd: () => dispatch({ type: "ADD" }),
onMinus: () => dispatch({ type: "MINUS" })
}
现在,我们自己实现的 react-redux 就可以在类组件中进行使用了。
在函数组件中使用 react-redux 是通过两个 hooks:useSelector
和 useDispatch
useDispatch的实现非常简单,就是一个将 store.dispatch 返回的函数
export function useDispatch() {
// 获取跨层级传递的 store
const store = useContext(Context);
return store.dispatch;
}
回顾一下它的使用:
const count = useSelector((state) => state.count);
useSelector 接收一个函数作为参数,给这个函数传递参数 state,然后返回一个 state 属性
export function useSelector(selector) {
// 获取跨层级传递的 store
const store = useContext(Context);
const state = store.getState();
const selectedState = selector(state);
const forceUpdate = useForceUpdate();
// state 改变,订阅更新
useLayoutEffect(() => {
const unsubscribe = store.subscribe(() => {
console.log("useSelector state change");
forceUpdate();
});
return () => {
unsubscribe();
};
}, [store, forceUpdate]);
return selectedState;
}
它的实现也非常简单,并且强制更新组件的方法也是和 connect 中一样的。
至此,我们就基本实现了 react-redux。
演示代码:myReactRedux