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>
    );

完美运行!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java内存溢出指的是在Java程序运行过程中,因为申请的内存超出了可用内存的限制,导致程序终止的现象。下面是一些排查Java内存溢出问题的方法: 1.确定是否是内存溢出问题:查看程序的错误日志或异常信息,如果有OutOfMemoryError的错误信息,可以确定是内存溢出问题。 2.分析问题发生的位置:追踪错误日志或异常堆栈,定位到代码中可能导致内存溢出的地方,比如不断创建对象、大量递归调用等。 3.查看程序的内存使用情况:可以使用Java的内存分析工具,如jmap、jstat等,查看程序运行时的内存使用情况,包括堆内存和非堆内存的使用情况。 4.检查代码中是否有资源未释放:Java中需要手动释放的资源包括文件流、数据库连接等,如果资源没有正确释放,会导致内存泄漏,最终导致内存溢出。 5.检查是否存在循环引用:循环引用指的是多个对象相互引用,导致垃圾回收器无法回收它们,最终导致内存溢出。可以使用内存分析工具来分析程序中是否存在循环引用的情况。 6.调整JVM参数:可以通过调整JVM的参数来增加可用内存,比如增加堆内存的大小。可以使用命令行参数'-Xms'和'-Xmx'来指定初始堆大小和最大堆大小。 7.优化代码:检查代码中是否存在不必要的对象创建、频繁的垃圾回收等问题,优化程序的设计和算法,减少内存使用。 8.升级JDK版本:某些JDK版本中可能存在内存泄漏或其他内存相关的问题,升级到最新的JDK版本可以解决一些内存溢出问题。 总之,排查Java内存溢出问题需要分析错误日志、查看内存使用情况、检查代码和资源释放等等,找出问题的根源并及时修复。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值