反应中没有构造函数的初始化状态

Posted

技术标签:

【中文标题】反应中没有构造函数的初始化状态【英文标题】:init state without constructor in react 【发布时间】:2017-08-17 01:35:52 【问题描述】:
import React,  Component  from 'react';

class Counter extends Component 
  state =  value: 0 ;

  increment = () => 
    this.setState(prevState => (
      value: prevState.value + 1
    ));
  ;

  decrement = () => 
    this.setState(prevState => (
      value: prevState.value - 1
    ));
  ;

  render() 
    return (
      <div>
        this.state.value
        <button onClick=this.increment>+</button>
        <button onClick=this.decrement>-</button>
      </div>
    )
  

通常我看到的是,如果他使用 es6 类,人们会在构造函数中执行 this.state。如果他不是,他可能会使用 getinitialstate 函数设置状态。但是上面的代码(是的,它是一个工作代码),两者都没有使用。我有 2 个问题,这里的状态是什么?那是局部变量吗?如果是,为什么它没有const? prevState 来自哪里?为什么在 setState 中使用箭头函数?做this.setState(value:'something')不容易吗?

【问题讨论】:

【参考方案1】:

我有两个问题,这里的state 是什么?

实例属性,例如在构造函数中设置this.state = value: 0;。它使用的是目前处于第 2 阶段的 Public Class Fields proposal。(incrementdecrement 也是如此,它们是实例字段,其值是箭头函数,因此它们关闭 this。)

这是一个局部变量吗?

没有。

prevState 来自哪里?为什么在 setState 中使用箭头函数?做 this.setState(value:'something') 不是很容易吗?

来自the documentation:

React 可以将多个 setState() 调用批处理到单个更新中以提高性能。

由于this.propsthis.state 可能会异步更新,因此您不应依赖它们的值来计算下一个状态。

例如,这段代码可能无法更新计数器:

// Wrong
this.setState(
  counter: this.state.counter + this.props.increment,
);

要修复它,请使用setState() 的第二种形式,它接受函数而不是对象。该函数将接收先前的状态作为第一个参数,并将应用更新时的道具作为第二个参数:

// Correct
this.setState((prevState, props) => (
  counter: prevState.counter + props.increment
));

...这正是引用的代码正在做的事情。这是错误的:

// Wrong
increment = () => 
  this.setState(
    value: this.state.value + 1
  );
;

...因为它依赖于this.state 的状态,上面告诉我们不要这样做;所以引用的代码会这样做:

increment = () => 
  this.setState(prevState => (
    value: prevState.value + 1
  ));
;

这里证明了 React 可能以不明显的方式批量调用,以及为什么我们需要使用 setState 的回调版本:这里,我们有 incrementdecrement 被调用两次 每次点击而不是一次(一次按按钮,一次按包含按钮的跨度)。单击+ 一次 应该将计数器增加到2,因为increment 被调用了两次。但是因为我们没有使用 setState 的函数回调版本,所以它没有:其中一个对 increment 的调用变成了无操作,因为我们使用了一个陈旧的 this.state.value 值:

class Counter extends React.Component 
  state =  value: 0 ;

  increment = () => 
    /*
    this.setState(prevState => (
      value: prevState.value + 1
    ));
    */
    console.log("increment called, this.state.value = " + this.state.value);
    this.setState(
      value: this.state.value + 1
    );
  ;
  
  fooup = () => 
    this.increment();
  ;

  decrement = () => 
    /*
    this.setState(prevState => (
      value: prevState.value - 1
    ));
    */
    console.log("decrement called, this.state.value = " + this.state.value);
    this.setState(
      value: this.state.value - 1
    );
  ;
  
  foodown = () => 
    this.decrement();
  ;

  render() 
    return (
      <div>
        this.state.value
        <span onClick=this.fooup>
          <button onClick=this.increment>+</button>
        </span>
        <span onClick=this.foodown>
          <button onClick=this.decrement>-</button>
        </span>
      </div>
    )
  


ReactDOM.render(
  <Counter />,
  document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

使用函数回调,它可以正常工作(没有对increment 的调用变为无操作):

class Counter extends React.Component 
  state =  value: 0 ;

  increment = () => 
    this.setState(prevState => 
      console.log("Incrementing, prevState.value = " + prevState.value);
      return 
        value: prevState.value + 1
      ;
    );
  ;
  
  fooup = () => 
    this.increment();
  ;

  decrement = () => 
    this.setState(prevState => 
      console.log("Decrementing, prevState.value = " + prevState.value);
      return 
        value: prevState.value - 1
      ;
    );
  ;
  
  foodown = () => 
    this.decrement();
  ;

  render() 
    return (
      <div>
        this.state.value
        <span onClick=this.fooup>
          <button onClick=this.increment>+</button>
        </span>
        <span onClick=this.foodown>
          <button onClick=this.decrement>-</button>
        </span>
      </div>
    )
  


ReactDOM.render(
  <Counter />,
  document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

在这种情况下,当然,我们可以查看render 方法并说“嘿,increment 在单击期间会被调用两次,我最好使用回调版本的setState。”但是,与其假设在确定下一个状态时使用this.state 是安全的,不如 假设这一点。在一个复杂的组件中,使用 mutator 方法很容易以一种 mutator 方法的作者可能没有想到的方式。因此,React 作者的声明:

因为this.propsthis.state 可能会异步更新,您不应依赖它们的值来计算下一个状态

【讨论】:

我用了很多这个this.setState( value: this.state.value + 1 ); 至少它有效,没有错。我没有遇到任何问题。 @JennyMok:“这没什么错”——是的,有。仅仅因为它还没有咬你,那并不意味着它没有任何问题。请参阅文档。 React 开发人员可能比我们更了解 React,并且不只是随心所欲地添加这种接受函数的表单。【参考方案2】:

关于问题 2,请参考 Dan 的精彩回答:Do I need to use setState(function) overload in this case?


    不,它不是局部变量。和在构造函数中声明this.state一样。

    是的,在这种情况下你可以使用this.setState( value: this.state.value + 1 ),结果是一样的。

但请注意,通过使用函数式setState,您可以获得一些好处:

    如果在外面声明 setState 函数可以重复使用:

    const increment = prevState => (
      value: prevState.value + 1
    )
    

    现在如果你有几个组件需要使用这个功能,你可以在任何地方导入和重用逻辑。

    this.setState(increment)
    

    React 压缩了几个 setState 并批量执行它们。这可能会导致一些意外行为。请看下面的例子:

    http://codepen.io/CodinCat/pen/pebLaZ

    add3 () 
      this.setState( count: this.state.count + 1 )
      this.setState( count: this.state.count + 1 )
      this.setState( count: this.state.count + 1 )
    
    

    执行这个函数count只会加1

    如果你使用函数式 setState:

    http://codepen.io/CodinCat/pen/dvXmwX

    const add = state => ( count: state.count + 1 )
    this.setState(add)
    this.setState(add)
    this.setState(add)
    

    count 会像你预期的那样 +3。

您可以在此处查看文档:https://facebook.github.io/react/docs/react-component.html#setstate

【讨论】:

“是的,在这种情况下你可以使用this.setState( value: this.state.value + 1 ),结果是一样的。” 完全不正确,正如我在回答中指出的文档(你在你的结尾)很清楚地说。说“你可以使用 [it]”是完全错误的。 我是说OP的代码的increment函数,只有1个setState语句,就可以了 不,不是。从文档中:React 可以将多个 setState() 调用批处理到单个更新中以提高性能...”(我的重点)。因此,如果有多个对 increment 的调用,React 认为它可以批处理(即使从代码中看不出它可以批处理),它会;结果将是错误的。如果只是你故意反复调用setState,你自己就能搞定。 当你在单个处理程序中多次调用 setState 时,React 只批处理 setState,所以如果你的函数只包含 1 个 setState 是安全的 请参阅我答案的结尾,以证明这不是真的。批处理可以以不明显的方式发生;在 mutator 中编写依赖于 this.state 的代码是一种糟糕的做法。同样,React 开发人员创建了 setState 的函数版本出于某种原因【参考方案3】:

问题 1 的答案:

是的,你可以在没有构造函数的情况下为 React 类组件初始化状态:

class Counter extends Component 
  state =  value: 0 ;

  handleIncrement = () => 
    this.setState(prevState => (
      value: prevState.value + 1
    ));
  ;

  handleDecrement = () => 
    this.setState(prevState => (
      value: prevState.value - 1
    ));
  ;

  render() 
    return (
      <div>
        this.state.value

        <button onClick=this.handleIncrement>+</button>
        <button onClick=this.handleDecrement>-</button>
      </div>
    )
  

它使用类字段声明。您可以通过here 查看示例应用程序。你也可以在 React 函数组件中使用 state(没有构造函数),但是使用高阶组件或 render prop 组件。您可以通过here了解更多信息。

问题 2 的答案:在 React 中使用 this.setState 更新本地状态有两种方法。您可以使用对象或函数。如果您不依赖组件的道具或状态来更新其状态,那么该对象就可以了。如果你依赖 props 或 state,你应该使用这个函数,因为它在执行时总是有最新的 props 和 state。所有这一切都是因为this.setState() 是异步执行的,因此在使用对象而不是函数更新本地状态时,您可以使用陈旧状态或道具。你可以阅读更多关于它的信息here。

【讨论】:

对于您在组件的本地空间中初始化状态的方式...就在此处...,您能否获得对 props 的引用来初始化状态值,例如 this.props.value 而不是比state = value: 0 即没有我想要的构造函数,但是:state = value: this.props.value - 可能不可能吧?

以上是关于反应中没有构造函数的初始化状态的主要内容,如果未能解决你的问题,请参考以下文章

使用构造函数与 state = 在反应组件中声明状态有啥区别?

构造函数

Flutter Bloc 对 Blocs 初始状态的反应

构造函数作用是啥

同一个MFC工程中的两个普通类,为啥一个构造函数打点有反应,一个构造函数打点没反应?是因为设置问题吗

复制构造函数的运用