介绍
复杂的 Web 项目通常需要使用第三方小工具,但如果您正在使用一个框架,而小工具只在纯 JavaScript 中可用呢?
要在您的项目中使用JavaScript小工具,最好的方法是创建一个框架特定的包装程序。
ag-Grid
是一个用于显示数据集中的信息的JavaScript小工具,它允许您动态地分类,过滤和选择信息。
在本文中,您将使用ag-grid-community
和ag-grid-react
作为学习如何将第三方 widget 包装到 React 组件的基础。
前提条件
要跟随这篇文章,你将需要:
您可以查看我们的 如何在 React.js 中编码系列
步骤 1 – 了解 JavaScript 视图
一般来说,大多数JavaScript Widget都具有:
- 配置选项
- 公共 API
- 广播事件
您可以找到网格的属性,事件,回调和API的良好描述(https://www.ag-grid.com/javascript-grid/grid-interface/)。
简而言之,DataGrid定义了:
- Grid Properties 允许网格特性,如行动画
- Grid API 在运行时与网格进行交互(例如,获取所有选定的行)
- Grid Events 由网格发出,当网格中发生某些事件时,如排序或行选择
- Grid Callbacks 用于在需要时从您的应用程序提供信息给网格(例如,每次显示允许您的应用程序定制菜单的菜单时呼叫回调用)
以下是非常基本的纯 JavaScript 配置,展示了使用网格选项:
1let gridOptions = {
2 // PROPERTIES - object properties, myRowData and myColDefs are created somewhere in your application
3 rowData: myRowData,
4 columnDefs: myColDefs,
5
6 // PROPERTIES - simple boolean / string / number properties
7 pagination: true,
8 rowSelection: 'single',
9
10 // EVENTS - add event callback handlers
11 onRowClicked: function(event) { console.log('a row was clicked'); },
12 onColumnResized: function(event) { console.log('a column was resized'); },
13 onGridReady: function(event) { console.log('the grid is now ready'); },
14
15 // CALLBACKS
16 isScrollLag: function() { return false; }
17}
首先,JavaScript datagrid 是这样初始化的:
1new Grid(this._nativeElement, this.gridOptions, ...);
然后,ag-Grid
将API方法的对象附加到gridOptions
,可用于控制JavaScript datagrid:
1// get the grid to refresh
2gridOptions.api.refreshView();
然而,当ag-Grid
被用作 React 组件时,我们不会直接实例化 datagrid. 这是包装组件的工作。
例如,我们没有直接访问网格附加的 API 对象,我们将通过组件的实例访问它。
步骤2 - 确定包装部件应该做什么
我们从不将配置选项和回调直接传递到网格,React包装组件通过React Props传递选项和回调。
凡妮拉 JavaScript 网格可用的所有网格选项也应该在 React datagrid中可用。我们也不会直接听取ag-Grid
的事件。
这意味着一个React特定的数据网包围器围绕ag-Grid
应该:
- 实施输入绑定(如
rowData
)和ag-Grid
的配置选项 之间的映射*应听取由ag-Grid
发出的事件并将其定义为组件输出 听取组件的输入绑定变化并更新网 中的配置选项通过其属性 暴露由ag-Grid
附加的API到gridOptions
下面的示例展示了如何在使用 React Props 的模板中配置 React datagrid:
1<AgGridReact
2 // useful for accessing the component directly via ref - optional
3 ref="agGrid"
4
5 // simple attributes, not bound to any state or prop
6 rowSelection="multiple"
7
8 // these are bound props, so can use anything in React state or props
9 columnDefs={this.props.columnDefs}
10 showToolPanel={this.state.showToolPanel}
11
12 // this is a callback
13 isScrollLag={this.myIsScrollLagFunction}
14
15 // these are registering event callbacks
16 onCellClicked={this.onCellClicked}
17 onColumnResized={this.onColumnEvent}
18
19 // inside onGridReady, you receive the grid APIs if you want them
20 onGridReady={this.onGridReady}
21/>
现在我们已经理解了这个要求,让我们看看我们如何在ag-Grid
中实现了这一要求。
步骤 3 – 实施一个 React Wrapper
首先,我们需要定义一个 React 组件 AgGridReact
代表我们的 React datagrid 在模板中. 这个组件将产生一个 DIV
元素,它将作为 datagrid 的容器。
1export class AgGridReact extends React.Component {
2 protected eGridDiv: HTMLElement;
3
4 render() {
5 return React.createElement("div", {
6 style: ...,
7 ref: e => {
8 this.eGridDiv = e;
9 }
10 }, ...);
11 }
12}
在我们能够实例化ag-Grid
之前,我们还需要收集所有选项。在AgGridReact
组件上,所有ag-Grid
属性和事件都以 React Props 形式出现。
要做到这一点,我们已经实现了 copyAttributesToGridOptions
函数,它是一个复制属性从一个对象到另一个对象的实用函数:
1export class ComponentUtil {
2 ...
3 public static copyAttributesToGridOptions(gridOptions, component, ...) {
4 ...
5 // copy all grid properties to gridOptions object
6 ComponentUtil.ARRAY_PROPERTIES
7 .concat(ComponentUtil.STRING_PROPERTIES)
8 .concat(ComponentUtil.OBJECT_PROPERTIES)
9 .concat(ComponentUtil.FUNCTION_PROPERTIES)
10 .forEach(key => {
11 if (typeof component[key] !== 'undefined') {
12 gridOptions[key] = component[key];
13 }
14 });
15
16 ...
17
18 return gridOptions;
19 }
20}
选项被复制到componentDidMount
生命周期方法后,所有代理已被更新. 这也是我们实时化网格的链接。当实例化时,我们需要将原生 DOM 元素传递给 datagrid,所以我们将使用使用 refs 功能捕获的DIV
元素:
1export class AgGridReact extends React.Component {
2 gridOptions: AgGrid.GridOptions;
3
4 componentDidMount() {
5 ...
6
7 let gridOptions = this.props.gridOptions || {};
8 if (AgGridColumn.hasChildColumns(this.props)) {
9 gridOptions.columnDefs = AgGridColumn.mapChildColumnDefs(this.props);
10 }
11
12 this.gridOptions = AgGrid.ComponentUtil.copyAttributesToGridOptions(gridOptions, this.props);
13
14 new AgGrid.Grid(this.eGridDiv, this.gridOptions, gridParams);
15
16 this.api = this.gridOptions.api;
17 this.columnApi = this.gridOptions.columnApi;
18 }
19}
您可以在上面看到,我们还检查是否有作为列传递的孩子,然后将其添加到配置选项中作为列定义:
1if (AgGridColumn.hasChildColumns(this.props)) {
2 gridOptions.columnDefs = AgGridColumn.mapChildColumnDefs(this.props);
3}
步骤 4 — 同步网格属性更新
一旦网格被初始化,我们需要跟踪 React Props 的更改,以更新数据网格的配置选项。 ag-Grid
实现了 API 来做到这一点。
React 使用「componentWillReceiveProps」生命周期方法通知组件的更改,这就是我们把更新逻辑放在哪里:
1export class AgGridReact extends React.Component {
2 componentWillReceiveProps(nextProps: any) {
3 const changes = <any>{};
4 const changedKeys = Object.keys(nextProps);
5
6 changedKeys.forEach((propKey) => {
7 ...
8 if (!this.areEquivalent(this.props[propKey], nextProps[propKey])) {
9 changes[propKey] = {
10 previousValue: this.props[propKey],
11 currentValue: nextProps[propKey]
12 };
13 }
14 });
15 AgGrid.ComponentUtil.getEventCallbacks().forEach((funcName: string) => {
16 if (this.props[funcName] !== nextProps[funcName]) {
17 changes[funcName] = {
18 previousValue: this.props[funcName],
19 currentValue: nextProps[funcName]
20 };
21 }
22 });
23
24 AgGrid.ComponentUtil.processOnChange(changes, this.gridOptions, this.api, this.columnApi);
25 }
26}
基本上,我们通过ag-Grid
的配置属性和回复列表,检查是否有任何更改,我们将所有更改放入更改
阵列,然后使用processOnChange
方法来处理。
该方法做了两件事. 首先,它通过 React Props 中的更改,并更新了gridOptions
对象上的属性。
1export class ComponentUtil {
2 public static processOnChange(changes, gridOptions, api, ...) {
3 ...
4 // reflect the changes in the gridOptions object
5 ComponentUtil.ARRAY_PROPERTIES
6 .concat(ComponentUtil.OBJECT_PROPERTIES)
7 .concat(ComponentUtil.STRING_PROPERTIES)
8 .forEach(key => {
9 if (changes[key]) {
10 gridOptions[key] = changes[key].currentValue;
11 }
12 });
13
14 ...
15
16 // notify Grid about the changes in header height
17 if (changes.headerHeight) {
18 api.setHeaderHeight(changes.headerHeight.currentValue);
19 }
20
21 // notify Grid about the changes in page size
22 if (changes.paginationPageSize) {
23 api.paginationSetPageSize(changes.paginationPageSize.currentValue);
24 }
25
26 ...
27 }
28}
步骤 5 – 曝光 API
在运行时与 React 网格进行交互是通过网格 API. 您可能需要调整列的大小,设置新数据源,获取所有选定的行列等。 当 JavaScript 数据网格启动时,它将api
对象附加到网格选项对象。
1export class AgGridReact extends React.Component {
2 componentDidMount() {
3 ...
4 new AgGrid.Grid(this.eGridDiv, this.gridOptions, gridParams);
5
6 this.api = this.gridOptions.api;
7 this.columnApi = this.gridOptions.columnApi;
8 }
9}
那就是它。
结论
在本教程中,我们学会了如何调整一个瓦尼拉JavaScript库以在React框架内运作。
有关此主题的更多信息,您可以参阅与其他图书馆集成
的官方 React 文档(https://reactjs.org/docs/integrating-with-other-libraries.html)。