setState 有条件地做出反应

Posted

技术标签:

【中文标题】setState 有条件地做出反应【英文标题】:setState conditionally in react 【发布时间】:2019-03-20 16:41:15 【问题描述】:

我知道有些反应,但我陷入了一个奇怪的境地。

我有两个输入和一个按钮, 当两个输入都不为空时,应启用该按钮。 所以,我为每个输入值使用了一个 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 查看之前的状态,而 enable 总是落后一步,所以如果我在第一个输入 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);

  

有更好的解决方案吗?

【问题讨论】:

【参考方案1】:

有更好的解决方案吗?

有 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:根据titletime 计算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>

【讨论】:

【参考方案2】:

首先,启用状态不需要维护,因为它可以从其他状态值派生出来,可以直接在渲染中完成

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

【讨论】:

【参考方案3】:

@Shubham Khatri 有正确的答案(您实际上并不需要该州的 enabled 属性),但重要的是要了解该州发生了什么以及为什么您使用 setTimeouthack 有效(在我看来很幸运)。

来自official docs

setState() 并不总是立即更新组件。有可能 批处理或推迟更新。这使得阅读 this.state 就在调用 setState() 之后,这是一个潜在的陷阱。相反,使用 componentDidUpdate 或 setState 回调(setState(updater, 回调)),其中任何一个都保证在更新后触发 已申请。

另外,state updates may be asynchronous:

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

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

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

【讨论】:

【参考方案4】:

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

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

【讨论】:

以上是关于setState 有条件地做出反应的主要内容,如果未能解决你的问题,请参考以下文章

卸载组件时对 setState 做出反应警告

对 useEffect 挂钩内的多个 setState() 调用做出反应批量更新

如何切换 css 类以与 setState Hook 做出反应

如何在反应中每 5 秒加 1 和 setState

在 componentDidMount 上反应本机 setState 不起作用

在solidity合约实例承诺中反应setState