【问题标题】: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'})不容易吗?

【问题讨论】:

    标签: javascript reactjs


    【解决方案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?


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

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

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

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

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

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

      this.setState(increment)
      
    2. 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 } - 可能不可能吧?
    猜你喜欢
    • 1970-01-01
    • 2016-10-13
    • 2021-04-29
    • 1970-01-01
    • 2021-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多