内容简介:为什么需要不同的系统,一定会存在不同的
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组件化入门:一步步搭建组件化架构
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
PHP and MySQL Web Development
Luke Welling、Laura Thomson / Sams / July 25, 2007 / $49.99
Book Description PHP and MySQL Web Development teaches you to develop dynamic, secure, commerical Web sites. Using the same accessible, popular teaching style of the three previous editions, this b......一起来看看 《PHP and MySQL Web Development》 这本书的介绍吧!