【问题标题】:Stop cursor jumping when formatting number in React在 React 中格式化数字时停止光标跳跃
【发布时间】:2016-06-02 19:40:17
【问题描述】:

我的 react 组件上有一个输入字段,显示项目的行价(两位小数,千位分隔符)。我希望在组件首次呈现时显示为货币格式的值,并在用户在字段中键入时保持货币格式。

目前我的组件中有以下代码:

var React = require('react');
import accounting from 'accounting';

MoneyInput = React.createClass({
    propTypes: {
        name: React.PropTypes.string.isRequired,
        onChange: React.PropTypes.func.isRequired,
        value: React.PropTypes.number,
        error: React.PropTypes.string,
    },

    onChange(event) {
        // get rid of any money formatting
        event.target.value = accounting.unformat(event.target.value);

        // pass the value on
        this.props.onChange(event);
    },

    getValue() {
        return accounting.formatNumber(this.props.value, 2)
    },

    render() {

        return (
                <div className="field">
                    <input type="text"
                           name={this.props.name}
                           className="form-control"
                           value={this.getValue()}
                           onChange={this.onChange} />
                    <div className="input">{this.props.error}</div>
                </div>
        );
    }
});

module.exports = MoneyInput;

该代码显示的数据格式正确,但每次我输入一个值时,光标都会跳到数字的末尾。

我理解为什么会发生这种情况(我认为),并且我在这里阅读了几个与 JavaScript 中不丢失光标位置相关的问题(例如herehere)。

我的问题是在 React 中处理这个问题的最佳方法是什么?

我认为理想情况下我不想将光标位置存储在状态中(例如,我希望这些为Presentation Components in Dan Abramov syntax)那么还有其他方法吗?

【问题讨论】:

    标签: reactjs


    【解决方案1】:

    我有同样的问题,对我来说这是因为我使用的是 Redux。 这篇文章解释的真好。

    https://medium.com/@alutom/in-order-to-understand-what-is-really-happening-it-might-be-helpful-to-artificially-increase-the-e64ce17b70a6

    浏览器管理输入光标,当它检测到新文本时,它会自动将光标踢到底,我的猜测是状态以触发浏览器行为的方式更新文本字段。

    【讨论】:

      【解决方案2】:

      在正在格式化的 React 的 &lt;input /&gt; 字段中丢失光标/插入符号位置的简单解决方案是自己管理位置:

          onChange(event) {
      
            const caret = event.target.selectionStart
            const element = event.target
            window.requestAnimationFrame(() => {
              element.selectionStart = caret
              element.selectionEnd = caret
            })
      
            // your code
          }
      

      您的光标位置重置的原因是因为 React 不知道您正在执行什么样的更改(如果您将文本完全更改为更短或更长的内容怎么办?)并且您现在负责控制插入符号的位置。

      示例:在我的一个输入文本字段中,我将三个点 (...) 自动替换为省略号。前者是三个字符长的字符串,而后者只是一个。尽管 React 会知道最终结果会是什么样子,但它不再知道将光标放在哪里,因为没有一个明确的逻辑答案。

      【讨论】:

      • 哇,这是一个如此简单的解决方案。干得好,它就像魔术一样!
      • 这是一个很棒的提示!我发现它无法处理一种极端情况——如果用户键入足够多的字符以足够快地触发更改,光标仍然可以跳到末尾。我怀疑这是因为 React 在动画帧之间不止一次调用 onChange 处理程序,第二次调用看到第一次调用留下的文本结束状态,然后使用该值调度动画帧。我必须真正敲击按键才能触发它,所以我不打算尝试修复它。
      • 超级简单的解决方案。非常感谢。
      【解决方案3】:
          set value(val) { // Set by external method
              if(val != this.state.value) {
                  this.setState({value: val, randKey: this.getKey()});
              }
          }
      
          getKey() {
              return 'input-key' + parseInt(Math.random() * 100000);
          }
      
          onBlur(e) {
              this.state.value = e.target.value; // Set by user
              if(this.props.blur) this.props.blur(e);
          }
      
          render() {
              return(
                  <input className="te-input" type="text" defaultValue={this.state.value} onBlur={this.onBlur} key={this.state.randKey} />
              )
          }
      

      如果您需要一个无需移动光标即可编辑并且也可以从外部设置的输入,您应该使用默认值并在外部更改值时使用不同的键呈现输入。

      【讨论】:

      • 改变key好像让元素改变后失去焦点。
      【解决方案4】:

      我认为我们可以在 DOM 级别上做到这一点。 我所做的是向输入字段提供了 id。 输入元素中有一个属性 selectionEnd。

      你可以做的只是在 normalize 函数中获取输入元素并获取它的 selectionEnd 属性

          const selectionEnd=inputElm &&inputElem.selectionEnd?inputElm.selectionEnd:0; 
      

      因为问题仅在我们按下后退按钮时出现。我们添加一个条件如下

          if(result.length<previousValue.length){
             inputElm.setSelectionRange(selectionEnd, selectionEnd)
          }
      

      但是由于这个值会在我们从函数返回之后被设置,并且返回的值会再次被设置为将光标推到末尾,所以我们返回只需添加一个settimeout。

          setTimeout(() => {   
              if(result.length<previousValue.length){
                  inputElm.setSelectionRange(selectionEnd, selectionEnd)
              }
          }, 50);
      

      我今天刚遇到这个问题,似乎超时 50 次就足够了。

      如果你想处理用户在中间添加数据的情况。以下代码似乎运行良好。

          if(result.length<previousValue.length){
               inputElm.setSelectionRange(selectionEnd, selectionEnd)
          } else if(selectionEnd!==result.length){ // result being the computed value
               inputElm.setSelectionRange(selectionEnd, selectionEnd)
          }
      

      【讨论】:

        【解决方案5】:
        onKeyUp(ev) {
          const cardNumber = "8318 3712 31"
          const selectionStart = ev.target.selectionStart; // save the cursor position before cursor jump
          this.setState({ cardNumber, }, () => {
            ev.target.setSelectionRange(selectionStart, selectionStart); // set the cursor position on setState callback handler
          });
        }
        

        【讨论】:

        • 这给了我TypeError: ev.target is nullsetState之后的部分
        • 通过添加const targetElement = ev.target 并将setSelectionRange 部分更改为targetElement.setSelectionRange(selectionStart, selectionStart); 使其工作
        【解决方案6】:

        您好,在 github.com https://github.com/facebook/react/issues/955 中已经对此问题进行了讨论,请参阅 11 月 30 日的评论

        您可能还想在输入组件上使用 setstate。

        【讨论】:

        • 哦,这很有趣,谢谢...关于“您可能希望在输入组件上使用 setstate” - 您可以扩展一下吗?我是 React 新手,并且一直在可能的错误理解下工作,您应该尽可能避免 state?
        • 嘿汤姆,是的,所以状态可以很好地用于输入组件,或者说一个组件可以在一次显示 3 个项目的数组中进行制表符。想想我真的需要我的应用程序的这种状态是全球性的吗?大多数东西都是全球性的,并通过您的商店。
        猜你喜欢
        • 1970-01-01
        • 2021-12-21
        • 1970-01-01
        • 1970-01-01
        • 2021-12-07
        • 1970-01-01
        • 1970-01-01
        • 2022-11-14
        • 1970-01-01
        相关资源
        最近更新 更多