项目场景:
最近有一个H5需求是需要核销验证码,因为antd-mobile的版本过低(最新版本有),别的组件没有这个功能,自己封装了一个,这里记录一下,效果图如下

使用:
如果想要直接给input赋值,可以使用通过useImperativeHandle暴漏出来的 changeInput事件 codeInputRef&&codeInputRef.current.changeInput(“1234”);
const getCodeNum = useCallback((num) => {
//num 就是组件反回来的值
setCode(num);
}, []);
<PasscodeInput onChange={getCodeNum} ref={codeInputRef} />
代码如下:
PasscodeInput 组件
import React, {
memo,
useImperativeHandle,
forwardRef,
useState,
useRef,
useCallback,
} from "react";
import styles from "./PasscodeInput.less";
export default memo(
forwardRef(({ onChange }, ref) => {
const inputRef = useRef(null);
const fieldList = useRef(null);
const [inputVal, setInputVal] = useState("");
useImperativeHandle(ref, () => {
return {
changeInput: (val) => {
changeHandle({
target: {
value: val,
},
});
},
};
});
const calcCursorPosition = useCallback(() => {
// 这里需要获取ref的value值,不然onchange的时候会有异步问题
const length = inputRef.current.value.length;
if (length < 4) {
fieldList.current.children[length].classList.add(
styles["field-item-focus"]
);
}
}, [inputVal]);
const removeCursor = useCallback(() => {
let newArr = Array.from(fieldList.current.children);
newArr.forEach((item) => {
if (
Array.from(item["classList"]).some((r) =>
r.includes("field-item-focus")
)
) {
item.classList.remove(
Array.from(item["classList"]).find((r) =>
r.includes("field-item-focus")
)
);
}
});
}, [inputVal]);
const changeHandle = useCallback(
(e) => {
// 监听input输入事件,只支持输入数字,过滤非数字字符
let v = e.target.value.replace(/[^\d]/g, "");
v = v.length > 4 ? v.substr(0, 4) : v;
// 这里需要改变inputRef.currnet的值,不然input的值会超出四位
inputRef.current.value = v;
onChange && onChange(v); //传递给父组件
setInputVal(v);
// 考虑粘贴情况,循环赋值
// 移除旧光标
removeCursor();
// 计算新光标出现位置
calcCursorPosition();
},
[inputVal]
);
const blurHandle = useCallback(() => {
removeCursor();
}, [inputVal]);
const focusHandle = useCallback(() => {
calcCursorPosition();
}, [inputVal]);
return (
<div className={styles.inputBox}>
<div className={styles["field-list"]} ref={fieldList}>
<div className={styles["field-item"]}>{inputVal[0]}</div>
<div className={styles["field-item"]}>{inputVal[1]}</div>
<div className={styles["field-item"]}>{inputVal[2]}</div>
<div className={styles["field-item"]}>{inputVal[3]}</div>
</div>
<input
ref={inputRef}
className={styles["field-input"]}
onChange={changeHandle}
onBlur={blurHandle}
onFocus={focusHandle}
type="text"
/>
</div>
);
})
);
样式文件:
.inputBox {
position: relative;
width: 100%;
overflow: hidden;
}
.field-list {
padding: 0 31px;
display: flex;
justify-content: space-between;
}
.field-item {
box-sizing: border-box;
width: 120px;
height: 130px;
line-height: 130px;
font-size: 78px;
color: #363a44;
text-align: center;
font-weight: bold;
border: 1px solid #e8e9ec;
border-radius: 16px 16px 16px 16px;
}
.field-item-focus::before {
content: "";
display: block;
width: 4px;
height: 65px;
margin:32px auto;
background: #428ffc;
animation: blink 1s steps(1) infinite;
}
@keyframes blink {
50% {
opacity: 0;
}
}
.field-input {
box-sizing: border-box;
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 130px;
padding: 0;
border: none;
outline: none;
opacity: 0;
background: transparent;
}