React Native 登录页--验证码

验证码输入是一个很常用的功能,这里我们尝试使用一个现成的组件来实现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;

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值