惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
博客园_首页
WordPress大学
WordPress大学
大猫的无限游戏
大猫的无限游戏
有赞技术团队
有赞技术团队
阮一峰的网络日志
阮一峰的网络日志
Hugging Face - Blog
Hugging Face - Blog
博客园 - 【当耐特】
酷 壳 – CoolShell
酷 壳 – CoolShell
Y
Y Combinator Blog
Vercel News
Vercel News
The GitHub Blog
The GitHub Blog
T
The Blog of Author Tim Ferriss
云风的 BLOG
云风的 BLOG
博客园 - 司徒正美
Engineering at Meta
Engineering at Meta
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
J
Java Code Geeks
Stack Overflow Blog
Stack Overflow Blog
N
Netflix TechBlog - Medium
Martin Fowler
Martin Fowler
宝玉的分享
宝玉的分享
G
Google Developers Blog
Last Week in AI
Last Week in AI

蚊子的前端博客

微说 | 有的人觉得只要不考XX,一定能考好!-蚊子的前端博客 微说 | 春天来了-蚊子的前端博客 明天和意外不知道哪个先来-蚊子的前端博客 微说 | 既然错过了路口,就及时止损,重新规划路线上路-蚊子的前端博客 微说 | 2025年的出生人口数是792万-蚊子的前端博客 微说 | 工作年终总结,让写对接的接口的数量?-蚊子的前端博客 微说 | 温家宝:没有政治体制改革的成功 经济体制改革不可能进行到底-蚊子的前端博客 微说 | 违法犯罪了该不该被禁言?-蚊子的前端博客 微说 | 不明白为什么在推荐去俄罗斯旅游-蚊子的前端博客 微说 | 易中天论骗子-蚊子的前端博客 微说 | 一场大风吹散了秋天-蚊子的前端博客 微说 | 既要、又要、还要、更要!-蚊子的前端博客 又是一年的国庆雨季-蚊子的前端博客 微说 | 预估下2025年的出生人口数据-蚊子的前端博客 微说 | 可惜了我那些小时候的书本-蚊子的前端博客 微说 | 牛马有的是,驴不够了!-蚊子的前端博客 微说 | 能否有一条非户口也能高考的路-蚊子的前端博客 微说 | 小聊中医-蚊子的前端博客 微说 | 将要制作的一款新产品-蚊子的前端博客 微说 | 做人要有信-蚊子的前端博客 微说 | 好一个正义联盟-蚊子的前端博客 微说 | 都是见过吃过的主儿-蚊子的前端博客 微说 | 追求8小时工作制有错吗?-蚊子的前端博客 微说 | 如果尖锐的批评完全消失-蚊子的前端博客 微说 | 很好!-蚊子的前端博客 微说 | 封禁用户可以,但要告知具体原因-蚊子的前端博客 微说 | 面朝大海,春暖花开-蚊子的前端博客 微说 | 程序员的悲哀是什么?-蚊子的前端博客 微说 | 2025年出生人口的预测-蚊子的前端博客 前端在 LiveKit 中如何获取所有的参与者-蚊子的前端博客
antd 的 InputNumber 输入框添加左右的加减按钮-蚊子的前端博客
author · 2023-10-17 · via 蚊子的前端博客

antd 现在提供的 <InputNumber /> 组件只能点击输入内的上下键来修改数值,不过我希望得到下图中的组件:

有左右加减按钮的数字输入框

我们这里来封装下。封装时要注意的几点:

  1. 能全部接收<InputNumber />所有的参数;
  2. 当数字达到上下限时,需将按钮置灰;
  3. 隐藏<InputNumber />本身内置的上下切换键;
  4. 点击左右按钮时,能实时调整数值;

1. 接收内置的所有参数 #

我们可以直接使用 antd 中内置的类型来扩展我们组件的类型:

import type { InputNumberProps } from "antd";

interface NumUpDownProps extends InputNumberProps {
  className?: string;
}

const NumberUpDown = (props: NumUpDownProps) => {};

export default NumberUpDown;

我们在原类型的基础上又扩展了出了自己的字段。

2. 数字达到上下限时,将按钮置灰 #

假如设置了输入框中数字的上下限,我们就得需要判断当前数值是否达到了下限或者上限,若达到了,则将其中一侧的按钮置灰。

import React, { useMemo } from "react";

const NumberUpDown = (props: NumUpDownProps) => {
  const { minDisabled, maxDisabled } = useMemo(() => {
    const min = props.min ?? Number.MIN_SAFE_INTEGER;
    const max = props.max ?? Number.MAX_SAFE_INTEGER;

    const minDisabled =
      typeof props.value === "number" ? props.value <= min : false;
    const maxDisabled =
      typeof props.value === "number" ? props.value >= max : false;

    return { minDisabled, maxDisabled };
  }, [props.value, props.min, props.max]);
};

若没有设置上下限,我们就认为是无限大和无限小,方便我们统一地判断。

3. 隐藏组件内置的上下切换键 #

<InputNumber />的属性中,没有可以隐藏上下切换键的属性,我们可以从 CSS 层面,将其隐藏。

.ant-input-number {
  border-top: none;
  border-bottom: none;
  border-radius: 0;

  .ant-input-number-input {
    padding: 0;
    text-align: center;
    height: 32px;
  }
  .ant-input-number-handler-wrap {
    display: none;
  }
}

上面代码中的display: none就是用来隐藏切换键的。

4. 点击左右按钮,可实时调整数值 #

按钮的行为只有两种:增加或者减少。

  1. 若没有初始值,则默认是 1;
  2. 若没有默认的调整步数 step,则默认是 1;
const handleClick = (type: "plus" | "minus") => {
  const value = Number(props?.value || 0);
  const step = Number(props?.step || 1);
  const newValue = type === "plus" ? value + step : value - step;

  props?.onChange?.(newValue);
};

将调整后的值回传回去。

5. 总结 #

整体来说,这个组件的封装并不是很难。在表单中调用时:

<Form.Item name="num" label="上下限">
  <NumberUpDown min={1} max={5} />
</Form.Item>

现将完整的代码公布一下。

5.1 React 代码 #

import { Button, InputNumber } from "antd";
import type { InputNumberProps } from "antd";
import { MinusOutlined, PlusOutlined } from "@ant-design/icons";
import classNames from "classnames";
import React, { useMemo } from "react";
import "./number-up-down.less";

interface NumUpDownProps extends InputNumberProps {
  className?: string;
}

const NumberUpDown = (props: NumUpDownProps) => {
  const { minDisabled, maxDisabled } = useMemo(() => {
    const min = props.min ?? Number.MIN_SAFE_INTEGER;
    const max = props.max ?? Number.MAX_SAFE_INTEGER;

    const minDisabled = typeof props.value === "number" ? props.value <= min : false;
    const maxDisabled = typeof props.value === "number" ? props.value >= max : false;

    return { minDisabled, maxDisabled };
  }, [props.value, props.min, props.max]);

  const handleClick = (type: "plus" | "minus") => {
    const value = Number(props?.value || 0);
    const step = Number(props?.step || 1);
    const newValue = type === "plus" ? value + step : value - step;

    props?.onChange?.(newValue);
  };

  return (
    <div className={classNames("number-up-down-field", props.className)}>
      <Button
        className="change-btn"
        icon={<MinusOutlined />}
        type="text"
        disabled={minDisabled}
        onClick={() => handleClick("minus")}
      ></Button>
      <InputNumber {...props} />
      <Button
        className="change-btn"
        icon={<PlusOutlined />}
        type="text"
        disabled={maxDisabled}
        onClick={() => handleClick("plus")}
      ></Button>
    </div>
  );
};
export default NumberUpDown;

5.2 样式代码 #

.number-up-down-field {
  display: flex;
  align-items: center;
  border: 1px solid #d9d9d9;
  border-radius: 2px;
  background-color: #ffffff;

  .change-btn {
    border-radius: 0;
  }
  .ant-input-number {
    border-top: none;
    border-bottom: none;
    border-radius: 0;

    .ant-input-number-input {
      padding: 0;
      text-align: center;
      height: 32px;
    }
    .ant-input-number-handler-wrap {
      display: none;
    }
  }
}