本篇说明如何在已有restful api情况下快速构建一个管理程式。

yarn create react-app test-admin --template typescript
# 使用typescript
cd test-admin/
yarn add react-admin ra-data-json-server prop-types
yarn start

由于我们使用了typescript,而react-admin并不支持typescript,于是我们需要写入一个src/declares.d.ts文件:

// src/declares.d.ts
declare module 'react-admin';

改写src/App.tsx如以下:

src/App.tsx
import React from 'react';
import { Admin } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';

const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com');
const App = () => <Admin dataProvider={dataProvider} />;

export default App;

浏览器中得到这样的结果:

App 组件渲染了一个 <Admin> 组件,这是我们的管理程式的根组件。这个组件允许传入是一个 dataProvider的 prop, 这个prop是一个函数能够向一个API获取数据。由于各个系统的API数据并不一致标准,你可以需要自己写一套dataProvider去链接到react-admin,后续我们会讨论如何自定义。现在我们使用ra-data-json-server这个包来构造数据获取功能。

现在,我们添加新功能。

使用resource组件映射API资源

Admin组件可以包含很多resource组件的children,这些resrouce能够映射API资源。下面是我们示例:

// src/App.tsx
import React from 'react';
import { Admin, Resource, ListGuesser } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';

const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com');
function App (){
  return (
      <Admin dataProvider={dataProvider} >
          <Resource name="users" list={ListGuesser} />
        </Admin>
    );
} 


export default App;


在<Resource name="users">这一行,我们向react-admin表达了我们想要从https://jsonplaceholder.typicode.com/users 这个地址中获取数据。<Resource>这个组件同样也定义了CURD操作(增删改查)

list={ListGuesser} 这个prop表达了react-admin应当使用ListGuesser组件来现实列表记录。这个组件会根据请求的数据结果猜测列表中的每一行的数据格式。

如果你打開瀏覽器開發工具,你可以觀察到應用的網絡請求。

這個列表已經有所功能了,你可以點擊表頭,試一下排序。

自定義行間格式

The <ListGuesser> component is not meant to be used in production - it’s just a way to quickly bootstrap an admin. That means you’ll have to replace the ListGuesser component in the users resource by a custom React component. Fortunately, ListGuesser dumps the code of the list it has guessed to the console:

<ListGusser>組件並不用於最終生產環境,它只是演示。所以你必須替換它。它的代碼結構會在控制臺中展示:

現在讓我們複製這段代碼,並且創建文件users.tsx,並且簡單翻譯爲typescript

// in src/users.tsx
import React from 'react';
import { List, Datagrid, TextField, EmailField } from 'react-admin';

export const UserList = (props: any) => (
    <List {...props}>
        <Datagrid rowClick="edit">
            <TextField source="id" />
            <TextField source="name" />
            <TextField source="username" />
            <EmailField source="email" />
            <TextField source="address.street" />
            <TextField source="phone" />
            <TextField source="website" />
            <TextField source="company.name" />
        </Datagrid>
    </List>
);

然後修改src/App.tsx爲:

// in src/App.tsx
import React from 'react';
import { Admin, Resource } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
import { UserList } from './users';

const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com');
function App (){
  return <Admin dataProvider={dataProvider} >
  <Resource name="users" list={UserList} />
</Admin>;
} 


export default App;

我們發現在瀏覽器中沒有變化,但是也說明自定義的列表組件UerList是可用的。

我們觀察<UserList>組件中的<List>組件,這個組件用於現實頁面的標題(title)和分頁(pagenination),同時這個組件也會向它的子組件指派列表實際內容的顯示。在本例中,這個子組件就是<Datagrid>。<Datagrid>的子組件中有各種<Field>組件去適配數據格式來顯示。

現在我們嘗試修改<UserList>來看看效果。

刪除一些<Field>試試。

// in src/users.tsx
import React from 'react';
import { List, Datagrid, TextField, EmailField } from 'react-admin';

export const UserList = (props: any) => (
    <List {...props}>
        <Datagrid rowClick="edit">
            <TextField source="id" />
            <TextField source="name" />
            <EmailField source="email" />
            <TextField source="phone" />
            <TextField source="website" />
            <TextField source="company.name" />
        </Datagrid>
    </List>
);

以上的步驟就是react-admin早期開發過程:讓guesser自動表達字段,選擇你需要的字段,然後開始自定義顯示字段。

格式化數據內容

在<UserList>,我們已經認識了<TextField>和<EmailField>這兩個組件。而react-admin提供了一系列的Field來現實不同的內容。比如:number, date, image, HTML, array, reference, etc.

舉例:

//in  src/users.tsx
import React from 'react';
import { List, Datagrid, TextField, EmailField } from 'react-admin';

export const UserList = (props: any) => (
    <List {...props}>
        <Datagrid rowClick="edit">
            <TextField source="id" />
            <TextField source="name" />
            <EmailField source="email" />
            <TextField source="phone" />
-           <TextField source="website" />
+           <UrlField source="website" />
            <TextField source="company.name" />
        </Datagrid>
    </List>
);

field也是簡單的React組件。這也意味我們能夠包裝自定義組件。現在自定義一個簡單的包裝UrlField的組件:

// in src/MyUrlField.tsx
import React from 'react';

const MyUrlField = ({ record = {}, source }:any) =>
    <a href={record[source]}>
        {record[source]}
    </a>;

export default MyUrlField;

你可以把這個組件在<UserList>替代<UrlField>,這樣運行是等效的:

// in src/users.tsx
import React from 'react';
-import { List, Datagrid, TextField, EmailField, UrlField } from 'react-admin';
+import { List, Datagrid, TextField, EmailField } from 'react-admin';
+import MyUrlField from './MyUrlField';
export const UserList = (props:any) => (
    <List {...props}>
        <Datagrid rowClick="edit">
            <TextField source="id" />
            <TextField source="name" />
            <EmailField source="email" />
            <TextField source="phone" />
-           <UrlField source="website" />
+           <MyUrlField source="website" />
            <TextField source="company.name" />
        </Datagrid>
    </List>
);

這樣的機制,使得Field組件高度可訂製,react-admin隨處都是這樣的等效組件包裝機制,使得react-admin是b2b應用的開發框架也是不僅僅是一個後臺管理系統的模板。

自定義樣式

<MyUrlField>組件是一個可以訂製樣式的演示。React-admin以來material-ui, 一個google材料化設計的組件庫。Materail-ui使用jss, 一個css-in-js的方案,用以給組件賦予樣式。讓我裝點組件吧:

// in src/MyUrlField.tsx
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import LaunchIcon from '@material-ui/icons/Launch';
const useStyles = makeStyles({
    link: {
        textDecoration: 'none',
    },
    icon: {
        width: '0.5em',
        paddingLeft: 2,
    },
});

const MyUrlField = ({ record = {}, source }:any) =>{
     const classes = useStyles();
       return (
        <a href={record[source]} className={classes.link}>
            {record[source]}
        <LaunchIcon className={classes.icon} />
    </a>
       );
   }

export default MyUrlField;

處理關聯資源

在JSONPlaceholder,每個post記錄都包含了userId字段,作爲一個user記錄的外鍵:

{
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto",
    "userId": 1
}

React-admin知道如何通过外键获取资源。让我看看ListGuesser如何在posts资源中獲取外鍵資源的。

//in src/App.tsx
import React from 'react';
import { Admin, Resource, ListGuesser } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
import { UserList } from './users';

const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com');
function App (){
  return <Admin dataProvider={dataProvider} >
  <Resource name="users" list={UserList} />
  <Resource name="posts"  list={ListGuesser} />
</Admin>;
} 


export default App;

ListGuesser建議了<ReferenceField>對應了userId字段。讓我們複製控制臺的ListGuesser來構建新的PostList組件,如同UserList:

// in src/posts.tsx
import React from 'react';
import {
    List,
    Datagrid,
    ReferenceField,
    TextField
} from 'react-admin'
export const PostList = (props: any) => (
    <List {...props}>
        <Datagrid rowClick="edit">
            <ReferenceField source="userId" reference="users"><TextField source="id" /></ReferenceField>
            <TextField source="id" />
            <TextField source="title" />
            <TextField source="body" />
        </Datagrid>
    </List>
);
// in src/App.tsx
import React from 'react';
import { Admin, Resource } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
import { UserList } from './users';
import { PostList } from './posts';

const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com');
function App (){
  return <Admin dataProvider={dataProvider} >
  <Resource name="users" list={UserList} />
  <Resource name="posts"  list={PostList} />
</Admin>;
} 


export default App;

當顯示posts列表,程式使用<TextField>組件顯示post的作者id。但是作者id不夠人性,我們來現實作者的名字。

// in src/posts.tsx
import React from 'react';
import {
    List,
    Datagrid,
    ReferenceField,
    TextField
} from 'react-admin'
export const PostList = (props: any) => (
    <List {...props}>
        <Datagrid rowClick="edit">
            <ReferenceField source="userId" reference="users">
                <TextField source="name" />
            </ReferenceField>
            <TextField source="id" />
            <TextField source="title" />
            <TextField source="body" />
        </Datagrid>
    </List>
);

提示:<ReferenceField>組件本身並不顯示什麼東西,它只負責獲取引用的數據,並把數據發給子組件。

提示:通過觀察網絡請求,我們會發現/users的資源請求同一個ID只請求了一次。這提高了性能。

從UX角度說,還有大段內容的字段不合適在列表中,所以在PostList的要去除body,但是我們也需要通過點擊一行來顯示一條記錄的詳細情況。點擊事件註冊在list的props中,內容爲rowCick="edit",意思是點擊一行來編輯記錄。

// in src/posts.tsx
import React from 'react';
import {
    List,
    Datagrid,
    ReferenceField,
    TextField,
    EditButton
} from 'react-admin'
export const PostList = (props: any) => (
    <List {...props}>
        <Datagrid rowClick="edit">
            <ReferenceField source="userId" reference="users">
                <TextField source="name" />
            </ReferenceField>
            <TextField source="id" />
            <TextField source="title" />
            <EditButton />
        </Datagrid>
    </List>
);


加入創建和編輯功能

React-admin提供<Edit>組件來編輯數據條目;並且提供<EditGuesser>來構建代碼模板。

// in src/App.tsx
import React from 'react';
import { Admin, Resource, EditGuesser  } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
import { UserList } from './users';
import { PostList } from './posts';

const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com');
function App (){
  return <Admin dataProvider={dataProvider} >
  <Resource name="users" list={UserList} />
  <Resource name="posts"  list={PostList} edit={EditGuesser} />
</Admin>;
} 


export default App;

用戶能夠通過點擊Edit按鈕來編輯頁面。可以試試,EditGuesser的組件是切實可用的;

在瀏覽器控制臺複製PostEdit代碼到posts.tsx,以便你能夠定製這個這個編輯表單。不要忘記從react-admin引入所需的新的組件哦。

當把<PostEdit>組件寫入src/posts.tsx,我們需要刪除對主鍵(id)的編輯,並且使用作者的名稱來選擇而不是作者的id;使用一個長文本輸入body字段,代碼如下:

//in src/posts.tsx
export const PostEdit = (props: any) => (
    <Edit {...props}>
        <SimpleForm>
            <TextInput source="id" />
            <TextInput source="title" />
           <TextInput multiline source="body" />
        </SimpleForm>
    </Edit>
);

和<List>組件類似,<Edit>組件也是從網絡API中獲取數據,並顯示頁面的title,然後把獲取的數據傳給<SimpleForm>組件。<SimpleForm>組件能夠對表單排版,顯示默認數據和表單驗證。類似<Datagrid>,<SingleForm>使用子組件完成表單輸入和顯示。React-admin有各種Input組件,比如<TextInput>、<ReferenceInput>、<SelectInput>等。

<ReferenceInput>和<ReferenceField>有同樣的props。<ReferenceInput>會根據這些props請求合適的Api。

Before you can use that custom component in the App.js, copy the PostEdit component into a PostCreate, and replace Edit by Create:

同樣的,我們也可以創建PostCreate組件來新建post记录:

//in src/posts.tsx
export const PostCreate = (props: any) => (
    <Create {...props}>
        <SimpleForm>
            <ReferenceInput source="userId" reference="users">
                <SelectInput optionText="name" />
            </ReferenceInput>
            <TextInput source="title" />
            <TextInput multiline source="body" />
        </SimpleForm>
    </Create>
);

提示:<PostEdit>和<PostCreate>组件有着差不多相同的子组件。但是在大部分的情况下,创建和编辑总有差异的。将表單的組件公共化,分享他們在不同的表單內是一個好主意。

如此使用使用我們創建的新的PostEdit、PostCreate:

// in src/App.tsx
import React from 'react';
import { Admin, Resource  } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
import { UserList } from './users';
import { PostList, PostEdit, PostCreate } from './posts';

const dataProvider = jsonServerProvider('https://jsonplaceholder.typicode.com');
function App (){
  return <Admin dataProvider={dataProvider} >
  <Resource name="users" list={UserList} />
  <Resource name="posts"  list={PostList} edit={PostEdit} create={PostCreate}/>
</Admin>;
} 


export default App;

積極渲染和撤銷

JSONPlaceholder不是一個真實的API,它是只讀的。這樣POST和PUT請求雖然可以被接受,但是創建和更新並不會真正起作用。

但是你會觀察到就視圖而言,似乎某一時刻真的創建和更新了。

這是因爲react-admin採取積極渲染的機制。當用戶點擊'save'的時候,UI在真的發送網絡請求之前就及時地顯示表單內容的更新,這樣的效果使得用戶的交互始終保持流暢而沒有阻塞感。

但爲了使得這個效果業務上是完整,我們需要'undo'特性:即我們能夠撤銷網絡請求。撤銷的功能,react-admin已經幫助你完成了的。看看下圖演示:

撤銷是通過延遲實現的,大約5秒。這個時長也是能夠自定義的,可以查看react-admin文檔

提示: 刪除操作也是同樣的撤銷機制。

讓我們自定義編輯頁面的標題:

// in src/posts.tsx
+const PostTitle = ({ record }:any) => {
+    return <span>Post {record ? `"${record.title}"` : ''}</span>;
+};

export const PostEdit = (props:any) => (
-   <Edit {...props}>
+   <Edit title={<PostTitle />} {...props}>
        // ...
    </Edit>
);

添加搜索和篩查功能

回顧post列表,我們已經有了分頁和排序和得到我們的內容。

React-admin can use Input components to create a multi-criteria search engine in the list view. First, create a <Filter> component just like you would write a <SimpleForm> component, using input components as children. Then, add it to the list using the filters prop:

React-admin能够是Input组件创建多标准的搜索引擎。首先,創建<Filter>組件,子組件<SimpleForm>,已經更多input組件,代碼如下:

// in src/posts.tsx
import { Filter, ReferenceInput, SelectInput, TextInput, List } from 'react-admin';

const PostFilter = (props: any) => (
    <Filter {...props}>
        <TextInput label="Search" source="q" alwaysOn />
        <ReferenceInput label="User" source="userId" reference="users" allowEmpty>
            <SelectInput optionText="name" />
        </ReferenceInput>
    </Filter>
);

export const PostList = (props: any) => (
    <List filters={<PostFilter />} {...props}>
        // ...
    </List>
);

第一個搜索框,傳入的參數是q,作爲全文搜索的內容。其次,用戶通過'add filter'按鈕,添加需要篩選的記錄。

篩選器就是"輸入即搜索",這意味着用戶輸入的同時,列表會刷新。

自定義菜單圖標

現在posts和users的導航在左側菜單中都使用同樣的圖標,讓我們在<Resource>組件中自定義他們。

// in src/App.tsx
import PostIcon from '@material-ui/icons/Book';
import UserIcon from '@material-ui/icons/Group';

const App = () => (
    <Admin dataProvider={dataProvider}>
        <Resource name="posts" list={PostList} edit={PostEdit} create={PostCreate} icon={PostIcon} />
        <Resource name="users" list={UserList} icon={UserIcon} />
    </Admin>
);

使用一個自定義主頁

默認情況下,react-admin使用第一個資源作爲主頁。可以通過在<Admin>組件傳入dashboard參數自定義主頁:

// in src/Dashboard.tsx
import React from 'react';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import CardHeader from '@material-ui/core/CardHeader';

export default () => (
    <Card>
        <CardHeader title="Welcome to the administration" />
        <CardContent>Lorem ipsum sic dolor amet...</CardContent>
    </Card>
);
// in src/App.tsx
import Dashboard from './Dashboard';

const App = () => (
    <Admin dashboard={Dashboard} dataProvider={dataProvider}>
        // ...
    </Admin>
);

添加登錄頁面

當創建了登錄頁面之後,React-admin在現實頁面之前檢查你的認證信息,若是沒有通過就跳轉登錄頁面並且拋出一個403錯誤。

採取合適認證策略是開發者必要思考的問題(basic auth, 0Atuh, custom route, etc),react-admin並不內置認證策略,只提供也一個authProider對象,讓開發者自己寫認證邏輯。

在本篇教程中,我們使用localstorage假造一個認證策略。

authProvider對象必須暴露5個方法,每個方法都返回一個Promise對象:

// in src/authProvider.ts
interface ILoginParams {
    username: string,
    password: string,
}
interface ICheckErrorParams {
    status: number,
    statusText: string,
}
export default {
    // called when the user attempts to log in
    login: ({ username }:ILoginParams) => {
        localStorage.setItem('username', username);
        // accept all username/password combinations
        return Promise.resolve();
    },
    // called when the user clicks on the logout button
    logout: () => {
        localStorage.removeItem('username');
        return Promise.resolve();
    },
    // called when the API returns an error
    checkError: ({ status }: ICheckErrorParams) => {
        if (status === 401 || status === 403) {
            localStorage.removeItem('username');
            return Promise.reject();
        }
        return Promise.resolve();
    },
    // called when the user navigates to a new location, to check for authentication
    checkAuth: () => {
        return localStorage.getItem('username')
            ? Promise.resolve()
            : Promise.reject();
    },
    // called when the user navigates to a new location, to check for permissions / roles
    getPermissions: () => Promise.resolve(),
    };

爲了使得我們認證策略生效,把策略引入<Admin>組件的authProvider中去。

// in src/App.tsx
import Dashboard from './Dashboard';
import authProvider from './authProvider';

const App = () => (
    <Admin dashboard={Dashboard} authProvider={authProvider} dataProvider={dataProvider}>
        // ...
    </Admin>
);

支援移動設備

react-admin佈局已經是響應式的了。您可以嘗試拖拽瀏覽器寬度發覺。

但是單單佈局響應式是不夠的。<Datagrid>組件並不響應移動端,react-admin會提供其他組件在小屏幕展示。

首先,你必須明白,你不一定要使用<Datagrid>作爲<List>的子組件。認識下<SimpleList>組件吧:

// in src/posts.tsx
import React from 'react';
import { List, SimpleList } from 'react-admin';

export const PostList = (props:any) => (
    <List {...props}>
        <SimpleList
            primaryText={(record: any) => record.title}
            secondaryText={(record: any) => `${record.views} views`}
            tertiaryText={( record: any) => new Date(record.published_at).toLocaleDateString()}
        />
    </List>
);

<SimpleList>組件使用material-ui的<List>和<ListItem>組件,所以也兼容這些組件的props。

現在讓我們使用useMediaQuery的鉤子來自動適應屏幕:

// in src/posts.tsx
import React from 'react';
import { useMediaQuery } from '@material-ui/core';
import { List, SimpleList, Datagrid, TextField, ReferenceField, EditButton } from 'react-admin';

export const PostList = (props: any) => {
    const isSmall = useMediaQuery(theme => theme.breakpoints.down('sm'));
    return (
        <List {...props}>
            {isSmall ? (
                <SimpleList
                    primaryText={(record: any) => record.title}
                    secondaryText={(record: any) => `${record.views} views`}
                    tertiaryText={(record: any) => new Date(record.published_at).toLocaleDateString()}
                />
            ) : (
                <Datagrid>
                    <TextField source="id" />
                    <ReferenceField label="User" source="userId" reference="users">
                        <TextField source="name" />
                    </ReferenceField>
                    <TextField source="title" />
                    <TextField source="body" />
                    <EditButton />
                </Datagrid>
            )}
        </List>
    );
}

連接到真正的 API

這是本教程的重點。 在現實世界的項目中,妳的 API 的規範(REST? GraphQL? 別的?不符合 JSONPLaceholder 規範。 編寫 Data Provider 可能是您必須做的第壹件事,以使 react-admin 工作。 根據您的 API, 這可能需要幾個小時的額外工作。


React-admin 將每個數據查詢委托給 Data Provider 函數。 這個函數必須針對結果返回壹個 promise。 這為映射任何 API 規範,添加身份驗證頭,使用來自多個域的 API 端點等提供了極大的自由度。


React-admin 為這個列表的每個動作定義了自定義動詞。 就像 HTTP 動詞(GET,POST 等)壹樣,react-admin 動詞限定壹個 request 到壹個 data provider 。 React-admin 動詞被叫做 GET_LIST, GET_ONE,GET_MANY,CREATE,UPDATE 和 DELETE。 Data Provider 將得映射這些動詞的每壹個對應壹個(或多個)HTTP 請求。


my.api.url API 的 Data Provider 代碼如下:

// in src/dataProvider
import {
  GET_LIST,
  GET_ONE,
  GET_MANY,
  GET_MANY_REFERENCE,
  CREATE,
  UPDATE,
  DELETE,
  fetchUtils
} from "react-admin";
import { stringify } from "query-string";

const API_URL = "my.api.url";

/**
 * @param {String} type One of the constants appearing at the top if this file, e.g. 'UPDATE'
 * @param {String} resource Name of the resource to fetch, e.g. 'posts'
 * @param {Object} params The Data Provider request params, depending on the type
 * @returns {Object} { url, options } The HTTP request parameters
 */
const convertDataProviderRequestToHTTP = (type, resource, params) => {
  switch (type) {
    case GET_LIST: {
      const { page, perPage } = params.pagination;
      const { field, order } = params.sort;
      const query = {
        sort: JSON.stringify([field, order]),
        range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]),
        filter: JSON.stringify(params.filter)
      };
      return { url: `${API_URL}/${resource}?${stringify(query)}` };
    }
    case GET_ONE:
      return { url: `${API_URL}/${resource}/${params.id}` };
    case GET_MANY: {
      const query = {
        filter: JSON.stringify({ id: params.ids })
      };
      return { url: `${API_URL}/${resource}?${stringify(query)}` };
    }
    case GET_MANY_REFERENCE: {
      const { page, perPage } = params.pagination;
      const { field, order } = params.sort;
      const query = {
        sort: JSON.stringify([field, order]),
        range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]),
        filter: JSON.stringify({ ...params.filter, [params.target]: params.id })
      };
      return { url: `${API_URL}/${resource}?${stringify(query)}` };
    }
    case UPDATE:
      return {
        url: `${API_URL}/${resource}/${params.id}`,
        options: { method: "PUT", body: JSON.stringify(params.data) }
      };
    case CREATE:
      return {
        url: `${API_URL}/${resource}`,
        options: { method: "POST", body: JSON.stringify(params.data) }
      };
    case DELETE:
      return {
        url: `${API_URL}/${resource}/${params.id}`,
        options: { method: "DELETE" }
      };
    default:
      throw new Error(`Unsupported fetch action type ${type}`);
  }
};

/**
 * @param {Object} response HTTP response from fetch()
 * @param {String} type One of the constants appearing at the top if this file, e.g. 'UPDATE'
 * @param {String} resource Name of the resource to fetch, e.g. 'posts'
 * @param {Object} params The Data Provider request params, depending on the type
 * @returns {Object} Data Provider response
 */
const convertHTTPResponseToDataProvider = (
  response,
  type,
  resource,
  params
) => {
  const { headers, json } = response;
  switch (type) {
    case GET_LIST:
      return {
        data: json.map(x => x),
        total: parseInt(
          headers
            .get("content-range")
            .split("/")
            .pop(),
          10
        )
      };
    case CREATE:
      return { data: { ...params.data, id: json.id } };
    default:
      return { data: json };
  }
};

/**
 * @param {string} type Request type, e.g GET_LIST
 * @param {string} resource Resource name, e.g. "posts"
 * @param {Object} payload Request parameters. Depends on the request type
 * @returns {Promise} the Promise for response
 */
export default (type, resource, params) => {
  const { fetchJson } = fetchUtils;
  const { url, options } = convertDataProviderRequestToHTTP(
    type,
    resource,
    params
  );
  return fetchJson(url, options).then(response =>
    convertHTTPResponseToDataProvider(response, type, resource, params)
  );
};

提示fetchJson()只是 fetch().then(r => r.json()) 的快捷方式。加上对 HTTP 响应代码的控制,以便在 4xx 或 5xx 响应的情况下抛出 HTTPError。 如果不符合您的需要, 请随意使用 fetch()

使用此提供程序代替以前的 jsonServerProvider 只是切换函数的问题:

結論

React-admin 是以考慮定制為基礎構建的。 您可以將任何 react-admin 組件替換為妳自己的組件, 例如, 顯示自定義列表布局或為壹個給定的 resource 不同的編輯表單。


現在, 您已經完成了教程, 請繼續閱讀 react-admin 文檔, 並閱讀 Material UI 組件文檔。