Вопрос проверяет понимание подходов к переиспользованию компонентов таблицы с общей бизнес-логикой в React-приложениях.
Когда один и тот же компонент таблицы используется в разных частях приложения, часто возникает соблазн скопировать логику пагинации, сортировки и фильтрации в каждый родительский компонент. Это приводит к дублированию кода, усложняет поддержку и увеличивает риск ошибок.
Лучший подход — вынести общую бизнес-логику в отдельный кастомный хук, например useTable. Этот хук управляет состоянием страницы, сортировки, фильтрации и предоставляет методы для их изменения. Сам компонент таблицы становится презентационным — он принимает данные и колбэки через пропсы и не содержит логики.
// useTable.js
import { useState, useMemo } from 'react';
export function useTable(data, config) {
const [page, setPage] = useState(1);
const [sortBy, setSortBy] = useState(null);
const [filter, setFilter] = useState('');
const processedData = useMemo(() => {
let result = [...data];
if (filter) {
result = result.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}
if (sortBy) {
result.sort((a, b) => (a[sortBy] > b[sortBy] ? 1 : -1));
}
const pageSize = config.pageSize || 10;
const start = (page - 1) * pageSize;
return result.slice(start, start + pageSize);
}, [data, page, sortBy, filter, config]);
return { processedData, page, setPage, sortBy, setSortBy, filter, setFilter };
}// Table.jsx
function Table({ data, onSort, onFilter, columns }) {
return (
<table>
<thead>
<tr>
{columns.map(col => (
<th key={col.key} onClick={() => onSort(col.key)}>
{col.label}
</th>
))}
</tr>
</thead>
<tbody>
{data.map(row => (
<tr key={row.id}>
{columns.map(col => (
<td key={col.key}>{row[col.key]}</td>
))}
</tr>
))}
</tbody>
</table>
);
}Теперь в любом компоненте, где нужна таблица, вы просто используете хук и передаёте результат в компонент:
// UsersPage.jsx
function UsersPage() {
const { processedData, page, setPage, sortBy, setSortBy, filter, setFilter } =
useTable(usersData, { pageSize: 20 });
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<Table
data={processedData}
onSort={setSortBy}
columns={[
{ key: 'name', label: 'Name' },
{ key: 'email', label: 'Email' },
]}
/>
<button onClick={() => setPage(page - 1)} disabled={page === 1}>Prev</button>
<span>Page {page}</span>
<button onClick={() => setPage(page + 1)}>Next</button>
</div>
);
}Аналогично для страницы заказов — просто вызываете useTable(ordersData, { pageSize: 10 }) и передаёте другие колонки.
Использование кастомного хука для бизнес-логики таблицы и презентационного компонента для отображения позволяет полностью избежать дублирования кода. Этот подход легко масштабируется, упрощает тестирование и делает приложение более поддерживаемым. Рекомендуется применять для любых повторяющихся UI-паттернов с общей логикой.
Frontend developer
Ментор по Frontend
Полное сопровождение до оффера — без дорогих курсов, с оплатой после трудоустройства
Записаться на консультацию