«Как создать сортируемую и фильтруемую таблицу в React – CodesCode»

Изучите, как создать компонент таблицы в React, позволяющий сортировать и фильтровать данные, что поможет ускорить процессы при работе с большими наборами данных.

Динамические таблицы часто используются в веб-приложениях для представления данных в структурированном формате. Сортировка и фильтрация набора данных могут ускорить процессы при работе с большими наборами данных. В этом руководстве мы рассмотрим, как создать сортируемую и фильтруемую компоненту таблицы в React.

Вы можете найти полный исходный код в одном куске, размещенном на GitHub. Конечный результат изображен ниже.

Конечный компонент таблицы

Предварительные требования

Перед началом работы с этим руководством предполагается, что у вас есть базовые знания HTML, CSS, JavaScript и React. Хотя мы пошагово рассмотрим проект, мы не будем подробно объяснять основные концепции в React или методы массивов JavaScript. Мы также будем использовать TypeScript, но то же самое можно достичь и без него. С этим сказано, давайте приступим к кодированию.

Настройка проекта

Для этого проекта мы будем использовать Vite, мощный и популярный фронтенд-инструмент. Если у вас еще нет существующего приложения React, вы можете создать новый проект в Vite, используя одну из следующих команд в терминале:

# Using NPMnpm create vite@latest folder-name -- --template react-ts# Using Yarnyarn create vite folder-name --template react-ts# Using PNPMpnpm create vite folder-name --template react-ts# Using Bunbunx create-vite folder-name --template react-ts

Когда вы будете готовы, создайте новую папку для компонента Table в проекте React со следующей структурой:

src├─ components│  ├─ Table│  │  ├─ index.ts │  │  ├─ table.css│  │  ├─ Table.tsx├─ App.tsx
  • index.ts. В этом файле мы будем экспортировать Table.tsx, чтобы упростить пути импорта.
  • table.css. Содержит стили, связанные с компонентом. В этом учебнике мы будем использовать чистый CSS.
  • Table.tsx. Сам компонент.

Откройте файл Table.tsx и экспортируйте следующее, чтобы мы могли проверить, загружается ли компонент при его импорте:

import './table.css'export const Table = () => {  return (    <h1>Table component</h1>  )}

Внутри файла index.ts переэкспортируйте компонент, используя следующую строку:

export * from './Table'

Теперь, когда у нас есть настроенные компоненты файлы, давайте проверим, загружается ли он, импортировав его в наше приложение. В этом учебнике мы будем использовать компонент App. Если у вас есть существующий проект React, вы можете импортировать его в нужное место. Импортируйте компонент Table в свое приложение так:

import { Table } from './components/Table'const App = () => {  return (    <Table />  )}export default App

Генерация псевдо-данных

Конечно, для работы с таблицей нам понадобятся сначала некоторые псевдо-данные. Для этого урока мы можем использовать Генератор JSON, бесплатный сервис для генерации случайных данных JSON. Мы будем использовать следующую схему для генерации данных:

[  '{{repeat(10)}}',  {    id: '{{index()}}',    name: '{{firstName()}} {{surname()}}',    company: '{{company().toUpperCase()}}',    active: '{{bool()}}',    country: '{{country()}}'  }]

JSON Генератор поставляется с различными встроенными функциональностями для генерации различных типов данных. Вышеприведенная схема создаст массив объектов с десятью случайными объектами в форме:

{  id: 0,                 // number - Index of the array, starting from 0  name: 'Jaime Wallace', // string - A random name  company: 'UBERLUX',    // string - Capitalized random string  active: false,         // boolean - either `true` or `false`  country: 'Peru'        // string - A random country name}

Сгенерируйте список записей, используя схему выше, затем создайте новый файл внутри папки src с названием data.ts и экспортируйте массив следующим образом:

export const data = [  {    id: 0,    name: 'Jaime Wallace',    company: 'UBERLUX',    active: false,    country: 'Peru'  },  { ... },]

Откройте файл App.tsx, и передайте эти данные в компонент Table в виде свойства с именем rows. Мы сгенерируем таблицу на основе этих данных:

  import { Table } from './components/Table'+ import { data } from './data'  const App = () => {    return (-     <Table />+     <Table rows={data} />    )  }  export default App

Создание компонента

Теперь, когда у нас есть и компонент, и настроенный набор данных, мы можем приступить к работе с таблицей. Чтобы динамически генерировать таблицу на основе переданных данных, замените все в компоненте Table следующими строками кода:

import { useState } from 'react'import './table.css'export const Table = ({ rows }) => {  const [sortedRows, setRows] = useState(rows)  return (    <table>      <thead>        <tr>          {Object.keys(rows[0]).map((entry, index) => (            <th key={index}>{entry}</th>          ))}        </tr>      </thead>      <tbody>        {sortedRows.map((row, index) => (          <tr key={index}>            {Object.values(row).map((entry, columnIndex) => (              <td key={columnIndex}>{entry}</td>            ))}          </tr>        ))}      </tbody>    </table>  )}

Это будет динамически генерировать заголовки и ячейки таблицы на основе свойства rows. Разберем, как это работает. Поскольку мы собираемся сортировать и фильтровать строки, нам нужно сохранить их в состоянии с помощью хука useState. Свойство передается в качестве начального значения хуку.

Чтобы отобразить заголовки таблицы, мы можем использовать Object.keys для первой записи в массиве, что вернет ключи объекта в виде списка строк:

const rows = [  {    id: 0,    name: 'Jaime Wallace'  },  { ... }]// #1 Turn object properties into an array of keys:Object.keys(rows[0]) -> ['id', 'name']// #2 Chain `map` from the array to display the values inside `th` elements:['id', 'name'].map((entry, index) => (...))

Чтобы отобразить ячейки таблицы, нам нужно использовать Object.values на каждой строке, которая возвращает значение каждого ключа в объекте, в отличие от Object.keys. Подробно, вот как мы отображаем ячейки таблицы:

const sortedRows = [  {    id: 0,    name: 'Jaime Wallace'  },  { ... }]// #1 Loop through each object in the array and create a `tr` element:{sortedRows.map((row, index) => (<tr key={index}>...</tr>))}// #2 Loop through each property of each object to create the `td` elements:Object.values(row) -> [0, 'Jaime Wallace']

Этот подход делает использование любого типа данных с нашим компонентом Table ä очень гибким, без необходимости переписывать логику. На данный момент, у нас есть следующая таблица, созданная с использованием нашего компонента. Однако, у таблицы есть некоторые проблемы с форматированием.

Проблема с форматированием в компоненте Table

Форматирование ячеек таблицы

На данный момент, колонка active не отображается. Это происходит потому, что значения для этих полей являются логическими и не выводятся как строки в JSX. Для решения этой проблемы, мы можем добавить новую функцию для форматирования записей на основе их значений. Добавьте следующий код в компонент Table и оберните entry в эту функцию в JSX:

const formatEntry = (entry: string | number | boolean) => {  if (typeof entry === 'boolean') {    return entry ? '✅' : '❌'  }  return entry}return (  <table>    <thead>...</thead>    <tbody>      {sortedRows.map((row, index) => (        <tr key={index}>          {Object.values(row).map((entry, columnIndex) => (            <td key={columnIndex}>{formatEntry(entry)}</td>          ))}        </tr>      ))}    </tbody>  </table>)

Функция formatEntry ожидает вход, который в нашем случае может быть либо строкой, либо числом, либо логическим значением, а затем возвращает отформатированное значение, если typeof entry является логическим значением. Это означает, что для значений true мы отобразим зеленую галочку, а для значений false мы отобразим красный крест. Используя похожий подход, мы также можем форматировать заголовки таблицы. Давайте сделаем их с заглавной буквы с помощью следующей функции:

export const capitalize = (  str: string) => str?.replace(/\b\w/g, substr => substr.toUpperCase())

Эта функция использует регулярное выражение для получения первой буквы каждого слова и преобразования ее в верхний регистр. Чтобы использовать эту функцию, мы можем создать файл utils.ts в корне папки src, экспортировать эту функцию, а затем импортировать ее в наш компонент Table для использования следующим образом:

import { capitalize } from '../../utils'export const Table = ({ rows }) => {  ...  return (      <table>        <thead>          <tr>            {Object.keys(rows[0]).map((entry, index) => (              <th key={index}>{capitalize(entry)}</th>            ))}          </tr>        </thead>        <tbody>...</tbody>      </table>  )}

Основываясь на этих изменениях, мы теперь имеем динамически созданную, отформатированную таблицу.

Отформатированная таблица в React

Набор типов props

Прежде чем мы приступим к стилизации таблицы, а затем добавлению элементов управления, давайте правильно указывать тип пропса rows. Для этого мы можем создать файл types.ts в корне папки src и экспортировать пользовательские типы, которые могут быть использованы повторно в проекте. Создайте файл и экспортируйте следующий тип:

export type Data = {    id: number    name: string    company: string    active: boolean    country: string}[]

Для ввода свойства rows в компоненте Table просто импортируйте этот тип и передайте его компоненту следующим образом:

import { Data } from '../../types'export type TableProps = {  rows: Data}export const Table = ({ rows }: TableProps) => { ... }

Оформление таблицы

Для оформления всего компонента таблицы нам потребуется всего несколько правил. Во-первых, мы хотим установить цвета и границы, для чего мы можем использовать следующие стили:

table {  width: 100%;  border-collapse: collapse;}thead {  text-align: left; /* `thead` is centered by default */  color: #939393;  background: #2f2f2f;}th,td {  padding: 4px 6px;  border: 1px solid #505050;}

Добавьте вышеприведенный код в table.css. Убедитесь, что установили border-collapse в collapse для <table>, чтобы избежать двойных границ. Поскольку таблица занимает весь экран, давайте также внесем некоторые изменения и удалим левую и правую границу, так как они все равно не видны:

th:first-child,td:first-child {  border-left: 0;}th:last-child,th:last-child {  border-right: 0;}

Это позволит избавиться от границ по краям элемента <table>, что приведет к более чистому внешнему виду. Наконец, давайте добавим эффект наведения на строки таблицы, чтобы визуально помочь пользователям при поиске в таблице:

tr:hover {  background: #2f2f2f;}

После всех проделанных действий, у нас теперь есть следующее поведение для компонента.

Эффект при наведении на таблицу

Добавление элементов управления

Теперь, когда мы стилизовали таблицу, давайте добавим элементы управления для функциональности сортировки и фильтрации. Мы создадим <input> для фильтра и элемент <select> для сортировки. Также добавим кнопку для переключения между порядком сортировки (возрастание/убывание).

Таблица с вариантами фильтрации

Для добавления полей ввода, нам также потребуются новые состояния для текущего порядка (возрастание или убывание) и переменная, чтобы отслеживать ключ сортировки (какой ключ в объекте используется для сортировки). Исходя из этого, расширьте компонент Table следующим образом:

const [order, setOrder] = useState('asc')const [sortKey, setSortKey] = useState(Object.keys(rows[0])[0])const filter = (event: React.ChangeEvent<HTMLInputElement>) => {}const sort = (value: keyof Data[0], order: string) => {}const updateOrder = () => {}return (  <>    <div className="controls">      <input        type="text"        placeholder="Filter items"        onChange={filter}      />      <select onChange={(event) => sort()}>        {Object.keys(rows[0]).map((entry, index) => (          <option value={entry} key={index}>            Order by {capitalize(entry)}          </option>        ))}      </select>      <button onClick={updateOrder}>Switch order ({order})</button>    </div>    <table>...</table>  </>)

Давайте двигаться по порядку, чтобы понять, что изменилось:

  • order. Сначала нам нужно создать новое состояние для порядка сортировки. Это может быть asc или desc. Мы будем использовать его значение в функции sort.
  • sortKey. Нам также нужно состояние для ключа сортировки. По умолчанию мы можем получить ключ первого свойства в нашем массиве объектов, используя Object.keys(rows[0])[0]. Мы будем использовать его, чтобы отслеживать сортировку при переключении между порядками.
  • filter. Нам понадобится функция для фильтрации результатов. Ее нужно передать в событие onChange элемента <input>. Обратите внимание, что React.ChangeEvent является обобщенным и может принимать тип HTML элемента, вызвавшего изменение.
  • sort. Как и функция filter, ее нужно присоединить к событию onChange, но на этот раз – к элементу <select>. Она будет принимать два параметра:
  • value. Она может принимать ключи нашего объекта данных. Мы можем указать тип с помощью ключевого слова keyof. Это означает, что value может быть одним из ключей id, name, company, active или country.
  • order. Порядок сортировки, либо asc, либо desc.
  • updateOrder. Наконец, нам также нужна функция для обновления порядка. Она будет вызываться при нажатии кнопки.
  • Обратите внимание, что мы используем ту же логику, что и для элементов <th>, для динамического генерирования вариантов в <select>. Мы также можем использовать утилитарную функцию capitalize для форматирования вариантов.

    Доступные варианты выбора

    Стилизация контролов

    Давайте стилизуем контролы, прежде чем продолжить. Это можно сделать всего лишь с помощью нескольких CSS-правил. Расширьте файл table.css следующим образом:

    .controls {  display: flex;}input,select {  flex: 1;  padding: 5px 10px;  border: 0;}button {  background: #2f2f2f;  color: #FFF;  border: 0;  cursor: pointer;  padding: 5px 10px;}

    Это обеспечит выравнивание вводимых значений рядом друг с другом. Используя flex: 1 для элементов <input> и <select>, мы можем сделать так, чтобы они занимали равное количество ширины из доступного пространства. Кнопка <button> будет занимать столько места, сколько необходимо для текста, который на ней находится.

    Фильтрация таблицы

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

    const rows = [  {    id: 0,    name: 'Jaime Wallace'  },  { ... }]// #1: Set `rows` to a filtered version using `filter`// The return value of `filter` will determine which rows to keepsetRows([ ...rows ].filter(row => { ... }))// From here on, we discuss the return value of `filter`// #2: Grab every field from the `row` object to use it for filteringObject.values(row) -> [0, 'Jaime Wallace']// #3: Join the values together into a single string[0, 'Jaime Wallace'].join('') -> '0Jaime Wallace'// #4: Convert the string into lowercase to make search case-insensitive'0Jaime Wallace'.toLowerCase() -> '0jaime wallace'// #5: Check if the string contains the value entered in the input'0jaime wallace'.includes(value) -> true / false

    С учетом всех этих моментов, мы можем создать значение return для функции filter на основе вышеуказанной логики. Это дает нам следующую реализацию для функции filter:

    const filter = (event: React.ChangeEvent<HTMLInputElement>) => {  const value = event.target.value  if (value) {    setRows([ ...rows.filter(row => {      return Object.values(row)        .join('')        .toLowerCase()        .includes(value)    }) ])  } else {    setRows(rows)  }}

    Обратите внимание, что мы также хотим проверить, присутствует ли значение value. Его отсутствие означает, что поле <input> пустое. В таких случаях мы хотим сбросить состояние и передать нефильтрованные rows в setRows, чтобы сбросить таблицу.

    Фильтрация таблицы

    Сортировка таблицы

    У нас есть функциональность фильтрации, но нам все еще не хватает сортировки. Для сортировки у нас есть две отдельные функции:

    • sort. Функция, которая будет обрабатывать сортировку.
    • updateOder. Функция, которая будет переключать порядок сортировки с возрастания на убывание и наоборот.

    Давайте начнем с функции сортировки. При каждом изменении элемента <select> будет вызываться функция sort. Мы хотим использовать значение элемента <select> для определения ключа, который будет использоваться для сортировки. Для этого мы можем использовать простой метод sort и квадратные скобки для динамического сравнения ключей объекта:

    const sort = (value: keyof Data[0], order: string) => {  const returnValue = order === 'desc' ? 1 : -1  setSortKey(value)  setRows([ ...sortedRows.sort((a, b) => {    return a[value] > b[value]      ? returnValue * -1      : returnValue  }) ])}

    Давайте пройдемся по функции сверху вниз для лучшего понимания реализации.

    • returnValue. Основываясь на состоянии order, мы хотим, чтобы возвращаемое значение было либо 1, либо -1. Это помогает нам определить порядок сортировки (1 для убывающего и -1 для возрастающего).
    • setSortKey. Значение, переданное в функцию, является значением элемента <select>. Мы хотим записать это значение в наше состояние (sortKey), что мы можем сделать, вызывая функцию обновления setSortKey.
    • setRows. Фактическая сортировка происходит в этом вызове. Используя квадратные скобки, мы можем сравнить a[value] с b[value] и вернуть либо -1, либо 1.

    Давайте возьмем следующий пример:

    const rows = [{ id: 0 }, { id: 1 }]const value = 'id'// This translate to a.id and b.idrows.sort((a, b) => a[value] > b[value] ? -1 : 1)// If `returnValue` is -1, the order changesrows.sort((a, b) => a[value] > b[value] ? 1 : -1)

    Переключение между порядками сортировки

    Для обновления порядка сортировки достаточно обновить состояние order при каждом щелчке по кнопке. Мы можем достичь этого с помощью следующей функциональности:

    const updateOrder = () => {  const updatedOrder = order === 'asc' ? 'desc' : 'asc'  setOrder(updatedOrder)  sort(sortKey as keyof Data[0], updatedOrder)}

    Это установит порядок в противоположное значение при каждом нажатии. Обратите внимание, что после обновления состояния order с помощью setOrder, нам также нужно вызвать функцию sort, чтобы сортировать таблицу на основе обновленного порядка. Чтобы вывести правильный тип для переменной sortKey, мы можем ссылаться на ключи типа Data, используя приведение типа: as keyof Data[0]. В качестве второго параметра мы также должны передать обновленный порядок.

    Упорядочение таблицы

    Обработка избыточной фильтрации

    Чтобы завершить этот проект, давайте добавим некоторую индикацию для состояния избыточной фильтрации. Мы хотим показать состояние избыточной фильтрации только при отсутствии результатов. Это легко сделать, проверив длину нашего состояния sortedRows. После элемента <table> добавьте следующее:

    return (  <>    <div className="controls">...</div>    <table>...</table>    {!sortedRows.length && (      <h1>No results... Try expanding the search</h1>    )}  </>)

    Состояние после фильтрации

    Вывод

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

    Как видно в начале этого руководства, весь проект доступен целиком на GitHub. Спасибо за чтение; приятного кодинга!

    Поделиться этой статьей


    Leave a Reply

    Your email address will not be published. Required fields are marked *