Несколько месяцев назад я искал React-компонент для отображения таблицы данных в одном из наших веб-приложений в Undertone. В предыдущем проекте, который не был основан на высокоуровневой библиотеке, такой как React, мы использовали jQuery-плагин DataTables, и мы были очень довольны той гибкостью, которую он предлагает. Теперь я искал нечто похожее, которое можно легко интегрировать как React-компонент в наше новое приложение.


Проведя довольно обширные исследования, я узнал две вещи. Во-первых, это то, что не рекомендуется использовать DataTables вместе с React, потому что обе библиотеки управляют DOM. Во-вторых, не было библиотеки, которая обеспечивала гибкость, необходимую для нашего веб-приложения (с момента моего исследования). Крайний срок для сдачи проекта приближался, и мне нужно было что-то выбрать. Я решил пойти по непопулярному пути и интегрировать DataTables в свой проект. Результат получился намного лучше, чем я ожидал, и весь процесс интеграции на самом деле был довольно плавным. В следующих разделах я опишу схему проекта, в котором работает интеграция React и DataTables.


Предварительная установка


Мы будем использовать Create-React-App (CRA) для строительных лесов нашего проекта. Если вы не знакомы с CRA, это в основном инструмент для создания приложений React без конфигурации. Первое, что нам нужно сделать, это установить CRA (если вы его еще не установили) и инициализировать новый проект с помощью команды create-react-app (см. документацию по CRA для получения подробных сведений о том, как инициализировать и запустить проект).


Мы завершаем нашу установку, добавляя модули jQuery и Datatables в качестве dev-dependencies.


$ npm i --save-dev datatables.net jquery 

После завершения установки модулей мы удаляем ненужные файлы, автоматически генерируемые CRA из репозитория. Окончательный результат проекта, который мы создаем здесь в этой статье, можно найти в репозитории react-datatables на GitHub.


Простой UI


Мы реализуем очень простой интерфейс. У пользователей есть область ввода, где они могут вводить имя и псевдоним в соответствующие текстовые поля. Когда нажимается кнопка «Добавить», данные, введенные в текстовые поля, добавляются в таблицу данных. Имя является уникальным «ключом» для каждой записи — если пользователь набирает имя, которое уже существует, новая пара имен-ников не создается, и только псевдоним существующей пары обновляется в таблице.



 


Настройка DataTables


У нас уже есть исходные файлы с пустым контентом, созданным CRA. Мы создаем новый файл, содержащий наш компонент Table — этот компонент отвечает за визуализацию и управление таблицей. Сначала мы импортируем как jQuery, так и DataTables и связываем их так, чтобы DataTables имел доступ к функциям jQuerey. Это можно сделать с помощью следующих двух строк:


const $ = require('jquery');
$.DataTable = require('datatables.net');

Мы определяем два столбца, которые извлекают соответствующие значения из пары имя-ник, которую мы собираемся предоставить таблице:


const columns = [
  {
    title: 'Name',
    width: 120,
    data: 'name'
  },
  {
    title: 'Nickname',
    width: 180,
    data: 'nickname'
  },
];

Наконец, само определение компонента:


class Table extends Component {
  componentDidMount() {
    $(this.refs.main).DataTable({
      dom: '<"data-table-wrapper"t>',
      data: this.props.names,
      columns,
      ordering: false,
    })
  }

  componentWillUnmount() {
    $('.data-table-wrapper')
      .find('table')
      .DataTable()
      .destroy(true)
  }

  shouldComponentUpdate(nextProps) {
    if (nextProps.names.length !== this.props.names.length) {
      reloadTableData(nextProps.names)
    } else {
      updateTable(nextProps.names)
    }
    return false
  }

  render() {
    return (
      <div>
        <table ref="main" />
      </div>
    )
  }
}

Несколько замечаний. В функции render мы создаем один HTML-элемент таблицы. Это требование DataTables, поскольку для заполнения DOM нужен элемент таблицы. React никогда не узнает, что внутри элемента таблицы будет больше DOM. Мы гарантируем, что никаких повторных попыток со стороны React не произойдет, всегда возвращая false из метода shouldComponentUpdate. Сама инициализация таблицы должна произойти только один раз, когда наш компонент монтируется, потому что мы хотим оставить все внутренние манипуляции DOM в DataTables. Наконец, нам нужно уничтожить таблицу, когда компонент должен быть размонтирован. Это делается в методе componentWillUnmount с соответствующим вызовом API DataTables.


В настоящее время наш компонент Table берет свои данные через props.names, но мы не реализовали способ добавления или обновления имен. Мы сделаем это в следующем разделе.


Обновление таблицы


Существует два способа изменения массива пар имя-ник. Во-первых, добавив на него новую пару имя-ник. Во-вторых, заменив существующую пару имя-ник новой парой с таким же именем и другим ником. Это делается в другом компоненте, внешнем по отношению к компоненту Table. (Я не буду вдаваться в подробности о том, как распространяются эти обновления — более подробную информацию см. в репозитории проекта на GitHub.) Здесь мы рассмотрим два типа обновлений с помощью двух разных методов.


1) Когда добавляется новая пара имя-ник, мы перезагружаем всю таблицу:


function reloadTableData(names) {
  const table = $('.data-table-wrapper').find('table').DataTable()
  table.clear()
  table.rows.add(names)
  table.draw()
}

Мы используем стандартный селектор jQuery, чтобы найти экземпляр таблицы, используя класс, который мы предоставили в componentDidMount (data-table-wrapper). Затем мы удаляем все предыдущие данные и загружаем новые данные (для краткости я не добавлял новую пару имя-ник — это также работает, если вы удаляете одну пару или несколько пар).


2) Когда пара обновляется, мы хотим найти эту пару и изменить конкретную часть данных, которые были изменены (поле ника в нашем примере):


function updateTable(names) {
  const table = $('.data-table-wrapper').find('table').DataTable()
  let dataChanged = false
  table.rows().every(function() {
    const oldNameData = this.data()
    const newNameData = names.find(nameData => {
      return nameData.name === oldNameData.name
    })
    if (oldNameData.nickname !== newNameData.nickname) {
      dataChanged = true
      this.data(newNameData)
    }
    return true
  })

  if (dataChanged) {
    table.draw()
  }
}

Мы должны не забыть перерисовывать таблицу с помощью API-метода draw, если какие-либо данные были изменены или изменения не будут видны пользователю.


Остается только различать два случая и вызывать соответствующий метод, когда компонент таблицы отображается в React. Мы делаем это в коде shouldComponentUpdate, чтобы проверить тип изменения и выполнить соответствующий метод.


  shouldComponentUpdate(nextProps) {
    if (nextProps.names.length !== this.props.names.length) {
      reloadTableData(nextProps.names)
    } else {
      updateTable(nextProps.names)
    }
    return false
  }

Теперь, каждый раз, когда количество пар в файлах props.names изменяется, мы повторно отображаем всю таблицу или обновляем соответствующие элементы. Обратите внимание, что в реальном приложении содержимое всего массива может измениться, а количество элементов может оставаться неизменным — случай, который не может произойти в нашем примере.


Вывод


Это определенно возможно и даже довольно просто интегрировать React и DataTables, если вы ограждаете React от манипуляций DataTables в DOM. Вариант использования, который мы реализовали в этой статье, довольно прост, но он обеспечивает все строительные блоки, необходимые для интеграции работы в реальном проекте.


PS


Пожалуйста, поделитесь опытом — какие используете React-компоненты для реализации функционала таблиц с сортировкой и пагинацией для манипуляции большими объемами данных?

Поделиться с друзьями
-->

Комментарии (15)


  1. maxfarseer
    10.06.2017 17:50
    +2

    Использую react-virtualized, вместо пагинации — бесконечная подгрузка. Сортировка из коробки работает, а вот возможности регулировать длину колонки мышкой и менять столбцы местами (вроде бы было в DataTable) — нет. В остальном, не могу нарадоваться, очень крутой компонент. Автор отвечает на StackOveflow, плюс есть gitter чат.
    P.S. у библиотеки одна из лучших документаций на моей памяти!


    1. comerc
      10.06.2017 18:13

      Выглядит очень вкусно, но вот что автор библиотеки отвечает на вопрос о пагинации:


      I still feel that pagination is something that doesn't quite fit into react-virtualized since the main purpose of this library is to display lots of unpaginated data.

      И про реализацию сортировки:


      I would encourage you (or anyone) to release an add-on library that works with react-virtualized to add this behavior though. I think a lot of people could benefit from it.

      Грусть-печаль. Мне нужно коробочное решение — "чик-чик и в продакшн".


      1. voidnugget
        11.06.2017 00:37

        Я думаю сначала нужно разобраться в offset и keyset паджинации что бы можно было утверждать что вот прям действительно "грусть-печалька".


        "Чик-чик и в продакшн"…
        А QA, a кто поддерживать будет, а ответственность ?


        1. comerc
          11.06.2017 01:40

          сначала нужно разобраться в offset и keyset паджинации

          Тыкните меня носом, пожалуйста, куда смотреть?


          1. voidnugget
            11.06.2017 11:18
            +1

            1. comerc
              11.06.2017 11:36

              Спасибо, очень интересно. Хозяйке на заметку. Но как это применимо к react-virtualized?


              1. voidnugget
                11.06.2017 12:05
                +1

                Можно реализовать нормальную Keyset паджинацию и докрутить нормальное API к react-virtualized. Сейчас есть довольно много нерешённых проблем с реализацией API'шек, пушами и синхронизацией… например вот.


                Если в двух словах: можно прикрутить непрерывную прокрутку ко всему, с помощью Keyset паджинации.


      1. xGromMx
        11.06.2017 14:21

        «Коробочные» программисты…


  1. PaulMaly
    10.06.2017 19:59

    Вот уж не думал, что в React есть такие проблемы. Сам правда его не использую, но в том же Ractive таких проблем вообще нет, ибо есть декораторы из коробки.


  1. BUY33
    10.06.2017 21:13
    +2

    В свое время тоже искал таблицы, из существующих больше всех понравились таблицы react-bootstrap-table.
    От jQuery избавились на проекте — имхо, инструмент неплохой, но в React не нужен и тащить за собой его как-то не хочется.


    1. frog
      10.06.2017 22:19

      Тоже остановился на react-bootstrap-table. Причём, автор реагирует на пожелания/замечания, проект постепенно развивается.


    1. comerc
      10.06.2017 23:44

      Останавливает, что оно заточено для Bootstrap V3, а надо для V4. Нашёл в issues много интересного.


  1. comerc
    11.06.2017 02:32
    +1

    Какие варианты вышли в финал в моём отборе:


    Griddle — Обещает ультра-расширяемость, варианты демонстрируются в React Storybook. Тащит за собой redux и immutable. Плагинов две штуки.


    rectabular — Примечательна, что первые полгода в контрибуторах был Дан Абрамов. Вообще не добавляет сторонних зависимостей. Плагинов хватает:


    image


  1. vitvad
    12.06.2017 19:41
    +1

    немного не в тему:
    недавно наткнулся на достаточно интересную альтернативу react-bootstrap вплане компонентов из коробки
    ant.design
    лично мне понравилось что есть вполне достойный coverage и множество компонентов из коробки. Та же таблица достаточно «конфигурабельна»


    1. comerc
      13.06.2017 21:57

      Очень даже в тему, таблица — полный фарш!