实现一个异步按钮组件
场景:
日常开发中按钮 loading
状态一般配合异步函数的调用,这种 button
状态的维护虽然可以,但是如果有很多按钮就需要定义对应的按钮状态,如果是按钮组的话还涉及关联影响的问题;是不是可以实现一个AsyncButton
组件,当传入异步函数的时候自动维护状态呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const ExampleButton = () => { const [loading, setLoading] = useState(false)
const handleSubmit = async (formData) => { setLoading(true) try { await new Promise((resolve) => setTimeout(resolve, 2000)) } catch (error) { } finally { setLoading(false) } }
return ( <Button loading={loading} // 添加loading onClick={async () => { await handleSubmit() }} > 提交 </Button> ) }
|
传入异步函数自动维护 loading 状态
点击按钮 → 执行 onClick → 检测到 Promise → 显示 loading → 执行完异步函数 → 关闭 loading
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import { FC } from 'react'
interface AsyncButtonProps extends Omit<ButtonProps, 'onClick'> { onClick: () => Promise<unknown>; }
const AsyncButton: FC<AsyncButtonProps> = ({ onClick, children, ...restProps }) => { const [innerLoading, setInnerLoading] = useState(false)
const handleClick = () => { const result = onClick()
if ( result && typeof result.then === 'function' && typeof result.finally === 'function' ) { setInnerLoading(true) result.finally(() => { setInnerLoading(false) }) } }
return ( <Button loading={innerLoading} onClick={handleClick} {...restProps}> {children} </Button> ) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { AsyncButton } from './components/AsyncButton'
function App() { const handleClick = () => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('异步操作完成') resolve('成功') }, 3000) }) } return ( <> <AsyncButton onClick={() => handleClick()}>点击</AsyncButton> </> ) }
export default App
|
通过检测 Promise
的 then
和 finally
方法来自动管理 loading
状态:
promise
是链路回调且无论是resolve
还是reject
,都会走到finally
中关闭loading
状态,保障loading
的正确结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import { FC } from 'react'
interface AsyncButtonProps extends Omit<ButtonProps, 'onClick'> { onClick: () => Promise<unknown>; }
const AsyncButton: FC<AsyncButtonProps> = ({ onClick, children, ...restProps }) => { const [innerLoading, setInnerLoading] = useState(false)
const handleClick = () => { const result = onClick()
if ( result && typeof result.then === 'function' && typeof result.finally === 'function' ) { setInnerLoading(true) result.finally(() => { setInnerLoading(false) }) } }
return ( <Button loading={innerLoading} onClick={handleClick} {...restProps}> {children} </Button> ) }
|
扩展功能:
1、 asyncButton 支持受控模式,此时是一个普通button;
2、 支持Modal弹框功能,点击按钮后弹出Modal;
3、 支持按钮组Button.Group,多个按钮组时,共享状态;
目录结构
组件的目录结构非常规范:
1 2 3 4 5 6
| EasyButton/ ├── ButtonProvider.tsx # 上下文提供者,管理多按钮状态 ├── README.md # 组件文档和使用示例 ├── hooks.ts # 自定义 hooks ├── index.tsx # 主组件文件 └── types.ts # TypeScript 类型定义
|
设计规范分析
1. 文件组织规范
单一职责原则:每个文件都有明确的职责
- :主组件逻辑
- :类型定义
- :可复用的状态逻辑
- :上下文管理
完整的文档: 包含详细的使用示例和 API 文档
2. 组件设计规范
核心特性
- 自动 Loading 管理:当
onClick
返回 Promise 时自动进入 loading 状态
- 二次确认交互:支持 modal 和 popup 两种确认方式
- 权限控制:内置权限管理,支持隐藏或禁用状态
- 多按钮状态同步:通过 Provider 实现按钮组的状态管理
API 设计规范
1 2 3 4 5 6 7 8 9 10
| export type EasyButtonProps = Omit<ButtonProps, 'onClick'> & { onClick?: () => void | Promise<unknown> confirm?: React.ReactNode confirmType?: 'modal' | 'popup' tooltip?: React.ReactNode disabledTooltip?: React.ReactNode group?: string hasPermission?: boolean noPermissionStatus?: 'hide' | 'disabled' }
|
3. 代码质量规范
TypeScript 使用
- 完整的类型定义,继承并扩展 Antd Button 的 Props
- 泛型和联合类型的合理使用
- 良好的类型推导和约束
Hooks 设计
- :管理 Popconfirm 和 Tooltip 的状态冲突
- :统一的 loading 状态管理
状态管理
- 使用 Context API 实现跨组件状态共享
- 自定义的安全 useState 防止组件卸载后的状态更新
4. 用户体验规范
交互设计
- 智能渲染:根据是否需要 tooltip 和 confirm 选择不同的渲染策略
- 状态冲突处理:Popconfirm 打开时自动隐藏 Tooltip
- 权限友好:无权限时可选择隐藏或禁用
国际化支持
- 集成了
useIntl
进行多语言支持
- 默认文案可配置
5. 扩展性规范
组合模式
1
| EasyButton.Provider = ButtonProvider
|
渐进增强
- 基础功能:自动 loading
- 增强功能:二次确认、权限控制、状态同步
- 保持向后兼容
设计亮点
- 渐进式 API:从简单的自动 loading 到复杂的多按钮状态管理
- 性能优化:智能渲染,避免不必要的组件嵌套
- 开发体验:减少样板代码,自动处理异步状态
- 可维护性:清晰的文件结构和职责分离
- 类型安全:完整的 TypeScript 支持
这个组件展现了优秀的 React 组件设计模式,在功能完整性、代码质量、用户体验和可维护性方面都达到了很高的标准。