2021SC@SDUSC
InputNumber数字输入框
通过鼠标或键盘,输入范围内的数值。
用法:
当需要获取标准数值时。
属性:
成员 | 说明 | 类型 | 默认值 | 版本 |
addonAfter | 带标签的 input,设置后置标签 | ReactNode | - | 4.17.0 |
addonBefore | 带标签的 input,设置前置标签 | ReactNode | - | 4.17.0 |
autoFocus | 自动获取焦点 | boolean | false | - |
bordered | 是否有边框 | boolean | true | 4.12.0 |
controls | 是否显示增减按钮 | boolean | true | 4.17.0 |
decimalSeparator | 小数点 | string | - | - |
defaultValue | 初始值 | number | - | - |
disabled | 禁用 | boolean | false | - |
formatter | 指定输入框展示值的格式 | function(value: number | string, info: { userTyping: boolean, input: string }): string | - | info: 4.17.0 |
keyboard | 是否启用键盘快捷行为 | boolean | true | 4.12.0 |
max | 最大值 | number | - | |
min | 最小值 | number | - | |
parser | 指定从 | function(string): number | - | - |
precision | 数值精度,配置 | number | - | - |
readOnly | 只读 | boolean | false | - |
size | 输入框大小 |
| - | - |
step | 每次改变步数,可以为小数 | number | string | 1 | - |
stringMode | 字符值模式,开启后支持高精度小数。同时 | boolean | false | 4.13.0 |
value | 当前值 | number | - | - |
onChange | 变化回调 | function(value: number | string | null) | - | - |
onPressEnter | 按下回车的回调 | function(e) | - | - |
onStep | 点击上下箭头的回调 | (value: number, info: { offset: number, type: ‘up’ | ‘down’ }) => void | - | 4.7.0 |
部分源码
import * as React from 'react';
import classNames from 'classnames';
import RcInputNumber, { InputNumberProps as RcInputNumberProps } from 'rc-input-number';
import UpOutlined from '@ant-design/icons/UpOutlined';
import DownOutlined from '@ant-design/icons/DownOutlined';
import { ConfigContext } from '../config-provider';
import SizeContext, { SizeType } from '../config-provider/SizeContext';
import { cloneElement } from '../_util/reactNode';
type ValueType = string | number;
export interface InputNumberProps<T extends ValueType = ValueType>
extends Omit<RcInputNumberProps<T>, 'size'> {
prefixCls?: string;
addonBefore?: React.ReactNode;
addonAfter?: React.ReactNode;
size?: SizeType;
bordered?: boolean;
}
const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props, ref) => {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const size = React.useContext(SizeContext);
const {
className,
size: customizeSize,
prefixCls: customizePrefixCls,
addonBefore,
addonAfter,
bordered = true,
readOnly,
...others
} = props;
const prefixCls = getPrefixCls('input-number', customizePrefixCls);
const upIcon = <UpOutlined className={`${prefixCls}-handler-up-inner`} />;
const downIcon = <DownOutlined className={`${prefixCls}-handler-down-inner`} />;
const mergeSize = customizeSize || size;
const inputNumberClass = classNames(
{
[`${prefixCls}-lg`]: mergeSize === 'large',
[`${prefixCls}-sm`]: mergeSize === 'small',
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-readonly`]: readOnly,
[`${prefixCls}-borderless`]: !bordered,
},
className,
);
const element = (
<RcInputNumber
ref={ref}
className={inputNumberClass}
upHandler={upIcon}
downHandler={downIcon}
prefixCls={prefixCls}
readOnly={readOnly}
{...others}
/>
);
if (addonBefore != null || addonAfter != null) {
const wrapperClassName = `${prefixCls}-group`;
const addonClassName = `${wrapperClassName}-addon`;
const addonBeforeNode = addonBefore ? (
<div className={addonClassName}>{addonBefore}</div>
) : null;
const addonAfterNode = addonAfter ? <div className={addonClassName}>{addonAfter}</div> : null;
const mergedWrapperClassName = classNames(`${prefixCls}-wrapper`, wrapperClassName, {
[`${wrapperClassName}-rtl`]: direction === 'rtl',
});
const mergedGroupClassName = classNames(
`${prefixCls}-group-wrapper`,
{
[`${prefixCls}-group-wrapper-sm`]: size === 'small',
[`${prefixCls}-group-wrapper-lg`]: size === 'large',
[`${prefixCls}-group-wrapper-rtl`]: direction === 'rtl',
},
className,
);
return (
<div className={mergedGroupClassName} style={props.style}>
<div className={mergedWrapperClassName}>
{addonBeforeNode}
{cloneElement(element, { style: null })}
{addonAfterNode}
</div>
</div>
);
}
return element;
});
export default InputNumber as (<T extends ValueType = ValueType>(
props: React.PropsWithChildren<InputNumberProps<T>> & {
ref?: React.Ref<HTMLInputElement>;
},
) => React.ReactElement) & { displayName?: string };
可以看出InputNumber的结构较为简单,与先前所分析的Input部分较为相似。
部分问题:
为何受控模式下,value可以超出 min 和 max 范围?
在受控模式下,开发者可能自行存储相关数据。如果组件将数据约束回范围内,会导致展示数据与实际存储数据不一致的情况。这使得一些如表单场景存在潜在的数据问题。
为何动态修改 min 和 max 让 value 超出范围不会触发 onChange 事件?
onChange 事件为用户触发事件,自行触发会导致表单库误以为变更来自用户操作。我们以错误样式展示超出范围的数值。
关于受控组件:
形式上,受控组件就是为组件添加了value属性;非受控组件就是没有添加value属性的组件。当input的状态发生变化时,都会被组件写入到组件的state中,这种组件在React中被称为受阻控件。在受阻控件中,组件渲染出的状态与它的value相对应。
React受控组件更新state的流程:
- 可以通过在初始state中设置表单的默认值
- 每当表单的值发生变化时,调用onChange事件处理器
- 事件处理器通过合成事件对象e拿到改变后的状态,并更新应用的state
- setState触发视图的重新渲染,完成表单组件值的更新
几个例子:
当通过受控将value超出边界时,提供警告样式。
import { InputNumber, Button, Space } from 'antd';
const Demo = () => {
const [value, setValue] = React.useState<string | number>('99');
return (
<Space>
<InputNumber min={1} max={10} value={value} onChange={setValue} />
<Button
type="primary"
onClick={() => {
setValue(99);
}}
>
Reset
</Button>
</Space>
);
};
ReactDOM.render(<Demo />, mountNode);
格式化展示
通过 formatter
格式化数字,以展示具有具体含义的数据,往往需要配合 parser
一起使用。
import { InputNumber, Space } from 'antd';
function onChange(value) {
console.log('changed', value);
}
ReactDOM.render(
<Space>
<InputNumber
defaultValue={1000}
formatter={value => `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
parser={value => value.replace(/\$\s?|(,*)/g, '')}
onChange={onChange}
/>
<InputNumber
defaultValue={100}
min={0}
max={100}
formatter={value => `${value}%`}
parser={value => value.replace('%', '')}
onChange={onChange}
/>
</Space>,
mountNode,
);