Несколько месяцев назад я искал 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)
PaulMaly
10.06.2017 19:59Вот уж не думал, что в React есть такие проблемы. Сам правда его не использую, но в том же Ractive таких проблем вообще нет, ибо есть декораторы из коробки.
BUY33
10.06.2017 21:13+2В свое время тоже искал таблицы, из существующих больше всех понравились таблицы react-bootstrap-table.
От jQuery избавились на проекте — имхо, инструмент неплохой, но в React не нужен и тащить за собой его как-то не хочется.frog
10.06.2017 22:19Тоже остановился на react-bootstrap-table. Причём, автор реагирует на пожелания/замечания, проект постепенно развивается.
comerc
10.06.2017 23:44Останавливает, что оно заточено для Bootstrap V3, а надо для V4. Нашёл в issues много интересного.
comerc
11.06.2017 02:32+1Какие варианты вышли в финал в моём отборе:
Griddle — Обещает ультра-расширяемость, варианты демонстрируются в React Storybook. Тащит за собой redux и immutable. Плагинов две штуки.
rectabular — Примечательна, что первые полгода в контрибуторах был Дан Абрамов. Вообще не добавляет сторонних зависимостей. Плагинов хватает:
vitvad
12.06.2017 19:41+1немного не в тему:
недавно наткнулся на достаточно интересную альтернативу react-bootstrap вплане компонентов из коробки
ant.design
лично мне понравилось что есть вполне достойный coverage и множество компонентов из коробки. Та же таблица достаточно «конфигурабельна»
maxfarseer
Использую react-virtualized, вместо пагинации — бесконечная подгрузка. Сортировка из коробки работает, а вот возможности регулировать длину колонки мышкой и менять столбцы местами (вроде бы было в DataTable) — нет. В остальном, не могу нарадоваться, очень крутой компонент. Автор отвечает на StackOveflow, плюс есть gitter чат.
P.S. у библиотеки одна из лучших документаций на моей памяти!
comerc
Выглядит очень вкусно, но вот что автор библиотеки отвечает на вопрос о пагинации:
И про реализацию сортировки:
Грусть-печаль. Мне нужно коробочное решение — "чик-чик и в продакшн".
voidnugget
Я думаю сначала нужно разобраться в offset и keyset паджинации что бы можно было утверждать что вот прям действительно "грусть-печалька".
"Чик-чик и в продакшн"…
А QA, a кто поддерживать будет, а ответственность ?
comerc
Тыкните меня носом, пожалуйста, куда смотреть?
voidnugget
use-the-index-luke.com/no-offset
comerc
Спасибо, очень интересно. Хозяйке на заметку. Но как это применимо к react-virtualized?
voidnugget
Можно реализовать нормальную Keyset паджинацию и докрутить нормальное API к react-virtualized. Сейчас есть довольно много нерешённых проблем с реализацией API'шек, пушами и синхронизацией… например вот.
Если в двух словах: можно прикрутить непрерывную прокрутку ко всему, с помощью Keyset паджинации.
xGromMx
«Коробочные» программисты…