如何包装一个用于React的VanillaJavaScript包
介绍
复杂的 Web 项目通常需要使用第三方小部件。 但是,如果您使用的是框架并且小部件仅在纯 JavaScript 中可用怎么办?
要在您的项目中使用 JavaScript 小部件,最好的方法是创建一个特定于框架的包装器。
ag-Grid 是一个 JavaScript 小部件,用于在数据网格中显示信息。 它允许您动态排序、过滤和选择信息。 ag-Grid
还提供了一个 React 包装器,ag-grid-react
。
在本文中,您将使用 ag-grid-community
和 ag-grid-react
作为学习如何将第三方小部件包装到 React 组件中的基础。 您将设置 React Props 和小部件的配置选项之间的映射。 您还将通过 React 组件公开小部件的 API。
先决条件
要阅读本文,您将需要:
- 对 React 有一定的了解。 你可以看看我们的 How To Code in React.js 系列。
第 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 文档“与其他库集成”。