Refine v5 中 useDrawer Hook 的使用与底层原理:轻松掌控 Ant Design Drawer
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
useDrawer是@refinedev/antd提供的状态管理 Hook,用于封装 Ant Design 的<Drawer>侧滑抽屉组件的打开、关闭与受控属性,让你无需手写useState和onClose样板代码即可管理抽屉。本文将从基础用法、返回值与参数、源码实现、测试验证以及与useDrawerForm的衔接四个层面,带你完整掌握这一 Hook 的实战姿势与内部机制。
什么是 useDrawer
在 Refine 的 Ant Design 集成中,useDrawer是一个专为 Ant Design Drawer 组件设计的 Hook。它的核心职责是:
- 通过
show/close两个函数控制抽屉的打开与关闭; - 通过
drawerProps一次性提供<Drawer>组件所需的全部受控属性(open、onClose),直接展开(spread)到组件上即可生效。
最基本的调用方式如下:
const { show, close, drawerProps } = useDrawer();拿到返回值后,将drawerProps解构到<Drawer />组件上,再用show和close控制可见性即可。
从源码类型定义可以确认其返回值结构(见 useDrawer/index.tsx):
export type useDrawerReturnType = { drawerProps: DrawerProps; } & Omit<useModalReturnType, "visible">;也就是说,返回值由两部分组成:drawerProps(Ant Design 的DrawerProps类型)加上来自@refinedev/core的useModal返回类型中去掉visible之后的show与close。
快速上手:在列表页中弹出一个抽屉
原文档给出了一段最简示例(src/pages/posts/list.tsx),我们在此基础上补充注释和细节:
import { useDrawer } from "@refinedev/antd"; import { Drawer, Button } from "antd"; export const PostList: React.FC = () => { const { show, drawerProps } = useDrawer(); return ( <> <Button onClick={show}>Show Drawer</Button> <Drawer {...drawerProps}> <p>Drawer Content</p> </Drawer> </> ); };工作流程如下:
- 页面上渲染一个按钮,把
show绑定到它的onClick回调; - 用户点击按钮,
show()内部将可见状态置为true; drawerProps中的open属性随之变为true,<Drawer>弹出;- 用户点击遮罩、关闭按钮或按 ESC 键时,Ant Design 的 Drawer 会触发
onClose,而drawerProps内置的onClose会调用close()把状态置回false,抽屉收起。
返回值详解:show / close / drawerProps
原文档的 API Reference 表格中show与close的描述存在笔误(两者说明被写反),这里依据仓库源码给出准确对照:
| 返回值 | 类型 | 说明(依据源码) |
|---|---|---|
show | () => void | 打开抽屉。内部调用useModal返回的show,将可见状态置为true |
close | () => void | 关闭抽屉。内部调用useModal返回的close,将可见状态置为false |
drawerProps | DrawerProps | 传给<Drawer />的受控属性集合,包含open(当前可见状态)与onClose(默认先透传用户自定义的onClose,再执行close()) |
drawerProps 的合并逻辑
drawerProps并非原样透传,而是经过了一层合并处理,源码如下(useDrawer/index.tsx):
return { drawerProps: { ...drawerProps, onClose: (e: React.MouseEvent | React.KeyboardEvent) => { drawerProps.onClose?.(e); close(); }, open: visible, }, show, close, };这里有三点值得注意:
open: visible会覆盖你传入的open,确保抽屉可见性始终由 Hook 内部状态驱动,避免受控/非受控状态打架;onClose会被包装:先调用你自定义的onClose(如果有),再调用close()。这意味着你可以在关闭前挂载自己的清理逻辑,同时不必担心抽屉无法正常关闭;- 传入的其余
DrawerProps(如width、title、placement等)会通过展开运算符保留。
参数详解:初始化默认可见性
useDrawer接受一个可选的配置对象:
useDrawer({ drawerProps: { open: true, // 初始状态直接打开抽屉 width: 500, title: "Post Detail", }, });唯一的参数是drawerProps,类型为 Ant Design 的DrawerProps。其中只有open会被 Hook 特殊对待——源码中通过useModal({ defaultVisible: drawerProps.open })把它作为抽屉的初始可见状态读取(见 useDrawer/index.tsx):
export const useDrawer = ({ drawerProps = {}, }: useDrawerProps = {}): useDrawerReturnType => { const { show, close, visible } = useModal({ defaultVisible: drawerProps.open, }); // ... };也就是说,open: true表示抽屉挂载时默认展开;未传时默认收起。其余DrawerProps会原样合并进返回的drawerProps。
源码剖析:useDrawer 与 useModal 的分层设计
useDrawer本身没有重复实现状态逻辑,而是复用了@refinedev/core中的通用useModalHook,这是典型的"核心层 + UI 层"分层设计。
useModal的实现非常精简(packages/core/src/hooks/modal/useModal/index.tsx):
export const useModal = ({ defaultVisible = false, }: useModalProps = {}): useModalReturnType => { const [visible, setVisible] = useState(defaultVisible); const show = useCallback(() => setVisible(true), [visible]); const close = useCallback(() => setVisible(false), [visible]); return { visible, show, close, }; };可以看到:
- 可见状态就是一个
useState,初始值由defaultVisible决定; show与close用useCallback缓存,仅依赖visible;- 返回的
visible在useDrawer中被映射为drawerProps.open,因此useDrawer的返回值刻意用Omit<useModalReturnType, "visible">去掉了visible,避免调用方在open与visible之间产生混淆。
这条调用链可以概括为:useDrawer(Ant Design 适配层)→useModal(核心通用状态层)→useState。理解这一点后,你在排查抽屉"打不开/关不上"问题时,就能直接定位到是show/close未被触发,还是drawerProps被外部覆盖了open。
测试验证:行为边界有据可查
仓库为useDrawer提供了完整的单元测试(index.spec.ts),覆盖了以下关键行为,这些行为可以作为你使用时的心智模型:
- 初始不可见:不传参调用
useDrawer(),drawerProps.open为false; - 传入
open: true初始可见:useDrawer({ drawerProps: { open: true } }),drawerProps.open为true; show()打开:调用show()后open变为true;close()关闭:先show()再close(),open回到false;- 自定义
onClose被调用且抽屉关闭:传入onClose后触发drawerProps.onClose,自定义回调恰好被调用 1 次,同时抽屉关闭; - 未传
onClose时关闭:无自定义onClose时触发关闭同样生效。
第 5、6 条直接印证了上文提到的onClose包装逻辑:无论你是否自定义关闭回调,抽屉都能正常收起。
进阶衔接:useDrawerForm 与抽屉表单
单纯的useDrawer适合展示型抽屉(如详情展示、自定义交互内容)。如果你需要"在抽屉里编辑表单",Refine 提供了更高层的useDrawerForm,它内部正是基于useDrawer构建的(见 useDrawerForm.ts):
const { show, close, drawerProps } = useDrawer({ drawerProps: { open: defaultVisible, }, });useDrawerForm在useDrawer的基础上额外整合了:
formProps:Ant Design 表单受控属性;saveButtonProps/deleteButtonProps:保存、删除按钮属性;formLoading:表单加载状态;- 默认的抽屉规格:
width: "500px"与forceRender: true; syncWithLocation:把抽屉打开状态和记录 id 同步到 URL 查询参数,刷新/分享后状态可恢复。
仓库中的可运行示例 examples/form-antd-use-drawer-form/src/pages/posts/list.tsx 展示了在同一列表页中同时管理"创建抽屉"与"编辑抽屉"的典型写法:
// 创建抽屉 const { formProps: createFormProps, drawerProps: createDrawerProps, show: createDrawerShow, saveButtonProps: createSaveButtonProps, } = useDrawerForm<IPost>({ action: "create", syncWithLocation: true, }); // 编辑抽屉 const { formProps: editFormProps, drawerProps: editDrawerProps, show: editDrawerShow, saveButtonProps: editSaveButtonProps, deleteButtonProps, id, formLoading: editFormLoading, } = useDrawerForm<IPost>({ action: "edit", syncWithLocation: true, });列表的"新建"按钮调用createDrawerShow(),每行的"编辑"按钮调用editDrawerShow(record.id)传入记录 id,两个<Drawer>分别展开createDrawerProps与editDrawerProps。想深入了解,可继续阅读 use-drawer-form 文档。
小结
useDrawer用约 30 行代码把 Ant DesignDrawer的受控逻辑封装干净,show/close/drawerProps三个返回值各司其职;- 底层复用
@refinedev/core的useModal,open状态与onClose行为均有单测保障,边界行为清晰可查; - 纯展示用
useDrawer,抽屉内嵌表单用useDrawerForm,两者在同一调用链上,可以平滑过渡升级。
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考