setState 意外更新非状态属性

Posted

技术标签:

【中文标题】setState 意外更新非状态属性【英文标题】:setState unexpectedly updates non-state properties 【发布时间】:2019-01-17 03:31:05 【问题描述】:

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

所以我们都知道,如果我们想在 React 中渲染一个响应式值,我们必须将值放入 state 中,并使用 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递增,结果也不会反应式显示在渲染():

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

对吗?基本的东西。

但是,当我尝试摆弄这段代码时,发生了一个有趣的事情。我们将 counter 保留为字段属性,其他所有内容均完好无损。唯一的区别是,在incrementButtonListener 中,我要在someStateProperty 上添加一个setState

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() 位以查看区别。

【问题讨论】:

***.com/questions/24718709/… 坦率地说,我没有看到 unexpected 行为。 React 只是确保(默认情况下)在每次 setState 调用之后调用 render。如果您在调用setState 的同时更改成员属性,那么render 输出更新 属性的值(因为它在setState 之前更改)就不足为奇了。 您可以使用 this.forceUpdate(); 强制渲染 - 无需通过 setState 调用它。这是(嗯?)已知的可能性,但被视为反模式。 【参考方案1】:

因为您没有拦截状态更改,所以它会导致重新渲染,这反过来又会导致您使用递增的实例属性。这是设计使然。对 React 状态的任何更改都会导致组件重新渲染,除非您使用生命周期挂钩来控制是否应该发生这种情况。

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

【讨论】:

【参考方案2】:

React 使用 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/

【讨论】:

【参考方案3】:

您可以在 forcrUpdate 上替换 setState。 如果你的团队使用装饰风格的代码,那么永远不要使用 setState() 或 forceUpdate()。 React 的作者建议制作像纯函数一样的组件。

【讨论】:

【参考方案4】:
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"));

我希望这可以帮助您做出反应!如果您有任何问题,请告诉我:)

【讨论】:

以上是关于setState 意外更新非状态属性的主要内容,如果未能解决你的问题,请参考以下文章

React Stateful Class Component:使用“this.setState”更新状态的属性,不起作用。没有错误。状态不变

反应:在构造函数上绑定方法时,在 setState 内实现计时器,状态属性未定义

ReactJS TypeError:无法读取未定义的属性“setState”[重复]

意外状态更新导致“useState”函数

ES6/React:为啥我的三重嵌套 setState 更新不起作用?

ZF_react 类组件状态的使用 实现组件的更新,合成事件,批量更新