内容简介:为什么需要不同的系统,一定会存在不同的
React-Admin 架构分析: Admin 组件源码解析之 dataProvider 属性
为什么需要 dataProvider ?
不同的系统,一定会存在不同的 API 风格。作为一个尽可能通用的中后台框架,抽象出一层去适配数据到一种固定的数据格式规范是必须的。
dataProvider 使用文档,大家自行翻看。
首先,我们需要把 Admin 组件跑起来:
- 这里选用 Create React App 作为基础脚手架。OK,我们快速用它创建一个 App:
npx create-react-app hello-react-admin cd hello-react-admin yarn start // 默认你已经安装了 yarn 复制代码
然后脚手架会为你自动在浏览器(我这里是 google chrome )中打开 http://localhost:3000 。如果没有,你也可以自己打开这个地址。你将看到如下界面:
安装 React-Admin,引入 Admin 组件:
yarn add react-admin 复制代码
进入 src/App.js ,我们引入我们期待已久的 <Admin> :
import React from 'react';
import { Admin } from 'react-admin';
export default () => <Admin/>;
复制代码
然后,我们运行起来,发现报了如下错:
它明确的告诉了我们, <Admin> 必须要一个 dataProvider 属性才能正常的工作。然后 dataProvider 还必须是一个函数:
看过 官方DataProviders文档 的同学都知道它是数据的来源。这里我们主要关注 <Admin> 是如何处理这个属性的。
我们按照官方教程 的说明为 <Admin> 加上一个 dataProvider 属性:
// in src/App.js
import React from 'react';
import { Admin } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
const dataProvider = jsonServerProvider('http://jsonplaceholder.typicode.com');
const App = () => <Admin dataProvider={dataProvider} />;
export default App;
复制代码
添加包并运行:
yarn add ra-data-json-server yarn start 复制代码
虽然没有报错,但我们看到如下提示, <Admin> 必须至少要一个 <Resource> 作为子组件。
这里我们按照教程文档给它加入 Resource 组件:
<Admin dataProvider={dataProvider}>
<Resource />
</Admin>
复制代码
终于看到一条蓝色的 bar 了:
根据文档,我们知到 <Resource> 需要 name 和 list 属性才能显示出列表,我们给它加上(注意看文档):
import React from 'react';
import { Admin, Resource, List, Datagrid, TextField } from 'react-admin';
import jsonServerProvider from 'ra-data-json-server';
const dataProvider = jsonServerProvider('http://jsonplaceholder.typicode.com');
const PostList = (props) => (
<List {...props}>
<Datagrid>
<TextField source="id" />
<TextField source="title" />
<TextField source="body" />
</Datagrid>
</List>
);
const App = () => (
<Admin dataProvider={dataProvider}>
<Resource name="posts" list={PostList} />
</Admin>
);
export default App;
复制代码
我重新进入 http://localhost:3000 ,发现会自动进入 http://localhost:3000/#/posts 这个路由,一个完整的列表页就展现了出来(仅仅只需要几行代码而已):
接下来,我们就要分析为什么只需这几行代码, React-Admin 就能完成一个完整的信息列表展示(导出,排序,分页等)。 Admin 组件到底帮我们干了什么(它里面的代码到底是咋写的)?
OK,让我们进入下一小节。
真身 CoreAdmin
React-Admin 暴露出来的 Admin 组件其实是在这个项目的 ra-core 包中,里面的 CoreAdmin.js 的 CoreAdmin 组件才是它的真身。
- 首先,
CoreAdmin组件会在它的构造函数中对这个dataProvider属性做一个必要的检测(发现如果props里面没有dataProvider的话就直接 throw 一个 Error):
// packages/ra-core/src/CoreAdmin.js
if (!props.dataProvider) {
throw new Error(`Missing dataProvider prop.
React-admin requires a valid dataProvider function to work.`);
}
复制代码
也就是我们在上面看到的错误。有意思的是在这个文件中,它并没有对 dataProvider 做其它的一些处理,而是将它传递到了 createAdminStore.js 暴露出的函数中进行处理,然后又将它传递到 sideEffects 文件中暴露的 adminSaga 中进行处理。然后它又将 dataProvider 传递进专门负责 fetch 的 Saga 中。到这里我们又似乎明白了,所谓的数据提供者肯定和 AJAX 请求是息息相关的。
这里我看看 dataProvider 被传递的流程:
// step1: packages/ra-core/src/CoreAdmin.js
<Provider
store={createAdminStore({
...this.props, // 这个 props 里面就有 dataProvider
history: this.history,
})}
>
{this.renderCore()}
</Provider>
// step2: packages/ra-core/src/createAdminStore.js
const saga = function* rootSaga() {
yield all(
[
adminSaga(dataProvider, authProvider, i18nProvider),
...customSagas,
].map(fork)
);
};
// step3: packages/ra-core/src/sideEffect/index.js
export adminSaga from './admin';
...
// other export
// step4: packages/ra-core/src/sideEffect/admin.js
export default (dataProvider, authProvider, i18nProvider) =>
function* admin() {
yield all([
...
fetch(dataProvider)(),
...
]);
};
// step5: packages/ra-core/src/sideEffect/fetch.js
const fetch = dataProvider => {
return function* watchFetch() {
// 终于看到 takeEvery 了
yield takeEvery(takeFetchAction, handleFetch, dataProvider);
};
};
export function* handleFetch(dataProvider, action) {
...
let response = yield call(
dataProvider,
restType,
meta.resource,
payload
);
...
}
复制代码
小伙伴们,看到这里,应该也就理解 dataProvider 的作用了吧。
快速理解下 Redux-Saga ,大家可以阅读Redux-Saga 漫谈
- 它是什么?
在这里,你可以理解为它就是 Redux 的一个中间件。主要是为了更优雅地 管理 Redux 应用程序中的 副作用( Side Effects )。
- 什么是
Side Effects?
Side Effects 主要指的就是:异步网络请求、本地读取 localStorage/Cookie 等外界操作。
- 这里
takeEvery是什么意思?
export const takeFetchAction = action => action.meta && action.meta.fetch;
const fetch = dataProvider => {
return function* watchFetch() {
yield takeEvery(takeFetchAction, handleFetch, dataProvider);
};
};
复制代码
这里我们可以理解为,当我们 dispatch 一个 action 对象时,如果里面有 meta 属性并且里面包含 fetch 属性,那么就执行 handleFetch 方法,并把 dataProvider 和这个符合要求的 action 给传进去进行下一步处理。
- 放一张Redux-Saga 漫谈 里面的图,非常清晰的解释了当
dispatch一个action后,Redux-Saga都干了那些事儿:
我们看看 React-Admin 是如何触发这个 fetchSaga 的?
- 首先,我们来找找
dispatch这个关键字。我们并没见到dispatch满天飞的情况。这要感谢react-redux:
这里是上面的 List 组件里面的代码:
// packages/ra-ui-materialui/src/list/List.js
const List = props => (
<ListController {...props}>
{controllerProps => <ListView {...props} {...controllerProps} />}
</ListController>
);
复制代码
这里我们重点关注下 <ListController> ,它是 RA/CRUD_GET_LIST action 的发起者。
我们来看看这个 action :
export const CRUD_GET_LIST = 'RA/CRUD_GET_LIST';
export const crudGetList = (resource, pagination, sort, filter) => ({
type: CRUD_GET_LIST,
payload: { pagination, sort, filter },
meta: {
resource,
fetch: GET_LIST,
onFailure: {
notification: {
body: 'ra.notification.http_error',
level: 'warning',
},
},
},
});
复制代码
O__O "…,发现这更像是一个 action creater 。
-
Action Creator就是一个创建 action 的函数。不要混淆 action 和 action creator 这两个概念。Action 是一个信息的负载,而 action creator 是一个创建 action 的工厂。调用action creator只会生产action,但不分发。你需要调用store的dispatch函数才会引起变化。
type ActionCreator = (...args: any) => Action | AsyncAction 复制代码
-
bindActionCreators就是把一个value为不同action creator的对象,转成拥有同名key的对象。同时使用dispatch对每个action creator进行包装,以便可以直接调用它们。
这里我直接放一段 bindActionCreator 的代码,大家就秒懂了:
// node_modules/redux/src/bindActionCreators.js
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
复制代码
-
在
ListController组件中,调用connect时,在mapDispatchTopProps参数上传入一个对象,在这个函数内部就会为每一个值为action creator的做bindActionCreator的操作。 -
ListController是如何调用的:
export class ListController extends Component {
...
componentDidMount() {
this.updateData();
}
updateData(query) {
...
this.props.crudGetList(
this.props.resource,
pagination,
{ field: sort, order },
{ ...filter, ...permanentFilter }
);
...
}
}
export default compose(
connect(
mapStateToProps,
{
crudGetList: crudGetListAction,
}
),
...
)(ListController);
复制代码
通过 connect 组件,我们的 action creator (crudGetList),变成了如下样子:
(...args) => dispatch(crudGetList(...args)) 复制代码
所以我们在调用的时候,就直接触发这个 fetchSaga ,分发了如下一些个与 fetch 相关的 action :
RA/CRUD_GET_LIST RA/CRUD_GET_LIST_LOADING RA/FETCH_START RA/CRUD_GET_LIST_SUCCESS RA/FETCH_END 复制代码
handleFetch 与 dataProvider
- 接下来我们详细分析下这个
Worker Saga
export function* handleFetch(dataProvider, action) {
...
let response = yield call(
dataProvider,
restType,
meta.resource,
payload
);
...
}
复制代码
-
call是什么?
call 方法用来创建 effect 对象,被称作是 effect factory。
-
yield是什么?
yield 语法将 effect 对象 传给 sagaMiddleware ,被解释执行,并返回值。
这里的 call effect 表示执行 dataProvider ,又因为它的返回值是 promise,为了等待异步结果返回, handleFetch 函数会暂时处于 阻塞 状态。
-
dataProvider就是根据不同的restType(GET_LIST) 对不同的resource(posts)和它所带的payload做真正的fetch。
目前 react-admin 围绕着(CRUD)出抽象出九种 restType :
每一种的应用场景,我们可以单独一节来分析。
源码:
// ra-core/src/dataFetchActions.js export const GET_LIST = 'GET_LIST'; export const GET_ONE = 'GET_ONE'; export const GET_MANY = 'GET_MANY'; export const GET_MANY_REFERENCE = 'GET_MANY_REFERENCE'; export const CREATE = 'CREATE'; export const UPDATE = 'UPDATE'; export const UPDATE_MANY = 'UPDATE_MANY'; export const DELETE = 'DELETE'; export const DELETE_MANY = 'DELETE_MANY'; ... 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- MVVM 架构解析及 Jetpack 架构组件的使用
- MVVM 架构解析及 Jetpack 架构组件的使用
- 微服务架构————基本组件
- 组件化架构漫谈
- Rabbitmq基础组件架构设计
- Android组件化入门:一步步搭建组件化架构
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。