字節(jié)封裝React組件手機(jī)號(hào)自動(dòng)校驗(yàn)格式FormItem
Situation 背景
- 多人開(kāi)發(fā)的老項(xiàng)目里面,很多地方都寫(xiě)了驗(yàn)證手機(jī)格式的需求,代碼各有千秋、百花齊放
- 實(shí)現(xiàn):有的寫(xiě)在公共組件庫(kù)里,有的是單獨(dú)開(kāi)發(fā)局部組件支持,有的直接手寫(xiě)不復(fù)用,有的抽離正則到utils再引入
- 正則:正則校驗(yàn)也各有千秋,比如/^\d{11}/、/1\d10/、/1[2−9]\d9/、/^1\d{10}/、/^1[2-9]\d{9}/、/1\d10/、/1[2−9]\d9/、/^1(3[0-9]|4[01456879]|5[0-35-9]|6[2567]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$/等等。
- 長(zhǎng)度限制:有的地方限制maxLength=11,而有的地方?jīng)]限制
- 輸入格式:有的地方滿(mǎn)足334格式(181 2222 3333)手機(jī)號(hào)碼輸入, 而有的地方只允許輸入純數(shù)字
Target 目標(biāo)
- 實(shí)現(xiàn)一個(gè)易用的手機(jī)號(hào)碼公共組件
- 使用較為寬松的正則校驗(yàn)并與后端達(dá)成一致
- 不再限制輸入長(zhǎng)度但會(huì)報(bào)錯(cuò)提示
- 支持334的格式輸入,并自動(dòng)去除所有空格
- 寫(xiě)好參數(shù)說(shuō)明,方便開(kāi)發(fā)使用
- 盡可能的保持靈活,適用更多場(chǎng)景
Action 行動(dòng)
import type {
FormItemProps,
InputProps,
RulesProps,
} from '@arco-design/web-react';
import { Form, Input } from '@arco-design/web-react';
import { usePersistCallback } from '@byted/hooks';
import type { ReactNode } from 'react';
import { useMemo } from 'react';
export type VerifyInputProps = {
/** ?? 綁定屬性 */
field: string;
/** ?? 顯示標(biāo)簽文字 */
label: string;
/** 標(biāo)簽前面是否顯示必填*號(hào) */
labelRequired?: boolean;
/** ?? rules是否加入必填校驗(yàn)(為true時(shí)無(wú)需再設(shè)置labelRequired=true) */
phoneRequired?: boolean;
/** 除檢驗(yàn)手機(jī)號(hào)格式外其他規(guī)則,默認(rèn)添加正則檢驗(yàn)/^1[2-9]\d{9}$/ */
rules?: RulesProps<any>[];
/** 顯示提示文字 */
placeholder?: string;
/** 是否禁用input */
disabled?: boolean;
/** phone輸入框onChange事件 */
onChange?: InputProps['onChange'];
/** FormItem其他屬性 */
formItemProps?: FormItemProps;
/** Input其他屬性 */
inputProps?: InputProps;
};
/** 手機(jī)號(hào)表單(帶有 Form.Item) */
export const VerifyPhoneInput = (props: VerifyInputProps) => {
const {
field,
label,
placeholder,
rules = [],
labelRequired = false,
phoneRequired = false,
disabled,
onChange,
formItemProps,
inputProps,
} = props;
const resultRules = useMemo(() => {
const arr = [
{
validator(val: string | undefined, cb: (error?: ReactNode) => void) {
const realVal = (`${val}` as any).replaceAll(' ', '');
if (!val || /^1[2-9]\d{9}$/.test(realVal)) {
cb();
} else {
cb(`請(qǐng)輸入正確的${label}`);
}
},
},
...rules,
];
if (phoneRequired) {
arr.unshift({
validator(val: string | undefined, cb: (error?: ReactNode) => void) {
if (!val) {
cb(`請(qǐng)輸入${label}`);
} else {
cb();
}
},
});
}
return arr;
}, [rules, phoneRequired, label]);
const normalize = usePersistCallback((val: string | undefined) => {
if (val) {
const realVal = (`${val}` as any).replaceAll(' ', '');
if (val !== realVal) {
return realVal;
}
}
return val;
});
const resultInputProps = {
disabled,
onChange,
allowClear: true,
placeholder: placeholder || `請(qǐng)輸入${label}`,
...inputProps,
};
return (
<Form.Item
required={labelRequired || phoneRequired}
field={field}
label={label}
formatter={normalize}
rules={resultRules}
{...formItemProps}
>
<Input {...resultInputProps} />
</Form.Item>
);
};
- 定義入?yún)㈩?lèi)型VerifyInputProps,并按使用頻率對(duì)屬性排序并加以說(shuō)明
- 將是否必填從rules中移出變成phoneRequired參數(shù),錯(cuò)誤提示也根據(jù)label自動(dòng)拼接請(qǐng)輸入正確的${label},封裝復(fù)雜、暴露簡(jiǎn)單
- placeholder默認(rèn)根據(jù)label自動(dòng)拼接成(請(qǐng)輸入${label}),提高易用性
- 將FormItem其他屬性收斂到formItemProps中,方便開(kāi)發(fā)同學(xué)擴(kuò)展,提高通用性
- 默認(rèn)allowClear=true,可以通過(guò)inputProps進(jìn)行覆蓋
- 通過(guò)formatter自動(dòng)去掉所有空格,支持334格式
- 將Input其他屬性收斂到inputProps中,方便開(kāi)發(fā)同學(xué)擴(kuò)展,提高通用性
- 暴露rules,支持自定義遠(yuǎn)程驗(yàn)證等規(guī)則
- 暴露常用的disabled、onChange在最外層方便使用
Result 結(jié)果
- 已經(jīng)投入使用,測(cè)試符合預(yù)期,提供給其他同事使用,后續(xù)繼續(xù)完善
Review 復(fù)盤(pán)
- 問(wèn)題1:使用normalize去空格后,input下面的錯(cuò)誤驗(yàn)證并沒(méi)有重新觸發(fā)導(dǎo)致輸入框數(shù)據(jù)已經(jīng)是對(duì)的,而錯(cuò)誤提示還存在的現(xiàn)象。
- 解決:第一種方案將form傳入,在formatter之后通過(guò)form?.validate(field)來(lái)重新觸發(fā)驗(yàn)證;第二種方案將rules改為validator的實(shí)現(xiàn)方式手動(dòng)去空格。不希望引入更多參數(shù),最終采用第二種。
- 問(wèn)題2:正則格式到底怎么定義才能平衡正確性和普適性
- 解決:和后端協(xié)商使用適用目前所有可見(jiàn)場(chǎng)景下的最嚴(yán)格的驗(yàn)證格式/^1[2-9]\d{9}$/
- 問(wèn)題3:老代碼治理
- 解決:一次性將所有手機(jī)號(hào)碼相關(guān)的代碼全部改掉,需要測(cè)試介入帶來(lái)大量的回歸測(cè)試時(shí)間,而收益很小。所以新需求使用此組件,老需求改動(dòng)的時(shí)候再改成此組件的方案代價(jià)更小。
- 問(wèn)題4:還能再豐富一哈嗎?
- 支持151****2222的顯示與編輯時(shí)未修改不校驗(yàn),保證手機(jī)號(hào)私密性
- 問(wèn)題5:VerifyPhoneInput實(shí)際是個(gè)FormItem組件,且api與FormItem原本的有很大區(qū)別,會(huì)不會(huì)給使用者帶來(lái)心智負(fù)擔(dān)?
- 解決:
import type { FormItemProps, InputProps } from '@arco-design/web-react';
import { Form, Input } from '@arco-design/web-react';
import { usePersistCallback } from '@byted/hooks';
import type { ReactNode } from 'react';
import { useMemo } from 'react';
const defaultLabel = '手機(jī)號(hào)碼';
export type PhoneFormItemProps = {
/** ?? rules是否加入必填校驗(yàn)(為true時(shí)無(wú)需再設(shè)置labelRequired=true) */
phoneRequired?: boolean;
/** ?? rules是否加入正則校驗(yàn)(/^1[2-9]\d{9}$/),默認(rèn)true */
phoneFormatCheck?: boolean;
/** Input其他屬性 */
inputProps?: InputProps;
} & FormItemProps;
/** 手機(jī)號(hào)表單(帶有 Form.Item) */
export const PhoneFormItem = (props: PhoneFormItemProps) => {
const {
phoneRequired = false,
phoneFormatCheck = true,
inputProps,
...formItemProps
} = props;
const resultRules = useMemo(() => {
const arr = [...(formItemProps.rules || [])];
if (phoneFormatCheck) {
arr.unshift({
validator(val: string | undefined, cb: (error?: ReactNode) => void) {
const realVal = (`${val}` as any).replaceAll(' ', '');
if (!val || /^1[2-9]\d{9}$/.test(realVal)) {
cb();
} else {
cb(`請(qǐng)輸入正確的${formItemProps?.label || defaultLabel}`);
}
},
});
}
if (phoneRequired) {
arr.unshift({
validator(val: string | undefined, cb: (error?: ReactNode) => void) {
if (!val) {
cb(`請(qǐng)輸入${formItemProps.label || defaultLabel}`);
} else {
cb();
}
},
});
}
return arr;
}, [
formItemProps.rules,
formItemProps.label,
phoneFormatCheck,
phoneRequired,
]);
const normalize = usePersistCallback((val: string | undefined) => {
if (val) {
const realVal = (`${val}` as any).replaceAll(' ', '');
if (val !== realVal) {
return realVal;
}
}
return val;
});
const resultInputProps = {
allowClear: true,
...inputProps,
placeholder:
inputProps?.placeholder || `請(qǐng)輸入${formItemProps.label || defaultLabel}`,
};
return (
<Form.Item
normalize={normalize}
rules={resultRules}
{...formItemProps}
required={formItemProps.required || phoneRequired}
>
<Input {...resultInputProps} />
</Form.Item>
);
};
以前都是用vue,來(lái)字節(jié)之后天天react,感覺(jué)自己變菜了好多,又或許我原本就菜的離譜。
以上就是字節(jié)封裝React組件手機(jī)號(hào)自動(dòng)校驗(yàn)格式FormItem的詳細(xì)內(nèi)容,更多關(guān)于React封裝手機(jī)號(hào)校驗(yàn)格式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
react+antd.3x實(shí)現(xiàn)ip輸入框
這篇文章主要為大家詳細(xì)介紹了react+antd.3x實(shí)現(xiàn)ip輸入框,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
React Native實(shí)現(xiàn)進(jìn)度條彈框的示例代碼
本篇文章主要介紹了React Native實(shí)現(xiàn)進(jìn)度條彈框的示例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
React過(guò)渡動(dòng)畫(huà)組件基礎(chǔ)使用介紹
在開(kāi)發(fā)中,我們想要給一個(gè)組件的顯示和消失添加某種過(guò)渡動(dòng)畫(huà),可以很好的增加用戶(hù)體驗(yàn)。 當(dāng)然,我們可以通過(guò)原生的CSS來(lái)實(shí)現(xiàn)這些過(guò)渡動(dòng)畫(huà),這篇文章主要介紹了React過(guò)渡動(dòng)畫(huà)組件使用2022-09-09
React跨端動(dòng)態(tài)化之從JS引擎到RN落地詳解
這篇文章主要為大家介紹了React跨端動(dòng)態(tài)化之從JS引擎到RN落地,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09

