在 React 中格式化数字时停止光标跳转

IT技术 reactjs
2021-04-04 19:41:47

我的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 中不丢失光标位置相关的问题(例如这里这里)。

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

我认为理想情况下我不想将光标位置存储在状态中(例如,我希望这些是Dan Abramov 语法中的演示组件)那么还有另一种方法吗?

6个回答

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

    onChange(event) {

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

      // your code
    }

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

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

哇,这是一个如此简单的解决方案。干得好,它就像魔法一样!
2021-06-05 19:41:47
超级简单的解决方案。非常感谢。
2021-06-17 19:41:47
这是一个很好的提示!我发现有一个它无法处理的边缘情况——如果用户输入足够多的字符来足够快地触发更改,光标仍然可以跳到最后。我怀疑这是因为 React 在动画帧之间多次调用 onChange 处理程序,第二次调用看到第一个调用留下的文本结束状态,然后使用该值安排动画帧。我必须真正敲击按键才能触发此问题,因此我不打算尝试修复它。
2021-06-18 19:41:47

嗨,关于 github.com https://github.com/facebook/react/issues/955 中的问题已对此进行了讨论,参阅 11 月 30 日的评论

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

哦,这很有趣,谢谢... Re'您可能想在输入组件上使用 setstate' - 你能扩展一下吗?我是 React 的新手,并且一直在可能错误的理解下工作,即您应该尽可能避免状态?
2021-06-15 19:41:47
嘿汤姆,是的,所以 state 可以用于输入组件,或者说一个组件可以一次显示 3 个项目的数组。想想我真的需要我的应用程序的这种状态是全局的吗?大多数东西将是全球性的,并通过您的商店。
2021-06-15 19:41:47
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 null之后的部分setState
2021-06-18 19:41:47
通过将零件添加const targetElement = ev.target和更改setSelectionRangetargetElement.setSelectionRange(selectionStart, selectionStart);
2021-06-21 19:41:47

我认为我们可以在 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)
    }
    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} />
        )
    }

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

更改键似乎让元素在更改后失去焦点。
2021-06-01 19:41:47