Refine Ant Design Inferencer 组件实战:用 @refinedev/inferencer 自动生成 List / Show / Create / Edit 页面
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
本文围绕 Refine 生态中的@refinedev/inferencer包,深入讲解其 Ant Design 集成组件AntdInferencer及其拆分的AntdListInferencer、AntdShowInferencer、AntdEditInferencer、AntdCreateInferencer。你将学会如何在路由中零配置地使用它们、如何在自定义组件中显式传入resource/action/id,并理解字段类型推断、关联资源探测与代码生成这三条底层流水线,最终把生成的可复制代码迁移到正式页面中。
概览:Inferencer 能做什么
@refinedev/inferencer是一个基于资源数据结构自动生成视图代码的实验性包。它通过<Refine/>组件提供的dataProvider拉取资源数据,据此推断每个字段的类型,再按 UI 框架生成对应的组件与源码。当前仓库中它按 UI 包划分导出作用域,@refinedev/inferencer/antd即对应@refinedev/antd(Ant Design 集成)的作用域,此外还有 Material UI、Mantine、Chakra UI 与 Headless 版本(见 Inferencer 集成文档)。
针对 Ant Design,@refinedev/inferencer/antd共导出五个组件:
| 组件 | 生成的视图 | 底层使用的 Ant Design / Refine 能力 |
|---|---|---|
AntdListInferencer | 列表页 | List+Table+useTable |
AntdShowInferencer | 详情页 | Show+ 各字段组件 +useShow |
AntdCreateInferencer | 创建页 | Create+useForm |
AntdEditInferencer | 编辑页 | Edit+useForm |
AntdInferencer | 以上四者的聚合入口 | 根据当前路由的action自动分发 |
从源码看,AntdInferencer的聚合逻辑很直接:它从@refinedev/core的useParsed()中读取当前路由解析出的action与id,然后按action分别渲染ShowInferencer、CreateInferencer、EditInferencer,默认(即list)渲染ListInferencer,实现代码见 antd/index.tsx。这也解释了为什么在路由中只放一个<AntdInferencer />就能覆盖四种页面。
安装
在项目(已安装@refinedev/antd、antd与@refinedev/core)中安装:
npm install @refinedev/inferencer # 或 pnpm add @refinedev/inferencer该包当前标记为experimental,官方建议仅用于开发环境辅助生成代码,不应用于生产环境(详见 packages/inferencer 文档)。
快速使用:两种接入方式
Inferencer 组件支持两种用法:路由推断与显式传参。官方文档给出了完整示例,这里逐条拆解。
方式一:放在 resources 对应的路由中(零 props)
当你配置了routerProvider时,AntdInferencer会自动从当前路由推断resource、action和id,因此可以不传任何 props:
import routerProvider from "@refinedev/react-router"; import { BrowserRouter } from "react-router"; // highlight-next-line import { AntdInferencer } from "@refinedev/inferencer/antd"; const App = () => { return ( <BrowserRouter> <Refine routerProvider={routerProvider} resources={[ { name: "samples", list: "/samples", }, ]} > <Routes> {/* highlight-next-line */} <Route path="/samples" element={<AntdInferencer />} /> </Routes> </Refine> </BrowserRouter> ); };要点:
resources中声明的list路径必须与<Route path="/samples">一致;- 若资源同时声明了
show、create、edit路径,只需在对应路由挂载同一个<AntdInferencer />,它会根据路由段自动切换到对应的视图(/samples→ list,/samples/show/123→ show,/samples/create→ create,/samples/edit/123→ edit)。
方式二:在自定义组件中显式传 props
当你不依赖路由、想把 Inferencer 嵌入现有页面时,可以用resource、action、id三个 props 精确指定要生成的视图:
// highlight-next-line import { AntdInferencer } from "@refinedev/inferencer/antd"; const SampleList = () => { return ( // highlight-next-line <AntdInferencer resource="samples" action="list" /> ); }; const SampleShow = () => { return ( // highlight-next-line <AntdInferencer resource="samples" action="show" id="1" /> ); }; const SampleCreate = () => { return ( // highlight-next-line <AntdInferencer resource="samples" action="create" /> ); }; const SampleEdit = () => { return ( // highlight-next-line <AntdInferencer resource="samples" action="edit" id="1" /> ); };注意:show与edit需要指定id,否则无法发起getOne请求;list与create不需要id(create视图基于列表接口返回的第一条记录来推断表单字段)。完整 props 定义(name/resource/action/id/fieldTransformer/meta/hideCodeViewerInProduction)可查看 types/index.ts。
更多细节参见 Inferencer 集成文档。
四种视图:各自生成什么
下面四个可运行示例均使用@refinedev/simple-rest提供的假数据 API(https://api.fake-rest.refine.dev)与ThemedLayout布局,读者可直接复制到自己的 Refine 应用中对照学习。
List:列表页
AntdListInferencer根据列表接口的响应推断列结构,使用@refinedev/antd的List、Table与useTable:
setInitialRoutes(["/samples"]); import { Refine } from "@refinedev/core"; import { ThemedLayout, RefineThemes } from "@refinedev/antd"; import routerProvider from "@refinedev/react-router"; import dataProvider from "@refinedev/simple-rest"; import { ConfigProvider } from "antd"; import { BrowserRouter, Routes, Route, Outlet } from "react-router"; // highlight-next-line import { AntdInferencer } from "@refinedev/inferencer/antd"; const API_URL = "https://api.fake-rest.refine.dev"; const App: React.FC = () => { return ( <BrowserRouter> <ConfigProvider theme={RefineThemes.Blue}> <Refine routerProvider={routerProvider} dataProvider={dataProvider(API_URL)} resources={[ { name: "samples", list: "/samples", }, ]} > <Routes> <Route element={ <ThemedLayout> <Outlet /> </ThemedLayout> } > {/* highlight-next-line */} <Route path="/samples" element={<AntdInferencer />} /> </Route> </Routes> </Refine> </ConfigProvider> </BrowserRouter> ); };生成的列表页会按字段类型挑选 Ant Design 字段组件:日期列用DateField、邮箱列用EmailField、图片列用ImageField(限制maxWidth: 100px)、布尔列用BooleanField、富文本列用MarkdownField(截取前 80 字符)、URL 列用UrlField、多值列用TagField平铺展示。列定义通过dataIndex={["key", "accessor"]}支持嵌套对象取值。资源若配置了canEdit/canShow/canDelete(或edit/show路由与resourceMeta),还会追加一列 "Actions",内置EditButton、ShowButton、DeleteButton(均为hideText size="small"样式)。上述逻辑的完整实现见 antd/list.tsx。
Show:详情页
AntdShowInferencer根据单条记录的结构生成详情视图,使用@refinedev/antd的Show与字段组件,数据由@refinedev/core的useShow提供:
setInitialRoutes(["/samples/show/123"]); import { Refine } from "@refinedev/core"; import { RefineThemes, ThemedLayout } from "@refinedev/antd"; import routerProvider from "@refinedev/react-router"; import dataProvider from "@refinedev/simple-rest"; import { ConfigProvider } from "antd"; import { BrowserRouter, Routes, Route, Outlet } from "react-router"; // highlight-next-line import { AntdInferencer } from "@refinedev/inferencer/antd"; const API_URL = "https://api.fake-rest.refine.dev"; const App: React.FC = () => { return ( <BrowserRouter> <ConfigProvider theme={RefineThemes.Blue}> <Refine routerProvider={routerProvider} dataProvider={dataProvider(API_URL)} resources={[ { name: "samples", show: "/samples/show/:id", }, ]} > <Routes> <Route element={ <ThemedLayout> <Outlet /> </ThemedLayout> } > {/* highlight-next-line */} <Route path="/samples/show/:id" element={<AntdInferencer />} /> </Route> </Routes> </Refine> </ConfigProvider> </BrowserRouter> ); };详情页会用 antd 的Typography体系逐字段渲染(标题、段落、文本等),日期、图片、邮箱、布尔、URL、富文本同样映射到对应字段组件。
Create:创建页
AntdCreateInferencer根据列表接口返回的第一条记录推断表单字段,使用@refinedev/antd的Create与useForm:
setInitialRoutes(["/samples/create"]); import { Refine } from "@refinedev/core"; import { ThemedLayout, RefineThemes } from "@refinedev/antd"; import routerProvider from "@refinedev/react-router"; import dataProvider from "@refinedev/simple-rest"; import { ConfigProvider } from "antd"; import { BrowserRouter, Routes, Route, Outlet } from "react-router"; // highlight-next-line import { AntdInferencer } from "@refinedev/inferencer/antd"; const API_URL = "https://api.fake-rest.refine.dev"; const App: React.FC = () => { return ( <BrowserRouter> <ConfigProvider theme={RefineThemes.Blue}> <Refine routerProvider={routerProvider} dataProvider={dataProvider(API_URL)} resources={[ { name: "samples", create: "/samples/create", }, ]} > <Routes> <Route element={ <ThemedLayout> <Outlet /> </ThemedLayout> } > {/* highlight-nextline */} <Route path="/samples/create" element={<AntdInferencer />} /> </Route> </Routes> </Refine> </ConfigProvider> </BrowserRouter> ); };Edit:编辑页
AntdEditInferencer根据单条记录推断表单字段(含默认值回填),使用@refinedev/antd的Edit与useForm:
setInitialRoutes(["/samples/edit/123"]); import { Refine } from "@refinedev/core"; import { ThemedLayout, RefineThemes } from "@refinedev/antd"; import routerProvider from "@refinedev/react-router"; import dataProvider from "@refinedev/simple-rest"; import { ConfigProvider } from "antd"; import { BrowserRouter, Routes, Route, Outlet } from "react-router"; // highlight-next-line import { AntdInferencer } from "@refinedev/inferencer/antd"; const API_URL = "https://api.fake-rest.refine.dev"; const App: React.FC = () => { return ( <BrowserRouter> <ConfigProvider theme={RefineThemes.Blue}> <Refine routerProvider={routerProvider} dataProvider={dataProvider(API_URL)} resources={[ { name: "samples", edit: "/samples/edit/:id", }, ]} > <Routes> <Route element={ <ThemedLayout> <Outlet /> </ThemedLayout> } > {/* highlight-next-line */} <Route path="/samples/edit/:id" element={<AntdInferencer />} /> </Route> </Routes> </Refine> </ConfigProvider> </BrowserRouter> ); };表单视图会根据字段类型选择对应的 antd 表单控件(文本输入、数字输入、日期选择、开关、选择器等),关联字段自动转换为Select并配合useSelect加载选项。
底层原理:数据如何获取与字段如何推断
数据获取策略
@refinedev/inferencer通过<Refine/>的dataProvider取数,策略与视图类型相关:
- edit / show:以
resource+id发起getOne请求; - list / create:发起
getList请求,并从返回列表中取一条记录作为推断依据(create需要表单字段,故也用列表数据)。
这些请求在你的应用内真实发生,因此需要应用已正确配置dataProvider。从源码结构看,取数与推断由 use-infer-fetch 与 use-relation-fetch 两个内部 hook 协作完成,最终在 create-inferencer/index.tsx 中驱动整个流水线。
字段类型推断
推断器是一组函数集合,每个函数检查字段是否符合某种类型并返回推断结果,同时附带priority(优先级)字段:当同一字段能匹配多种类型时(例如created_at既是字符串又像日期),优先级高的类型胜出。默认推断器在 field-inferencers/index.ts 中注册,覆盖以下类型:
type Types = | "relation" | "array" | "object" | "date" | "email" | "image" | "url" | "richtext" | "text" | "number" | "boolean" | "unknown" | `custom_${string}`;其中custom_${string}由各 UI 包的 Inferencer 内部使用(例如 Ant Design 对某些字段有专属控件表示);目前用户还不能向组件传入自定义类型与推断函数。
对多值属性会判定为array,并对其元素值递归执行同样的推断以确定元素类型;object类型同理递归处理。两者在返回值中都可以带accessor字段,用于在生成视图与代码时定位取值路径。对于object类型,Inferencer 会尝试挑选一个代表性键来展示该属性,例如{ label: string; id: string }这样的category字段会选择label作为展示键;这类可展示的object字段在返回结果中fieldable为true。可作展示键的字段名(PresentationalKeys)包括name、label、title、count、content、username、nickname、login、firstName、lastName、url。
关联关系如何判定
在字段被判定为relation之前,会先检查若干不会触发 API 调用的条件:
- 属性名以
id或ids结尾(支持 camelCase、PascalCase、snake_case、kebab-case、UPPER_CASE、lower_case,且允许带数组括号); - 属性是仅含单个
id键的对象; - 属性是"仅含单个
id键的对象数组"或"由 UUID 兼容字符串/数字组成的数组"; - 属性是字符串或数字,且属性名与某个已知资源(单数或复数)匹配。
满足其一即标记为relation,随后按以下顺序确定关联资源:
- 优先在
resources数组中按属性名(单数或复数)匹配资源; - 匹配失败时,向
defaultdataProvider 分别发送单数与复数(去掉id后缀)资源名的两次请求; - 请求成功(HTTP 200)则确定为关联资源,并使用该资源及其
dataProvider按属性值发请求; - 全部失败则撤销
relation标记,退回普通字段;若为object类型则尝试寻找最佳展示键。
若你的dataProvider/resources组织方式让 Inferencer 无法自动识别关联,可以改用fieldTransformer手动修正推断结果(见下文)。
组件渲染与代码生成
字段确定后,createInferencer调用各 UI 包、各 action 专属的renderer函数:它返回一段组件源码字符串,既用于在页面上实时渲染(通过支持 TypeScript 的react-livefork 执行),也用于在代码查看器中展示,用户可一键复制到自己的工程。组件名由当前资源与 action 组合而成:优先取resource.meta.label,否则取resource.name,例如资源categories的 list 视图组件名为CategoryList。整条渲染管线与 props 说明见 create-inferencer/index.tsx 与 types/index.ts。
高级用法
为 GraphQL 后端传 meta 值
Refine 通过数据 hook 的meta属性支持 GraphQL 后端。Inferencer 的metaprop 采用嵌套结构,允许按"资源 × 方法"分别定义 meta 值,因为 Inferencer 可能发现关联资源并额外发起getMany/getOne等请求:
<AntdListInferencer meta={{ [resourceNameOrIdentifier: string]: { [methodName: "default" | "getList" | "getMany" | "getOne" | "update"]: Record<string, unknown>, } }} />default是全部方法的兜底 meta 值;某资源某方法未单独配置时回退到default。示例:
<AntdListInferencer meta={{ posts: { getList: { fields: ["id", "title", "content", "category_id", "created_at"], }, }, categories: { default: { fields: ["id", "title"], }, }, }} />用 fieldTransformer 修改推断结果
若想定制输出——例如为object字段设置自定义accessor、改变字段type、或调整relation的关联资源——可使用fieldTransformerprop。它是一个接收字段、返回修改后字段的函数;返回undefined | false | null时该字段会从预览与代码中一并移除:
<AntdListInferencer fieldTransformer={(field) => { // 隐藏 createdAt 字段 if (field.key === "createdAt") return undefined; // 其余字段原样保留 return field; }} />该函数在每个字段完成内置推断与转换之后、进入渲染器之前被调用,实现见 create-inferencer/index.tsx。
隐藏代码查看器与开发警告
生产环境下可用hideCodeViewerInProduction隐藏代码查看器与提示信息块(开发环境下始终可见)。但请注意:Inferencer 组件本身不应用于生产环境,它定位为开发期脚手架工具,帮助快速生成可复制的页面代码。
完整示例与源码导航
- 仓库内置了完整可运行的 inferencer-antd 示例,包含 Ant Design 版 List / Show / Create / Edit 全部视图,是最佳上手参照。
- Ant Design 专属的四个 renderer 分别位于 inferencers/antd/list.tsx、show.tsx、create.tsx、edit.tsx,并有配套快照测试(
__tests__/__snapshots__)锁定生成代码的形态。 - 字段推断器的单元测试(如 date.test.ts、relation.test.ts)可用于理解各类字段的判定边界。
小结
@refinedev/inferencer把"为资源编写 CRUD 页面"这件事压缩成了三个步骤:挂路由(或传 props)、让组件读取数据并推断字段、把生成代码复制进正式组件。对 Ant Design 用户来说,AntdInferencer一个组件即可覆盖四种页面,而fieldTransformer与meta两个 prop 又提供了对推断结果和后端差异的修正能力。在实际项目中,推荐把它当作开发期脚手架使用:先用 Inferencer 快速产出原型,再基于生成的代码按业务需求定制,从而兼顾开发速度与代码可控性。
【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考