在React中有条件地设置状态

10

我了解一些React,但目前卡在一个奇怪的情况下。

我有两个输入框和一个按钮, 当两个输入框都有值时,按钮应该启用。 因此,我为每个输入框的值使用了一个状态属性,并且还使用了一个属性来告诉我这两个输入框是否都有值:

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

此外,我为每个输入框添加了onChange事件来相应地设置状态:

<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查看先前的状态,而enabled始终落后一步,因此如果我在第一行输入X,在第二行输入Y,enabled仍将为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个回答

15

还有更好的解决方案吗?

您有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:基于标题和时间计算enabled


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>


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
如果您想在当前更改发生后设置状态,可以将第二个参数(回调函数)传递给setState,该函数将在状态已设置并再次设置状态之后发生。这将导致额外的重新渲染,但似乎这就是你所要求的。这只是让您知道如何在当前状态更改后执行某些操作...但我会选择Shubham Khatri的答案。那将更加合理。

1

@Shubham Khatri的答案是正确的(你不需要在state中使用enabled属性),但重要的是要理解状态的情况以及为什么你使用setTimeouthack起作用(在我看来很幸运)。

官方文档中可以了解到:

setState()并不总是立即更新组件。它可能会批处理或延迟更新,这使得在调用setState()后立即读取this.state成为潜在的陷阱。相反,使用componentDidUpdate或setState回调(setState(updater,callback)),两者都保证在更新应用后触发。

此外,状态更新可能是异步的

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

因为this.props和this.state可能会异步更新,所以不应该依赖它们的值来计算下一个状态。

希望这能解决您的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接