setState有条件地反应

时间:2018-10-16 07:55:03

标签: javascript reactjs setstate

我知道有些反应,但是我陷入了一种奇怪的情况。

我有两个输入和一个按钮, 当两个输入都不为空时,应启用该按钮。 因此,我为每个输入值都使用了state属性,并且还使用了一个属性告诉我两个输入是否都具有值:

this.state = {
  title: '',
  time :'', 
  enabled : false
}

对于每个输入,我都有一个onChange来相应地设置State:

<input type="text" id="time" name="time" onChange={this.onChange.bind(this)} value={this.state.time}></input>
<input type="text" id="title" name="title" onChange={this.onChange.bind(this)} value={this.state.title}></input>

而onChange就是这样

onChange(e){
    this.setState({
        [e.target.id] : e.target.value,
        enabled : ( (this.state.title==='' || this.state.time==='' ) ? false : true)
      });
  }

问题在于setState会查看先前的状态,而启用状态始终落后一步,因此,如果我先输入X,然后输入Y,则启用状态仍然为false。

我设法通过使用setTimeout并在其中添加第二行来解决它,但对我来说似乎错了。

  onChange(e){

    this.setState({
        [e.target.id] : e.target.value,
    });

    setTimeout(() => {
      this.setState({
        enabled : ( (this.state.title==='' || this.state.time==='' ) ? false : true)
    });
    }, 0);

  }

有更好的解决方案吗?

4 个答案:

答案 0 :(得分:6)

首先,您无需维护已启用状态,因为它可以从其他状态值派生并可以直接在渲染中完成

onChange(e){
    this.setState({
        [e.target.id] : e.target.value,
    });
}
render() {
   const enabled = this.state.title !== "" && this.state.time !== "";
   return (
       <div>
          <input type="text" id="time" name="time" onChange={this.onChange.bind(this)} value={this.state.time}></input>
          <input type="text" id="title" name="title" onChange={this.onChange.bind(this)} value={this.state.title}></input>
          <button disabled={!enabled}>Button</button>
       </div>
   )
}

答案 1 :(得分:4)

  

有更好的解决方案吗?

有3种解决方案可帮助您实现这一目标,只需选择一种


解决方案1:使用componentDidUpdate()

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
      time: "",
      enabled: false
    };
  }
  onChange(e) {
    this.setState({
      [e.target.id]: e.target.value
    });
  }
   componentDidUpdate(prevProps, prevState) {
     if (
       prevState.time !== this.state.time ||
       prevState.title !== this.state.title
     ) {
       if (this.state.title && this.state.time) {
         this.setState({ enabled: true });
       } else {
         this.setState({ enabled: false });
       }
     }
   }
  render() {
    return (
      <React.Fragment>
        <input
          type="text"
          id="time"
          name="time"
          onChange={this.onChange.bind(this)}
          value={this.state.time}
        />
        <input
          type="text"
          id="title"
          name="title"
          onChange={this.onChange.bind(this)}
          value={this.state.title}
        />
        <button disabled={!this.state.enabled}>Button</button>
      </React.Fragment>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>


解决方案2:使用setState回调

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
      time: "",
      enabled: false
    };
  }
  onChange(e) {
     this.setState({[e.target.id]: e.target.value},
       () => {
         if (this.state.title && this.state.time) {
           this.setState({ enabled: true });
         } else {
           this.setState({ enabled: false });
         }
       }
     );
  }
  render() {
    return (
      <React.Fragment>
        <input
          type="text"
          id="time"
          name="time"
          onChange={this.onChange.bind(this)}
          value={this.state.time}
        />
        <input
          type="text"
          id="title"
          name="title"
          onChange={this.onChange.bind(this)}
          value={this.state.title}
        />
        <button disabled={!this.state.enabled}>Button</button>
      </React.Fragment>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>


解决方案3:基于enabledtitle计算time

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
      time: ""
    };
  }
  onChange(e) {
    this.setState({
      [e.target.id]: e.target.value
    });
  }
  render() {
    const enabled = this.state.time && this.state.title;
    return (
      <React.Fragment>
        <input
          type="text"
          id="time"
          name="time"
          onChange={this.onChange.bind(this)}
          value={this.state.time}
        />
        <input
          type="text"
          id="title"
          name="title"
          onChange={this.onChange.bind(this)}
          value={this.state.title}
        />
        <button disabled={!enabled}>Button</button>
      </React.Fragment>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>

答案 2 :(得分:1)

如果要在当前更改发生后设置状态,则可以将第二个参数(回调)传递给setState,这将在状态已设置并在其中再次设置状态后发生。这将导致额外的重新渲染,但这似乎正是您要的。这只是为了让您知道在当前状态更改发生后如何做...

但是,我会同意Shubham Khatri的回答。那将更加合乎逻辑。

答案 3 :(得分:1)

@Shubham Khatri给出了正确的答案(您在该州实际上不需要enabled属性),但重要的是要了解该州的情况以及您使用hack的原因setTimeout工作(幸运的是,我认为如此)。

official docs

  

setState()并不总是立即更新组件。它可能   批处理或将更新推迟到以后。这使得阅读this.state   在调用setState()之后立即发生潜在的陷阱。相反,使用   componentDidUpdate或setState回调(setState(updater,   回调)),保证在更新后都会触发   已应用。

另外,state updates may be asynchronous

  

React可以将多个setState()调用批处理为单个更新,以用于   性能。

     

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

我希望这可以澄清您的问题。