【问题标题】:ReactJS: Save input value on blur and not on every key strokeReactJS:将输入值保存在模糊上,而不是每次击键上
【发布时间】:2018-11-26 10:55:32
【问题描述】:

我创建了一个 React View,比如 MyView,它有 2 个文本输入,其初始值将由从数据库读取的父级传递。

我还希望将更改后的值保存回数据库。因此,视图也传递了相同的回调函数。

考虑到数据库保存操作很繁重,您不应该经常这样做。因此,我决定在输入框中收听onBlur 事件而不是onChange 事件,因为每次击键都会调用onChange

第一种方法:

class MyView extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            <div>
              <input type="url" value={this.props.values.A}
                   onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />
              <input type="url" value={this.props.values.B}
                   onBlur={(evt)=>{this.props.saveValue('B', evt.target.value)}} />         

              <button type="button" onClick={this.props.resetValues}>Reset</button>
            </div>
        );
     }
}

但是,这不起作用,因为React 强制一个受控输入(带有value 属性)始终伴随着onChange 侦听器。

第二种方法:

所以,我尝试将这些输入设为uncontrolled。也就是说,用defaultValue代替value属性。

<input type="url" defaultValue={this.props.values.A}
       onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />

但这在重置/清除按钮单击时也不起作用,虽然视图已重新渲染,但 defaultValue 在创建视图后不会更新。

第三种方法:

所以,我终于添加了一个 onChange 监听器,但没有操作。

<input type="url" value={this.props.values.A}
       onChange={()=>{console.log('do nothing')}
       onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />

同样,这不起作用,因为视图在调用 onChange 后重新渲染,并且由于值尚​​未反映在 props 中,value 似乎在每次击键时重置回初始值。

第四种方法:

最后我尝试的是在组件中维护一个state,并从状态中读取值,然后在每个onChange 上将值保存回状态。这在很大程度上是有效的,但是每当对 props 进行外部更改并且重新渲染视图时,state 不会更新。所以,我添加了一个getDerivedStateFromProps 函数来查看:

static getDerivedStateFromProps(props, state) {
    return props.values;
}

现在,这又不起作用了。原因是即使我暂时将值保存到状态并且状态已重置为道具中的初始值,也会调用此函数。

ReactJS 专家可以帮我解决我的用例吗?

【问题讨论】:

  • 作为第一个信息,onChange 是否有效?数据发生变化时是否将数据发送到数据库?

标签: javascript reactjs onblur react-props getderivedstatefromprops


【解决方案1】:

您仍然需要onChange 来帮助您设置两个 url 输入的状态。 onBlur 仅用于触发保存,它是用于不同目的的 2 个不同事件。

因为您的 A & B 值是从父组件传递下来的。 MyView 的父组件应该传递 this.state.values 和设置状态的函数。

如果所有内容都在单个组件中,请参阅此 sn-p。您应该能够将 handleChange 函数向上移动到其父组件。

class App extends React.Component {

  state = {
    values: {
      A: '',
      B: ''
    }
  }

  handleChange = e => {
    this.setState({
      values: {
      ...this.state.values,
      [e.target.name]: e.target.value
    })
  }

  handleBlur = e => {
    if (e.target.name === 'A') {
      alert(`Saving A: ${this.state.values.A}`)
    }

    if (e.target.name === 'B') {
      alert(`Saving B: ${this.state.values.B}`)
    }
  }

  render() {
    return (
      <div>
        <label>Value A</label>
        <input
          type="url"
          name="A"
          value={this.state.values.B}
          onChange={this.handleChange}
          onBlur={this.handleBlur}
        />
        <label>Value B</label>
        <input
          type="url"
          name="B"
          value={this.state.values.A}
          onChange={this.handleChange}
          onBlur={this.handleBlur}
        />
      </div>
    )
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('container')
);
<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>
<div id="container">
</div>

编辑:您的第四种方法应该适用于以下内容:

static getDerivedStateFromProps(props, state) {
  return { values: props.values }
}

constructor(props) {
  super(props)
  this.state = {
    values: props.values
  }
}

所以基本上this.state.values 是最终的真相来源。当用户键入内容时,您在此组件中 setState 并更改它。但是如果props.values 发生变化(来自外部源),getDerivedStateFromProps 将更新值状态。

【讨论】:

  • 谢谢@liren-yeo。我的用例是从this.props 读取初始值,但同时反映this.props 内部或外部发生的任何新变化。
  • @lazyvab 我可以知道是什么触发了this.props.values 上的更改吗?用户可以在 URL 输入中键入吗?
  • 您可以假设this.props.values 从 3 个位置更改 - 第一个是用户输入,第二个是单击重置按钮,第三个是来自不同服务的外部。但在外部更改的情况下,父容器负责重新渲染视图,例如:ReactDOM.render(React.createElement(MyView, {..props}
  • @lazyvab 当外部变化发生时,你还想立即触发保存吗?
  • 通过外部更改,我的意思是有一个并行组件已经在数据库中保存了值,我想在这个视图中反映这些更新。因此,无需进一步保存。只需使用新值查看渲染。
【解决方案2】:

按照 Liren Yeo 的解决方案的 cmets,我将在 componentDidUpdate 上处理 props-state 对账,在那里你会得到旧的 stateprops。通过这种方式,您可以确定this.props 的更新方式并采取相应措施。当props 中的值与stateoldProps 不匹配时,更新是外部的,您应该覆盖状态中未保存的更改。

代码应该是这样的

componentDidUpdate(prevProps) {

if (this.props.values !== prevProps.values && this.props.values !== this.state.values) {
  this.setState({values:this.props.values});
   }
}

如果你走这条路,你也可以留下输入uncontrolled并通过引用更新它的值。这解决了 controlled 输入的一些不可靠性问题,例如,当您键入十进制逗号时,type='number' 返回 undefined 作为其值。您仍然需要存储值onChange,但只需将其保存onBlur 并在componentDidUpdate 中处理 state-prop-dom 协调

【讨论】:

  • 太棒了!谢谢@diego。这就是我一直在寻找的。 :)
【解决方案3】:

所以考虑到 onChange 有效,我建议你看看这个:

https://schier.co/blog/2014/12/08/wait-for-user-to-stop-typing-using-javascript.html 导航到标题:等待键入停止

希望它能以某种方式引导您实现您想要实现的目标。

【讨论】:

  • 感谢@craws 分享。是的,这可能是我不使用onBlur 而是使用基于时间的onChange 的一种方法。但是对于这个特定的用例,我不希望它像在我的实际代码中那样基于时间,还有其他输入以及 textarea 将包含长文本,我无法承受触发多个数据库保存请求。
  • “Jenky”解决方案是为每个输入字段设置状态并在 onChange 之后触发 setState。在所有状态都有效/notNull 后允许数据库请求。
  • 你能帮我一些代码 sn-p 或示例吗?您是否要求触发相当于重新渲染的虚拟setState?如果你明白我的意思,基本问题是值是从 props 读取的,但在 state 中更新,而 props 在稍后阶段更新。
猜你喜欢
  • 2021-08-02
  • 2020-12-24
  • 2015-01-29
  • 1970-01-01
  • 1970-01-01
  • 2012-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多