Использование пакетов JavaScript в React

Сложные веб-проекты часто требуют присутствия сторонних виджетов. Но что, если вы используете фреймворк, а необходимый виджет доступен только на чистом JavaScript?

Чтобы использовать виджеты JavaScript в своем проекте, лучше всего создать оболочку для конкретного фреймворка.

ag-Grid – это виджет JavaScript для отображения информации в сетке данных. Он позволяет динамически сортировать, фильтровать и выбирать информацию. Также ag-Grid предоставляет оболочку для React по имени ag-grid-react.

В этом мануале мы покажем, как использовать ag-grid-community и ag-grid-react для создания оболочки стороннего виджета в компоненте React. Здесь мы настроим сопоставление между React Props и параметрами конфигурации виджета, а также откроем API виджета через компонент React.

Требования

Чтобы следовать этому руководству, нужно иметь базовые навыки работы с React.

1: Общие сведения о виджете ag-Grid

Как правило, большинство виджетов JavaScript имеют:

  • Параметры конфигурации
  • Публичный API
  • Транслируемые события

Именно так происходит и взаимодействие с ag-Grid. Хорошее описание свойств, событий, обратных вызовов и API можно найти в официальной документации виджета.

Сетка данных определяет:

  • Свойства, позволяющие использовать функции сетки, например анимацию строк.
  • Grid 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 присоединяет объект с методами API к gridOptions, которые можно использовать для управления сеткой данных JavaScript:

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

Однако, когда ag-Grid используется в качестве компонента React, экземпляр datagrid не создается напрямую. Это работа компонента оболочки. Все взаимодействия с экземпляром ag-Grid происходят через экземпляр компонента.

Следовательно, у нас нет прямого доступа к объекту API, прикрепленному сеткой. Мы будем обращаться к нему через экземпляр компонента.

2: Определение действий оболочки

Параметры конфигурации и обратные вызовы никогда не передаются в сетку напрямую. Компонент оболочки React принимает параметры и обратные вызовы через React Props.

Все параметры сетки, доступные в чистом JavaScript, также должны быть доступны в сетке данных React. События в экземпляре ag-Grid также не отслеживаются напрямую. Если мы используем ag-Grid в качестве компонента React, все генерируемые этим виджетом события должны быть доступны через свойства компонентов React.

Все это означает, что специфичная для React оболочка вокруг ag-Grid должна:

  • реализовывать сопоставление между входящими данными (например, rowData) и параметрами конфигурации ag-Grid
  • слушать события, генерируемые ag-Grid, и определять их как выходные данные компонентов.
  • отслеживать изменения во входных привязках компонентов и обновлять параметры конфигурации в сетке
  • предоставлять API, прикрепленный ag-Grid к gridOptions через его свойства

В следующем примере показано, как настроить сетку данных React в шаблоне с помощью React Props:

<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: Реализация 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 поступают как React Props в компонент AgGridReact. Свойство 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 в сетку данных, поэтому мы будем использовать элемент DIV, полученный с помощью функции refs:

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;
    }
}

Заключение

В этом руководстве вы узнали, как адаптировать обычную библиотеку JavaScript к работе в рамках фреймворка React.

За дополнительной информацией по этой теме рекомендуем обратиться к официальной документации React.

Читайте также: Обработка CSS на сервере приложений React

Tags: ,

Добавить комментарий