likes
comments
collection
share

如何实现一个简单版的react-redux

作者站长头像
站长
· 阅读数 3

在现代的Web开发中,构建复杂的用户界面变得越来越普遍,而React已经成为了许多开发人员的首选框架之一。但随着应用程序的规模和复杂性的增加, 有效地管理应用程序的状态变得至关重要。这就是Redux和React Redux的威力所在。React Redux是一个为React应用程序提供状态管理的库,它建立在Redux之上, 为React组件提供了一种优雅的方式来连接到Redux存储,并在组件之间传递数据。本文将介绍如何实现一个简单版的react-redux。

react-redux的核心方法是Providerconnect方法 我们先看一下Provider组件,

Provider

Provider组件的作用是把store对象通过react context传到下级组件, 先定义一个react context

import React from 'react';
const ReactReduxContext = React.createContext();
export default ReactReduxContext;

再实现Provider组件

import ReactReduxContext from './Context';
function Provider(props) {
    const {children, store} = props;
    const contextValue = { store };
    return (
        <ReactReduxContext.Provider value={contextValue}>
            {children}
        </ReactReduxContext.Provider>
    )
}

逻辑很简单,就是通过Context把redux的store对象传到下级组件

connect

conncet实现的功能主要有2点 1.react store的state和dispatch方法通过props传给包裹的下层组件 2.监听store的改变,比较包裹组件的props是否改变,如果改变则触发一次渲染

connect的用法如下

const mapStateToProps = (state) => {
  const counter = state?.counter;
  return {
    count: counter && counter.count? counter.count: 0
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    incrementHandler: () => dispatch("INCREMENT"),
    decrementHandler: () => dispatch("DECREMENT"),
    resetHandler: () => dispatch("RESET"),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
先来实现一个高阶组件,接收mapStateToProps和mapDispatchToProps并传到下层组件

function connect(mapStateToProps, mapDispatchToProps) {
    return function HocConnect(WrapperComponent) {
        return function WrappedComponent(props) {
          //通过useContext拿到ReactReduxContext上存的store对象
          const { store } = useContext(ReactReduxContext);
          const stateProps = mapStateToProps && mapStateToProps(store.getState(), props);
          const dispatchProps = mapDispatchToProps && mapDispatchToProps(store.dispatch);
          const mergedProps = { ...stateProps, ...dispatchProps, ...props };
            const { store } = useContext(ReactReduxContext);
            return <WrapperComponent {...mergedProps}></WrapperComponent>;
        };
    };
}

监听store的改变,比较props是否改变,这里要比较props是否改变,就是记录上一次渲染的props, 我们可以把上一次渲染的props存在ref里面,然后通过 shallowEqual比较props是否改变,shallowEqual可以找一个现成的实现

function connect(mapStateToProps, mapDispatchToProps) {
    return function HocConnect(WrapperComponent) {
        return function WrappedComponent(props) {
          //通过useContext拿到ReactReduxContext上存的store对象
          const { store } = useContext(ReactReduxContext);
          const stateProps = mapStateToProps && mapStateToProps(store.getState(), props);
          const dispatchProps = mapDispatchToProps && mapDispatchToProps(store.dispatch);
          const mergedProps = { ...stateProps, ...dispatchProps, ...props };
          const lastChildProps = useRef();
          useEffect(() => {
                store.subscribe(() => {
                      const stateProps = mapStateToProps && mapStateToProps(store.getState(), props);
                      const dispatchProps = mapDispatchToProps && mapDispatchToProps(store.dispatch);
                      const newProps = { ...stateProps, ...dispatchProps, ...props };
                      if(shallowEqual(newProps, lastChildProps.current)) {

                      }
                })
            }, []);
          useEffect(() => {
            lastChildProps.current = mergedProps;
          })
          return <WrapperComponent {...mergedProps}></WrapperComponent>;
        };
    };
}

// shallowEqual.js 
function is(x, y) {
    if (x === y) {
      return x !== 0 || y !== 0 || 1 / x === 1 / y
    } else {
      return x !== x && y !== y
    }
  }
  
  export default function shallowEqual(objA, objB) {
    if (is(objA, objB)) return true
  
    if (
      typeof objA !== 'object' ||
      objA === null ||
      typeof objB !== 'object' ||
      objB === null
    ) {
      return false
    }
  
    const keysA = Object.keys(objA)
    const keysB = Object.keys(objB)
  
    if (keysA.length !== keysB.length) return false
  
    for (let i = 0; i < keysA.length; i++) {
      if (
        !Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
        !is(objA[keysA[i]], objB[keysA[i]])
      ) {
        return false
      }
    }
  
    return true
  }

这里我们把代码优化一下,把获取props的逻辑抽一个方法出来

function connect(mapStateToProps, mapDispatchToProps) {
    return function HocConnect(WrapperComponent) {
        const mergeChildProps = (store, props) => {
            const stateProps = mapStateToProps && mapStateToProps(store.getState(), props);
            const dispatchProps = mapDispatchToProps && mapDispatchToProps(store.dispatch);
            const mergedProps = { ...stateProps, ...dispatchProps, ...props };
            return mergedProps;
        }

        return function WrappedComponent(props) {
          //通过useContext拿到ReactReduxContext上存的store对象
          const { store } = useContext(ReactReduxContext);
          const mergedProps = mergeChildProps(store, props);
          const lastChildProps = useRef();
          useEffect(() => {
                store.subscribe(() => {
                      const newProps = mergeChildProps(store, props);
                      if(shallowEqual(newProps, lastChildProps.current)) {
                        //执行强制更新
                      }
                })
            }, []);
          useEffect(() => {
            lastChildProps.current = mergedProps;
          })
          return <WrapperComponent {...mergedProps}></WrapperComponent>;
        };
    };
}

接下来实现强制更新的逻辑,通过下面的方法,我们可以创建一个forceUpdate方法,逻辑很简单,通过每次给useState返回的updateState方法设置一个空对象,来触发更新

const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);

这我们就已经实现了react-redux的基本功能,完整代码如下

function connect(mapStateToProps, mapDispatchToProps) {
    return function HocConnect(WrapperComponent) {
        const mergeChildProps = (store, props) => {
            const stateProps = mapStateToProps && mapStateToProps(store.getState(), props);
            const dispatchProps = mapDispatchToProps && mapDispatchToProps(store.dispatch);
            const mergedProps = { ...stateProps, ...dispatchProps, ...props };
            return mergedProps;
        }

        return function WrappedComponent(props) {
          //通过useContext拿到ReactReduxContext上存的store对象
          const { store } = useContext(ReactReduxContext);
          const mergedProps = mergeChildProps(store, props);
          const lastChildProps = useRef();
          const [, updateState] = React.useState();
          
          const forceUpdate = React.useCallback(() => updateState({}), []);

          useEffect(() => {
                store.subscribe(() => {
                      const newProps = mergeChildProps(store, props);
                      if(shallowEqual(newProps, lastChildProps.current)) {
                        //执行强制更新
                        forceUpdate();
                      }
                })
            }, []);
          useEffect(() => {
            lastChildProps.current = mergedProps;
          })
          return <WrapperComponent {...mergedProps}></WrapperComponent>;
        };
    };
}

当前的实现方式存在一个问题,思考下面的场景, 我们有一个TaskList组件,TaskList的子组件是TaskItem, TaskListTaskItem都connect了redux store,TaskList根据store里面的存的task list循环渲染TaskItem TaskItem里面会通过store拿task list里的item的值。

这时候如果我们删除了一个item,因为TaskListTaskItem都监听了store改变,所以TaskList和被删除TaskItem会同时触发渲染,TaskItem会从store里面里面拿一个已经删除的值,这时候就会报错。 其实按照正常的渲染逻辑,删除了一个item只要TaskList触发渲染,然后等TaskList触发TaskItem的渲染,这样就不会有报错的情况,因为被删除的TaskItemTaskLIst循环渲染的时候直接被销毁了。

所以react-redux官方在实现的时候是只在Provider监听store的改变,其他子组件通过自己实现一套Subscription来通知更新 但是Subscription的实现依赖于connect,如果是通过hook方式来使用react-redux,这个方案也是行不通的。 对于hook的方式,官方的建议是从写法上来约束,避免报错,感兴趣的可以看看下面的文章 react-redux.js.org/api/hooks#s…