React - 如何将电话号码格式化为用户类型

Posted

技术标签:

【中文标题】React - 如何将电话号码格式化为用户类型【英文标题】:React - How to format phone number as user types 【发布时间】:2019-09-23 01:52:19 【问题描述】:

我需要将 10 位字符串格式化为以下格式:'(123) 456-7890'。 但是,我需要在用户键入时发生这种情况。因此,如果用户仅输入了 3 位数字,则输入应显示:'(123)'。 如果他们输入了 5 位数字,则输入应显示:'(123) 45'

使用我当前的代码,格式仅在输入第 10 个字符后进行。我希望它从第三个字符开始格式化。

const phoneRegex = /^\(?([0-9]3)\)?[-. ]?([0-9]3)[-. ]?([0-9]4)$/

const handleInput = (value) => 
  return (
    value.replace(phoneRegex, '($1) $2-$3')
  )


class FindASubscriber extends React.Component 
  constructor(props) 
    super(props)

    this.state = 
      value: ''
    
  

  render() 
    const  label, placeholder, feedback  = this.props
    const  value = this.state
    return (
      <div className="find__a__subscriber">
        <FlexGrid>
          <FlexGrid.Col>
            <FlexGrid.Row>
              <Input
                feedback=feedback
                label=label
                type="text"
                pattern="[0-9]*"
                placeholder=placeholder
                value=handleInput(value)
                maxLength="10"
                onChange=
                 (event) => this.setState(value: event.target.value)
                
              />
            </FlexGrid.Row>
          </FlexGrid.Col>
        </FlexGrid>
      </div>
    )
  
```

【问题讨论】:

【参考方案1】:

你可以像这样normalizeinput

value 相对于event.target.value 是最新的 previousValue 已经过验证并设置为 state

这是为了防止无效字符更新输入,并将输入限制为 10 个数字。

点击下面的Run code snippet 按钮查看工作示例。


const normalizeInput = (value, previousValue) => 
  // return nothing if no value
  if (!value) return value; 

  // only allows 0-9 inputs
  const currentValue = value.replace(/[^\d]/g, '');
  const cvLength = currentValue.length; 

  if (!previousValue || value.length > previousValue.length) 

    // returns: "x", "xx", "xxx"
    if (cvLength < 4) return currentValue; 

    // returns: "(xxx)", "(xxx) x", "(xxx) xx", "(xxx) xxx",
    if (cvLength < 7) return `($currentValue.slice(0, 3)) $currentValue.slice(3)`; 

    // returns: "(xxx) xxx-", (xxx) xxx-x", "(xxx) xxx-xx", "(xxx) xxx-xxx", "(xxx) xxx-xxxx"
    return `($currentValue.slice(0, 3)) $currentValue.slice(3, 6)-$currentValue.slice(6, 10)`; 
  
;

const normalizeInput = (value, previousValue) => 
  if (!value) return value;
  const currentValue = value.replace(/[^\d]/g, '');
  const cvLength = currentValue.length;
  
  if (!previousValue || value.length > previousValue.length) 
    if (cvLength < 4) return currentValue;
    if (cvLength < 7) return `($currentValue.slice(0, 3)) $currentValue.slice(3)`;
    return `($currentValue.slice(0, 3)) $currentValue.slice(3, 6)-$currentValue.slice(6, 10)`;
  
;

const validateInput = value => 
  let error = ""
  
  if (!value) error = "Required!"
  else if (value.length !== 14) error = "Invalid phone format. ex: (555) 555-5555";
  
  return error;
;
    
class Form extends React.Component 
  constructor() 
    super();
    
    this.state =  phone: "", error: "" ;
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleReset = this.handleReset.bind(this);
  
  
  handleChange( target:  value  )    
    this.setState(prevState=> ( phone: normalizeInput(value, prevState.phone) ));
  ;
  
  handleSubmit(e) 
    e.preventDefault();
    const error = validateInput(this.state.phone);
    
    this.setState( error , () => 
       if(!error) 
         setTimeout(() => 
           alert(JSON.stringify(this.state, null, 4));
         , 300)
       
    );
  
  
  handleReset() 
     this.setState( phone: "", error: "" );
  ;
  
  render() 
    return(
      <form className="form" onSubmit=this.handleSubmit>
        <div className="input-container">
          <p className="label">Phone:</p>
          <input
            className="input"
            type="text"
            name="phone"
            placeholder="(xxx) xxx-xxxx"
            value=this.state.phone
            onChange=this.handleChange
          />
          this.state.error && <p className="error">this.state.error</p>
        </div>
        <div className="btn-container">
          <button 
             className="btn danger"
             type="button"
             onClick=this.handleReset
           >
            Reset
          </button>
          <button className="btn primary" type="submit">Submit</button>
        </div>
      </form>
    );
  


ReactDOM.render(
  <Form />,
  document.getElementById('root')
);
html 
  font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
  font-size: 16px;
  font-weight: 400;
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
  background: #fff;
  color: #666;


.btn 
  color: #fff;
  border: 1px solid transparent;
  margin: 0 10px;
  cursor: pointer;
  text-align: center;
  box-sizing: border-box;
  padding: 0 30px;
  vertical-align: middle;
  font-size: .875rem;
  line-height: 38px;
  text-align: center;
  text-decoration: none;
  text-transform: uppercase;
  transition: .1s ease-in-out;
  transition-property: color,background-color,border-color;


.btn:focus 
  outline: 0;


.btn-container 
  text-align: center;
  margin-top: 10px;


.form 
  width: 550px;
  margin: 0 auto;


.danger 
  background-color: #f0506e;
  color: #fff;
  border: 1px solid transparent;

 
.danger:hover 
  background-color: #ee395b;
  color: #fff;


.error 
  margin: 0;
  margin-top: -20px;
  padding-left: 26%;
  color: red;
  text-align: left;


.input 
  display: inline-block;
  height: 40px;
  font-size: 16px;
  width: 70%;
  padding: 0 10px;
  background: #fff;
  color: #666;
  border: 1px solid #e5e5e5;
  transition: .2s ease-in-out;
  transition-property: color,background-color,border;
 

.input-container 
  width: 100%;
  height: 60px;
  margin-bottom: 20px;
  display: inline-block;


.label 
  width: 25%;
  padding-top: 8px;
  display: inline-block;
  text-align: center;
  text-transform: uppercase;
  font-weight: bold;
  height: 34px;
  border-top-left-radius: 4px;
  border-bottom-left-radius: 4px;
  background: rgb(238, 238, 238);


.primary 
  background-color: #1e87f0;


.primary:hover 
  background-color: #0f7ae5;
  color: #fff;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>


<div id='root'>
</div>

或者...有 3 个单独的输入并在完成后将它们组合起来。

const validateInput = value => 
  let error = ""
  
  if (!value) error = "Required!"
  else if (value.length !== 14) error = "Invalid phone format. ex: (555) 555-5555";
  
  return error;
;

const initialState = 
 areaCode: "",
 prefix: "",
 suffix: "",
 error: ""
;
    
class Form extends React.Component 
  constructor() 
    super();
    
    this.state = initialState;
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleReset = this.handleReset.bind(this);
    this.setInputRef = this.setInputRef.bind(this);
  
  
  handleChange( target:  name, value  ) 
    let valueChanged = false;
    this.setState(prevState => 
      const nextValue = value.replace(/[^\d]/g, '');
      const changedValue = prevState[name];
      if (changedValue.length !== nextValue.length) valueChanged = true;
  
      return  [name]: nextValue 
    , () => 
      if(valueChanged) this.handleFocus(name)
    );
  ;
  
  setInputRef(name, element) 
   this[name] = element;
  
  
  handleFocus(name)
    const  areaCode, prefix, suffix  = this.state;
    const areaCodeFilled = areaCode.length === 3;
    const prefixFilled = prefix.length === 3;

    if(areaCodeFilled && name === "areaCode") 
      this.prefix.focus();
      this.prefix.selectionEnd = 0;
     else if(prefixFilled && name === "prefix") 
      this.suffix.focus();
      this.suffix.selectionEnd = 0;
    
  
  
  handleSubmit(e) 
    e.preventDefault();
    const  areaCode, prefix, suffix  = this.state;
    const phoneNumber = `($areaCode) $prefix-$suffix`
    const error = validateInput(phoneNumber);
    
    this.setState( error , () => 
       if(!error) 
         setTimeout(() => 
           alert(phoneNumber);
         , 300)
       
    );
  
  
  handleReset() 
     this.setState(initialState);
  ;
  
  render() 
    return(
      <form className="form" onSubmit=this.handleSubmit>
        <div className="input-container">
          <div className="label">
            Phone:
          </div>
          <div className="parenthesis" style= marginLeft: 10, marginRight: 2>&#40;</div>
          <input
            className="input area-code"
            type="text"
            name="areaCode"
            placeholder="xxx"
            value=this.state.areaCode
            onChange=this.handleChange
            maxLength="3"
          />
           <div className="parenthesis" style= marginRight: 2>&#41;</div>
          <input
            ref=node => this.setInputRef("prefix", node)
            className="input prefix"
            type="text"
            name="prefix"
            placeholder="xxx"
            value=this.state.prefix
            onChange=this.handleChange
            maxLength="3"
          />
          <div className="dash">-</div>
          <input
            ref=node => this.setInputRef("suffix", node)
            className="input suffix"
            type="text"
            name="suffix"
            placeholder="xxxx"
            value=this.state.suffix
            onChange=this.handleChange
            maxLength="4"
          />
        </div>
         <p className="error">this.state.error</p>
        <div className="btn-container">
          <button 
             className="btn danger"
             type="button"
             onClick=this.handleReset
           >
            Reset
          </button>
          <button className="btn primary" type="submit">Submit</button>
        </div>
      </form>
    );
  


ReactDOM.render(
  <Form />,
  document.getElementById('root')
);
html 
  font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
  font-size: 16px;
  font-weight: 400;
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
  background: #fff;
  color: #666;


.btn 
  color: #fff;
  border: 1px solid transparent;
  margin: 0 10px;
  cursor: pointer;
  text-align: center;
  box-sizing: border-box;
  padding: 0 30px;
  vertical-align: middle;
  font-size: .875rem;
  line-height: 38px;
  text-align: center;
  text-decoration: none;
  text-transform: uppercase;
  transition: .1s ease-in-out;
  transition-property: color,background-color,border-color;


.btn:focus 
  outline: 0;


.btn-container 
  text-align: center;
  margin-top: 10px;


.form 
  width: 550px;
  margin: 0 auto;


.danger 
  background-color: #f0506e;
  color: #fff;
  border: 1px solid transparent;

 
.danger:hover 
  background-color: #ee395b;
  color: #fff;


.error 
  margin: 0;
  height: 24px;
  margin-top: -20px;
  padding-left: 26%;
  color: red;
  text-align: right;


.input 
  display: flex;
  height: 40px;
  font-size: 16px;
  width: 33%;
  padding: 0 3px;
  background: #fff;
  color: #666;
  outline: none;
  border: 0;

 
.area-code,.prefix 
 width: 27px;


.suffix 
 width: 38px;


.dash,.parenthesis 
 display: flex;


.input-container 
  width: 100%;
  margin-bottom: 20px;
  display: flex;
  flex-direction: row;
  align-items: center;
  border-top-left-radius: 4px;
  border-bottom-left-radius: 4px;

    border: 1px solid #e5e5e5;
  transition: .2s ease-in-out;
  transition-property: color,background-color,borde


.label 
  height: 100%;
  background: rgb(238, 238, 238);
  width: 25%;
  padding-top: 8px;
  display: flex;
  text-transform: uppercase;
  justify-content: space-around;
  font-weight: bold;
  height: 34px;


.primary 
  background-color: #1e87f0;


.primary:hover 
  background-color: #0f7ae5;
  color: #fff;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>


<div id='root'>
</div>

【讨论】:

我在寻找***.com/questions/61240217/… 的答案时找到了这个解决方案。我认为您的解决方案也存在我面临的问题,即“当您在字符串中间的某个位置进行编辑时,光标会跳到输入的末尾”。 虽然我了解您要完成的工作,但不幸的是,使用此解决方案是不可能的(因为它会根据其长度对值进行切片)。相反,您可能需要考虑将输入分成 3 个输入:填写区号:“123”,然后一旦填写完毕,将光标聚焦在下一个输入:“567”上,一旦填写完毕,聚焦光标在最后四位:“8910”。每个都是一个独立的输入,但对用户来说,它似乎被格式化为一个输入。然后,更改一个不会影响另一个,因为它们是具有不同值的单独输入。 更新了上面的示例以包含 3 个单独的输入。不完美,但它基本上做同样的事情。您还可以摆脱基于值变化对输入的自动关注,而是让用户选项卡通过它们。 Matt- 这是解决此问题的好方法。然而,我的问题是我在 JSON 响应中从后端获取输入字段的名称和 ID。当我提交表单时,后端希望我提交电话值作为分配给它首先发送的名称的一个输入字段。 我没有发现问题。当您从后端检索它时,您可以将其保存到相应的组件状态...前 3 位:区号,后 3 位:前缀,后 4 位后缀。使用输入根据需要操作状态,然后在提交时,在将其发送到后端之前,将它们重新组合在一起(就像第二个示例在 onSubmit 函数中所做的那样),然后将其作为具有相同 id 的相同名称字段发送回。 【参考方案2】:

一切都与格式化有关。任何打印字符的键 应该会导致重写输入字段。 这样,无论他做什么,用户都只能看到有效的格式化字段。

正则表达式很简单^\D*(\d0,3)\D*(\d0,3)\D*(\d0,4)

function getFormattedPhoneNum( input ) 
  let output = "(";
  input.replace( /^\D*(\d0,3)\D*(\d0,3)\D*(\d0,4)/, function( match, g1, g2, g3 )
      
        if ( g1.length ) 
          output += g1;
          if ( g1.length == 3 ) 
              output += ")";
              if ( g2.length ) 
                  output += " " + g2; 
                  if ( g2.length == 3 ) 
                      output += " - ";
                      if ( g3.length ) 
                          output += g3;
                      
                  
              
           
        
             
    );        
  return output;
        

console.log( getFormattedPhoneNum("") );
console.log( getFormattedPhoneNum("2") );
console.log( getFormattedPhoneNum("asdf20as3d") );
console.log( getFormattedPhoneNum("203") );
console.log( getFormattedPhoneNum("203-44") );
console.log( getFormattedPhoneNum("444sg52asdf22fd44gs") );
console.log( getFormattedPhoneNum("444sg526sdf22fd44gs") );
console.log( getFormattedPhoneNum("444sg526sdf2244gs") );
console.log( getFormattedPhoneNum(" ra098 848 73653k-atui ") );

你甚至可以变得更漂亮,并在字符的位置显示下划线 应该在任何给定时间。 点赞(___) ___ - ____(20_) ___ - ____(123) 456 - ____

等等...(如果你想要这个,请告诉我)

【讨论】:

惊人的答案,正是我想要的。谢谢。 如果某些国家/地区的运营商代码为 2 位数怎么办。我如何涵盖所有案例?【参考方案3】:

您当前代码的正则表达式仅在输入十位数字(3、3、4)时匹配。您可以更新正则表达式以接受一系列数字,例如:

^\(?([0-9]0,3)\)?[-. ]?([0-9]0,3)[-. ]?([0-9]0,4)$

或者您可以让正则表达式简单地确保输入 0-10 位数字 ([0-9]0,10),然后自己将字符串拆分为长度为 3、3 和 4 的子字符串。这样做后一种方式似乎更好,因为您只想根据用户输入的数字显示某些字符:

1 -> (1

123 -> (123)

1234567 -> (123) 456-7

1234567890 -> (123) 456-7890

您必须处理这些情况,而简单的替换无法做到。

【讨论】:

【参考方案4】:

我用过这个库:https://www.npmjs.com/package/libphonenumber-js 它有一个 AsYouType 函数,你可以在你的输入上使用它

【讨论】:

【参考方案5】:

也许,以下内容对某人有用:

   onChange=(e) => 
                if (e.target.value.length < 13) 
                  var cleaned = ("" + e.target.value).replace(/\D/g, "");

                  let normValue = `$cleaned.substring(0, 3)$
                    cleaned.length > 3 ? "-" : ""
                  $cleaned.substring(3, 6)$
                    cleaned.length > 6 ? "-" : ""
                  $cleaned.substring(6, 11)`;

                  handleChange(normValue);
                
              

【讨论】:

以上是关于React - 如何将电话号码格式化为用户类型的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 PhoneNumberUtils 库将电话号码格式化为没有国家前缀的国家格式?

xcode swift - 将文本格式化为电话号码[重复]

Pebble 模板电话号码格式

如何使用 jQuery 格式化电话号码

将 TextView 格式化为看起来像链接

如何使用包括 iOS 的 jQuery 格式化电话号码