Этот пост является продолжением моей статьи Использование React Query с React Table. Я рекомендую сначала прочитать это, чтобы понять полный контекст.
Мы также будем следовать тому же набору примеров кода, который можно найти на github.com/nafeu/react-query-table-sandbox. Мы сосредоточимся на файле ReactTableExpanding.jsx.
Вы можете быстро настроить его с помощью:
git clone https://github.com/nafeu/react-query-table-sandbox.git
cd react-query-table-sandbox
npm install
npm start
Расширяемый стол, который мы хотим построить
Понимание наших данных
Когда мы думаем о табличных данных, их легче визуализировать, если мы рассматриваем их как плоскую структуру, например, следующая коллекция имеет только один уровень глубины:
[
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' },
{ id: 3, name: 'baz' }
]
И в таблице отобразит:
Но что, если бы у нас была более иерархическая (или древовидная) структура, такая как:
[
{
id: 1,
name: 'foo'
children: [
{ id: 4, name: 'qux' },
{ id: 5, name: 'quux' }
]
},
{ id: 2, name: 'bar' },
{ id: 3, name: 'baz' }
]
Здесь все становится шатким, и нам нужно немного изменить наш подход. Допустим, мы хотим отобразить это, но отображаем children
только тогда, когда мы нажимаем на отдельную строку. Например, если бы мы щелкнули по строке с id: 1
, мы бы хотели увидеть плоскую таблицу, отображаемую следующим образом:
В реальном мире у нас есть много примеров, когда это так, и они представлены в виде таблиц детализации. Это полезно для панелей управления бизнес-аналитикой, списков лидеров видеоигр, бухгалтерского учета или практически в любом случае, когда вам нужно дополнительно изучить одну строку в таблице.
В index.mjs
нашего примера кода мы определили API, который возвращает фиктивные данные о воображаемой группе обсуждения разработчиков программного обеспечения.
Конечная точка api/
возвращает данные в форме:
[
{
"id": 1,
"name": "How to use react-table in reporting dashboard",
"active": 40,
"status": "locked",
"upvotes": 30
},
{
"id": 2,
"name": "How to use react-query for BI solution",
"active": 31,
"status": "resolved",
"upvotes": 39
}
...
]
Конечная точка api/child
возвращает аналогичные данные, но каждый раз предоставляет новые записи, тогда как конечная точка корневого api/
возвращает исходную полезную нагрузку с небольшими изменениями. Это сделано для имитации «активного веб-сайта», на котором данные изменяются в псевдореальном времени.
Хотя пример немного грубоват, представьте на мгновение, что каждая из этих тем обсуждения имеет подвопросы, которые структурированы одинаково. Они будут использоваться для сохранения «иерархической» структуры в нашей таблице.
В нашем внешнем коде мы имеем:
const fetchParentData = () => axios.get(`http://localhost:8000/api`);
const fetchChildData = () => axios.get(`http://localhost:8000/api/child`);
Это помощники API на основе обещаний, которые извлекают частично динамические родительские данные и новый набор дочерних данных соответственно (для React Query необходимо использовать основанные на обещаниях помощники API).
Рекурсивное изменение данных строки
Прежде чем мы углубимся в код реакции, мы должны выяснить способ рекурсивной вставки одного набора rows
в существующий набор rows
, например, как мы можем получить из:
row-0
row-1
row-2
to
row-0
row-0.0
row-0.1
row-1
row-2
и даже дальше
row-0
row-0.0
row-0.0.0
row-0.0.1
row-0.1
row-2
row-3
и так далее и тому подобное. В нашем примере кода мы делаем это с помощью функции recursivelyUpdateTable
, которая выглядит следующим образом:
Здесь мы берем:
tableData
- ›existingRows
это наши существующие данные таблицыchildData
- ›subRowsToInsert
, который представляет новые строки, которые мы хотим вставитьid
- ›path
, который отформатирован какx.y.z
и разделен на[x, y, z]
, гдеx
,y
иz
могут быть индексами, представляющими определенные строки в коллекции, а весьid
фактически служитpath
для определенногоnode
(строки),
Мы используем эти значения и некоторые базовые знания рекурсивных алгоритмов, чтобы сначала получить наш текущий путь для обхода:
const id = path[0];
Затем мы формализуем наш базовый случай (который в нашем случае - это когда остался только один индекс пути), решаем, есть ли у достигнутого узла подстроки или нет, а затем вставляем строку:
Если мы не в нашем базовом случае, мы рекурсивно используем функцию insertIntoTable
и загружаем версию подмножества наших текущих строк:
Это может быть немного сложно, особенно если вы не писали много рекурсивного кода в течение некоторого времени, поэтому не стесняйтесь не торопиться, чтобы проследить и понять его.
Цель этой статьи не в том, чтобы сосредоточиться на рекурсии, а в том, чтобы сосредоточиться на использовании React Table для создания расширяемых строк с отложенной загрузкой. Однако этот помощник по-прежнему остается очень важной его частью.
Основываясь на нашем запросе React + предложение React Table
В моем предыдущем посте я предложил схему объединения React Query и React Table, этот код также существует в нашем примере кода на ReactQueryWithTable.jsx
Предложение состоит из следующего структурирования уровня запроса, уровня обработки данных и уровня рендеринга между тремя компонентами:
TableQuery
TableInstance
TableLayout
Давайте построим или расширим этот пример, обратитесь к ReactTableExpanding
Обновление нашего компонента TableQuery
Давайте посмотрим на наш TableQuery
компонент. Мы создаем новую переменную с именем isRowLoading
и реализуем функцию-обработчик handleClickRow
, которая будет вызываться каждый раз при нажатии отдельной строки в нашей таблице.
Следует отметить, что isRowLoading
- это переменная состояния загрузки. Это сопоставление paths
с логическим значением, которое помогает нам отслеживать, какая строка в данный момент находится в процессе выборки данных:
Мы также используем recursivelyUpdateTable
для изменения данных нашей существующей таблицы с помощью дочерних данных, полученных от нашего API, поэтому наша рекурсивная функция так важна. Одно из преимуществ React Table заключается в том, что он уже знает, как работать со встроенными структурами, все, что вам нужно сделать, это предоставить их, используя свойство subRows
внутри отдельной строки, а он позаботится обо всем остальном.
У нас также есть вызов useQuery
, который мы изменяем:
const {
data: apiResponse,
isLoading
} = useQuery('discussionGroups', fetchParentData, { enabled: !tableData });
Мы добавляем enabled: !tableData
, чтобы предотвратить повторную выборку табличной формы в фоновом режиме, так как это может испортить встроенную структуру. Реализация повторной выборки со встроенной структурой - это более сложная тема, которая не будет рассматриваться в этой статье.
Мы также передаем еще несколько реквизитов в TableInstance
, например:
return (
<TableInstance
tableData={tableData}
onClickRow={handleClickRow}
isRowLoading={isRowLoading}
/>
);
Обновление нашего компонента TableInstance
Помимо использования двух новых свойств onClickRow
и isRowLoading
, мы в основном сосредоточимся на новом заголовке столбца, который мы добавляем:
Вот где происходит некоторая магия. Мы используем свойство Cell
, чтобы указать React Table, как следует форматировать отдельную ячейку, в этом случае мы добавляем дополнительный столбец перед нашей таблицей, который будет содержать кнопку расширения, а также добавляем некоторую дополнительную логику для запуска нашего пользовательского onClickRow
обработчик, если строка находится в соответствующем состоянии загрузки и раскрытия.
Вам может быть интересно, откуда взялся { row, isLoading, isExpanding }
? Объект row
на самом деле является тем, что React Table предоставляет нам для доступа при использовании свойства Cell
, это очень полезно для нас, поскольку row
также имеет полезный метод под названием getToggleRowExpandedProps()
, который мы можем использовать для ручного запуска функции расширения строки React Table.
Мы также добавляем ловушку useExpanded
в экземпляр нашего экземпляра таблицы с параметром autoResetExpanded
, установленным в false:
const tableInstance = useTable(
{ columns, data, autoResetExpanded: false },
useExpanded
);
И мы передаем объект isRowLoading
в TableLayout
:
return (
<TableLayout
{...tableInstance}
isRowLoading={isRowLoading}
/>
);
Обновление нашего компонента TableLayout
В нашем TableLayout
компоненте мы извлекаем дополнительную опору state.expanded
из нашего tableInstance
. Это предоставляется нам React Table благодаря хук useExpanded
, он обеспечивает сопоставление, подобное тому, как мы определили наше сопоставление isRowLoading
.
const TableLayout = ({
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
isRowLoading,
state: { expanded }
}) => {
...
}
Основное обновление заключается в отрисовке отдельных строк:
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>
{cell.render('Cell', {
isLoading: isRowLoading[row.id],
isExpanded: expanded[row.id]
})}
</td>
})}
</tr>
)
В методе cell.render
мы добавляем дополнительный объект, который заполняется:
{
isLoading: isRowLoading[row.id],
isExpanded: expanded[row.id]
}
Это работает в тандеме с нашим ранее объявленным кодом в компоненте TableInstance
, где мы можем передавать пользовательские значения непосредственно в Cell
. Мы сами определили isRowLoading
- ›isLoading
и подаем expanded
-› isExpanded
, чтобы обеспечить выполнение состояния загрузки и развернутого состояния. Это даже не так сложно читать, это то, что делает React Table такой невероятной для работы.
Когда вы соберете все это вместе, у вас получится вот такой шикарный стол:
Удачного кодирования!