内容简介:为什么需要不同的系统,一定会存在不同的
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组件化入门:一步步搭建组件化架构
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。