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】:你可以像这样normalize
input
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>(</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>)</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 - 如何将电话号码格式化为用户类型的主要内容,如果未能解决你的问题,请参考以下文章