剛開始用React,有不對的地方,歡迎指正react
最近在作Json編輯器時,使用到InputNumber,可是ant-design的InputNumber不是在Input組件內部實現的,不少Input的擴展功能(如addonAffter ,suffix,prefix等)都沒有。而後編輯器的表單字段須要支持addonAfter這些字段,須要在輸入組件的粒度上實現這些擴展功能。git
首先,查下有沒有現成的解決方案。搜索了下ant-design的Issues,能夠找到相關的提議#14284,官方建議是使用Input的type=number功能。可是我又須要InputNumber的自動轉換類型和精度等的控制,因此只能本身實現了。github
在現有基礎上改造有如下解決方案:antd
因爲Input中的addons,prefix,suffix這些實現都須要改動DOM結構,實現起來很繁瑣。因此採起基於Input,實現InputNumber功能。編輯器
查看了下ant-design的代碼,InputNumber的主要邏輯實如今rc-input-number中。是使用React組件的形式實現的。大體看了下源碼,接下來須要注意的是:ide
首先,整理下我須要實現的InputNumber的功能吧函數
原InputNumber的鍵盤操做什麼的,暫時不加了ui
最近用慣了Hook,仍是繼續使用函數實現吧。spa
主要是使用Input組件,指定type爲number,min和max屬性仍是設置在Input上,繼續使用原生的number輸入。rest
直接貼代碼:
import React, { useCallback, useState, useEffect, RefObject } from 'react'
import { Input } from 'antd';
import { InputProps } from 'antd/lib/input';
interface IInputNumberExtend extends Omit<InputProps, 'onChange' | 'value'> {
max?: number;
min?: number;
precision?: number;
step?: number;
autoFocus?: boolean;
value?: number;
onChange?: (value: number | undefined) => void;
}
/** * InputString convert to number * @param strNum * @param option */
function inputStrToNum(strNum: string | undefined, option: { precision?: number, min?: number, max?: number }) {
if (!strNum) {
return;
}
let num = parseFloat(strNum);
return parseNum(num, option);
}
function parseNum(num: number, option: { precision?: number, min?: number, max?: number }) {
if (isNaN(num)) {
return;
}
// parse By precision
if (option.precision && option.precision > 0) {
let precisionPow = Math.pow(10, option.precision);
num = Math.round(num * precisionPow) / precisionPow;
}
if (option.min != null) {
if (num < option.min) {
num = option.min;
return num;
}
}
if (option.max != null) {
if (num > option.max) {
num = option.max;
return num;
}
}
return num;
}
/** * number convert to display string * @param num * @param precision */
function numToStr(num: number, precision?: number) {
if (typeof num === 'string') {
return num;
}
if (isNaN(num)) {
return num + '';
}
if (precision != null) {
return num.toFixed(precision);
} else {
if (!/e/i.test(String(num))) {
return num + '';
}
let str = Number(num).toFixed(18).replace(/\.?0+$/, '');
return str;
}
}
function InputNumberExtend(props: IInputNumberExtend, ref: React.MutableRefObject<Input | null> | ((instance: Input | null) => void) | null): React.ReactElement {
const {
max,
min,
precision,
step,
value,
onChange,
onBlur,
onFocus,
autoFocus,
...restOption
} = props;
const [focused, setFocused] = useState(autoFocus);
const [inputValue, setInputValue] = useState<string | undefined>(undefined);
useEffect(() => {
if (value == null) {
setInputValue(undefined);
return;
}
if (typeof value === 'number') {
let num = parseNum(value, { precision, min, max });
if (num != null) {
setInputValue(numToStr(num, precision))
} else {
onChange && onChange(undefined);
}
} else {
let iNum = inputStrToNum(value, { precision, min, max });
if (iNum == null) {
onChange && onChange(undefined);
} else {
onChange && onChange(iNum);
}
}
}, [max, min, onChange, precision, value])
const onFocusHandle = useCallback((e) => {
setFocused(true);
onFocus && onFocus(e);
}, [onFocus]);
const onBlurHandle = useCallback((e) => {
setFocused(false);
onBlur && onBlur(e);
let parsedNum = inputStrToNum(inputValue, { precision, min, max });
if (parsedNum !== value) {
if (parsedNum) {
setInputValue(numToStr(parsedNum, precision));
}
onChange && onChange(parsedNum);
}
}, [inputValue, max, min, onBlur, onChange, precision, value]);
const inputOnChange = useCallback(
(e) => {
let strV = e.target.value.trim();
if (focused) {
setInputValue(strV);
} else {
// 重置value
let parsedNum = inputStrToNum(strV, { precision, min, max });
if (parsedNum != null) {
onChange && onChange(parsedNum);
}
// else {
// // Input invalid,do nothing
// }
}
},
[focused, max, min, onChange, precision],
)
return <Input
{...restOption}
ref={ref}
type="number"
autoFocus={autoFocus}
min={min}
max={max}
step={step}
onChange={inputOnChange}
value={inputValue}
onFocus={onFocusHandle}
onBlur={onBlurHandle}
/>
}
// export default React.memo(InputNumberExtend); 忘記考慮ref了
export default React.memo(React.forwardRef<Input, IInputNumberExtend>(InputNumberExtend));
複製代碼