react-redux使用及源码分析(下)

左丘元徽
2023-12-01

react-redux使用及源码分析(下)

上节主要讲解了 react-redux 在类组件和函数组件中的用法,本节主要分析一下如何自己实现一个 react-redux

1、Provider 组件的实现

我们发现 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 对象了。

2、connect 函数的实现

回顾一下 connect 函数的使用:

const ConnectCounter = connect(mapStateToProps, mapDispatchToProps)(Counter);

connect 函数接收两个参数:mapStateToPropsmapDispatchToProps

其中 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:useSelectoruseDispatch

3、useDispatch 的实现

useDispatch的实现非常简单,就是一个将 store.dispatch 返回的函数

export function useDispatch() {
  // 获取跨层级传递的 store
  const store = useContext(Context);
  return store.dispatch;
}
4、useSelector 的实现

回顾一下它的使用:

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

 类似资料: