处理电话号码的表格输入是出了名的烦人。如果你的受众是国际性的,你的电话号码输入字段必须能适应处理不同国家的呼叫代码。为了准确起见,最佳做法还需要添加掩码,它可以自动将电话号码的格式化为它所在国家的特定布局。
在这篇文章中,我们将学习如何在React中使用一个叫做react-phone-number-input的包来实现上述所有的功能。我们还将利用浏览器中的Navigator API和谷歌地图API。
要跟上这个教程,你可以在我的GitHub仓库中访问这个项目的源代码。让我们开始吧!
反应式手机号码输入字段示例
开始吧
为了开始,我们将建立一个新的React项目。
npx create-react-app
接下来,我们将为我们的项目安装所需的包,包括 react-hone-number-input。我们还将安装styled-components来添加样式,然而这是可选的。
npm install --save styled-components
npm i react-phone-number-input
在安装了React和我们所需的包之后,接下来,我们将从谷歌获得一个免费的API密钥,使我们能够在项目的后期使用谷歌地图API。前往 "入门"页面;谷歌为谷歌地图API提供了200美元的月度信用额度,所以你将能够遵循本教程而无需花费任何费用。
创建和设计我们的表单
现在,让我们来定义我们的表单。我们将创建一个基本的表单,包括一个输入字段、一个选择按钮和一些标签。下面是我们在添加样式之前的基本表单的代码。
<div>
<form>
<div>
<label htmlFor="countrySelect">Country Select</label>
<select name="countrySelect"/>
</div>
<div>
<label htmlFor="phoneNumber">Phone Number</label>
<input placeholder="Enter phone number" name="phoneNumber" />
</div>
</form>
</div>
让我们使用styled-components来创建两个组件,StyledPage 作为容器,StyledForm 作为表单。
<StyledPage>
<StyledForm>
<div>
<label htmlFor="countrySelect">Country Select</label>
<select name="countrySelect"/>
</div>
<div>
<label htmlFor="phoneNumber">Phone Number</label>
<input placeholder="Enter phone number" name="phoneNumber" />
</div>
<StyledForm>
<StyledPage>
现在,我们将分别为这两个组件添加样式。
const StyledPage = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2rem;
height: 100vh;
background-color: hsl(15, 67%, 99%);
& label,
p {
font-size: 20px;
font-weight: 300;
margin: 0;
& > span {
font-weight: 400;
}
}
`;
const StyledForm = styled.form`
display: flex;
flex-direction: column;
gap: 2rem;
& > div {
display: flex;
flex-direction: column;
gap: 0.5rem;
width: 400px;
& > input,
select {
padding: 1rem;
border-radius: 5px;
border: none;
box-shadow: 0px 0px 1px hsla(0, 0%, 12%, 0.5);
}
}
`;
在上面的代码块中,我们添加了一些Flexbox容器,以使表单在页面上居中,我们还包含了一些视觉样式元素。现在,让我们加入我们的自定义输入。
添加自定义表单输入
我们将使用 react-phone-number-input 包中的两个自定义输入:Input ,用于输入电话号码;CountrySelect ,一个自定义选择元素,我们将使用该包中的两个函数getCountries 和getCountryCallingCode 来定义。
虽然CountrySelect 组件在包的文档中有所记载,但它没有被导出,这意味着我们需要自己定义它。首先,让我们导入上面提到的元素和函数。
import Input, { getCountries, getCountryCallingCode } from 'react-phone-number-input/input';
import en from 'react-phone-number-input/locale/en.json';
import 'react-phone-number-input/style.css';
在上面的代码块中,我们还导入了一个locale文件,将callingCodes 转换为英文的国家名称。此外,我们还根据包的要求导入了一些CSS样式。现在,我们可以定义我们的CountrySelect 组件,如下所示。
const CountrySelect = ({ value, onChange, labels, ...rest }) => (
<select {...rest} value={value} onChange={(event) => onChange(event.target.value || undefined)}>
<option value="">{labels.ZZ}</option>
{getCountries().map((country) => (
<option key={country} value={country}>
{labels[country]} +{getCountryCallingCode(country)}
</option>
))}
</select>
);
接下来,我们可以用软件包中的新的自定义输入来替换我们之前创建的占位符的表单元素。
<StyledPage>
<StyledForm>
<div>
<label htmlFor="countrySelect">Country Select</label>
<CountrySelect labels={en} name="countrySelect" />
</div>
<div>
<label htmlFor="phoneNumber">Phone Number</label>
<Input placeholder="Enter phone number" name="phoneNumber" />
</div>
</StyledForm>
</StyledPage>
你可能会注意到,控制其中显示的标签的labels 道具被传递给了CountrySelect 组件。为了生成下拉菜单的值,我们将之前从locale文件中导入的国家调用代码传递给labels 。
现在,我们几乎已经为手动使用设置好了表单。最后,我们需要给组件添加状态,这样我们就可以操作select 和input 元素中显示的值。
让我们先把useState Hook导入我们的组件中。
import React, { useState } from 'react';
接下来,我们将在我们的组件中创建两块状态,一块用于input ,另一块用于select 元素。
const [phoneNumber, setPhoneNumber] = useState();
const [country, setCountry] = useState();
最后,我们将把状态和元素连接起来,把值作为道具传给它们。
<StyledPage>
<StyledForm>
<div>
<label htmlFor="countrySelect">Country Select</label>
<CountrySelect labels={en} value={country} onChange={setCountry} name="countrySelect" />
</div>
<div>
<label htmlFor="phoneNumber">Phone Number</label>
<Input country={country} value={phoneNumber} onChange={setPhoneNumber} placeholder="Enter phone number" name="phoneNumber" />
</div>
</StyledForm>
</StyledPage>
我们传递给Input 组件的country 道具,控制了通过国家呼叫代码提供的电话号码的格式和掩码。我们稍后在应用用户的位置时将会用到它。
现在,我们有了一个功能齐全的手动表格。让我们看看我们如何自动检测用户的经度和纬度,然后使用Google Maps Geocoding API将坐标转换成一个国家。
获取用户的经度和纬度
为了获得用户的经度和纬度,我们将使用浏览器的Navigator API。更具体地说,我们将使用navigator.geolocation.getCurrentPosition 方法,它将返回关于用户当前位置的数据,我们可以从中提取经度和纬度数据。
我将使检测在应用程序的初始加载时运行。另外,你也可以将检测运行与按下的按钮挂钩,这样用户就不会在页面加载时直接被提示。
为了确保我们在每个页面加载时只运行一次请求,我们将使用useEffect Hook。让我们把它导入我们的项目中。
import React, { useEffect, useState } from 'react';
在我们的功能组件中,我们可以添加我们的useEffect Hook,并传递一个空数组作为依赖关系数组。useEffect Hook将只在应用程序的初始渲染中运行一次。
useEffect(() => {
// Insert our code here
}, []);
现在,让我们加入Navigator API来检测用户的位置。
useEffect(() => {
navigator.geolocation.getCurrentPosition(success, rejected);
}, []);
当导航器在页面的初始渲染中运行时,用户将被提示是否允许访问他们的位置。根据他们的决定,两个回调函数中的一个将被调用,用于批准或拒绝。
在本教程中,我们不会专注于错误处理。相反,如果权限被拒绝,我们将把它记录到控制台。对于成功回调,让我们定义一个单独的函数来调用。
async function handleNavigator(pos) {
const { latitude, longitude } = pos.coords;
}
useEffect(() => {
navigator.geolocation.getCurrentPosition(handleNavigator, () => console.warn('permission was rejected'));
}, []);
当位置请求被批准时,我们将调用handleNavigator 函数,该函数提供了对用户位置数据的访问,允许我们重组他们当前位置的经度和纬度数据。然后,我们将使用Geocoding API请求和响应来查找国家。
地理编码的经纬度到一个国家
为了从API获取数据,让我们在src 目录内的utils 目录中创建一个新的函数,称为lookupCountry.js 。lookupCountry.js 将处理所有的数据获取和处理,返回一个国家代码给handleNavigator ,我们最初的回调函数。
在我们新的lookupCountry.js 文件中,让我们定义一个具有相同名称的新的异步函数,然后导出它。
async function lookupCountry({ latitude, longitude }) {
}
export default lookupCountry;
我们还将定义我们的参数,这将包括一个包含纬度和经度的对象,我们将在调用该函数时传入。
接下来,我们需要定义将执行获取请求的URL。在定义URL时,我们还需要加入我们的经度和纬度数据,以及我们之前得到的API密钥。
最简单的方法是使用JavaScript的模板字面,将变量插进字符串中。
async function lookupCountry({ latitude, longitude }) {
const URL = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}`;
}
export default lookupCountry;
注意:我是通过环境变量添加我的API密钥的,这是推荐的。然而,如果你在本地使用该项目,并且不打算发布它,你可以将你的API密钥硬编码进去。
现在,我们将使用浏览器内置的fetch API从API中获取数据。
async function lookupCountry({ latitude, longitude }) {
const URL = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}`;
const locationData = await fetch(URL).then((res) => res.json());
}
export default lookupCountry;
我们等待从API返回的数据,然后将响应转换为JSON,再将其存储到一个变量中。
API将从我们提供的经度和纬度数据中返回各种级别的细节,包括从街道地址到用户所在的国家,这正是我们所感兴趣的。
幸运的是,谷歌已经为我们对返回的数组进行了格式化和分类。数组中的第一项是最精确的(街道地址),最后一项是最一般的(国家)。
每个项目还包括数据的类型,如街道地址、城镇、国家等,使我们很容易通过过滤类型找到我们感兴趣的项目country 。
async function lookupCountry({ latitude, longitude }) {
const URL = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}`;
const locationData = await fetch(URL).then((res) => res.json());
const [{ address_components }] = locationData.results.filter(({ types }) => types.includes('country'));
}
export default lookupCountry;
在对数组进行过滤后,我们将进行一个数组去结构,因为.filter 方法会返回一个数组给我们。接下来,我们将做一个对象去结构,这样我们就可以访问我们感兴趣的属性,address_components 。下面是address_components 中的一个数据样本。
[ { "long_name": "United Kingdom", "short_name": "GB", "types": [ "country", "political" ]
}
]
我们对获取用户的国家代码感兴趣,所以我们需要从对象中解构出short_name 这个变量。
async function lookupCountry({ latitude, longitude }) {
const URL = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}`;
const locationData = await fetch(URL).then((res) => res.json());
const [{ address_components }] = locationData.results.filter(({ types }) => types.includes('country'));
const [{ short_name}] = address_components;
}
export default lookupCountry;
一旦我们从对象中解构出国家代码,我们就可以从我们的函数中把它返回到先前的原始回调函数handleNavigator 。
async function lookupCountry({ latitude, longitude }) {
const URL = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}`;
const locationData = await fetch(URL).then((res) => res.json());
const [{ address_components }] = locationData.results.filter(({ types }) => types.includes('country'));
const [{ short_name}] = address_components;
return short_name;
}
export default lookupCountry;
把所有东西放在一起
让我们把我们的lookupCountry 函数添加到我们前面的handleNavigator 回调函数中,并把它连接到我们表单的其他部分。
async function handleNavigator(pos) {
const { latitude, longitude } = pos.coords;
const userCountryCode = await lookupCountry({ latitude, longitude });
}
现在,如果用户在应用程序的初始页面加载时授予权限,我们就会从Navigator API中获得我们的经纬度数据。然后,这些数据被传递到我们的lookupCountry ,该函数又使用Google的Geocoding API将经纬度转换为国家代码,然后再返回给我们。
最后,我们只需要覆盖country 的状态,它可以控制select 元素中显示的值和输入组件的格式,用户将在这里输入他们的电话号码。
async function handleNavigator(pos) {
const { latitude, longitude } = pos.coords;
const userCountryCode = await lookupCountry({ latitude, longitude });
setCountry(userCountryCode);
}
现在,一切都连接好了。当用户加载页面时,他们将被提示允许应用程序访问他们的位置。如果他们同意,应用程序将检测他们的位置,查找正确的国家代码,然后将其应用于显示屏上的表单输入。
总结
当我试图在一个表单上做一个可以在全球范围内使用的电话号码输入时,我有了这个题目的想法。我很快意识到处理全球范围内的电话号码的所有要求是多么困难,尤其是在仍然试图提供一个良好的用户体验时。
react-phone-number-input大大简化了在表单中收集全球电话号码的过程。我希望你觉得这个教程有帮助;如果你觉得有帮助,请查看我的Twitter。
The postreact-phone-number-input:检测国际位置首次出现在LogRocket博客上。