Создание сортируемых таблиц с помощью React

В этом руководстве при помощи React мы реализуем модуль для сортировки табличных данных. Мы подробно рассмотрим каждый шаг и изучим кучу полезных приемов.

Создание таблицы при помощи React

Сначала создадим образец компонента таблицы. Он принимает массив товаров и отображает простую таблицу со списком характеристик каждого товара.

function ProductTable(props) { const { products } = props; return( <table> <caption>Our products</caption> <thead> <tr>    <th>Name</th>          <th>Price</th>          <th>In Stock</th>        </tr>      </thead>      <tbody>        {products.map(product =>(          <tr key={product.id}>            <td>{product.name}</td>            <td>{product.price}</td>            <td>{product.stock}</td>          </tr>))}      </tbody>    </table>);}

После этого массив товаров добавляется в таблицу.

Сортировка данных

Сортировать данные в JavaScript довольно просто благодаря встроенной возможности sort(). Она сортирует массивы чисел и строк без дополнительного аргумента.

const array = ['mozzarella', 'gouda', 'cheddar'];array.sort();console.log(array); // ['cheddar', 'gouda', 'mozzarella']

Если нужна более сложная сортировка, используйте возможность sorting. Она получает два элемента списка в виде аргументов и размещает их один перед иным.

Начнем с сортировки данных по имени в алфавитном порядке.

function ProductTable(props) {  const { products } = props;  let sortedProducts = [...products];  sortedProducts.sort((a, b) => {    if(a.name < b.name) {      return -1;    }    if(a.name > b.name) {      return 1;    }    return 0;  });  return(    <Table>      {/* как и раньше */}    </Table>);}

Сначала мы создаем копию товара, которую можем изменять по усмотрению при помощи Array.prototype.sort. Данный способ изменяет исходный массив, а не возвращает новую отсортированную копию.

Далее мы вызываем sortedProducts.sort и передаем ей возможность sorting. Далее проверяем, располагается ли свойство name первого аргумента a раньше свойства name второго аргумента b. Если да, то возвращаем отрицательное значение.

Это указывает на то, что a должно предшествовать в списке b. Если name первого аргумента находится после name второго, возвращаем положительное число. Оно указывает на то, что необходимо поместить b перед a. Если они равны(оба имеют одинаковое name) , возвращаем 0.

Делаем таблицу сортируемой

Чтобы настроить поле ввода, по которому осуществляется сортировка, необходимо запомнить отсортированное поле ввода при помощи хука useState.

Хук может «зацепить» некоторые основные возможности React. Этот хук может поддерживать часть внутреннего состояния в компоненте и изменять его.

const [sortedField, setSortedField] = React.useState(null);

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

const ProductsTable =(props) => {  const { products } = props;  const [sortedField, setSortedField] = React.useState(null);  return(    <table>      <thead>        <tr>          <th>            <button type="button" onClick={() => setSortedField('name')}>              Name            </button>          </th>          <th>            <button type="button" onClick={() => setSortedField('price')}>              Price            </button>          </th>          <th>            <button type="button" onClick={() => setSortedField('stock')}>              In Stock            </button>          </th>        </tr>      </thead>      {/* As before */}    </table>);};

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

Сейчас реализуем сортировку. Помните алгоритм сортировки, который мы использовали ранее? Вот его измененная версия для работы с любым из полей ввода.

const ProductsTable =(props) => {  const { products } = props;  const [sortedField, setSortedField] = React.useState(null);  let sortedProducts = [...products];  if (sortedField !== null) {    sortedProducts.sort((a, b) => {      if (a[sortedField] < b[sortedField]) {        return -1;      }      if (a[sortedField] > b[sortedField]) {        return 1;      }      return 0;    });  }  return (    <table>

Сначала мы проверяем, что выбрали поле ввода для сортировки. После чего сортируем данные по данному столбцу.

По возрастанию и по убыванию

Сейчас реализуем метод переключения между порядком сортировки по возрастанию и убыванию при помощи клика по заголовку таблицы.

Для этого необходимо добавить второй элемент состояния — порядок сортировки. Проведем рефакторинг текущей переменной состояния sortedField, чтобы сохранить имя поля ввода и направление сортировки. Вместо строки эта переменная состояния будет содержать объект с ключом (именем поля ввода) и направлением. Мы переименуем ее в sortConfig.

Ниже приведена новая функцию сортировки.

 sortedProducts.sort((a, b) => {  if (a[sortConfig.key] < b[sortConfig.key]) {    return sortConfig.direction === 'ascending' ? -1 : 1;  }  if (a[sortConfig.key] > b[sortConfig.key]) {    return sortConfig.direction === 'ascending' ? 1 : -1;  }  return 0;});

Сейчас, если выбрано направление «по возрастанию», делать то же самое, что и раньше. Если нет, мы сделаем обратное, что даст нам порядок «по убыванию».

Далее создадим возможность requestSort, которая принимает имя поля ввода и обновляет состояние.

const requestSort = key => {  let direction = 'ascending';  if (sortConfig.key === key && sortConfig.direction === 'ascending') {    direction = 'descending';  }  setSortConfig({ key, direction });}

Также необходимо настроить обработчики кликов.

return (  <table>    <thead>      <tr>        <th>          <button type="button" onClick={() => requestSort('name')}>            Name          </button>        </th>        <th>          <button type="button" onClick={() => requestSort('price')}>            Price          </button>        </th>        <th>          <button type="button" onClick={() => requestSort('stock')}>            In Stock          </button>        </th>      </tr>    </thead>  {/* as before */}  </table>);

На данный момент мы сортируем все данные при каждом рендеринге, что приведет к проблемам с производительностью. Вместо этого воспользуемся встроенным хуком useMemo .

const ProductsTable = (props) => {  const { products } = props;  const [sortConfig, setSortConfig] = React.useState(null);    React.useMemo(() => {    let sortedProducts = [...products];    if (sortedField !== null) {      sortedProducts.sort((a, b) => {        if (a[sortConfig.key] < b[sortConfig.key]) {          return sortConfig.direction === 'ascending' ? -1 : 1;        }        if (a[sortConfig.key] > b[sortConfig.key]) {          return sortConfig.direction === 'ascending' ? 1 : -1;        }        return 0;      });    }    return sortedProducts;  }, [products, sortConfig]);

useMemo – это метод кеширования затратные вычисления. Его использование может не сортировать товары дважды, если мы повторно предоставим компонент.

Создаем повторно используемый компонент

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

const useSortableData = (items, config = null) => {  const [sortConfig, setSortConfig] = React.useState(config);    const sortedItems = React.useMemo(() => {    let sortableItems = [...items];    if (sortConfig !== null) {      sortableItems.sort((a, b) => {        if (a[sortConfig.key] < b[sortConfig.key]) {          return sortConfig.direction === 'ascending' ? -1 : 1;        }        if (a[sortConfig.key] > b[sortConfig.key]) {          return sortConfig.direction === 'ascending' ? 1 : -1;        }        return 0;      });    }    return sortableItems;  }, [items, sortConfig]);  const requestSort = key => {    let direction = 'ascending';    if (sortConfig && sortConfig.key === key && sortConfig.direction === 'ascending') {      direction = 'descending';    }    setSortConfig({ key, direction });  }  return { items: sortedItems, requestSort };}

Для этого мы вставим код из предыдущего фрагмента и переименуем некоторые элементы. Функцию useSortableData принимает элементы и необязательное начальное состояние сортировки. Она возвращает объект с отсортированными элементами и возможность для повторной сортировки элементов.

Программный код таблицы сейчас выглядит следующим образом:

const ProductsTable = (props) => {  const { products } = props;  const { items, requestSort } = useSortableData(products);  return (    <table>{/* ... */}</table>  );};

Последний штрих

Чтобы указывать, как сортируется таблица, необходимо вернуть внутреннее состояние sortConfig и использовать его для генерации стилей заголовков таблицы.

const ProductTable = (props) => {  const { items, requestSort, sortConfig } = useSortableData(props.products);  const getClassNamesFor = (name) => {    if (!sortConfig) {      return;    }    return sortConfig.key === name ? sortConfig.direction : undefined;  };  return (    <table>      <caption>Products</caption>      <thead>        <tr>          <th>            <button type="button"              onClick={() => requestSort('name')}              className={getClassNamesFor('name')}            >              Name            </button>          </th>         {/* … */}        </tr>      </thead>      {/* … */}    </table>  );};

Мы закончили.

Заключение

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

Вы можете без труда увидеть демонстрацию таблицы на CodeSandbox .

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *