验证码输入是一个很常用的功能,这里我们尝试使用一个现成的组件来实现react-native-cofirmation-code-field。
一、VScode前端开发常用插件
看教学视频的过程中,发现博主的代码不会自动格式化,而且经常需要删除空格,否则会报错,类似String需要包裹到Text组件中。
其实,VSCode提供了很多很好用的插件,帮助我们更方便地编写代码。参考30个实用VSCode 插件,让你的开发效率倍增!_善良的小丑的博客-CSDN博客_vscode常用插件可以进行安装。
这里也给我们一个很好的提示,我们要善于思考,发现不那么舒服的地方,一定要寻求解决方法,优先选择一些工具帮我们改善。
二、Demo测试添加
我们经常需要尝试一些新的组件,为了不污染原来的项目,我们新建一个文件Demo.js,专门用来运行需要测试的代码。
在src/pages下,新建一个文件夹Demo,然后新建一个文件Demo.js,运行我们的代码。
这里我们选择在首页运行Demo,也就是说,我们App一进入就是Demo这个页面,也就是说我们需要添加到src/nav.js中
现在有个新问题,需要控制一下显不显示Demo,我们测试的时候,需要进入app就看到Demo,但是我们测试结束的时候,需要进入app就看到我们的首页。
我们当然可以选择每次都修改nav.js文件,但是团队协作中,通常只需要一个状态来控制,可以是一个变量,也可以是一个state,习惯原因,这里选择变量控制。
1、变量控制是否显示Demo
在src/constant下,新建一个文件Limits.js,里边存放公共的变量。
export const isShowDemo = false;
然后,在nav.js中添加Demo页。
首先,引入常量isShowDemo和页面Demo。
import Demo from './pages/Demo/Demo';
import {isShowDemo} from './constant/Limits';
接下来配置路由
<NavigationContainer>
<Stack.Navigator initialRouteName={isShowDemo ? 'Demo' : 'Login'}>
<Stack.Screen
name="Demo"
options={{headerShown: false}}
component={Demo}
/>
<Stack.Screen
name="Login"
options={{headerShown: false}}
component={Login}
/>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
这样的话,修改isShowDemo的值就可以控制是否展示Demo页了。
2、Demo测试
首先,安装react-native-confirmation-code-field
npm install -S react-native-confirmation-code-field
然后,参考官方文档https://github.com/retyui/react-native-confirmation-code-field,将代码粘贴到Demo.js中
import React, {useState} from 'react';
import {SafeAreaView, Text, StyleSheet} from 'react-native';
import {
CodeField,
Cursor,
useBlurOnFulfill,
useClearByFocusCell,
} from 'react-native-confirmation-code-field';
const styles = StyleSheet.create({
root: {flex: 1, padding: 20},
title: {textAlign: 'center', fontSize: 30},
codeFieldRoot: {marginTop: 20},
cell: {
width: 40,
height: 40,
lineHeight: 38,
fontSize: 24,
borderWidth: 2,
borderColor: '#00000030',
textAlign: 'center',
},
focusCell: {
borderColor: '#000',
},
});
const CELL_COUNT = 6;
const App = () => {
const [value, setValue] = useState('');
const ref = useBlurOnFulfill({value, cellCount: CELL_COUNT});
const [props, getCellOnLayoutHandler] = useClearByFocusCell({
value,
setValue,
});
return (
<SafeAreaView style={styles.root}>
<Text style={styles.title}>Verification</Text>
<CodeField
ref={ref}
{...props}
// Use `caretHidden={false}` when users can't paste a text value, because context menu doesn't appear
value={value}
onChangeText={setValue}
cellCount={CELL_COUNT}
rootStyle={styles.codeFieldRoot}
keyboardType="number-pad"
textContentType="oneTimeCode"
renderCell={({index, symbol, isFocused}) => (
<Text
key={index}
style={[styles.cell, isFocused && styles.focusCell]}
onLayout={getCellOnLayoutHandler(index)}>
{symbol || (isFocused ? <Cursor /> : null)}
</Text>
)}
/>
</SafeAreaView>
);
};
export default App;
最后,真机运行一下。
接下来,调整样式,最终代码如下
import React, {useState} from 'react';
import {Text, StyleSheet} from 'react-native';
import {CodeField, Cursor} from 'react-native-confirmation-code-field';
import {pxToDp} from '../../utils/stylesKits';
const styles = StyleSheet.create({
root: {
flex: 1,
padding: pxToDp(20),
},
codeFieldRoot: {
marginTop: 20,
},
cell: {
width: pxToDp(40),
height: pxToDp(40),
lineHeight: pxToDp(38),
fontSize: pxToDp(24),
borderBottomWidth: pxToDp(2),
borderColor: '#7d53ea',
textAlign: 'center',
color: '#7d53ea',
},
focusCell: {
borderColor: '#7d53ea',
},
});
const App = () => {
const [value, setValue] = useState('');
return (
<CodeField
// Use `caretHidden={false}` when users can't paste a text value, because context menu doesn't appear
value={value}
onChangeText={setValue}
cellCount={6}
rootStyle={styles.codeFieldRoot}
keyboardType="number-pad"
renderCell={({index, symbol, isFocused}) => (
<Text key={index} style={[styles.cell, isFocused && styles.focusCell]}>
{symbol || (isFocused ? <Cursor /> : null)}
</Text>
)}
/>
);
};
export default App;
稍后将Demo中的代码添加到登录页面中就可以了。
三、页面切换
1、添加状态控制当前页面是展示“手机号登录注册”,还是展示“输入验证码”
// 是显示“手机号登录注册”页面,还是显示“验证码输入”页面
const [isShowLogin, setShowLogin] = useState(true);
这样的话,原来的登录代码就需要整合成一个函数了
<View style={Styles.loginContainer}>
{isShowLogin ? loginView() : vCodeView()}
</View>
其中,loginView如下
loginView = () => {
return (
<View>
<Text style={styles.Title}>手机号登录注册</Text>
<View style={Styles.inputContainer}>
<Input
placeholder="请输入手机号码"
leftIcon={{
type: 'font-awesome',
name: 'phone',
color: '#ccc',
size: pxToDp(20),
}}
maxLength={11}
keyboardType="phone-pad"
inputStyle={{color: '#333'}}
errorMessage={isValid ? '' : '手机号码格式不正确'}
value={phoneNumber}
onChangeText={setPhoneNumber}
onSubmitEditing={phoneNumberSubmitEditing}
/>
</View>
<View style={Styles.btnContainer}>
<LoginBtn onPress={phoneNumberSubmitEditing}>获取验证码</LoginBtn>
</View>
</View>
);
};
2、将Demo中的代码选择性粘贴到vCodeView()中,代码如下
// 验证码
const [vCodeValue, setVCodeValue] = useState('');
vCodeView = () => {
return (
<View>
<Text style={styles.Title}>输入六位验证码</Text>
<View style={Styles.sepTitle}>
<Text style={styles.Title}>已经发到:+86 {phoneNumber}</Text>
</View>
<CodeField
// Use `caretHidden={false}` when users can't paste a text value, because context menu doesn't appear
value={vCodeValue}
onChangeText={setVCodeValue}
cellCount={6}
rootStyle={Styles.codeFieldRoot}
keyboardType="number-pad"
renderCell={({index, symbol, isFocused}) => (
<Text
key={index}
style={[Styles.cell, isFocused && Styles.focusCell]}>
{symbol || (isFocused ? <Cursor /> : null)}
</Text>
)}
/>
<View style={[Styles.btnContainer, Styles.btnMargin]}>
<LoginBtn onPress={phoneNumberSubmitEditing}>重新获取</LoginBtn>
</View>
</View>
);
};
3、控制验证码输入显示
当手机号码输入完毕,并且校验通过,或者点击“重新获取”按钮后,切换到验证码输入界面
phoneNumberSubmitEditing = async () => {
console.log('手机号码输入完毕!', validator.validatePhone(phoneNumber));
// 校验手机号码
// 校验不通过
if (!validator.validatePhone(phoneNumber)) {
setValid(false);
return;
}
// 校验通过
setValid(true);
// const res = await request.post(ACCOUNT_LOGIN, {phone: phoneNumber});
setShowLogin(false);
};
登录页面完整代码如下
import React, {useState} from 'react';
import {View, Image, StyleSheet, StatusBar, Text} from 'react-native';
import {styles} from '../../../constant/Styles';
import {Images} from '../../../constant/Images';
import {pxToDp} from '../../../utils/stylesKits';
import {Input} from '@rneui/themed';
import validator from '../../../utils/validator';
import {ACCOUNT_LOGIN} from '../../../utils/pathMap';
import request from '../../../utils/request';
import LoginBtn from '../../../components/LoginBtn';
import {CodeField, Cursor} from 'react-native-confirmation-code-field';
const Index = () => {
// 电话号码
const [phoneNumber, setPhoneNumber] = useState('');
// 电话号码校验是否通过
const [isValid, setValid] = useState(true);
// 是显示“手机号登录注册”页面,还是显示“验证码输入”页面
const [isShowLogin, setShowLogin] = useState(true);
// 验证码
const [vCodeValue, setVCodeValue] = useState('');
phoneNumberSubmitEditing = async () => {
console.log('手机号码输入完毕!', validator.validatePhone(phoneNumber));
// 校验手机号码
// 校验不通过
if (!validator.validatePhone(phoneNumber)) {
setValid(false);
return;
}
// 校验通过
setValid(true);
// const res = await request.post(ACCOUNT_LOGIN, {phone: phoneNumber});
setShowLogin(false);
};
loginView = () => {
return (
<View>
<Text style={styles.Title}>手机号登录注册</Text>
<View style={Styles.inputContainer}>
<Input
placeholder="请输入手机号码"
leftIcon={{
type: 'font-awesome',
name: 'phone',
color: '#ccc',
size: pxToDp(20),
}}
maxLength={11}
keyboardType="phone-pad"
inputStyle={{color: '#333'}}
errorMessage={isValid ? '' : '手机号码格式不正确'}
value={phoneNumber}
onChangeText={setPhoneNumber}
onSubmitEditing={phoneNumberSubmitEditing}
/>
</View>
<View style={Styles.btnContainer}>
<LoginBtn onPress={phoneNumberSubmitEditing}>获取验证码</LoginBtn>
</View>
</View>
);
};
vCodeView = () => {
return (
<View>
<Text style={styles.Title}>输入六位验证码</Text>
<View style={Styles.sepTitle}>
<Text style={styles.Title}>已经发到:+86 {phoneNumber}</Text>
</View>
<CodeField
// Use `caretHidden={false}` when users can't paste a text value, because context menu doesn't appear
value={vCodeValue}
onChangeText={setVCodeValue}
cellCount={6}
rootStyle={Styles.codeFieldRoot}
keyboardType="number-pad"
renderCell={({index, symbol, isFocused}) => (
<Text
key={index}
style={[Styles.cell, isFocused && Styles.focusCell]}>
{symbol || (isFocused ? <Cursor /> : null)}
</Text>
)}
/>
<View style={[Styles.btnContainer, Styles.btnMargin]}>
<LoginBtn onPress={phoneNumberSubmitEditing}>重新获取</LoginBtn>
</View>
</View>
);
};
return (
<View style={styles.Container}>
{/* 状态栏 */}
<StatusBar backgroundColor={'transparent'} translucent={true} />
{/* 背景图片 */}
<Image source={Images.loginBG} style={Styles.imgSize} />
{/* 登录信息 */}
<View style={Styles.loginContainer}>
{isShowLogin ? loginView() : vCodeView()}
</View>
</View>
);
};
const Styles = StyleSheet.create({
imgSize: {
width: '100%',
height: pxToDp(200),
},
loginContainer: {
padding: pxToDp(20),
},
inputContainer: {
marginTop: pxToDp(30),
},
btnContainer: {
width: '85%',
height: pxToDp(40),
alignSelf: 'center',
},
sepTitle: {
marginTop: pxToDp(15),
},
root: {
flex: 1,
padding: pxToDp(20),
},
codeFieldRoot: {
marginTop: 20,
},
cell: {
width: pxToDp(40),
height: pxToDp(40),
lineHeight: pxToDp(38),
fontSize: pxToDp(24),
borderBottomWidth: pxToDp(2),
borderColor: '#7d53ea',
textAlign: 'center',
color: '#7d53ea',
},
focusCell: {
borderColor: '#7d53ea',
},
btnMargin: {
marginTop: pxToDp(60),
},
});
export default Index;
四、添加防重
重新获取按钮,需要添加防重。
添加两个state,一个控制按钮显示文本,一个控制是否处于倒计时中。
// 重新获取验证码btn显示文字
const [btnTxt, setBtnTxt] = useState('重新获取');
// 重新获取--防重
const [isDisabled, setDisable] = useState(false);
“重新获取”点击事件如下
// 重新获取验证码倒计时
countDown = () => {
if (isDisabled) {
return;
}
setDisable(true);
let seconds = 5;
setBtnTxt(`重新获取(${seconds}s)`);
let timeId = setInterval(() => {
seconds--;
setBtnTxt(`重新获取(${seconds}s)`);
if (seconds === 0) {
clearInterval(timeId);
setBtnTxt('重新获取');
setDisable(false);
}
}, 1000);
};
按钮有个disabled属性,也需要添加一下
父组件控制如下
<LoginBtn disabled={isDisabled} onPress={countDown}>
{btnTxt}
</LoginBtn>
登录页面完整代码如下
import React, {useState} from 'react';
import {View, Image, StyleSheet, StatusBar, Text} from 'react-native';
import {styles} from '../../../constant/Styles';
import {Images} from '../../../constant/Images';
import {pxToDp} from '../../../utils/stylesKits';
import {Input} from '@rneui/themed';
import validator from '../../../utils/validator';
import {ACCOUNT_LOGIN} from '../../../utils/pathMap';
import request from '../../../utils/request';
import LoginBtn from '../../../components/LoginBtn';
import {CodeField, Cursor} from 'react-native-confirmation-code-field';
const Index = () => {
// 电话号码
const [phoneNumber, setPhoneNumber] = useState('');
// 电话号码校验是否通过
const [isValid, setValid] = useState(true);
// 是显示“手机号登录注册”页面,还是显示“验证码输入”页面
const [isShowLogin, setShowLogin] = useState(true);
// 验证码
const [vCodeValue, setVCodeValue] = useState('');
// 重新获取验证码btn显示文字
const [btnTxt, setBtnTxt] = useState('重新获取');
// 重新获取--防重
const [isDisabled, setDisable] = useState(false);
phoneNumberSubmitEditing = async () => {
console.log('手机号码输入完毕!', validator.validatePhone(phoneNumber));
// 校验手机号码
// 校验不通过
if (!validator.validatePhone(phoneNumber)) {
setValid(false);
return;
}
// 校验通过
setValid(true);
// const res = await request.post(ACCOUNT_LOGIN, {phone: phoneNumber});
setShowLogin(false);
countDown();
};
// 手机号登录
loginView = () => {
return (
<View>
<Text style={styles.Title}>手机号登录注册</Text>
<View style={Styles.inputContainer}>
<Input
placeholder="请输入手机号码"
leftIcon={{
type: 'font-awesome',
name: 'phone',
color: '#ccc',
size: pxToDp(20),
}}
maxLength={11}
keyboardType="phone-pad"
inputStyle={{color: '#333'}}
errorMessage={isValid ? '' : '手机号码格式不正确'}
value={phoneNumber}
onChangeText={setPhoneNumber}
onSubmitEditing={phoneNumberSubmitEditing}
/>
</View>
<View style={Styles.btnContainer}>
<LoginBtn onPress={phoneNumberSubmitEditing}>获取验证码</LoginBtn>
</View>
</View>
);
};
// 输入验证码
vCodeView = () => {
return (
<View>
<Text style={styles.Title}>输入六位验证码</Text>
<View style={Styles.sepTitle}>
<Text style={styles.Title}>已经发到:+86 {phoneNumber}</Text>
</View>
<CodeField
// Use `caretHidden={false}` when users can't paste a text value, because context menu doesn't appear
value={vCodeValue}
onChangeText={setVCodeValue}
cellCount={6}
rootStyle={Styles.codeFieldRoot}
keyboardType="number-pad"
renderCell={({index, symbol, isFocused}) => (
<Text
key={index}
style={[Styles.cell, isFocused && Styles.focusCell]}>
{symbol || (isFocused ? <Cursor /> : null)}
</Text>
)}
/>
<View style={[Styles.btnContainer, Styles.btnMargin]}>
<LoginBtn disabled={isDisabled} onPress={countDown}>
{btnTxt}
</LoginBtn>
</View>
</View>
);
};
// 重新获取验证码倒计时
countDown = () => {
if (isDisabled) {
return;
}
setDisable(true);
let seconds = 5;
setBtnTxt(`重新获取(${seconds}s)`);
let timeId = setInterval(() => {
seconds--;
setBtnTxt(`重新获取(${seconds}s)`);
if (seconds === 0) {
clearInterval(timeId);
setBtnTxt('重新获取');
setDisable(false);
}
}, 1000);
};
return (
<View style={styles.Container}>
{/* 状态栏 */}
<StatusBar backgroundColor={'transparent'} translucent={true} />
{/* 背景图片 */}
<Image source={Images.loginBG} style={Styles.imgSize} />
{/* 登录信息 */}
<View style={Styles.loginContainer}>
{isShowLogin ? loginView() : vCodeView()}
</View>
</View>
);
};
const Styles = StyleSheet.create({
imgSize: {
width: '100%',
height: pxToDp(200),
},
loginContainer: {
padding: pxToDp(20),
},
inputContainer: {
marginTop: pxToDp(30),
},
btnContainer: {
width: '85%',
height: pxToDp(40),
alignSelf: 'center',
},
sepTitle: {
marginTop: pxToDp(15),
},
root: {
flex: 1,
padding: pxToDp(20),
},
codeFieldRoot: {
marginTop: 20,
},
cell: {
width: pxToDp(40),
height: pxToDp(40),
lineHeight: pxToDp(38),
fontSize: pxToDp(24),
borderBottomWidth: pxToDp(2),
borderColor: '#7d53ea',
textAlign: 'center',
color: '#7d53ea',
},
focusCell: {
borderColor: '#7d53ea',
},
btnMargin: {
marginTop: pxToDp(60),
},
});
export default Index;