基于React封装一个验证码输入控件

 更新时间:2024年03月27日 10:27:28   作者:墨渊君  
邮箱、手机验证码输入是许多在线服务和网站常见的安全验证方式之一,本文主要来和大家讨论一下如何使用React封装一个验证码输入控件,感兴趣的可以了解下
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

引言

邮箱、手机验证码输入是许多在线服务和网站常见的安全验证方式之一。这种方式通常用于确保用户在进行敏感操作(例如注册、修改密码、重置密码等)时的身份验证。

最近在做项目刚好有验证码相关的需求, 本着不重复造轮子的原则, 一顿 Google 试图找到一个现成的组件, 奈何找了一圈都没找到满意的, 要么就是交互感觉不太合理、要么就是基本停止维护了的!!

最后没办法就自己造一个了, 这里主要参考了 react-auth-code-input, 而本文则是整个思路开发流程的记录!!

DEMO 演示可查阅: blog/auth-codes

本文完整源码可查阅: coding/blog/AuthCodes

一、需求描述

开始前我们先梳理下一般验证码输入控件的常规需求有哪些:

  • 假设我们验证码有 6 位, 则我们需要有 6 个输入框, 每个输入框只允许输入一位数字(这里假设验证码都是数字组成)
  • 在输入验证码过程中, 可连续进行输入、删除等操作
  • 支持黏贴复制的内容
  • ...

二、布局

在开始前我们先来完成基本的布局, 如下代码所示:

  • 声明状态 codes 用于存储每个验证码, 也就是每个输入框的值, 这里我将 codes 设置为一个数组, 方便后面修改每个位置的验证码
  • 假设我们验证码长度为 6 位, 所以这里我为 codes 默认值了一个长度为 6 的数组, 数组每个初始值为空字符串
  • 然后我们通过 codes.map 渲染出所有输入框
  • 最后我们还声明了 inputsRef 来存储所有输入框的 DOM 节点, 我们后面需要通过它来调用原生 DOMAPI
import React, { useState, useRef } from 'react';
import scss from './com.module.scss';

export default () => {
  const [codes, setCodes] = useState(Array.from({ length: 6 }, () => ''));
  const inputsRef = useRef([]);

  return (
    <div>
      {codes.map((value, index) => (
        <input
          type="text"
          key={index}
          value={value}
          maxLength={1}
          className={scss.input}
          ref={(ele) => (inputsRef.current[index] = ele)}
        />
      ))}
    </div>
  );
};

这里我们对输入框设置了一些基本的样式

.input {
  width: 40px;
  margin: 10px;

  font-size: 18px;
  line-height: 40px;
  text-align: center;

  border-radius: 4px;
  border: 1px solid #d9d9d9;
}

到此页面的基本效果如下:

三、动态绑定(处理 onChange 事件)

上文只是完成了基本的布局, 并且输入框 value 和状态 codes 内的值绑定在了一起, 这里输入框输入值会发现并没有生效, 那是因为状态 codes 没有被修改!

下面我们为输入框设置 onChange 事件, 在输入框输入值时动态的修改状态 codes 中对应位置的值!!

下面是 onChange 事件的处理函数:

  • 两个参数, indexevent, 正如命名所示, index 对应输入框索位置, event 则是输入对应的 change 事件对象, 通过它来获取输入值
  • 特别说明, 本文验证码都是数字, 所以在函数内部还需要针对输入内容进行校验, 只允许输入数字 0~9
  • 函数内还有一个特殊处理逻辑, 就是当我们输入有效值后, 需要将鼠标光标聚焦到下一个输入框, 如此用户就可以连续进行输入了, 至于实现方法很简单, 这里直接调用 inputsRef 中对应输入框 DOM 节点的 focus 方法即可
  • 最后我们调用, setCodes 修改状态 codes, 这样输入框的值才能动态的修改
const handleChange = useCallback((index, event) => {
  const currentValue = event.target.value.match(/[0-9]{1}/)
    ? event.target.value
    : '';

  // 如果输入有效值, 则自动聚焦到下一个输入框
  if (currentValue) {
    inputsRef.current[index + 1]?.focus();
  }

  setCodes((pre) => {
    const newData = [...pre];
    newData[index] = currentValue;
    return newData;
  });
}, []);

最为为每个输入框绑定 onChange 事件, 主要这里使用了 bind 来绑定 index:

<div>
  {codes.map((value, index) => (
    <input
      ...
+     onChange={handleChange.bind(null, index)}
      ref={(ele) => (inputsRef.current[index] = ele)}
    />
  ))}
</div>

最后效果如下: 输入验证码, 光标自动跳转到下一个输入框

四、删除处理

上文我们完成验证码的输入, 但是在输入过程中, 难免会输入错误的数字, 所以就需要实现删除验证码的能力, 需求如下:

  • 当我们按下 删除键
  • 如果当前输入框有值, 则删除当前输入框中的内容
  • 如果当前输入框没有值, 则删除上一个输入框内容, 并且聚焦到上一个输入框

需求其实已经很明确了, 我只需要通过 onKeyDown 来监听键盘按下事件, 从而判断用户是否按下 删除键, 如果按下 删除键 则按照需求逻辑进行编码即可, 具体代码如下:

  • 函数接收两个参数 indexevent, index 表示当前光标所在的输入框索引位置, event 则是事件对象
  • 通过 event.key 的值来确定是否按下 删除键(Backspace), 如果不是, 则不进行任何处理
  • 剩下就按需求来, 如果当前输入框有值则清除当前输入框内容
  • 如果当前输入框没值, 则清除上一个输入框内容, 并且将光标移到上一个输入框中, 这里还需要考虑下边界情况, 如果当前输入框已经是第一个了, 就无需进行任何处理
const handleDelete = useCallback((index, event) => {
  const { key } = event;

  // 是否按下删除键, 否提前结束
  if (key !== 'Backspace') {
    return;
  }

  // 1. 如果当前输入框有值, 则删除当前输入框内容
  if (codes[index]) {
    setCodes((pre) => {
      const newData = [...pre];
      newData[index] = '';
      return newData;
    });
  } else if (index > 0) {
    // 2. 如果当前输入框没有值(考虑下边界的情况 index === 0): 则删除上一个输入框内容, 并且光标聚焦到上一个输入框
    setCodes((pre) => {
      const newData = [...pre];
      newData[index - 1] = '';
      return newData;
    });
    inputsRef.current[index - 1].focus();
  }
}, [codes]);

最后为每个输入框绑定 onKeyDown 事件, 主要这里使用了 bind 来绑定 index:

<div>
  {codes.map((value, index) => (
    <input
      ...
+     onKeyDown={handleDelete.bind(null, index)}
      onChange={handleChange.bind(null, index)}
      ref={(ele) => (inputsRef.current[index] = ele)}
    />
  ))}
</div>

最后效果如下: 输入验证码后, 按下删除键, 能够连续删除验证码内容

五、粘贴处理

在大部分情况下, 我们都是直接复制验证码然后直接黏贴使用, 所以我们接下来来实现的功能就是:

  • 允许在任意输入框黏贴数据
  • 自动将剪切板的数字回填到输入框中
  • 这里不做过多的处理, 不管光标在哪个位置, 都从第一个输入框开始填充数字
  • 注意的是, 这里光标还需要自动聚焦到最后一个输入框内容为空的位置

具体实现代码如下:

  • 通过 event.clipboardData.getData 获取到剪切板内容
  • 过滤掉剪切板中非数值部分内容
  • 生成新状态 codes: 先创建了一长度为 6 的数组, 并使用剪切板的数字就行填充, 不够的用空字符进行填充, 最后使用 setCodes 来修改状态值
  • 光标位置修改, 根据剪切板数字长度来进行计算
const handlePaste = useCallback((event) => {
  const pastedValue = event.clipboardData.getData('Text'); // 读取剪切板数据
  const pastNum = pastedValue.replace(/[^0-9]/g, ''); // 去除数据中非数字部分, 只保留数字

  // 重新生成 codes: 6 位, 每一位取剪切板对应位置的数字, 没有则置空
  const newData = Array.from(
    { length: 6 },
    (_, index) => pastNum.charAt(index) || '',
  );

  setCodes(newData); // 修改状态 codes

  // 光标要聚焦的输入框的索引, 这里取 pastNum.length 和 5 的最小值即可, 当索引为 5 就表示最后一个输入框了
  const focusIndex = Math.min(pastNum.length, 5);
  inputsRef.current[focusIndex]?.focus();
}, []);

最后为每个输入框绑定 onPaste(黏贴) 事件

<input
  ...
  onPaste={handlePaste}
/>

最后效果如下: 光标聚焦在任意输入框, 进行黏贴后, 即可自动用剪切板内的数字来填充输入框

六、第一阶段完成

到此整体功能已经差不多了, 下面是目前为止完整的代码(删除了 CSS 部分)

import React, { useState, useRef, useCallback } from 'react';

export default () => {
  const [codes, setCodes] = useState(Array.from({ length: 6 }, () => ''));
  const inputsRef = useRef([]);

  const handleChange = useCallback((index, event) => {
    const currentValue = event.target.value.match(/[0-9]{1}/)
      ? event.target.value
      : '';

    // 如果输入有效值, 则自动聚焦到下一个输入框
    if (currentValue) {
      inputsRef.current[index + 1]?.focus();
    }

    setCodes((pre) => {
      const newData = [...pre];
      newData[index] = currentValue;
      return newData;
    });
  }, []);

  const handleDelete = useCallback((index, event) => {
    const { key } = event;

    // 是否按下删除键, 否提前结束
    if (key !== 'Backspace') {
      return;
    }

    // 1. 如果当前输入框有值, 则删除当前输入框内容
    if (codes[index]) {
      setCodes((pre) => {
        const newData = [...pre];
        newData[index] = '';
        return newData;
      });
    } else if (index > 0) {
      // 2. 如果当前输入框没有值(考虑下边界的情况 index === 0): 则删除上一个输入框内容, 并且光标聚焦到上一个输入框
      setCodes((pre) => {
        const newData = [...pre];
        newData[index - 1] = '';
        return newData;
      });
      inputsRef.current[index - 1].focus();
    }
  }, [codes]);

  const handlePaste = useCallback((event) => {
    const pastedValue = event.clipboardData.getData('Text'); // 读取剪切板数据
    const pastNum = pastedValue.replace(/[^0-9]/g, ''); // 去除数据中非数字部分, 只保留数字

    // 重新生成 codes: 6 位, 每一位取剪切板对应位置的数字, 没有则置空
    const newData = Array.from(
      { length: 6 },
      (_, index) => pastNum.charAt(index) || '',
    );

    setCodes(newData); // 修改状态 codes

    // 光标要聚焦的输入框的索引, 这里取 pastNum.length 和 5 的最小值即可, 当索引为 5 就表示最后一个输入框了
    const focusIndex = Math.min(pastNum.length, 5);
    inputsRef.current[focusIndex]?.focus();
  }, []);

  return (
    <div>
      {codes.map((value, index) => (
        <input
          type="text"
          key={index}
          value={value}
          maxLength={1}
          onPaste={handlePaste}
          onKeyDown={handleDelete.bind(null, index)}
          onChange={handleChange.bind(null, index)}
          ref={(ele) => (inputsRef.current[index] = ele)}
        />
      ))}
    </div>
  );
};

基本功能有了, 下面我们对组件进行简单的封装、优化....

七、暴露 onChange 事件

这里我们希望父组件可以通过 onValueChange 来监听到内部状态 codes 的变更, 做法就很简单了:

  • 抽离一个通过方法 resetCodes, 修改状态的地方全部使用 resetCodes 方法
  • resetCodes 方法内部则是调用 setCodes 方法修改 codes 同时调用父组件传进来的 onValueChange 方法
  • resetCodes 支持传一个数组进来, 也可以是一个 index 一个 value; 这么做的原因主要是为了支持不同场景下修改状态 codes 的需求
// 修改状态 codes
const resetCodes = useCallback((index, value) => {
  setCodes((pre) => {
    let newData = [...pre];

    if (Array.isArray(index)) {
      newData = index;
    }

    if (typeof index === 'number') {
      newData[index] = value;
    }

    onValueChange?.(newData.join(''));

    return newData;
  });
}, [onValueChange]);

最后还需要将代码里调用 setCodes 的地方改为 resetCodes, 这里就不做演示了; 修改完成之后, 我们就可以通过 onValueChange 监听到组件内部 codes 的变更了

<AuthCode onValueChange={(codes) => console.log(codes)} />

最后效果如下:

八、暴露 onComplete 事件

这里我们还希望在输入完所有验证码后, 能够被组件外部监听到, 这样就可以直接拿到完整的验证码向后端服务发起校验....

其实有了上面的基础, 我们可以直接在 resetCodes 中进行处理: 在修改状态 codes 前判断下所有验证码是否都已经输入, 如果已全部输入则调用父组件的 onComplete 事件

// 修改状态 codes
const resetCodes = useCallback((index, value) => {
  setCodes((pre) => {
    let newData = [...pre];

    if (Array.isArray(index)) {
      newData = index;
    }

    if (typeof index === 'number') {
      newData[index] = value;
    }

+   // 处理 onComplete
+   if (newData.every(Boolean) && onComplete) {
+     onComplete(newData.join(''));
+   }

    onValueChange?.(newData.join(''));

    return newData;
  });
+ }, [onValueChange, onComplete]);

接下来我们就可以在验证码全部输入后, 通过 onComplete 监听到

<AuthCode onComplete={(codes) => console.log(codes)} />

最后效果如下:

九、自动聚焦

这个需求就很简单咯, 就是希望组件在初始化时可以将鼠标光标自动聚焦到第一个输入框, 这样用户就可以直接进行输入, 完成验证码的校验!!!

实现方法就更简单, 直接在 useEffect 中调用第一个输入框的 DOM 节点的原生 focus 方法即可

useEffect(() => {
  inputsRef.current[0].focus();
}, []);

十、聚焦时选中输入框内容

下面我们希望能够在输入框聚焦情况下, 能够自动选中输入框的内容, 这样的话就可以直接输入内容, 而不是先删除再输入内容!!

实现方法很简单:

  • 通过 onFocus 事件来实现, 监听 Focus(获取焦点) 事件
  • 然后在事件处理函数内调用事件 select 方法来选中输入框的内容
const handleOnFocus = useCallback((e) => {
  e.target.select();
}, []);

最后效果如下:

十一、暴露外面接口

最后我们希望父组件可以通过 ref 来获取到一些组件内部预设好的方法, 比如自动获取焦点、清空所有输入框内容等等

如下代码使用 forwardRef 配合 useImperativeHandle 完成 ref 的绑定

export default forwardRef((props, ref) => {
  // ...
  useImperativeHandle(ref, () => ({
    // 获取焦点
    focus: (index = 0) => {
      if (inputsRef.current) {
        inputsRef.current[index].focus();
      }
    },
    // 清空内容
    clear: () => {
      resetCodes(codes.map(() => ''));
    },
  }));
  // ...
}

调用方法如下所示:

export default () => {
  const ref = useRef();
  return (
    <>
      <Com ref={ref} />
      <Button onClick={() => ref.current?.clear()}>
        清空
      </Button>
    </>
  );
};

最后效果如下:

十二、后续

到此基本差不多了, 剩下更多的可能是组件的封装上的事情, 比如:

  • 允许设置默认值
  • 支持双向绑定
  • 支持设置验证码长度
  • 支持设置验证码规则(纯数字、纯字母、字母数字混合)
  • 支持设置 input 参数(比如 placeholder 等等)
  • ...

以上就是基于React封装一个验证码输入控件的详细内容,更多关于React封装验证码输入控件的资料请关注程序员之家其它相关文章!

相关文章

  • React利用lazy+Suspense实现路由懒加载

    React利用lazy+Suspense实现路由懒加载

    这篇文章主要为大家详细介绍了React如何利用lazy+Suspense实现路由懒加载,文中的示例代码简洁易懂,感兴趣的小伙伴可以跟随小编一起了解一下
    2023-06-06
  • React-Router v6实现页面级按钮权限示例详解

    React-Router v6实现页面级按钮权限示例详解

    这篇文章主要介绍了使用 reac+reactRouter来实现页面级的按钮权限功能,这篇文章分三部分,实现思路、代码实现、踩坑记录,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2023-10-10
  • react分页显示数据的方法

    react分页显示数据的方法

    分页在很多地方都可以用到,本文主要实现了react分页显示,主要使用三个组件,父组件listBox、列表组件List、按钮组件PageButton,感兴趣的可以了解一下
    2021-08-08
  • react国际化化插件react-i18n-auto使用详解

    react国际化化插件react-i18n-auto使用详解

    这篇文章主要介绍了react国际化化插件react-i18n-auto使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • react-native动态切换tab组件的方法

    react-native动态切换tab组件的方法

    在APP中免不了要使用tab组件,有的是tab切换,也有的是tab分类切换.这篇文章主要介绍了react-native动态切换tab组件的方法,非常具有实用价值,需要的朋友可以参考下
    2018-07-07
  • React-View-UI组件库封装Loading加载中源码

    React-View-UI组件库封装Loading加载中源码

    这篇文章主要介绍了React-View-UI组件库封装Loading加载样式,主要包括组件介绍,组件源码及组件测试源码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06
  • React RenderProps模式超详细讲解

    React RenderProps模式超详细讲解

    render props是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的技术。简单来说,给一个组件传入一个prop,这个props是一个函数,函数的作用是用来告诉这个组件需要渲染什么内容,那么这个prop就成为render prop
    2022-11-11
  • react-diagram 序列化Json解读案例分析

    react-diagram 序列化Json解读案例分析

    今天带来大家学习react-diagram 序列化Json解读的相关知识,本文通过多种案例给大家分析序列化知识,通过图文并茂的形式给大家介绍的非常详细,感兴趣的朋友一起看看吧
    2021-05-05
  • React使用Canvas绘制大数据表格的实例代码

    React使用Canvas绘制大数据表格的实例代码

    之前一直想用Canvas做表格渲染的,最近发现了一个很不错的Canvas绘图框架Leafer,api很友好就试着写了一下,文中有详细的代码示例供大家参考,感兴趣的小伙伴可以自己动手试试
    2023-09-09
  • React使用PropTypes实现类型检查功能

    React使用PropTypes实现类型检查功能

    这篇文章主要介绍了React高级指引中使用PropTypes实现类型检查功能的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-02-02

最新评论

?


http://www.vxiaotou.com