亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

基于React封裝一個(gè)驗(yàn)證碼輸入控件

 更新時(shí)間:2024年03月27日 10:27:28   作者:墨淵君  
郵箱、手機(jī)驗(yàn)證碼輸入是許多在線服務(wù)和網(wǎng)站常見的安全驗(yàn)證方式之一,本文主要來和大家討論一下如何使用React封裝一個(gè)驗(yàn)證碼輸入控件,感興趣的可以了解下

引言

郵箱、手機(jī)驗(yàn)證碼輸入是許多在線服務(wù)和網(wǎng)站常見的安全驗(yàn)證方式之一。這種方式通常用于確保用戶在進(jìn)行敏感操作(例如注冊(cè)、修改密碼、重置密碼等)時(shí)的身份驗(yàn)證。

最近在做項(xiàng)目剛好有驗(yàn)證碼相關(guān)的需求, 本著不重復(fù)造輪子的原則, 一頓 Google 試圖找到一個(gè)現(xiàn)成的組件, 奈何找了一圈都沒找到滿意的, 要么就是交互感覺不太合理、要么就是基本停止維護(hù)了的!!

最后沒辦法就自己造一個(gè)了, 這里主要參考了 react-auth-code-input, 而本文則是整個(gè)思路開發(fā)流程的記錄!!

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

本文完整源碼可查閱: coding/blog/AuthCodes

一、需求描述

開始前我們先梳理下一般驗(yàn)證碼輸入控件的常規(guī)需求有哪些:

  • 假設(shè)我們驗(yàn)證碼有 6 位, 則我們需要有 6 個(gè)輸入框, 每個(gè)輸入框只允許輸入一位數(shù)字(這里假設(shè)驗(yàn)證碼都是數(shù)字組成)
  • 在輸入驗(yàn)證碼過程中, 可連續(xù)進(jìn)行輸入、刪除等操作
  • 支持黏貼復(fù)制的內(nèi)容
  • ...

二、布局

在開始前我們先來完成基本的布局, 如下代碼所示:

  • 聲明狀態(tài) codes 用于存儲(chǔ)每個(gè)驗(yàn)證碼, 也就是每個(gè)輸入框的值, 這里我將 codes 設(shè)置為一個(gè)數(shù)組, 方便后面修改每個(gè)位置的驗(yàn)證碼
  • 假設(shè)我們驗(yàn)證碼長度為 6 位, 所以這里我為 codes 默認(rèn)值了一個(gè)長度為 6 的數(shù)組, 數(shù)組每個(gè)初始值為空字符串
  • 然后我們通過 codes.map 渲染出所有輸入框
  • 最后我們還聲明了 inputsRef 來存儲(chǔ)所有輸入框的 DOM 節(jié)點(diǎn), 我們后面需要通過它來調(diào)用原生 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>
  );
};

這里我們對(duì)輸入框設(shè)置了一些基本的樣式

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

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

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

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

三、動(dòng)態(tài)綁定(處理 onChange 事件)

上文只是完成了基本的布局, 并且輸入框 value 和狀態(tài) codes 內(nèi)的值綁定在了一起, 這里輸入框輸入值會(huì)發(fā)現(xiàn)并沒有生效, 那是因?yàn)闋顟B(tài) codes 沒有被修改!

下面我們?yōu)檩斎肟蛟O(shè)置 onChange 事件, 在輸入框輸入值時(shí)動(dòng)態(tài)的修改狀態(tài) codes 中對(duì)應(yīng)位置的值!!

下面是 onChange 事件的處理函數(shù):

  • 兩個(gè)參數(shù), indexevent, 正如命名所示, index 對(duì)應(yīng)輸入框索位置, event 則是輸入對(duì)應(yīng)的 change 事件對(duì)象, 通過它來獲取輸入值
  • 特別說明, 本文驗(yàn)證碼都是數(shù)字, 所以在函數(shù)內(nèi)部還需要針對(duì)輸入內(nèi)容進(jìn)行校驗(yàn), 只允許輸入數(shù)字 0~9
  • 函數(shù)內(nèi)還有一個(gè)特殊處理邏輯, 就是當(dāng)我們輸入有效值后, 需要將鼠標(biāo)光標(biāo)聚焦到下一個(gè)輸入框, 如此用戶就可以連續(xù)進(jìn)行輸入了, 至于實(shí)現(xiàn)方法很簡單, 這里直接調(diào)用 inputsRef 中對(duì)應(yīng)輸入框 DOM 節(jié)點(diǎn)的 focus 方法即可
  • 最后我們調(diào)用, setCodes 修改狀態(tài) codes, 這樣輸入框的值才能動(dòng)態(tài)的修改
const handleChange = useCallback((index, event) => {
  const currentValue = event.target.value.match(/[0-9]{1}/)
    ? event.target.value
    : '';

  // 如果輸入有效值, 則自動(dòng)聚焦到下一個(gè)輸入框
  if (currentValue) {
    inputsRef.current[index + 1]?.focus();
  }

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

最為為每個(gè)輸入框綁定 onChange 事件, 主要這里使用了 bind 來綁定 index:

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

最后效果如下: 輸入驗(yàn)證碼, 光標(biāo)自動(dòng)跳轉(zhuǎn)到下一個(gè)輸入框

四、刪除處理

上文我們完成驗(yàn)證碼的輸入, 但是在輸入過程中, 難免會(huì)輸入錯(cuò)誤的數(shù)字, 所以就需要實(shí)現(xiàn)刪除驗(yàn)證碼的能力, 需求如下:

  • 當(dāng)我們按下 刪除鍵
  • 如果當(dāng)前輸入框有值, 則刪除當(dāng)前輸入框中的內(nèi)容
  • 如果當(dāng)前輸入框沒有值, 則刪除上一個(gè)輸入框內(nèi)容, 并且聚焦到上一個(gè)輸入框

需求其實(shí)已經(jīng)很明確了, 我只需要通過 onKeyDown 來監(jiān)聽鍵盤按下事件, 從而判斷用戶是否按下 刪除鍵, 如果按下 刪除鍵 則按照需求邏輯進(jìn)行編碼即可, 具體代碼如下:

  • 函數(shù)接收兩個(gè)參數(shù) indexevent, index 表示當(dāng)前光標(biāo)所在的輸入框索引位置, event 則是事件對(duì)象
  • 通過 event.key 的值來確定是否按下 刪除鍵(Backspace), 如果不是, 則不進(jìn)行任何處理
  • 剩下就按需求來, 如果當(dāng)前輸入框有值則清除當(dāng)前輸入框內(nèi)容
  • 如果當(dāng)前輸入框沒值, 則清除上一個(gè)輸入框內(nèi)容, 并且將光標(biāo)移到上一個(gè)輸入框中, 這里還需要考慮下邊界情況, 如果當(dāng)前輸入框已經(jīng)是第一個(gè)了, 就無需進(jìn)行任何處理
const handleDelete = useCallback((index, event) => {
  const { key } = event;

  // 是否按下刪除鍵, 否提前結(jié)束
  if (key !== 'Backspace') {
    return;
  }

  // 1. 如果當(dāng)前輸入框有值, 則刪除當(dāng)前輸入框內(nèi)容
  if (codes[index]) {
    setCodes((pre) => {
      const newData = [...pre];
      newData[index] = '';
      return newData;
    });
  } else if (index > 0) {
    // 2. 如果當(dāng)前輸入框沒有值(考慮下邊界的情況 index === 0): 則刪除上一個(gè)輸入框內(nèi)容, 并且光標(biāo)聚焦到上一個(gè)輸入框
    setCodes((pre) => {
      const newData = [...pre];
      newData[index - 1] = '';
      return newData;
    });
    inputsRef.current[index - 1].focus();
  }
}, [codes]);

最后為每個(gè)輸入框綁定 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>

最后效果如下: 輸入驗(yàn)證碼后, 按下刪除鍵, 能夠連續(xù)刪除驗(yàn)證碼內(nèi)容

五、粘貼處理

在大部分情況下, 我們都是直接復(fù)制驗(yàn)證碼然后直接黏貼使用, 所以我們接下來來實(shí)現(xiàn)的功能就是:

  • 允許在任意輸入框黏貼數(shù)據(jù)
  • 自動(dòng)將剪切板的數(shù)字回填到輸入框中
  • 這里不做過多的處理, 不管光標(biāo)在哪個(gè)位置, 都從第一個(gè)輸入框開始填充數(shù)字
  • 注意的是, 這里光標(biāo)還需要自動(dòng)聚焦到最后一個(gè)輸入框內(nèi)容為空的位置

具體實(shí)現(xiàn)代碼如下:

  • 通過 event.clipboardData.getData 獲取到剪切板內(nèi)容
  • 過濾掉剪切板中非數(shù)值部分內(nèi)容
  • 生成新狀態(tài) codes: 先創(chuàng)建了一長度為 6 的數(shù)組, 并使用剪切板的數(shù)字就行填充, 不夠的用空字符進(jìn)行填充, 最后使用 setCodes 來修改狀態(tài)值
  • 光標(biāo)位置修改, 根據(jù)剪切板數(shù)字長度來進(jìn)行計(jì)算
const handlePaste = useCallback((event) => {
  const pastedValue = event.clipboardData.getData('Text'); // 讀取剪切板數(shù)據(jù)
  const pastNum = pastedValue.replace(/[^0-9]/g, ''); // 去除數(shù)據(jù)中非數(shù)字部分, 只保留數(shù)字

  // 重新生成 codes: 6 位, 每一位取剪切板對(duì)應(yīng)位置的數(shù)字, 沒有則置空
  const newData = Array.from(
    { length: 6 },
    (_, index) => pastNum.charAt(index) || '',
  );

  setCodes(newData); // 修改狀態(tài) codes

  // 光標(biāo)要聚焦的輸入框的索引, 這里取 pastNum.length 和 5 的最小值即可, 當(dāng)索引為 5 就表示最后一個(gè)輸入框了
  const focusIndex = Math.min(pastNum.length, 5);
  inputsRef.current[focusIndex]?.focus();
}, []);

最后為每個(gè)輸入框綁定 onPaste(黏貼) 事件

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

最后效果如下: 光標(biāo)聚焦在任意輸入框, 進(jìn)行黏貼后, 即可自動(dòng)用剪切板內(nèi)的數(shù)字來填充輸入框

六、第一階段完成

到此整體功能已經(jīng)差不多了, 下面是目前為止完整的代碼(刪除了 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
      : '';

    // 如果輸入有效值, 則自動(dòng)聚焦到下一個(gè)輸入框
    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;

    // 是否按下刪除鍵, 否提前結(jié)束
    if (key !== 'Backspace') {
      return;
    }

    // 1. 如果當(dāng)前輸入框有值, 則刪除當(dāng)前輸入框內(nèi)容
    if (codes[index]) {
      setCodes((pre) => {
        const newData = [...pre];
        newData[index] = '';
        return newData;
      });
    } else if (index > 0) {
      // 2. 如果當(dāng)前輸入框沒有值(考慮下邊界的情況 index === 0): 則刪除上一個(gè)輸入框內(nèi)容, 并且光標(biāo)聚焦到上一個(gè)輸入框
      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'); // 讀取剪切板數(shù)據(jù)
    const pastNum = pastedValue.replace(/[^0-9]/g, ''); // 去除數(shù)據(jù)中非數(shù)字部分, 只保留數(shù)字

    // 重新生成 codes: 6 位, 每一位取剪切板對(duì)應(yīng)位置的數(shù)字, 沒有則置空
    const newData = Array.from(
      { length: 6 },
      (_, index) => pastNum.charAt(index) || '',
    );

    setCodes(newData); // 修改狀態(tài) codes

    // 光標(biāo)要聚焦的輸入框的索引, 這里取 pastNum.length 和 5 的最小值即可, 當(dāng)索引為 5 就表示最后一個(gè)輸入框了
    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>
  );
};

基本功能有了, 下面我們對(duì)組件進(jìn)行簡單的封裝、優(yōu)化....

七、暴露 onChange 事件

這里我們希望父組件可以通過 onValueChange 來監(jiān)聽到內(nèi)部狀態(tài) codes 的變更, 做法就很簡單了:

  • 抽離一個(gè)通過方法 resetCodes, 修改狀態(tài)的地方全部使用 resetCodes 方法
  • resetCodes 方法內(nèi)部則是調(diào)用 setCodes 方法修改 codes 同時(shí)調(diào)用父組件傳進(jìn)來的 onValueChange 方法
  • resetCodes 支持傳一個(gè)數(shù)組進(jìn)來, 也可以是一個(gè) index 一個(gè) value; 這么做的原因主要是為了支持不同場(chǎng)景下修改狀態(tài) codes 的需求
// 修改狀態(tài) 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]);

最后還需要將代碼里調(diào)用 setCodes 的地方改為 resetCodes, 這里就不做演示了; 修改完成之后, 我們就可以通過 onValueChange 監(jiān)聽到組件內(nèi)部 codes 的變更了

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

最后效果如下:

八、暴露 onComplete 事件

這里我們還希望在輸入完所有驗(yàn)證碼后, 能夠被組件外部監(jiān)聽到, 這樣就可以直接拿到完整的驗(yàn)證碼向后端服務(wù)發(fā)起校驗(yàn)....

其實(shí)有了上面的基礎(chǔ), 我們可以直接在 resetCodes 中進(jìn)行處理: 在修改狀態(tài) codes 前判斷下所有驗(yàn)證碼是否都已經(jīng)輸入, 如果已全部輸入則調(diào)用父組件的 onComplete 事件

// 修改狀態(tài) 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]);

接下來我們就可以在驗(yàn)證碼全部輸入后, 通過 onComplete 監(jiān)聽到

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

最后效果如下:

九、自動(dòng)聚焦

這個(gè)需求就很簡單咯, 就是希望組件在初始化時(shí)可以將鼠標(biāo)光標(biāo)自動(dòng)聚焦到第一個(gè)輸入框, 這樣用戶就可以直接進(jìn)行輸入, 完成驗(yàn)證碼的校驗(yàn)!!!

實(shí)現(xiàn)方法就更簡單, 直接在 useEffect 中調(diào)用第一個(gè)輸入框的 DOM 節(jié)點(diǎn)的原生 focus 方法即可

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

十、聚焦時(shí)選中輸入框內(nèi)容

下面我們希望能夠在輸入框聚焦情況下, 能夠自動(dòng)選中輸入框的內(nèi)容, 這樣的話就可以直接輸入內(nèi)容, 而不是先刪除再輸入內(nèi)容!!

實(shí)現(xiàn)方法很簡單:

  • 通過 onFocus 事件來實(shí)現(xiàn), 監(jiān)聽 Focus(獲取焦點(diǎn)) 事件
  • 然后在事件處理函數(shù)內(nèi)調(diào)用事件 select 方法來選中輸入框的內(nèi)容
const handleOnFocus = useCallback((e) => {
  e.target.select();
}, []);

最后效果如下:

十一、暴露外面接口

最后我們希望父組件可以通過 ref 來獲取到一些組件內(nèi)部預(yù)設(shè)好的方法, 比如自動(dòng)獲取焦點(diǎn)、清空所有輸入框內(nèi)容等等

如下代碼使用 forwardRef 配合 useImperativeHandle 完成 ref 的綁定

export default forwardRef((props, ref) => {
  // ...
  useImperativeHandle(ref, () => ({
    // 獲取焦點(diǎn)
    focus: (index = 0) => {
      if (inputsRef.current) {
        inputsRef.current[index].focus();
      }
    },
    // 清空內(nèi)容
    clear: () => {
      resetCodes(codes.map(() => ''));
    },
  }));
  // ...
}

調(diào)用方法如下所示:

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

最后效果如下:

十二、后續(xù)

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

  • 允許設(shè)置默認(rèn)值
  • 支持雙向綁定
  • 支持設(shè)置驗(yàn)證碼長度
  • 支持設(shè)置驗(yàn)證碼規(guī)則(純數(shù)字、純字母、字母數(shù)字混合)
  • 支持設(shè)置 input 參數(shù)(比如 placeholder 等等)
  • ...

以上就是基于React封裝一個(gè)驗(yàn)證碼輸入控件的詳細(xì)內(nèi)容,更多關(guān)于React封裝驗(yàn)證碼輸入控件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 基于Node的React圖片上傳組件實(shí)現(xiàn)實(shí)例代碼

    基于Node的React圖片上傳組件實(shí)現(xiàn)實(shí)例代碼

    本篇文章主要介紹了基于Node的React圖片上傳組件實(shí)現(xiàn)實(shí)例代碼,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2017-05-05
  • 詳解React中的組件通信問題

    詳解React中的組件通信問題

    本篇文章中主要介紹了詳解React中的組件通信問題,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-07-07
  • 使用React?MUI庫實(shí)現(xiàn)用戶列表分頁功能

    使用React?MUI庫實(shí)現(xiàn)用戶列表分頁功能

    MUI是一款基于React的UI組件庫,可以方便地構(gòu)建美觀的用戶界面,使用MUI的DataTable組件和分頁器組件可以輕松實(shí)現(xiàn)用戶列表分頁功能,這篇文章使用MUI庫實(shí)現(xiàn)了用戶列表分頁功能,感興趣的同學(xué)可以參考下文
    2023-05-05
  • react-router browserHistory刷新頁面404問題解決方法

    react-router browserHistory刷新頁面404問題解決方法

    本篇文章主要介紹了react-router browserHistory刷新頁面404問題解決方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2017-12-12
  • react?redux的原理以及基礎(chǔ)使用講解

    react?redux的原理以及基礎(chǔ)使用講解

    這篇文章主要介紹了react?redux的原理以及基礎(chǔ)使用講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • React useMemo與useCallabck有什么區(qū)別

    React useMemo與useCallabck有什么區(qū)別

    useCallback和useMemo是一樣的東西,只是入?yún)⒂兴煌?,useCallback緩存的是回調(diào)函數(shù),如果依賴項(xiàng)沒有更新,就會(huì)使用緩存的回調(diào)函數(shù);useMemo緩存的是回調(diào)函數(shù)的return,如果依賴項(xiàng)沒有更新,就會(huì)使用緩存的return
    2022-12-12
  • React?Fiber構(gòu)建completeWork源碼解析

    React?Fiber構(gòu)建completeWork源碼解析

    這篇文章主要為大家介紹了React?Fiber構(gòu)建completeWork源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • 在console中打印React Fiber樹的操作步驟

    在console中打印React Fiber樹的操作步驟

    React Fiber 是 React 16 中引入的新的協(xié)調(diào)引擎或重寫的核心算法, 真針Fiber的一個(gè)重要的核心概念Fiber Node,這次主要的研究對(duì)象是: 如何從使用者/學(xué)習(xí) 者角度 在js 代碼上 拿到fiber 樹結(jié)構(gòu)的信息,,需要的朋友可以參考下
    2024-04-04
  • webpack4 + react 搭建多頁面應(yīng)用示例

    webpack4 + react 搭建多頁面應(yīng)用示例

    這篇文章主要介紹了webpack4 + react 搭建多頁面應(yīng)用示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-08-08
  • 簡易的redux?createStore手寫實(shí)現(xiàn)示例

    簡易的redux?createStore手寫實(shí)現(xiàn)示例

    這篇文章主要介紹了簡易的redux?createStore手寫實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10

最新評(píng)論