记一次react不正当操作state引发的bug

634 阅读3分钟

太长不看版 👉 直接跳到找到原因

bug复现

一个表单里,用户对之前填写的数据进行修改,原始数据存在redux里,用户修改后会把redux里的数据进行更新,当内容有修改时保存按钮才可以点击,否则保存按钮处于置灰状态,数据是否有修改是通过componentWillReceiveProps新旧redux中的数据diff操作(用的lodash的isEqual),可以说是很常见的场景了。但是呢,测试同学却一直反馈,保存按钮在点击之后仍然处于可点击的状态,不多说了,查bug了😑

查找原因

这里不得不吐槽一下我们出现问题的代码文件十分的冗长,有2000多行,查找问题非常不便

因为是按钮状态的问题,而按钮的状态又是通过新旧数据diff的,所以想到的第一个点肯定就是数据问题,是不是数据传给接口有问题,或者接口返回的数据有问题,排查了一下,数据没问题,当然这也是意料之中。再看一下redux中的数据也是最新的数据。哎,继续查呗😣

既然数据都没有问题,那么就必然是diff的问题了,当然不能排除lodash的问题,但是作为gitlab中40k+ star的库还是相当可靠的,实在查不出问题了再翻一下isEqual的源码。因为我们的diff操作是放在componentWillReceiveProps中,所以不可避免的打印了上次和这次的props的值,果然,问题出现了,componentWillReceiveProps中this.props和next.props的值是一样的,想必造成这次bug的原因肯定在这了。但是呢,这就很奇怪,因为我们都知道componentWillReceiveProps中this.props的值和next.props的值通常情况下是不一样的,那么是什么原因造成的呢,带着这样的疑问,打开了react官网🤔

找到问题

一番查找后,我发现了这段话

image.png

大致意思就是对于引用类型来说,当我们修改里面的内容时,尽管内容已经修改,但是还是同一个实例,所以react没法判断里面的数据是否修改,所以,为了避免别的问题出现或者进行深比较,react会把props向下传递。

所以会不会是在我们对表单的数据进行处理时,修改了引用数据的内容,从而引发react触发了componentWillReceiveProps,所以当redux保存新的数据时再触发componentWillReceiveProps,此时this.props已经被更新了呢,这时再回头看一下保存时的逻辑,果然里面的代码直接修改了state中的值而没有通过setState。

错误代码

import React from "react";
import { connect } from "react-redux";
import { illegalChangeState } from "../../actions/index";

class Test extends React.Component {
  constructor(...props) {
    super(...props);
    this.state = {
      obj: {
        a: 0,
      },
    };
  }
  componentWillReceiveProps(nextProps) {
    // 下面不正当操作state导致这里this.props和nextProps的值是一样的
    console.log(this.props);
    console.log(nextProps);
  }
  componentDidMount() {
    this.setState({
      obj: this.props.obj,
    });
  }
  changeState = () => {
    const { obj } = this.state;
    // 这里直接修改了state
    obj.a = 2;
    this.props.illegalChangeState(obj.a);
  };

  render() {
    return <div onClick={this.changeState}>{this.state.obj.a}</div>;
  }
}

const mstp = (state) => {
  return {
    obj: state.obj,
  };
};

export default connect(mstp, { illegalChangeState })(Test);

知道了原因之后,问题自然而然迎刃而解。