react如何實現一個密碼強度檢測器詳解
前言
密碼強度文件校驗器; 註冊帳號的時候我們需要對用戶當前的密碼強度進行一個評估,這個過程我們需要做一個檢測器,最好寫的靈活點,這樣方便產品修改規則。
先看下效果吧~~ 下面是截圖對應的狀態
使用
1 參數傳遞
const PasswordForce = passwordForce({ inputValue, className: ‘password-force’, });
2 使用
<PasswordForce.View />
3 校驗
檢測是否超出字符PasswordForce.invaildWord
實現例子
我們配置antd實現下密碼輸入框上面綁定一個提示器吧
1,2都是不需要改的,但是實際我們需要監聽input的值然後設置值。於是我們可以定義一個監聽修改value的函數
const [inputValue, setInputValue] = useState(''); const passwordChange = (value: string) => { setInputValue(value); }; const onPasswordInput = (e: any) => { passwordChange(e?.target?.value || ''); };
然後綁定即可,綁定好瞭我們就可以正常顯示瞭,但是,如果輸入瞭非法字符,這時候我們需要通過攔截器攔截。
<Form.Item ... rules={[ { required: true, message: 'Password not empty', }, ({ getFieldValue }) => ({ validator(_, value) { passwordChange(value); if (PasswordForce.invaildWord) { return Promise.reject( new Error('Password contains invalid characters.'), ); } return Promise.resolve(); }, }), ]} ...
好瞭,使用片結束,我們實現下吧。
組件編寫
編寫組件
import { getRuleMatchResult, IpasswordForce, IpasswordRule, isMatchForceResultConfig, matchResultConfig, passwordBreakKey, } from '@/utils/passwordStrengthChecker'; import React, { CSSProperties } from 'react'; import { useEffect } from 'react'; import { useState } from 'react'; import styled from 'styled-components'; interface props { inputValue: string; color?: string; style?: CSSProperties; className?: string; customRule?: IpasswordRule[]; } enum ForceMap { high = 'High', middle = 'Mid', low = 'Low', } const boolNumSum = (list: boolean[]) => list.reduce<number>( (previousValue, currentValue) => currentValue ? previousValue + 1 : previousValue, 0, ); const passwordForce: (props: props) => { View: React.FC; invaildWord: boolean; force: IpasswordForce; } = ({ inputValue, style = {}, className, customRule = [] }) => { const [force, setforce] = useState<IpasswordForce>(false); const [invaildWord, setIsInvaildWord] = useState(false); const inputValueLen = inputValue?.length || 0; const setData = () => { setforce(false); const isFirstWordUp = inputValue[0] === inputValue[0].toLocaleUpperCase(); const ruleRsult = getRuleMatchResult(customRule, inputValue, undefined, ''); const matchNum = boolNumSum(ruleRsult.list.map((e) => e[passwordBreakKey])); const matchResultConfig: matchResultConfig[] = [ { min: 0, max: 32, matchNum: 1, value: 'low' }, { min: 7, max: 32, matchNum: 2, value: 'middle' }, { min: 7, max: 32, matchNum: 3, value: 'middle' }, { min: 15, max: 32, matchNum: 3, value: 'high', need: isFirstWordUp }, ]; setIsInvaildWord(ruleRsult.invaildWord); matchResultConfig.forEach((config) => { isMatchForceResultConfig(config, matchNum, inputValueLen) && setforce(config.value); }); }; useEffect(() => { inputValue ? setData() : setforce(false); }, [inputValue]); return { View: () => force ? ( <PasswordForceWrap {...{ style, className }}> {ForceMap[force]} </PasswordForceWrap> ) : ( <></> ), invaildWord, force, }; }; export default passwordForce; const PasswordForceWrap = styled.span` color: ${({ color }) => color ?? '#000'}; `;
數據結構解析
- list 規則的集合,每一個規則都有是否匹配到和規則名及已規則數據本身。
- map 就是方便直接獲取對應規則的數據。
- matchCount 就是匹配到的字符數
- invaildWord 可以根據這個來判斷是否有非法字符(超過規則本身規定的字符)
流程解析
這個其實就兩個流程
- 根據輸入的值和規則獲取處理後的數據,獲得的數據結構如上面所示。
- 然後再編寫具體符合業務需求的config數據交給isMatchForceResultConfig函數去匹配設置強度
嗯。 業務代碼差不多就這麼多瞭。 然後裡面關於依賴那就是屬於基本不會改動的代碼,基於下面底層的文件,在業務代碼我們可以配置出很復雜的校驗器,這部分代碼我們可以在其他文件上實現。
底層代碼解析
讓我們來康康吧。
下面是純ts代碼,可以運行任意框架哦。
passwordStrengthChecker.ts
import { numberList, specialList, wordList } from './constants'; type map = <U, T>(opstion: { array: U[]; range: number; matchList: T[]; tokenMap: (updateItem: T, token: U, index: number) => T; breakKey?: string; arrayMap?: (item: U, index: number) => void; }) => T[]; /** * match array and set */ export const setArrayMatch: map = ({ array, range, matchList, breakKey, tokenMap, arrayMap, }) => { const tokenLen = array.length; for (let tokenIndex = tokenLen - 1; tokenIndex >= 0; tokenIndex--) { const arrayToken = array[tokenIndex]; arrayMap && arrayMap(arrayToken, tokenIndex); for (let findIndex = range - 1; findIndex >= 0; findIndex--) { matchList = matchList.map((item) => tokenMap(item, arrayToken, findIndex), ); } if (breakKey && !matchList.map((e) => (e as any)[breakKey]).includes(false)) break; } return matchList; }; export const passwordBreakKey = 'isMatch'; export type IpasswordRule = { list: string[]; isMatch: boolean; name: string; }; export const defaultPasswordRuleList = [ { name: 'special', list: specialList }, { name: 'num', list: numberList }, { name: 'word', list: wordList }, ]; type PickValue<T, K extends keyof T> = T[K]; export const getRuleMatchResult: ( customRule: IpasswordRule[], inputValue: string, disableDefaultRule?: boolean, breakKey?: string, ) => { list: IpasswordRule[]; map: Map<PickValue<IpasswordRule, 'name'>, boolean>; matchCount: number; invaildWord: boolean; } = (customRule, inputValue, disableDefaultRule = true, breakKey) => { let ruleList = [ ...(disableDefaultRule ? defaultPasswordRuleList : []), ...customRule, ].map((item) => ({ ...item, [passwordBreakKey]: false })); const range = Math.max(...ruleList.map((ruleItem) => ruleItem.list.length)); let matchCount = 0; ruleList = setArrayMatch<string, IpasswordRule>({ array: inputValue.split(''), range, matchList: ruleList, // not breakKey full match breakKey: breakKey === void 0 ? passwordBreakKey : breakKey, tokenMap: (ruleItem, inputToken, findIndex) => { const match = ruleItem?.list[findIndex] === inputToken; if (match) { matchCount++; return { ...ruleItem, isMatch: true }; } return ruleItem; }, }); return { list: ruleList, map: new Map(ruleList.map((e) => [e.name, e[passwordBreakKey]])), matchCount, // 想要獲取這個值,必須breakKey設置為空字符,如果提前退出會導致提前中止條件 // To get this value, breakkey must be set to null string invaildWord: matchCount !== inputValue.length, }; }; export const isMatchForceResultConfig = ( config: matchResultConfig, matchNum: number, inputValueLen: number, ) => { return ( matchNum === config.matchNum && inputValueLen >= config.min && inputValueLen <= config.max && (config.need !== undefined ? config.need : true) ); }; export type matchResultConfig = { min: number; max: number; matchNum: number; value: IpasswordForce; need?: boolean; back?: IpasswordForce; }; export type IpasswordForce = false | 'high' | 'middle' | 'low';
流程就是合並規則,一個是默認規則一個是自定義規則,如果自定義規則,那麼就會覆蓋默認規則。
從規則中,尋找規則數量最長的規則,因為等下我們遍歷的時候可以合並所有的規則,不管多少規則,其實遍歷數是區別不大的。
遍歷函數是個單獨的高階函數,可以自定義處理內部的邏輯,這時候,我們匹配到瞭之後對應的規則,激活對應規則的屬性,並累計匹配到的字符。
最後正常全部匹配到瞭就應該中止遍歷,但是有一個情況是不能中止的,那就是需要判斷是否有非法字符。
最後這個函數把處理過的數據丟給上層組件,流程就是這樣
在數據拋出的過程中,有些場景可能需要對對應規則的數據進行特殊處理,但是如果是array結構就很不方便,於是拋出的數據應該分為list和map類型,上層應用想要獲取對應規則的情況可以map.get(規則名稱)來操作
constants.ts
export const specialList = ["~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "=", "-", "/", ",", ".", "?", "<", ">", ";", ":", "[", "]", "{", "}", "|", "\\"]; export const numberList = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']; export const wordList = ["q", "a", "z", "w", "s", "x", "e", "d", "c", "r", "f", "v", "t", "g", "b", "y", "h", "n", "u", "j", "m", "i", "k", "o", "l", "p", "Q", "A", "Z", "W", "S", "X", "E", "D", "C", "R", "F", "V", "T", "G", "B", "Y", "H", "N", "U", "J", "M", "I", "K", "O", "L", "P"];
其他
很多人可能會有疑問,一個代碼檢測器有必要搞這麼復雜嗎,直接正則不好嗎。其實從實用角度來說,確實正則更方便點,但是有時候我們不想要循規蹈矩,或者想要手動編碼的快感,或者要從無聊中代碼獲得更多的可玩性等,於是編寫一個看起來挺復雜的代碼,不過把底層的封裝住,然後保留靈活性,在業務層裡面盡量簡單點,其實也不是不可以試試的,但是也會在review的時候被懟,各位看官拷貝請註意哈,時間緊迫或者編碼能力不強的不建議使用本代碼,出問題本人概不負責。
總結
到此這篇關於react如何實現一個密碼強度檢測器的文章就介紹到這瞭,更多相關react密碼強度檢測器內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Javascript類型轉換詳解
- react中使用forEach或map兩種方式遍歷數組
- JavaScript的11個小技巧整理
- JavaScript小技巧帶你提升你的代碼技能
- 前端常用的js函數方法