react-button md 使用说明

28 阅读9分钟

Button 按钮组件

概述

Button 是一个功能丰富的按钮组件,提供了多种样式、尺寸和状态,用于触发操作和交互。该组件基于 React 实现,支持完整的 TypeScript 类型定义。

特性

  • 🎨 多种类型:支持 primary、normal、dashed、link、text 五种按钮类型
  • 📏 三种尺寸:small、medium、large 可选
  • 🔘 多种形状:支持 circle(圆形)和 round(圆角)形状
  • 🎯 图标支持:支持图标按钮和图标位置配置(start | end)
  • ⚠️ 危险按钮:支持 danger 危险状态样式
  • 👻 幽灵按钮:支持 ghost 透明背景样式
  • 加载状态:内置 loading 状态支持,带旋转图标动画
  • 🚫 禁用状态:支持 disabled 禁用状态
  • 📱 块级按钮:支持 block 块级显示
  • 完全可访问:支持键盘导航和屏幕阅读器

引入方式

import Button from "@/button";
// 或者
import Button from "@/button/index";

API 参数

ButtonProps

参数说明类型默认值必填
基础属性
className自定义类名string-
style自定义样式React.CSSProperties-
children按钮内容ReactNode-
htmlType原生 button 的 type 属性'button' | 'submit' | 'reset''button'
样式属性
type按钮类型'normal' | 'primary' | 'dashed' | 'link' | 'text''normal'
size按钮尺寸'small' | 'medium' | 'large''medium'
shape按钮形状'circle' | 'round'-
danger设置危险按钮booleanfalse
ghost设置幽灵按钮(透明背景)booleanfalse
图标属性
icon设置按钮的图标组件ReactNode-
iconPosition设置图标的位置'start' | 'end''start'
功能属性
loading设置按钮载入状态booleanfalse
disabled设置按钮禁用状态booleanfalse
block将按钮宽度调整为其父宽度booleanfalse
事件处理
onClick点击按钮时的回调React.MouseEventHandler<HTMLButtonElement>-
onBlur失去焦点时的回调React.FocusEventHandler<HTMLButtonElement>-
onFocus获取焦点时的回调React.FocusEventHandler<HTMLButtonElement>-

其他 HTML 属性

组件还支持所有原生 <button> 元素的属性,如 idnametitle 等。

使用示例

基础用法

import Button from "@/button";

function BasicExample() {
  return (
    <div>
      <Button type="primary">主要按钮</Button>
      <Button>默认按钮</Button>
      <Button type="dashed">虚线按钮</Button>
      <Button type="text">文本按钮</Button>
      <Button type="link">链接按钮</Button>
    </div>
  );
}

图标按钮

按钮可以配置图标,提供更好的视觉效果和用户体验。

import Button from "@/button";
import {
  SearchOutlined,
  DownloadOutlined,
  PlusOutlined,
} from "@ant-design/icons";

function IconExample() {
  return (
    <div>
      {/* 带文本的图标按钮 */}
      <Button type="primary" icon={<SearchOutlined />}>
        搜索
      </Button>
      <Button icon={<DownloadOutlined />}>下载</Button>
      <Button type="dashed" icon={<PlusOutlined />}>
        添加
      </Button>
    </div>
  );
}

纯图标按钮

只有图标,没有文字的按钮。适合在空间有限的场景使用。

import Button from "@/button";
import {
  SearchOutlined,
  EditOutlined,
  DeleteOutlined,
} from "@ant-design/icons";

function IconOnlyExample() {
  return (
    <div>
      <Button type="primary" icon={<SearchOutlined />} />
      <Button icon={<EditOutlined />} />
      <Button type="primary" shape="circle" icon={<SearchOutlined />} />
      <Button danger shape="circle" icon={<DeleteOutlined />} />
    </div>
  );
}

图标位置

可以通过 iconPosition 属性控制图标在文本的左侧或右侧。

import Button from "@/button";
import { LeftOutlined, RightOutlined } from "@ant-design/icons";

function IconPositionExample() {
  return (
    <div>
      <Button type="primary" icon={<LeftOutlined />}>
        上一页
      </Button>
      <Button type="primary" icon={<RightOutlined />} iconPosition="end">
        下一页
      </Button>
    </div>
  );
}

按钮尺寸

function SizeExample() {
  return (
    <div>
      <Button type="primary" size="large">
        大号按钮
      </Button>
      <Button type="primary" size="medium">
        中号按钮
      </Button>
      <Button type="primary" size="small">
        小号按钮
      </Button>
    </div>
  );
}

按钮形状

function ShapeExample() {
  return (
    <div>
      <Button type="primary" shape="round">
        圆角按钮
      </Button>
      <Button type="primary" shape="circle">
        +
      </Button>
      <Button type="primary" shape="circle" size="large"></Button>
    </div>
  );
}

危险按钮

function DangerExample() {
  return (
    <div>
      <Button type="primary" danger>
        删除
      </Button>
      <Button danger>危险操作</Button>
      <Button type="link" danger>
        危险链接
      </Button>
      <Button type="text" danger>
        危险文本
      </Button>
    </div>
  );
}

幽灵按钮

适合用在有色背景上。

function GhostExample() {
  return (
    <div style={{ background: "#1890ff", padding: 20 }}>
      <Button type="primary" ghost>
        主要按钮
      </Button>
      <Button ghost>默认按钮</Button>
      <Button type="dashed" ghost>
        虚线按钮
      </Button>
    </div>
  );
}

加载状态

用于异步操作的反馈。加载状态会显示旋转的加载图标,并自动禁用按钮。

import { useState } from "react";
import Button from "@/button";
import { UploadOutlined } from "@ant-design/icons";

function LoadingExample() {
  const [loading, setLoading] = useState(false);

  const handleClick = async () => {
    setLoading(true);
    try {
      // 模拟异步操作
      await new Promise((resolve) => setTimeout(resolve, 2000));
      console.log("操作完成");
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      {/* 普通加载按钮 */}
      <Button type="primary" loading={loading} onClick={handleClick}>
        {loading ? "处理中..." : "点击提交"}
      </Button>

      {/* 带图标的加载按钮,loading 时会替换为加载图标 */}
      <Button
        type="primary"
        icon={<UploadOutlined />}
        loading={loading}
        onClick={handleClick}
      >
        {loading ? "上传中..." : "上传文件"}
      </Button>
    </div>
  );
}

禁用状态

function DisabledExample() {
  return (
    <div>
      <Button type="primary" disabled>
        主要按钮
      </Button>
      <Button disabled>默认按钮</Button>
      <Button type="link" disabled>
        链接按钮
      </Button>
    </div>
  );
}

块级按钮

适合在移动端或需要按钮占满容器宽度的场景。

function BlockExample() {
  return (
    <div>
      <Button type="primary" block>
        主要按钮
      </Button>
      <Button block style={{ marginTop: 8 }}>
        默认按钮
      </Button>
      <Button type="dashed" block style={{ marginTop: 8 }}>
        虚线按钮
      </Button>
    </div>
  );
}

表单提交

function FormExample() {
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log("表单已提交");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" placeholder="输入内容" />
      <Button htmlType="submit" type="primary">
        提交
      </Button>
      <Button htmlType="reset">重置</Button>
    </form>
  );
}

组合使用

function CombinedExample() {
  return (
    <div>
      {/* 大号危险按钮 */}
      <Button type="primary" danger size="large">
        删除用户
      </Button>

      {/* 圆形按钮组 */}
      <Button type="primary" shape="circle"></Button>
      <Button type="primary" shape="circle" danger></Button>

      {/* 幽灵风格危险按钮 */}
      <div style={{ background: "#1890ff", padding: 20 }}>
        <Button type="primary" ghost>
          接受
        </Button>
        <Button type="primary" danger ghost>
          拒绝
        </Button>
      </div>
    </div>
  );
}

使用 Ref

import { useRef } from "react";
import Button from "@/button";

function RefExample() {
  const btnRef = useRef<HTMLButtonElement>(null);

  const focusButton = () => {
    btnRef.current?.focus();
  };

  return (
    <div>
      <Button type="primary" ref={btnRef}>
        目标按钮
      </Button>
      <Button onClick={focusButton}>聚焦目标按钮</Button>
    </div>
  );
}

最佳实践

1. 按钮类型选择

  • Primary 主要按钮:用于页面的主要操作,一个页面建议只出现一个主要按钮
  • Default 默认按钮:用于次要操作
  • Dashed 虚线按钮:常用于添加操作
  • Text 文本按钮:用于最弱的操作,如取消
  • Link 链接按钮:用于次要或外链跳转
// ✅ 推荐:主次分明
<div>
  <Button type="primary">确认提交</Button>
  <Button>取消</Button>
</div>

// ❌ 不推荐:多个主要按钮
<div>
  <Button type="primary">操作A</Button>
  <Button type="primary">操作B</Button>
  <Button type="primary">操作C</Button>
</div>

2. 图标使用建议

合理使用图标可以提升用户体验和识别度。

import { SearchOutlined, PlusOutlined, DeleteOutlined } from "@ant-design/icons";

// ✅ 推荐:图标语义明确
<div>
  <Button type="primary" icon={<SearchOutlined />}>搜索</Button>
  <Button icon={<PlusOutlined />}>添加</Button>
  <Button danger icon={<DeleteOutlined />}>删除</Button>
</div>

// ✅ 推荐:空间有限时使用纯图标按钮
<div>
  <Button type="primary" shape="circle" icon={<SearchOutlined />} />
  <Button shape="circle" icon={<PlusOutlined />} />
</div>

// ✅ 推荐:导航按钮使用合适的图标位置
<div>
  <Button icon={<LeftOutlined />}>上一页</Button>
  <Button icon={<RightOutlined />} iconPosition="end">下一页</Button>
</div>

// ❌ 不推荐:图标语义不清晰
<Button icon={<QuestionOutlined />}>确认</Button>

3. 危险操作确认

对于删除等危险操作,建议使用 danger 属性,并配合二次确认。

import { DeleteOutlined } from "@ant-design/icons";

function DeleteExample() {
  const handleDelete = () => {
    if (window.confirm("确认删除吗?")) {
      // 执行删除操作
      console.log("已删除");
    }
  };

  return (
    <Button
      type="primary"
      danger
      icon={<DeleteOutlined />}
      onClick={handleDelete}
    >
      删除数据
    </Button>
  );
}

4. 异步操作处理

对于异步操作,应该使用 loading 状态,避免重复提交。

// ✅ 推荐:使用 loading 状态
function GoodAsyncExample() {
  const [loading, setLoading] = useState(false);

  const handleSubmit = async () => {
    setLoading(true);
    try {
      await submitData();
    } finally {
      setLoading(false);
    }
  };

  return (
    <Button type="primary" loading={loading} onClick={handleSubmit}>
      提交
    </Button>
  );
}

// ❌ 不推荐:没有 loading 反馈
function BadAsyncExample() {
  const handleSubmit = async () => {
    await submitData(); // 用户可能重复点击
  };

  return (
    <Button type="primary" onClick={handleSubmit}>
      提交
    </Button>
  );
}

5. 按钮组间距

多个按钮并排时,应该保持适当间距。

// ✅ 推荐:设置合适的间距
<div>
  <Button type="primary">确认</Button>
  <Button style={{ marginLeft: 8 }}>取消</Button>
</div>

// 或使用容器样式
<div style={{ display: 'flex', gap: 8 }}>
  <Button type="primary">确认</Button>
  <Button>取消</Button>
</div>

6. 响应式布局

在移动端,考虑使用 block 属性使按钮占满宽度。

function ResponsiveButton() {
  const isMobile = window.innerWidth < 768;

  return (
    <Button type="primary" block={isMobile}>
      提交订单
    </Button>
  );
}

样式定制

通过 className 定制

// 在你的样式文件中
.custom-button {
  border-radius: 8px;
  text-transform: uppercase;
  font-weight: 600;
}

// 使用
<Button type="primary" className="custom-button">
  自定义按钮
</Button>

通过 style 定制

<Button
  type="primary"
  style={{
    borderRadius: 8,
    textTransform: "uppercase",
    fontWeight: 600,
  }}
>
  自定义样式
</Button>

覆盖主题色

如需全局修改按钮主题色,可以修改 index.scss 文件中的颜色变量:

// src/button/index.scss
$color-primary: #1890ff; // 主色
$color-primary-hover: #40a9ff; // 悬停色
$color-primary-active: #096dd9; // 激活色

$color-danger: #ff4d4f; // 危险色
$color-danger-hover: #ff7875; // 危险悬停色
$color-danger-active: #d9363e; // 危险激活色

技术实现要点

1. 组件结构

组件使用 React.forwardRef 实现,支持 ref 转发到原生 button 元素:

interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
  // 基础属性
  className?: string;
  style?: React.CSSProperties;
  children?: ReactNode;
  htmlType?: "button" | "submit" | "reset";

  // 样式属性
  type?: "normal" | "primary" | "dashed" | "link" | "text";
  size?: "small" | "medium" | "large";
  shape?: "circle" | "round";
  danger?: boolean;
  ghost?: boolean;

  // 功能属性
  loading?: boolean;
  disabled?: boolean;
  block?: boolean;

  // 事件
  onClick?: React.MouseEventHandler<HTMLButtonElement>;
  onBlur?: React.FocusEventHandler<HTMLButtonElement>;
  onFocus?: React.FocusEventHandler<HTMLButtonElement>;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(

2. 类名生成

使用 classnames 库动态生成类名,保证样式的灵活组合:

    const cls = classNames({
      "ant-btn": true,
      [`ant-btn-${sizeClass}`]: sizeClass,
      [`ant-btn-${type}`]: type,
      [`ant-btn-${shape}`]: shape,
      "ant-btn-danger": danger && type !== "text",
      "ant-btn-ghost": ghost,
      "ant-btn-block": block,
      "ant-btn-loading": loading,
      [className as string]: !!className,
    });

3. 加载状态处理

加载状态会自动禁用按钮,防止重复点击:

    const isDisabled = disabled || loading;

4. 样式架构

样式采用 SCSS 编写,使用 Mixin 实现样式复用:

@mixin disabled-state($color-text: $color-text-disabled) {
  color: $color-text;
  border-color: $color-border-disabled;
  background: $color-bg-disabled;
  text-shadow: none;
  box-shadow: none;

  @include innerA();
}

@mixin innerA() {
  >a:only-child {
    color: currentColor;
    &:after {
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      background: transparent;
      content: '';
    }
  }
}

@mixin button-type($color, $border, $bg) {
  color: $color;
  border-color: $border;
  background: $bg;
  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);

  @include innerA();

  &:hover, &:focus {
    color: $color-text-white;
    border-color: $color-primary-hover;
    background: $color-primary-hover;
    @include innerA();
  }

  &:active {
    color: $color-text-white;
    border-color: $color-primary-active;
    background: $color-primary-active;
    @include innerA();
  }

  &[disabled] { @include disabled-state(); }
}

@mixin button-plain($color, $border: transparent, $bg: transparent, $box-shadow: none) {
  color: $color;
  border-color: $border;
  background: $bg;
  box-shadow: $box-shadow;

  @include innerA();

  &:hover, &:focus {
    color: $color-primary-hover;
    border-color: $color-primary-hover;
    @include innerA();
  }

  &:active {
    color: $color-primary-active;
    border-color: $color-primary-active;
    @include innerA();
  }

  &[disabled] { @include disabled-state(); }
}

常见问题

Q1: 按钮点击事件触发两次?

A: 检查是否在 loading 状态下没有禁用按钮。组件内部会自动处理,但如果自定义了点击逻辑,需要手动防抖:

import { useState } from "react";

function Example() {
  const [loading, setLoading] = useState(false);

  const handleClick = async () => {
    if (loading) return; // 手动防止重复点击

    setLoading(true);
    try {
      await someAsyncOperation();
    } finally {
      setLoading(false);
    }
  };

  return (
    <Button loading={loading} onClick={handleClick}>
      提交
    </Button>
  );
}

Q2: 如何实现按钮组?

A: 可以使用容器包裹多个按钮:

function ButtonGroup() {
  return (
    <div style={{ display: "inline-flex", gap: 0 }}>
      <Button>左侧</Button>
      <Button>中间</Button>
      <Button>右侧</Button>
    </div>
  );
}

Q3: 如何添加图标?

A: 使用 icon 属性添加图标:

import { SearchOutlined } from "@ant-design/icons";

function IconButton() {
  return (
    <div>
      {/* 推荐方式:使用 icon 属性 */}
      <Button type="primary" icon={<SearchOutlined />}>
        搜索
      </Button>

      {/* 纯图标按钮 */}
      <Button type="primary" icon={<SearchOutlined />} />

      {/* 图标在右侧 */}
      <Button icon={<SearchOutlined />} iconPosition="end">
        搜索
      </Button>
    </div>
  );
}

Q4: loading 图标是什么?

A: loading 状态会显示 Ant Design 的 LoadingOutlined 旋转图标,并自动禁用按钮。如果按钮同时设置了 iconloading={true},loading 图标会替换原来的图标。

import { UploadOutlined } from "@ant-design/icons";

// loading 时会显示旋转的加载图标,替换上传图标
<Button icon={<UploadOutlined />} loading={true}>
  上传
</Button>;

注意事项

  1. 避免过度使用主要按钮:一个页面/对话框中建议只有一个主要按钮(primary)
  2. 危险操作需二次确认:使用 danger 属性的按钮,建议加上确认弹窗
  3. 异步操作要有反馈:使用 loading 状态避免用户重复点击
  4. 移动端适配:在小屏幕上考虑使用 block 属性
  5. 加载状态禁用自动处理:loading 为 true 时,按钮会自动禁用
  6. 文本按钮慎用:text 和 link 类型按钮容易被忽略,不适合重要操作
  7. 保持一致性:在同一个项目中,相同功能的按钮应使用相同的类型和样式
  8. 图标语义要清晰:使用图标时确保图标的含义清晰,必要时配合文字说明
  9. 纯图标按钮需谨慎:只有图标的按钮可能让用户困惑,建议配合 Tooltip 提示
  10. loading 状态会替换图标:当按钮处于 loading 状态时,会显示加载图标替换原有图标

维护指南

文件结构

src/button/
├── index.tsx              # 组件主文件
├── index.scss             # 组件样式
├── index.test.tsx         # 单元测试
└── Button.stories.tsx     # Storybook 文档

修改建议

  1. 添加新的按钮类型

    • ButtonPropstype 属性中添加新类型
    • index.scss 中添加对应样式
    • Button.stories.tsx 中添加示例
  2. 修改默认样式

    • 修改 index.scss 中的变量定义
    • 注意保持向后兼容性
  3. 添加新功能

    • ButtonProps 接口中添加新属性
    • 在组件实现中处理新属性
    • 更新文档和示例

测试

运行单元测试:

npm test src/button/index.test.tsx

运行 Storybook:

npm run storybook

更新日志

v1.1.0 (2025-10-27)

  • ✨ 新增 icon 属性,支持配置按钮图标
  • ✨ 新增 iconPosition 属性,支持设置图标位置(start | end)
  • ✨ 实现真实的 loading 旋转图标(LoadingOutlined)
  • ✨ 自动识别纯图标按钮(icon-only)
  • ✨ 图标和文本之间自动添加合适间距
  • ✅ 新增 11 个图标相关测试用例
  • 📝 完善图标使用文档和最佳实践

v1.0.0

  • ✅ 支持五种按钮类型
  • ✅ 支持三种尺寸
  • ✅ 支持圆形和圆角形状
  • ✅ 支持危险和幽灵样式
  • ✅ 支持加载和禁用状态
  • ✅ 支持块级显示
  • ✅ 完整的 TypeScript 类型定义
  • ✅ 支持 ref 转发

相关组件

  • Icon:图标组件,常与按钮配合使用
  • Dropdown:下拉菜单,可以与按钮组合
  • Tooltip:工具提示,可以包裹按钮提供说明

贡献

如有问题或建议,请联系组件维护团队。