setState意外更新非状态属性

时间:2018-08-09 15:29:08

标签: javascript reactjs

我不知道这是已知问题还是预期功能,但我发现了一个有趣的问题。

所以我们都知道,如果要在React中呈现反应式值,我们必须将值放入状态并使用setState:

constructor() {
  super();
  this.state = { counter: 0 }
  this.incrementButtonListener = (e) => {
    e.preventDefault();
    this.setState(prevState => ({ counter: prevState.counter + 1 }));
  };
}

render() {
  return (
    <div>
      <h1>{this.state.counter}</h1>
      // When clicked, counter increments by 1 and re-renders
      <button onChange={this.incrementButtonListener}>Increment</button> 
    </div>
  )
}

但是,如果我们将counter作为字段属性,则render()仅在创建组件时才捕获counter的快照,甚至在counter递增时,结果将不会在render()中反应性地显示:

constructor() {
  super();
  this.counter = 0;
  this.incrementButtonListener = (e) => {
    e.preventDefault();
    this.counter++;
  };
}

render() {
  return (
    <div>
      <h1>{this.counter}</h1>
      // When clicked, counter increments by 1 but the difference is NOT rendered
      <button onChange={this.incrementButtonListener}>Increment</button> 
    </div>
  )
}

对吗?基本的东西。

但是,当我尝试弄乱这段代码时,会发生一种有趣的情况。我们将字段属性作为其他属性保持不变。唯一的区别是,在incrementButtonListener中,我要在setState上添加一个someStateProperty

constructor() {
  super();
  this.counter = 0;
  this.incrementButtonListener = (e) => {
    e.preventDefault();
    this.counter++;
    /*-------------------------------ADD THIS*/
    this.setState({});
    // You have to pass an object, even if it's empty. this.setState() won't work.
    /*-----------------------------------------*/
  };
}

render() {
  return (
    <div>
      <h1>{this.counter}</h1>
      // Surprise surprise, now this.counter will update as if it was in the state! 
      <button onChange={this.incrementButtonListener}>Increment</button> 
    </div>
  )
}

这次,this.counter就像状态一样更新!

所以我的假设是,每次调用setState时(甚至使用一个空对象作为参数),render()都会再次运行,this.counter将得到重新计算,并因此而递增。当然,它不会像状态属性那样具有100%的反应性。但是,在这种使用情况下,this.counter唯一会改变的时间是我单击Increment按钮时。因此,如果我将setState放在侦听器中,它将像this.counter一样工作。

现在,我不确定这是一种可以接受的行为,还是只是一个意外的黑客行为,以及我是否应该使用它。有人可以帮我详细说明吗?

如果要查看实际行为,请在此处fiddle。您可以注释掉第7行中的this.setState({})位以查看区别。

4 个答案:

答案 0 :(得分:1)

因为您没有截获状态变化,所以会导致重新渲染,进而导致使用增量实例属性。这是设计使然。对React状态的任何更改都将导致组件重新呈现,除非您使用生命周期挂钩来控制是否应该发生这种情况。

请参见https://reactjs.org/docs/react-component.html#shouldcomponentupdate

答案 1 :(得分:1)

请使用this.statesetState,因为它有自己的用途。

通过使用React状态机制,我们得到了诸如冗长,类似于函数的编程风格这样的好处,即函数应该是纯函数而不是对数据进行变异。

每次调用setState时,它将使用新值来代替现有值,并使行为不可预测。

通过使用shouldComponentUpdate检查组件内部的道具/状态,您还可以防止组件重新渲染或更新。

在您的应用变得越来越复杂之后,诸如Redux之类的库可能会在将来为您省钱。对于更简单的组件或应用,React状态就足够了。

进一步阅读:

https://reactjs.org/docs/faq-state.html

https://reactjs.org/docs/state-and-lifecycle.html

https://reactjs.org/docs/react-component.html#shouldcomponentupdate

https://spin.atomicobject.com/2017/06/07/react-state-vs-redux-state/

答案 2 :(得分:1)

您可以在forcrUpdate上替换setState。 如果您的团队使用装饰风格的代码,则切勿使用setState({})或forceUpdate()。 React的作者建议使组件像纯函数一样。

答案 3 :(得分:0)

class Hello extends React.Component {
    state = { counter: 0 };

    increment = () => {
        const { counter } = this.state;

        this.setState({ counter: counter + 1 });
    };

    decrement = () => {
        const { counter } = this.state;

        this.setState({ counter: counter - 1 });
    };

    render() {
        const { counter } = this.state;

        return (
            <div>
                <h1>{counter}</h1>

                <p>
                    <button onClick={this.increment}>Increment</button>
                </p>
                <p>
                    <button onClick={this.decrement}>Decrement</button>
                </p>
            </div>
        );
    }
}

ReactDOM.render(<Hello />, document.getElementById("container"));

我希望这可以帮助您做出反应!让我知道您是否有任何问题:)