Reactで使用するためにVanillaJavaScriptパッケージをラップする方法
序章
複雑なWebプロジェクトでは、多くの場合、サードパーティのウィジェットを使用する必要があります。 しかし、フレームワークを使用していて、ウィジェットが純粋なJavaScriptでのみ利用可能である場合はどうでしょうか。
プロジェクトでJavaScriptウィジェットを使用するには、フレームワーク固有のラッパーを作成するのが最善の方法です。
ag-Grid は、データグリッドに情報を表示するためのJavaScriptウィジェットです。 これにより、情報を動的に並べ替え、フィルタリング、および選択できます。 ag-Grid
は、Reactラッパーag-grid-react
も提供します。
この記事では、サードパーティのウィジェットをReactコンポーネントにラップする方法を学ぶための基礎として、ag-grid-community
とag-grid-react
を使用します。 ReactPropsとウィジェットの構成オプションの間のマッピングを設定します。 また、Reactコンポーネントを介してウィジェットのAPIを公開します。
前提条件
この記事をフォローするには、次のものが必要です。
- Reactにある程度精通している。 React.jsシリーズのコーディング方法をご覧ください。
ステップ1—JavaScriptウィジェットを理解する
一般に、ほとんどのJavaScriptウィジェットには次のものがあります。
- 構成オプション
- パブリックAPI
- 放送されたイベント
それがまさにag-Grid
とのやりとりです。 グリッドのプロパティ、イベント、コールバック、およびAPI の適切な説明は、公式ドキュメントにあります。
つまり、データグリッドは次のことを定義します。
- グリッドプロパティは、行アニメーションなどのグリッドの機能を有効にします。
- グリッドAPIは、実行時にグリッドと対話します(たとえば、選択したすべての行を取得します)
- グリッドイベント行の並べ替えや行の選択など、グリッドで特定のイベントが発生したときにグリッドから発行されます
- グリッドコールバックは、必要なときにアプリケーションからグリッドに情報を提供するために使用されます(たとえば、メニューが表示されるたびにコールバックが呼び出され、アプリケーションがメニューをカスタマイズできるようになります)
グリッドオプションの使用法を示す非常に基本的な純粋な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
は、JavaScriptデータグリッドの制御に使用できるgridOptions
に、APIメソッドを使用してオブジェクトをアタッチします。
// get the grid to refresh gridOptions.api.refreshView();
ただし、ag-Grid
をReactコンポーネントとして使用する場合、データグリッドを直接インスタンス化することはありません。 それがラッパーコンポーネントの仕事です。 ag-Grid
のインスタンスとのすべての対話は、コンポーネントインスタンスを介して行われます。
たとえば、グリッドによってアタッチされたAPIオブジェクトに直接アクセスすることはできません。 コンポーネントのインスタンスを介してアクセスします。
ステップ2—ラッパーコンポーネントが何をすべきかを決定する
構成オプションとコールバックをグリッドに直接渡すことはありません。 Reactラッパーコンポーネントは、ReactPropsを介してオプションとコールバックを受け取ります。
バニラJavaScriptグリッドで使用できるすべてのグリッドオプションは、 Reactdatagridでも使用できる必要があります。 また、ag-Grid
のインスタンスでイベントを直接リッスンすることもありません。 ag-Grid
をReactコンポーネントとして使用している場合、ag-Grid
によって発行されるすべてのイベントは、Reactコンポーネントの小道具を介して利用できる必要があります。
これはすべて、ag-Grid
の周りのReact固有のデータグリッドラッパーが次のことを行う必要があることを意味します。
- 入力バインディング(
rowData
など)とag-Grid
の構成オプション間のマッピングを実装します ag-Grid
によって発行されたイベントをリッスンし、それらをコンポーネント出力として定義する必要があります- コンポーネントの入力バインディングの変更をリッスンし、グリッドの構成オプションを更新します
ag-Grid
によって接続されたAPIをそのプロパティを介してgridOptions
に公開します
次の例は、ReactPropsを使用してテンプレートで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
でどのように実装したかを見てみましょう。
ステップ3—ReactWrapperを実装する
まず、テンプレートでReactデータグリッドを表すReactコンポーネントAgGridReactを定義する必要があります。 このコンポーネントは、データグリッドのコンテナとして機能する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
コンポーネントのReactPropsとして提供されます。 gridOptions
プロパティは、すべてのデータグリッドオプションを格納するために使用されます。 利用可能になり次第、Reactプロップからすべての構成オプションをコピーする必要があります。
そのために、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; } }
すべての小道具が更新された後、オプションは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—グリッドプロパティの更新を同期する
グリッドが初期化されたら、データグリッドの構成オプションを更新するためにReactPropsへの変更を追跡する必要があります。 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
メソッドを使用して処理します。
この方法は2つのことを行います。 まず、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; } }
以上です。
結論
このチュートリアルでは、バニラJavaScriptライブラリをReactフレームワーク内で機能するように適合させる方法を学びました。
このトピックの詳細については、「他のライブラリとの統合」に関する公式のReactドキュメントを参照してください。