序章

複雑なWebプロジェクトでは、多くの場合、サードパーティのウィジェットを使用する必要があります。 しかし、フレームワークを使用していて、ウィジェットが純粋なJavaScriptでのみ利用可能である場合はどうでしょうか。

プロジェクトでJavaScriptウィジェットを使用するには、フレームワーク固有のラッパーを作成するのが最善の方法です。

ag-Grid は、データグリッドに情報を表示するためのJavaScriptウィジェットです。 これにより、情報を動的に並べ替え、フィルタリング、および選択できます。 ag-Gridは、Reactラッパーag-grid-reactも提供します。

この記事では、サードパーティのウィジェットをReactコンポーネントにラップする方法を学ぶための基礎として、ag-grid-communityag-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ドキュメントを参照してください。