如何包装一个用于React的VanillaJavaScript包

来自菜鸟教程
跳转至:导航、​搜索

介绍

复杂的 Web 项目通常需要使用第三方小部件。 但是,如果您使用的是框架并且小部件仅在纯 JavaScript 中可用怎么办?

要在您的项目中使用 JavaScript 小部件,最好的方法是创建一个特定于框架的包装器。

ag-Grid 是一个 JavaScript 小部件,用于在数据网格中显示信息。 它允许您动态排序、过滤和选择信息。 ag-Grid 还提供了一个 React 包装器,ag-grid-react

在本文中,您将使用 ag-grid-communityag-grid-react 作为学习如何将第三方小部件包装到 React 组件中的基础。 您将设置 React Props 和小部件的配置选项之间的映射。 您还将通过 React 组件公开小部件的 API。

先决条件

要阅读本文,您将需要:

第 1 步 — 了解 JavaScript 小部件

一般来说,大多数 JavaScript 小部件都有:

  • 配置选项
  • 公共 API
  • 广播事件

这正是您与 ag-Grid 交互的方式。 您可以在官方文档 中找到关于网格的属性、事件、回调和 API 的详细说明。

简而言之,数据网格定义:

  • Grid Properties 启用网格功能,如行动画。
  • Grid API 在运行时与网格交互(例如,获取所有选定的行)
  • Grid Events 当网格中发生某些事件时由网格发出,例如行排序或行选择
  • Grid Callbacks 用于在需要时从应用程序向网格提供信息(例如,每次显示菜单时都会调用回调,以允许应用程序自定义菜单)

这是一个非常基本的纯 JavaScript 配置,演示了网格选项的用法:

let gridOptions = {
    // PROPERTIES - object properties, myRowData and myColDefs are created somewhere in your application
    rowData: myRowData,
    columnDefs: myColDefs,

    // PROPERTIES - simple boolean / string / number properties
    pagination: true,
    rowSelection: 'single',

    // EVENTS - add event callback handlers
    onRowClicked: function(event) { console.log('a row was clicked'); },
    onColumnResized: function(event) { console.log('a column was resized'); },
    onGridReady: function(event) { console.log('the grid is now ready'); },

    // CALLBACKS
    isScrollLag: function() { return false; }
}

首先,JavaScript 数据网格是这样初始化的:

new Grid(this._nativeElement, this.gridOptions, ...);

然后,ag-Grid 将具有 API 方法的对象附加到可用于控制 JavaScript 数据网格的 gridOptions

// get the grid to refresh
gridOptions.api.refreshView();

但是,当 ag-Grid 用作 React 组件时,我们不会直接实例化数据网格。 这就是包装器组件的工作。 与 ag-Grid 实例的所有交互都是通过组件实例发生的。

例如,我们无法直接访问网格附加的 API 对象。 我们将通过组件的实例访问它。

第 2 步 — 确定包装器组件应该做什么

我们从不将配置选项和回调直接传递给网格。 React 包装器组件通过 React Props 获取选项和回调。

所有可用于原生 JavaScript 网格的网格选项都应该在 React datagrid 中可用。 我们也不直接监听 ag-Grid 实例上的事件。 如果我们使用 ag-Grid 作为 React 组件,则 ag-Grid 发出的所有事件都应该可以通过 React 组件道具获得。

这一切都意味着围绕 ag-Grid 的 React 特定数据网格包装器应该:

  • 实现输入绑定(如 rowData)和 ag-Grid 的配置选项之间的映射
  • 应该监听 ag-Grid 发出的事件并将它们定义为组件输出
  • 监听组件输入绑定的变化并更新网格中的配置选项
  • 通过属性将 ag-Grid 附加的 API 暴露给 gridOptions

以下示例演示了如何使用 React Props 在模板中配置 React 数据网格:

<AgGridReact
    // useful for accessing the component directly via ref - optional
    ref="agGrid"

    // simple attributes, not bound to any state or prop
    rowSelection="multiple"

    // these are bound props, so can use anything in React state or props
    columnDefs={this.props.columnDefs}
    showToolPanel={this.state.showToolPanel}

    // this is a callback
    isScrollLag={this.myIsScrollLagFunction}

    // these are registering event callbacks
    onCellClicked={this.onCellClicked}
    onColumnResized={this.onColumnEvent}

    // inside onGridReady, you receive the grid APIs if you want them
    onGridReady={this.onGridReady}
/>

现在我们了解了需求,让我们看看我们如何在 ag-Grid 上实现它。

第三步——实现一个 React Wrapper

首先,我们需要在模板中定义一个 React 组件 AgGridReact 来表示我们的 React 数据网格。 该组件将呈现一个 DIV 元素,该元素将用作数据网格的容器。 为了获得原生 DIV 元素,我们使用 Refs 功能

export class AgGridReact extends React.Component {
    protected eGridDiv: HTMLElement;

    render() {
        return React.createElement("div", {
            style: ...,
            ref: e => {
                this.eGridDiv = e;
            }
        }, ...);
    }
}

在我们可以实例化 ag-Grid 之前,我们还需要收集所有选项。 所有 ag-Grid 属性和事件都作为 AgGridReact 组件上的 React Props 出现。 gridOptions 属性用于存储所有数据网格选项。 我们需要在 React props 可用时立即复制所有配置选项。

为此,我们实现了 copyAttributesToGridOptions 函数。 它是一种实用函数,可将属性从一个对象复制到另一个对象:

export class ComponentUtil {
    ...
    public static copyAttributesToGridOptions(gridOptions, component, ...) {
        ...
        // copy all grid properties to gridOptions object
        ComponentUtil.ARRAY_PROPERTIES
            .concat(ComponentUtil.STRING_PROPERTIES)
            .concat(ComponentUtil.OBJECT_PROPERTIES)
            .concat(ComponentUtil.FUNCTION_PROPERTIES)
            .forEach(key => {
                if (typeof component[key] !== 'undefined') {
                    gridOptions[key] = component[key];
                }
            });

         ...

         return gridOptions;
    }
}

在所有 props 更新后,这些选项将复制到 componentDidMount 生命周期方法中。 这也是我们实例化网格的钩子。 我们需要在实例化时将本机 DOM 元素传递给数据网格,因此我们将使用使用 refs 功能捕获的 DIV 元素:

export class AgGridReact extends React.Component {
    gridOptions: AgGrid.GridOptions;

    componentDidMount() {
        ...

        let gridOptions = this.props.gridOptions || {};
        if (AgGridColumn.hasChildColumns(this.props)) {
            gridOptions.columnDefs = AgGridColumn.mapChildColumnDefs(this.props);
        }

        this.gridOptions = AgGrid.ComponentUtil.copyAttributesToGridOptions(gridOptions, this.props);

        new AgGrid.Grid(this.eGridDiv, this.gridOptions, gridParams);

        this.api = this.gridOptions.api;
        this.columnApi = this.gridOptions.columnApi;
    }
}

您可以在上面看到,我们还检查是否有作为列传递的子项,然后将其添加到配置选项作为列定义:

if (AgGridColumn.hasChildColumns(this.props)) {
    gridOptions.columnDefs = AgGridColumn.mapChildColumnDefs(this.props);
}

第 4 步 - 同步网格属性更新

网格初始化后,我们需要跟踪 React Props 的更改以更新数据网格的配置选项。 ag-Grid 实现了一个 API 来做到这一点。 例如,如果 headerHeight 属性发生更改,则 setHeaderHeight 方法可以更新标题的高度。

React 使用 componentWillReceiveProps 生命周期方法来通知组件有关更改。 这是我们放置更新逻辑的地方:

export class AgGridReact extends React.Component {
    componentWillReceiveProps(nextProps: any) {
        const changes = <any>{};
        const changedKeys = Object.keys(nextProps);

        changedKeys.forEach((propKey) => {
            ...
            if (!this.areEquivalent(this.props[propKey], nextProps[propKey])) {
                changes[propKey] = {
                    previousValue: this.props[propKey],
                    currentValue: nextProps[propKey]
                };
            }
        });
        AgGrid.ComponentUtil.getEventCallbacks().forEach((funcName: string) => {
            if (this.props[funcName] !== nextProps[funcName]) {
                changes[funcName] = {
                    previousValue: this.props[funcName],
                    currentValue: nextProps[funcName]
                };
            }
        });

        AgGrid.ComponentUtil.processOnChange(changes, this.gridOptions, this.api, this.columnApi);
    }
}

基本上,我们检查 ag-Grid 的配置属性和回调列表,并检查它们是否发生了变化。 我们将所有更改放入 changes 数组中,然后使用 processOnChange 方法处理它们。

该方法做了两件事。 首先,它会检查 React Props 中的更改并更新 gridOptions 对象上的属性。 接下来,它调用 API 方法来通知网格有关更改:

export class ComponentUtil {
    public static processOnChange(changes, gridOptions, api, ...) {
        ...
        // reflect the changes in the gridOptions object
        ComponentUtil.ARRAY_PROPERTIES
            .concat(ComponentUtil.OBJECT_PROPERTIES)
            .concat(ComponentUtil.STRING_PROPERTIES)
            .forEach(key => {
                if (changes[key]) {
                    gridOptions[key] = changes[key].currentValue;
                }
            });

        ...

        // notify Grid about the changes in header height
        if (changes.headerHeight) {
            api.setHeaderHeight(changes.headerHeight.currentValue);
        }

        // notify Grid about the changes in page size
        if (changes.paginationPageSize) {
            api.paginationSetPageSize(changes.paginationPageSize.currentValue);
        }

        ...
    }
}

第 5 步 — 公开 API

在运行时与 React 网格交互是通过网格 API 完成的。 您可能想要调整列的大小、设置新的数据源、获取所有选定行的列表等。 当 JavaScript 数据网格启动时,它会将 api 对象附加到网格选项对象。 为了公开这个对象,我们将它分配给组件实例:

export class AgGridReact extends React.Component {
    componentDidMount() {
        ...
        new AgGrid.Grid(this.eGridDiv, this.gridOptions, gridParams);

        this.api = this.gridOptions.api;
        this.columnApi = this.gridOptions.columnApi;
    }
}

就是这样。

结论

在本教程中,我们学习了如何调整 vanilla JavaScript 库以在 React 框架中运行。

有关此主题的更多信息,您可以参考 官方 React 文档“与其他库集成”