zoukankan      html  css  js  c++  java
  • react-redux componentWillMount 递归调用导致内存溢出问题排查

    在学习 redux react-redux 模拟实现过程中, 为了方便派发 dispatch 使react-redux 接受一个 mapDispatchToProps 函数, 内部将 dispatch 和 props 传入并接受调用的返回值, 由使用者自行定义触发的 dispatch 事件, 通过高阶组件的形式再通过 props 流向目标组件。

    react-redux 实现如下:

    import React from "react";
    import PropTypes from "prop-types";
    
    export const connect = (mapStateToProps, mapDispatchToProps) => WrappedComponent => {
      // mapStateToProps 保存在闭包环境中
      return class Connect extends React.Component {
        static contextTypes = {
          store: PropTypes.object
        };
    
        constructor(props) {
          super(props);
          this.state = {
            allProps: {}
          };
        }
    
        componentWillMount() {
          const { store } = this.context;
          this._updateProps();
          // state 已经更新
          store.subscribe(() => this._updateProps());
        }
    
        _updateProps() {
          const { store } = this.context;
          // 额外传入 props, 让获取数据更加灵活方便
          const stateProps = mapStateToProps?.(store.getState(), this.props) ?? {};
          const dispatchProps = mapDispatchToProps?.(store.dispatch, this.props) ?? {};
          this.setState({
            allProps: {
              ...stateProps,
              ...dispatchProps,
              ...this.props
            }
          })
        }
    
        render() {
          const { store } = this.context;
          const stateProps = mapStateToProps(store.getState());
          return <WrappedComponent {...this.state.allProps} />;
        }
      };
    };
    

    使用的组件 ThemeSwitch 组件代码如下:

    import React, { Component } from "react";
    import PropTypes from "prop-types";
    import { connect } from "./util/react-redux";
    
    class ThemeSwitch extends Component {
      static contextTypes = {
        themeColor: PropTypes.string,
        onSwitchColor: PropTypes.func
      };
    
      render() {
        const { themeColor, onSwitchColor } = this.props;
        return (
          <div>
            <button
              onClick={onSwitchColor('red')}
              style={{ color: themeColor }}
            >
              Red
            </button>
            <button
              onClick={onSwitchColor(this,'blue')}
              style={{ color: themeColor }}
            >
              Blue
            </button>
          </div>
        );
      }
    }
    
    const mapDispatchToProps = dispatch => {
      return {
        onSwitchColor(color) {
          dispatch({ type: "CHANGE_COLOR", themeColor: color });
        }
      };
    };
    
    const mapStateToProps = store => {
      return {
        themeColor: store.themeColor
      };
    };
    
    ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch);
    
    export default ThemeSwitch;
    
    

    运行后报错结果:

    Maximum update depth exceeded. 
    This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. 
    React limits the number of nested updates to prevent infinite loops.
    

    报错结果可以看出,堆栈溢出,因为重复调用某个生命周期钩子 componentWillUpdatecomponentDidUpdate 导致一直重新渲染。

    首先排查了 react-redux 的代码,并且在别的组件都完全正常,所以问题原因就锁定在 ThemeSwitch 组件。
    通过在 react-redux 文件的 _updatePropsdebugger 发现该方法会无限的调用,调用的原因无非第一次 componentWillMount 钩子中对其初始化一次,并且订阅了 store 中 state 的变化,那么之所以重读被调用,肯定是 store.subscribe 内一直发布事件,导致订阅函数一直被更新,所以一直调用 _updateProps 函数。

    所以问题锁定在 state 一直被修改 state 数据定义在 redux 模拟实现中。redux 简单的实现如下:

    function createStore(reducer) {
      let state = null;
      // 缓存旧的 state
      const listeners = [];
      const subscribe = listener => listeners.push(listener);
      const getState = () => state;
      const dispatch = action => {
        state = reducer(state, action);
        listeners.forEach(listener => listener());
      };
      dispatch({}); // 初始化 state
      return {
        getState,
        dispatch,
        subscribe
      };
    }
    

    发现 state 更新的唯一途径就是 dispatch 函数的触发,哪里无限调用 dispatch 函数了呢? 最后发现 ThemeSwitch 组件的给按钮绑定监听事件,修改主题色的函数是直接调用的 - -
    就导致 dispatch 一直更新。无限循环导致内存泄露的流程:
    onSwitchColor方法调用 -> 触发dispatch更新 -> _updateProps触发 -> setState更新状态 -> 更新目标组件Prop -> 目标组件重新 Render -> onSwitchColor方法调用 -> 触发dispatch更新

    修改 ThemeSwitch 文件有问题的代码:

    return (
          <div>
            <button
              onClick={onSwitchColor('red')}
              style={{ color: themeColor }}
            >
              Red
            </button>
            <button
              onClick={onSwitchColor('blue')}
              style={{ color: themeColor }}
            >
              Blue
            </button>
          </div>
        );
    

    修改后:

    return (
          <div>
            <button
              onClick={onSwitchColor.bind(this ,'red')}
              style={{ color: themeColor }}
            >
              Red
            </button>
            <button
              onClick={onSwitchColor.bind(this,'blue')}
              style={{ color: themeColor }}
            >
              Blue
            </button>
          </div>
        );
    

    完美运行!

  • 相关阅读:
    Python—字符编码转换、函数基本操作
    零散知识点
    Python—集合的操作、文件的操作
    Python—字典的操作
    Python—字符串的操作
    Spring基础—— 在 Spring Config 中使用外部属性文件
    Spring基础—— Bean 的作用域
    Spring基础——在 IOC 容器中 Bean 之间的关系
    Spring基础——在 Spring Config 文件中基于 XML 的 Bean 的自动装配
    Spring基础——一个简单的例子
  • 原文地址:https://www.cnblogs.com/qiqingfu/p/12431897.html
Copyright © 2011-2022 走看看