如何封装 Vanilla JavaScript 包以便在 React 中使用

介绍

复杂的 Web 项目通常需要使用第三方小工具,但如果您正在使用一个框架,而小工具只在纯 JavaScript 中可用呢?

要在您的项目中使用JavaScript小工具,最好的方法是创建一个框架特定的包装程序。

ag-Grid是一个用于显示数据集中的信息的JavaScript小工具,它允许您动态地分类,过滤和选择信息。

在本文中,您将使用ag-grid-communityag-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)。

Published At
Categories with 技术
Tagged with
comments powered by Disqus