关于 react-admin
react-admin 是一份开发指南,介绍如何使用 React-admin 为 REST 或 GraphQL API 构建单页应用。内容涵盖 providers、hooks、路由、数据操作、授权和表单,并指导使用框架现有抽象。
主要功能
- 01通过 provider 访问 API
- 02数据查询与变更 hooks
- 03上下文与控制器 hooks
- 04CRUD 与自定义路由
- 05身份验证与访问控制
- 06表单与关联记录字段
适用场景
- 01构建 REST 或 GraphQL 管理应用
- 02为资源添加 CRUD 页面
- 03通过 provider 处理 API 数据
- 04实现授权与表单
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ marmelab/ react-admin --skill react-admin安装后在 Claude Code 中输入/react-admin确认已加载。
详细介绍
React-Admin 开发指南
React-admin 是一个用于构建基于 REST/GraphQL API 的单页应用的框架。它基于 React Query、react-hook-form、react-router 和 Material UI 构建,提供 150 多个组件和数十个 Hook。在编写自定义代码之前,请始终先检查 react-admin 是否已提供适用于该任务的组件或 Hook。完整文档:https://marmelab.com/react-admin/doc/
Providers(后端抽象)
React-admin 不会直接调用 API。所有通信都通过 providers 进行——这些适配器会将 react-admin 的标准化调用转换为特定 API 的请求。三种主要的 provider 是:
- dataProvider:所有 CRUD 操作(
getList、getOne、create、update、delete、getMany、getManyReference、updateMany、deleteMany)。请参阅 DataProviders 和 50 多个现有适配器。 - authProvider:身份验证和授权。请参阅 Authentication。
- i18nProvider:翻译(
translate、changeLocale、getLocale)。
重要规则:切勿在组件中使用 fetch、axios 或直接发起 HTTP 调用。始终使用 data provider Hook。这样可以确保正确的缓存、加载状态、错误处理、身份验证和乐观渲染。
组合(避免巨型组件)
React-admin 采用组合而非配置的方式。通过传递子组件来覆盖行为,而不是设置数十个属性:
<Edit actions={<MyCustomActions />}>
<SimpleForm>
<TextInput source="title" />
</SimpleForm>
</Edit>
若要自定义布局,请将自定义布局组件传递给 <Admin layout={MyLayout}>。若要自定义菜单,请将其传递给 <Layout menu={MyMenu}>。这种链式组合是有意设计的——请参阅 Architecture。
Context:主动获取,而非向下传递
React-admin 组件通过 React contexts 向后代组件提供数据。使用 Hook 访问数据,而不是逐层传递属性:
useRecordContext()— Show/Edit/Create 视图中的当前记录。请参阅 useRecordContext。useListContext()— List 视图中的列表数据、筛选器、分页和排序。请参阅 useListContext。useShowContext()、useEditContext()、useCreateContext()— 详情视图的页面级状态。useTranslate()— 来自 i18nProvider 的翻译函数。useGetIdentity()— 来自 authProvider 的当前用户。
优先使用 Hook,而非自定义组件
如果 react-admin 组件的 UI 不符合需求,请使用底层 Hook,而不是从头构建。控制器 Hook(名称以 use*Controller 开头)提供所有逻辑,但不包含 UI:
useListController()— 列表数据获取、筛选和分页逻辑useEditController()— 编辑表单的数据获取和提交逻辑useShowController()— 展示页面的数据获取逻辑
路由
<Resource> 会自动声明 CRUD 路由(/posts、/posts/create、/posts/:id/edit、/posts/:id/show)。对于非 CRUD 页面,请使用 <CustomRoutes>。使用 useCreatePath() 构建资源 URL,并使用 react-admin 的 <Link> 进行导航。默认路由器是 react-router(HashRouter),但也可通过 routerProvider 使用 TanStack Router。请参阅 Routing。
数据获取
查询 Hook(读取数据)
const { data, total, isPending, error } = useGetList('posts', {
pagination: { page: 1, perPage: 25 },
sort: { field: 'created_at', order: 'DESC' },
filter: { status: 'published' },
});
const { data: record, isPending } = useGetOne('posts', { id: 123 });
const { data: records } = useGetMany('posts', { ids: [1, 2, 3] });
const { data, total } = useGetManyReference('comments', {
target: 'post_id', id: 123,
pagination: { page: 1, perPage: 25 },
});
请参阅 useGetList、useGetOne。
变更钩子(写入数据)
所有变更都会返回 [mutate, state]。它们支持三种变更模式:
- pessimistic(默认):等待服务端响应,然后更新 UI。
- optimistic:立即更新 UI,服务端出错时恢复。
- undoable:更新 UI,显示撤销通知,并在延迟后提交。
const [create, { isPending }] = useCreate();
const [update] = useUpdate();
const [deleteOne] = useDelete();
// Call with resource and params
create('posts', { data: { title: 'Hello' } });
update('posts', { id: 1, data: { title: 'Updated' }, previousData: record });
deleteOne('posts', { id: 1, previousData: record });
传入 mutationMode: 'optimistic' 或 'undoable',即可立即获得 UI 反馈。参见 useCreate、useUpdate。
身份验证与授权
const authProvider = {
login: ({ username, password }) => Promise<void>,
logout: () => Promise<void>,
checkAuth: () => Promise<void>, // Verify credentials are valid
checkError: (error) => Promise<void>, // Detect auth errors from API responses
getIdentity: () => Promise<{ id, fullName, avatar }>,
getPermissions: () => Promise<any>,
canAccess: ({ resource, action, record }) => Promise<boolean>, // RBAC
};
每个 auth provider 方法都有对应的钩子(例如 useGetIdentity()、useCanAccess())。
- 自定义路由默认是公开的。 使用
<Authenticated>包裹,或调用useAuthenticated()来要求用户登录。参见 Authenticated。 - 在
authProvider.canAccess()中集中处理授权,而不是在各个组件中处理。使用useCanAccess()检查权限。参见 useCanAccess 和 AuthRBAC。 - dataProvider 必须在请求中携带凭据(Bearer 令牌、Cookie)——authProvider 负责登录,而 dataProvider 负责 API 调用。在 data provider 配置中设置
httpClient。
实体之间的关系
为某个页面预先获取所有数据(包括关联数据)是一种反模式。应改为使用引用字段和输入控件,按需获取关联记录。
显示关联记录(字段)
{/* Show a the company of the current record based on its company_id */}
<ReferenceField source="company_id" reference="companies" />
{/* Show a list of related records (reverse FK) */}
<ReferenceManyField reference="comments" target="post_id">
<DataTable>
<TextField source="body" />
<DateField source="created_at" />
</DataTable>
</ReferenceManyField>
{/* Show multiple referenced records (array of IDs) */}
<ReferenceArrayField source="tag_ids" reference="tags">
<SingleFieldList>
<ChipField source="name" />
</SingleFieldList>
</ReferenceArrayField>
参见 ReferenceField、ReferenceManyField、ReferenceArrayField。
编辑关联记录(输入控件)
{/* Select from another resource (FK) */}
<ReferenceInput source="company_id" reference="companies" />
{/* Multi-select from another resource (array of IDs) */}
<ReferenceArrayInput source="tag_ids" reference="tags" />
参见 ReferenceInput、ReferenceArrayInput。
表单
React-admin 表单基于 react-hook-form 构建。单列布局使用 <SimpleForm>,多标签页布局使用 <TabbedForm>。参见 SimpleForm、TabbedForm。
向输入组件传入验证器:required()、minLength(min)、maxLength(max)、minValue(min)、maxValue(max)、number()、email()、regex(pattern, message),或返回错误字符串的自定义函数。
<TextInput source="title" validate={[required(), minLength(3)]} />
使用 RHF 的 useWatch() 创建可根据字段值动态响应的表单:
资源定义
将资源组件封装在索引文件中,以便简洁地导入:
// posts/index.ts
export default {
list: PostList,
create: PostCreate,
edit: PostEdit,
icon: PostIcon,
recordRepresentation: (record) => record.title, // How records appear in references
};
参见 Resource、RecordRepresentation。
自定义数据提供者方法
使用特定领域的方法扩展 dataProvider:
const dataProvider = {
...baseDataProvider,
archivePost: async (id) => { /* custom logic */ },
};
// Call via useDataProvider and useQuery:
// const dp = useDataProvider();
// const { data } = useQuery(['archivePost', id], () => dp.archivePost(id));
持久化客户端状态(Store)
使用 useStore() 保存持久化的用户偏好(主题、列可见性、已保存的筛选条件):
const [theme, setTheme] = useStore('theme', 'light');
参见 Store。
通知、重定向、刷新
const notify = useNotify();
const redirect = useRedirect();
const refresh = useRefresh();
notify('Record saved', { type: 'success' });
redirect('list', 'posts'); // Navigate to /posts
redirect('edit', 'posts', 123); // Navigate to /posts/123
refresh(); // Invalidate all queries
弃用项
- 使用 DataTable 替代 Datagrid
- 授权检查优先使用
<CanAccess>和useCanAccess