react-admin
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact-Admin Development Guide
React-Admin 开发指南
React-admin is a framework for building single-page applications on top of REST/GraphQL APIs. It builds on top of React Query, react-hook-form, react-router, and Material UI. It provides 150+ components and dozens of hooks. Before writing custom code, always check if react-admin already provides a component or hook for the task. Full documentation: https://marmelab.com/react-admin/doc/
React-admin 是一个基于REST/GraphQL API构建单页应用的框架,它基于React Query、react-hook-form、react-router和Material UI开发,提供了150多个组件和数十个钩子。在编写自定义代码之前,请先确认react-admin是否已提供了完成该任务的组件或钩子。完整文档:https://marmelab.com/react-admin/doc/
Providers (Backend Abstraction)
提供者(后端抽象层)
React-admin never calls APIs directly. All communication goes through providers — adapters that translate react-admin's standardized calls into API-specific requests. The three main providers are:
- dataProvider: All CRUD operations (,
getList,getOne,create,update,delete,getMany,getManyReference,updateMany). See DataProviders and 50+ existing adapters.deleteMany - authProvider: Authentication and authorization. See Authentication.
- i18nProvider: Translations (,
translate,changeLocale).getLocale
Critical rule: Never use , , or direct HTTP calls in components. Always use data provider hooks. This ensures proper caching, loading states, error handling, authentication, and optimistic rendering.
fetchaxiosReact-admin 从不直接调用API,所有通信都通过providers进行——这些适配器会将react-admin的标准化调用转换为特定API的请求。主要有三类提供者:
- dataProvider:处理所有CRUD操作(、
getList、getOne、create、update、delete、getMany、getManyReference、updateMany)。查看DataProviders和50+现有适配器。deleteMany - authProvider:处理身份验证与授权。查看Authentication。
- i18nProvider:处理翻译功能(、
translate、changeLocale)。getLocale
重要规则:永远不要在组件中使用、或直接HTTP调用,务必使用数据提供者钩子。这能确保正确的缓存、加载状态、错误处理、身份验证和乐观渲染。
fetchaxiosComposition (Not God Components)
组合而非全能组件
React-admin uses composition over configuration. Override behavior by passing child components, not by setting dozens of props:
jsx
<Edit actions={<MyCustomActions />}>
<SimpleForm>
<TextInput source="title" />
</SimpleForm>
</Edit>To customize the layout, pass a custom layout component to . To customize the menu, pass it to . This chaining is by design — see Architecture.
<Admin layout={MyLayout}><Layout menu={MyMenu}>React-admin 采用组合而非配置的方式,通过传入子组件来覆盖默认行为,而非设置大量属性:
jsx
<Edit actions={<MyCustomActions />}>
<SimpleForm>
<TextInput source="title" />
</SimpleForm>
</Edit>要自定义布局,可将自定义布局组件传入;要自定义菜单,可传入。这种链式设计是有意为之——查看Architecture。
<Admin layout={MyLayout}><Layout menu={MyMenu}>Context: Pull, Don't Push
上下文:拉取而非推送
React-admin components expose data to descendants via React contexts. Access data using hooks rather than passing props down:
- — current record in Show/Edit/Create views. See useRecordContext.
useRecordContext() - — list data, filters, pagination, sort in List views. See useListContext.
useListContext() - ,
useShowContext(),useEditContext()— page-level state for detail views.useCreateContext() - — translation function from i18nProvider.
useTranslate() - — current user from authProvider.
useGetIdentity()
React-admin 组件通过React上下文向后代组件暴露数据,使用钩子访问数据而非逐层传递属性:
- — 查看/编辑/创建视图中的当前记录。查看useRecordContext。
useRecordContext() - — 列表视图中的列表数据、筛选器、分页、排序信息。查看useListContext。
useListContext() - 、
useShowContext()、useEditContext()— 详情页的页面级状态。useCreateContext() - — 来自i18nProvider的翻译函数。
useTranslate() - — 来自authProvider的当前用户信息。
useGetIdentity()
Hooks Over Custom Components
优先使用钩子而非自定义组件
When a react-admin component's UI doesn't fit, use the underlying hook instead of building from scratch. Controller hooks (named ) provide all the logic without the UI:
use*Controller- — list fetching, filtering, pagination logic
useListController() - — edit form fetching and submission logic
useEditController() - — show page data fetching logic
useShowController()
当react-admin组件的UI不符合需求时,优先使用其底层钩子而非从零构建。控制器钩子(命名为)提供所有逻辑但不包含UI:
use*Controller- — 列表数据获取、筛选、分页逻辑
useListController() - — 编辑表单的数据获取与提交逻辑
useEditController() - — 详情页的数据获取逻辑
useShowController()
Routing
路由
<Resource>/posts/posts/create/posts/:id/edit/posts/:id/show<CustomRoutes>useCreatePath()<Link>routerProvider<Resource>/posts/posts/create/posts/:id/edit/posts/:id/show<CustomRoutes>useCreatePath()<Link>routerProviderData Fetching
数据获取
Query Hooks (Reading Data)
查询钩子(读取数据)
jsx
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 },
});See useGetList, useGetOne.
jsx
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 },
});Mutation Hooks (Writing Data)
变更钩子(写入数据)
All mutations return . They support three mutation modes:
[mutate, state]- pessimistic (default): Wait for server response, then update UI.
- optimistic: Update UI immediately, revert on server error.
- undoable: Update UI, show undo notification, commit after delay.
jsx
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 });所有变更钩子都会返回,支持三种变更模式:
[mutate, state]- 悲观模式(默认):等待服务器响应后再更新UI。
- 乐观模式:立即更新UI,若服务器返回错误则回滚。
- 可撤销模式:更新UI并显示撤销通知,延迟后再提交。
jsx
const [create, { isPending }] = useCreate();
const [update] = useUpdate();
const [deleteOne] = useDelete();
// 传入资源和参数调用
create('posts', { data: { title: 'Hello' } });
update('posts', { id: 1, data: { title: 'Updated' }, previousData: record });
deleteOne('posts', { id: 1, previousData: record });Authentication & Authorization
身份验证与授权
typescript
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
};Each auth provider method has a corresponding hook (e.g. , ).
useGetIdentity()useCanAccess()- Custom routes are public by default. Wrap them with or call
<Authenticated>to require login. See Authenticated.useAuthenticated() - Centralize authorization in , not in individual components. Use
authProvider.canAccess()to check permissions. See useCanAccess and AuthRBAC.useCanAccess() - The dataProvider must include credentials (Bearer token, cookies) in requests — authProvider handles login, but dataProvider handles API calls. Configure in data provider setup.
httpClient
typescript
const authProvider = {
login: ({ username, password }) => Promise<void>,
logout: () => Promise<void>,
checkAuth: () => Promise<void>, // 验证凭证是否有效
checkError: (error) => Promise<void>, // 从API响应中检测身份验证错误
getIdentity: () => Promise<{ id, fullName, avatar }>,
getPermissions: () => Promise<any>,
canAccess: ({ resource, action, record }) => Promise<boolean>, // 基于角色的访问控制(RBAC)
};每个身份验证提供者方法都有对应的钩子(例如、)。
useGetIdentity()useCanAccess()- 自定义路由默认是公开的。使用包裹路由或调用
<Authenticated>来要求登录。查看Authenticated。useAuthenticated() - 在中集中处理授权逻辑,而非在单个组件中处理。使用
authProvider.canAccess()检查权限。查看useCanAccess和AuthRBAC。useCanAccess() - DataProvider必须在请求中包含凭证(Bearer令牌、Cookie)——authProvider处理登录,而dataProvider处理API调用。在数据提供者设置中配置。
httpClient
Relationships Between Entities
实体间关系
Fetching all the data (including relationships) upfront for a given page is an anti-pattern. Instead, fetch related records on demand using reference fields and inputs.
提前获取给定页面的所有数据(包括关联数据)是一种反模式。应按需使用引用字段和输入组件获取关联记录。
Displaying Related Records (Fields)
显示关联记录(字段)
jsx
{/* 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>jsx
{/* 根据当前记录的company_id显示对应的公司 */}
<ReferenceField source="company_id" reference="companies" />
{/* 显示关联记录列表(反向外键) */}
<ReferenceManyField reference="comments" target="post_id">
<DataTable>
<TextField source="body" />
<DateField source="created_at" />
</DataTable>
</ReferenceManyField>
{/* 显示多个关联记录(ID数组) */}
<ReferenceArrayField source="tag_ids" reference="tags">
<SingleFieldList>
<ChipField source="name" />
</SingleFieldList>
</ReferenceArrayField>Editing Related Records (Inputs)
编辑关联记录(输入组件)
jsx
{/* 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" />jsx
{/* 从另一个资源中选择(外键) */}
<ReferenceInput source="company_id" reference="companies" />
{/* 从另一个资源中多选(ID数组) */}
<ReferenceArrayInput source="tag_ids" reference="tags" />Forms
表单
React-admin forms are built on react-hook-form. Use for single-column layouts and for multi-tab layouts. See SimpleForm, TabbedForm.
<SimpleForm><TabbedForm>Pass validators to input components: , , , , , , , , or a custom function returning an error string.
required()minLength(min)maxLength(max)minValue(min)maxValue(max)number()email()regex(pattern, message)jsx
<TextInput source="title" validate={[required(), minLength(3)]} />Use RHF's to create dynamic forms that react to field values:
useWatch()React-admin 表单基于react-hook-form构建。使用创建单列布局,使用创建多标签页布局。查看SimpleForm、TabbedForm。
<SimpleForm><TabbedForm>为输入组件传入验证器:、、、、、、、,或返回错误字符串的自定义函数。
required()minLength(min)maxLength(max)minValue(min)maxValue(max)number()email()regex(pattern, message)jsx
<TextInput source="title" validate={[required(), minLength(3)]} />使用RHF的创建能响应字段值变化的动态表单:
useWatch()Resource Definition
资源定义
Encapsulate resource components in index files for clean imports:
jsx
// posts/index.ts
export default {
list: PostList,
create: PostCreate,
edit: PostEdit,
icon: PostIcon,
recordRepresentation: (record) => record.title, // How records appear in references
};See Resource, RecordRepresentation.
将资源组件封装在索引文件中以实现清晰的导入:
jsx
// posts/index.ts
export default {
list: PostList,
create: PostCreate,
edit: PostEdit,
icon: PostIcon,
recordRepresentation: (record) => record.title, // 记录在引用中的显示方式
};Custom Data Provider Methods
自定义数据提供者方法
Extend the dataProvider with domain-specific methods:
jsx
const dataProvider = {
...baseDataProvider,
archivePost: async (id) => { /* custom logic */ },
};
// Call via useDataProvider and useQuery:
// const dp = useDataProvider();
// const { data } = useQuery(['archivePost', id], () => dp.archivePost(id));使用领域特定方法扩展dataProvider:
jsx
const dataProvider = {
...baseDataProvider,
archivePost: async (id) => { /* 自定义逻辑 */ },
};
// 通过useDataProvider和useQuery调用:
// const dp = useDataProvider();
// const { data } = useQuery(['archivePost', id], () => dp.archivePost(id));Persistent Client State (Store)
持久化客户端状态(存储)
Use for persistent user preferences (theme, column visibility, saved filters):
useStore()jsx
const [theme, setTheme] = useStore('theme', 'light');See Store.
使用存储持久化用户偏好(主题、列可见性、已保存筛选器):
useStore()jsx
const [theme, setTheme] = useStore('theme', 'light');查看Store。
Notification, Redirect, Refresh
通知、重定向、刷新
jsx
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 queriesjsx
const notify = useNotify();
const redirect = useRedirect();
const refresh = useRefresh();
notify('Record saved', { type: 'success' });
redirect('list', 'posts'); // 导航到/posts
redirect('edit', 'posts', 123); // 导航到/posts/123
refresh(); // 使所有查询失效Deprecations
废弃内容
- Use DataTable instead of Datagrid
- Prefer and
<CanAccess>for authorization checksuseCanAccess
- 使用DataTable替代Datagrid
- 优先使用和
<CanAccess>进行授权检查useCanAccess