refine 中 Ant Design ListButton 完整指南:从列表页跳转到权限控制
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
<ListButton>是 refine 面向 Ant Design 封装的一个导航型按钮组件,用于在详情页(Show)、编辑页(Edit)等场景中一键跳回当前资源的列表页。它底层复用useNavigation的list方法,并自动根据资源名称生成按钮文案,开箱即用;同时支持自定义跳转目标、纯图标模式以及接入 access control 权限体系。本文以 v3 版本文档(documentation/versioned_docs/version-3.xx.xx/api-reference/antd/components/buttons/list.md)为主体,结合当前仓库中的源码实现与共用测试套件,完整讲解它的用法、属性、内部原理与测试保障。
ListButton 是什么
<ListButton>基于 Ant Design 的<Button>组件构建,本质是一个带导航语义的链接型按钮。它的典型使用场景是:
- 在
Show(详情)页面提供"返回列表"按钮; - 在
Edit(编辑)页面提供返回列表的入口; - 任何需要回到某个资源列表页的自定义页面。
从仓库源码看,Ant Design 适配层对它的实现非常薄,核心逻辑全部交给 core 包:
- UI 组件:packages/antd/src/components/buttons/list/index.tsx
- 类型定义:packages/antd/src/components/buttons/types.ts
// packages/antd/src/components/buttons/list/index.tsx 中导出 export const ListButton: React.FC<ListButtonProps> = ({ resource: resourceNameFromProps, hideText = false, accessControl, meta, children, onClick, ...rest }) => { ... }可以看到它接收resource(v3 文档中名为resourceNameOrRouteName)、hideText、accessControl、meta、onClick等属性,并把剩余 props 原样透传给 Ant Design 的<Button>。
Swizzle 支持:该组件支持通过refine CLI的 swizzle 命令拷贝到项目本地进行深度定制(对应文档中的
swizzle: true标记,CLI 实现位于 packages/cli)。
快速上手:在 Show 页面中放置返回列表按钮
最典型的用法是把<ListButton />放进<Show>组件的headerButtons插槽,这样详情页头部就会自动出现一个"返回列表"按钮:
// visible-block-start import { useShow } from "@pankod/refine-core"; import { Show, Typography, // highlight-next-line ListButton, } from "@pankod/refine-antd"; const { Title, Text } = Typography; const PostShow: React.FC = () => { const { queryResult } = useShow<IPost>(); const { data, isLoading } = queryResult; const record = data?.data; return ( // highlight-next-line <Show headerButtons={<ListButton />} isLoading={isLoading}> <Title level={5}>Id</Title> <Text>{record?.id}</Text> <Title level={5}>Title</Title> <Text>{record?.title}</Text> </Show> ); }; interface IPost { id: number; title: string; } // visible-block-end说明:上述示例使用 v3 时代的包名
@pankod/refine-antd、@pankod/refine-core;在当前仓库的 v4/v5 源码中对应包已更名为@refinedev/antd、@refinedev/core,用法保持一致。
点击该按钮后,refine 会调用useNavigation的list方法,跳转到当前资源(posts)的列表页路由(如/posts)。
按钮文案自动生成:文档中特别注明,按钮文字由 refine 根据resource对象的name属性自动定义。结合源码可以看得更具体——useNavigationButton中 list 动作的 label 生成逻辑为:
// packages/core/src/hooks/button/navigation-button/index.tsx#L79-L88 const label = props.action === "list" ? translate( `${identifier ?? props.resource}.titles.list`, getUserFriendlyName( resource?.meta?.label ?? identifier ?? props.resource, "plural", ), ) : translate(`buttons.${props.action}`, humanize(props.action));即:优先使用 i18n 键<resource>.titles.list的翻译;未配置翻译时,回退到资源名(或meta.label)的"人性化复数"形式——例如资源名为posts时按钮显示为Posts。这也意味着它天然支持多语言。
属性详解
resourceNameOrRouteName
跳转目标由resourceNameOrRouteName属性决定,最终生成的地址为resourceNameOrRouteName/list。默认情况下,<ListButton>使用 resource 对象的name属性作为点击后的跳转端点。
// visible-block-start import { ListButton } from "@pankod/refine-antd"; const MyListComponent = () => { return <ListButton resourceNameOrRouteName="categories" />; }; // visible-block-end点击该按钮会触发useNavigation的list方法,并跳转到/categories。
版本演化提示:在 v3 文档(本文主体)中该属性名为
resourceNameOrRouteName;而当前仓库源码已将入参更名为resource,并支持使用资源的identifier代替name。类型定义见 packages/ui-types/src/types/button.tsx:
export type RefineButtonResourceProps = { /** * Resource name for API data interactions. `identifier` of the resource can be used instead of the `name` of the resource. * @default Inferred resource name from the route */ resource?: string; ... };未显式传入时,useResourceParams会从当前路由推断资源,见 packages/core/src/hooks/button/navigation-button/index.tsx。
hideText
用于控制是否显示按钮文字。设为true时只显示图标:
// visible-block-start import { ListButton } from "@pankod/refine-antd"; const MyListComponent = () => { return ( <ListButton // highlight-next-line hideText={true} /> ); }; // visible-block-end对应 UI 层实现:hideText默认为false,渲染时!hideText && (children ?? label)决定文字是否输出(packages/antd/src/components/buttons/list/index.tsx)。ListButton 的图标为 Ant Design 的BarsOutlined(列表图标)。类型定义中该属性位于RefineButtonCommonProps(packages/ui-types/src/types/button.tsx)。
accessControl
该属性用于控制权限校验行为,仅在向<Refine/>提供了accessControlProvider时生效:
enabled:是否启用访问控制检查;hideIfUnauthorized:当用户对目标资源没有权限时,是否直接隐藏按钮。
import { ListButton } from "@pankod/refine-antd"; export const MyListComponent = () => { return ( <ListButton accessControl={{ enabled: true, hideIfUnauthorized: true }} /> ); };类型层面accessControl的默认值为{ enabled: true }(packages/ui-types/src/types/button.tsx)。实际校验由useButtonCanAccess完成,它会调用 access control provider 的can方法(action 为list);校验失败时按钮默认进入禁用态并显示reason作为title提示,若hideIfUnauthorized为true则直接不渲染(对应 packages/core/src/hooks/button/navigation-button/index.tsx 及 UI 层的if (isHidden) return null逻辑)。
其他可用属性
- Ant Design Button 全部属性:由于
...rest会透传,type、size、danger、loading等原生ButtonProps均可直接使用; children:自定义按钮文字,优先级高于自动生成的 label;onClick:点击回调;若按钮处于禁用态,点击会被拦截(e.preventDefault())且不触发回调;disabled/hidden:分别强制禁用与隐藏按钮;meta:生成目标 URL 时携带的附加 meta 数据。
源码级原理:点击后发生了什么
1. 组件渲染层(antd 适配)
packages/antd/src/components/buttons/list/index.tsx 完整实现了渲染逻辑:
const { to, label, title, hidden, disabled, LinkComponent } = useListButton({ resource: resourceNameFromProps, meta, accessControl, }); const isDisabled = disabled || rest.disabled; const isHidden = hidden || rest.hidden; if (isHidden) return null; return ( <LinkComponent to={to} replace={false} onClick={(e) => { if (isDisabled) { e.preventDefault(); return; } if (onClick) { e.preventDefault(); onClick(e); } }} > <Button icon={<BarsOutlined />} disabled={isDisabled} title={title} >export const useListButton = ( props: Prettify<Omit<NavigationButtonProps, "action" | "id">>, ) => useNavigationButton({ ...props, action: "list" });在 navigation-button/index.tsx 中,跳转地址由 action 决定:
const to = React.useMemo(() => { if (!resource) return ""; switch (props.action) { case "create": case "list": return navigation`${props.action}Url`; ... } }, [resource, id, props.meta, navigation[`${props.action}Url`]]);对于list动作,最终调用navigation.listUrl(resource, meta)——这正是useNavigation暴露的list方法,与文档中"使用useNavigation的list方法"的描述完全对应,从而生成/{resourceName}/list形式的完整路由(如/categories)。
3. 类型契约
ListButtonProps的完整类型为RefineListButtonProps<ButtonProps>(packages/antd/src/components/buttons/types.ts),其构成如下(packages/ui-types/src/types/button.tsx):
export type RefineListButtonProps< TComponentProps extends {} = Record<string, unknown>, TExtraProps extends {} = {}, > = RefineButtonCommonProps & // hideText RefineButtonResourceProps & // resource / accessControl RefineButtonLinkingProps & // onClick RefineButtonURLProps & // meta TComponentProps & // 在这里即 antd 的 ButtonProps TExtraProps & {};测试保障:共用测试套件
<ListButton>的测试位于 packages/antd/src/components/buttons/list/index.spec.tsx,它直接复用了@refinedev/ui-tests中的共用测试套件:
import { buttonListTests } from "@refinedev/ui-tests"; import { ListButton } from "./"; describe("List Button", () => { buttonListTests.bind(this)(ListButton); });共用测试套件定义在 packages/ui-tests/src/tests/buttons/list.tsx,覆盖了以下行为契约:
- 基础渲染:按钮可正常渲染且默认非禁用;
- 测试标识:存在
data-testid(RefineButtonTestIds.ListButton); - 禁用态:传入
disabled后按钮禁用,且点击不会触发onClick; - 隐藏态:传入
hidden后按钮不渲染; - 文案优先级:
children优先渲染;未传 children 时按资源 label 生成(如资源meta.label = "test"时显示 "Tests"); - 纯图标模式:
hideText时资源名文字不出现; - 权限控制矩阵:覆盖全局
accessControlProvider配置、按钮级accessControl覆盖、hideIfUnauthorized全局/局部开关、无权限时禁用并显示 reason 等 10 余种组合; - 点击回调:点击后
onClick被正确调用。
这保证了无论使用哪种 UI 适配层(Ant Design、MUI、Mantine、Chakra UI 等),ListButton 的导航、禁用、隐藏与权限行为都保持一致。
常见问题与最佳实践
- 如何在多个资源间跳转:在非当前资源上下文(如 Dashboard)中使用时,务必显式传入
resourceNameOrRouteName(v3)或resource(当前版本),避免依赖路由推断。 - 如何自定义文案:优先使用 i18n 键
<resource>.titles.list;临时场景直接传children即可覆盖自动 label。 - 权限不足时想要隐藏而不是禁用:设置
accessControl={{ hideIfUnauthorized: true }};注意只有配置了accessControlProvider时该行为才会生效。 - 按钮文字与列表页标题联动:由于 label 与列表页标题共享
<resource>.titles.list翻译键,保持文案一致性的同时只需维护一处配置。
综上,<ListButton>是一个"薄封装、强约定"的组件:文档层告诉你如何即插即用地返回列表页,源码层则揭示了它如何通过useListButton → useNavigationButton(action: "list") → useNavigation.listUrl()的调用链完成路由生成,并通过共用测试套件保证跨 UI 适配层的行为一致。理解这层实现后,无论是使用默认行为、定制跳转目标,还是接入权限体系,都能做到心中有数。
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考