Создание сортируемых таблиц с помощью 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.
Данная публикация является переводом статьи «Creating Sortable Tables With React» , подготовленная редакцией проекта.