如何包装一个用于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 文档“与其他库集成”。