← Назад к вопросам
Как реализуешь таблицы в проекте?
2.3 Middle🔥 131 комментариев
#Soft Skills и рабочие процессы
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Реализация таблиц на фронтенде
1. Семантический HTML для базовых таблиц
Начинаю с правильной HTML-структуры:
<table>
<thead>
<tr>
<th scope="col">Имя</th>
<th scope="col">Email</th>
<th scope="col">Статус</th>
</tr>
</thead>
<tbody>
<tr>
<td>Иван</td>
<td>ivan@example.com</td>
<td><span class="badge badge-success">Active</span></td>
</tr>
</tbody>
</table>
2. React компонент с типизацией
Структурирую компонент таблицы с правильной архитектурой:
interface TableColumn<T> {
key: keyof T;
label: string;
render?: (value: T[keyof T], row: T) => ReactNode;
width?: string;
sortable?: boolean;
}
interface TableProps<T> {
data: T[];
columns: TableColumn<T>[];
isLoading?: boolean;
onRowClick?: (row: T) => void;
}
export function Table<T extends { id: string | number }>({
data,
columns,
isLoading,
onRowClick
}: TableProps<T>) {
return (
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead className="bg-surface-secondary">
<tr>
{columns.map((col) => (
<th
key={String(col.key)}
className="text-left px-4 py-3 font-semibold"
style={{ width: col.width }}
>
{col.label}
</th>
))}
</tr>
</thead>
<tbody>
{isLoading ? (
<tr><td colSpan={columns.length} className="text-center py-8">Загрузка...</td></tr>
) : data.length === 0 ? (
<tr><td colSpan={columns.length} className="text-center py-8">Нет данных</td></tr>
) : (
data.map((row) => (
<tr
key={row.id}
className="border-b hover:bg-surface-hover cursor-pointer"
onClick={() => onRowClick?.(row)}
>
{columns.map((col) => (
<td key={String(col.key)} className="px-4 py-3">
{col.render ? col.render(row[col.key], row) : String(row[col.key])}
</td>
))}
</tr>
))
)}
</tbody>
</table>
</div>
);
}
3. Пагинация и сортировка
Добавляю управление данными через хук:
interface UseTableOptions {
pageSize: number;
}
export function useTable<T>(data: T[], options: UseTableOptions) {
const [page, setPage] = useState(0);
const [sortBy, setSortBy] = useState<keyof T | null>(null);
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc');
const sortedData = useMemo(() => {
if (!sortBy) return data;
return [...data].sort((a, b) => {
const aVal = a[sortBy];
const bVal = b[sortBy];
if (aVal < bVal) return sortOrder === 'asc' ? -1 : 1;
if (aVal > bVal) return sortOrder === 'asc' ? 1 : -1;
return 0;
});
}, [data, sortBy, sortOrder]);
const paginatedData = useMemo(() => {
const start = page * options.pageSize;
return sortedData.slice(start, start + options.pageSize);
}, [sortedData, page, options.pageSize]);
const totalPages = Math.ceil(sortedData.length / options.pageSize);
return {
data: paginatedData,
page,
setPage,
totalPages,
sortBy,
setSortBy,
sortOrder,
setSortOrder,
total: sortedData.length
};
}
4. Использование в компоненте
interface User {
id: string;
name: string;
email: string;
status: 'active' | 'inactive';
joinedDate: string;
}
export function UsersTable() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const table = useTable(users, { pageSize: 10 });
const columns: TableColumn<User>[] = [
{
key: 'name',
label: 'Имя',
sortable: true,
render: (name) => <span className="font-medium">{name}</span>
},
{
key: 'email',
label: 'Email',
sortable: true
},
{
key: 'status',
label: 'Статус',
render: (status) => (
<span className={status === 'active' ? 'bg-green-100' : 'bg-gray-100'}>
{status}
</span>
)
}
];
return (
<div className="space-y-4">
<Table
data={table.data}
columns={columns}
isLoading={loading}
/>
</div>
);
}
5. Фиксированные заголовки (sticky header)
Для больших таблиц делаю заголовок липким:
<div className="relative h-96 overflow-y-auto">
<table className="w-full">
<thead className="sticky top-0 bg-surface-secondary z-10">
{/* thead содержимое */}
</thead>
<tbody>
{/* tbody содержимое */}
</tbody>
</table>
</div>
6. Фильтрация и поиск
export function TableWithFilters() {
const [searchText, setSearchText] = useState('');
const filteredData = useMemo(() => {
return data.filter(row =>
!searchText || row.name.toLowerCase().includes(searchText.toLowerCase())
);
}, [data, searchText]);
return (
<div className="space-y-4">
<input
type="text"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder="Поиск..."
/>
<Table data={filteredData} columns={columns} />
</div>
);
}
7. Responsive таблица для мобилки
Для мобильных устройств преобразую таблицу в карточки:
export function ResponsiveTable() {
const isMobile = window.innerWidth < 768;
return isMobile ? (
<div className="space-y-4">
{data.map(row => (
<div key={row.id} className="border p-4 rounded">
{columns.map(col => (
<div key={String(col.key)} className="flex justify-between mb-2">
<span className="font-semibold">{col.label}</span>
<span>{row[col.key]}</span>
</div>
))}
</div>
))}
</div>
) : (
<Table data={data} columns={columns} />
);
}
8. Best Practices для таблиц
- Используй семантический HTML (table, thead, tbody)
- Типизируй данные через TypeScript generics
- Реализуй пагинацию для больших наборов
- Добавь сортировку и фильтрацию
- Обеспечь доступность (a11y)
- Сделай таблицу responsive для мобилки
- Используй виртуализацию для 1000+ строк
Таблицы — один из важных элементов UI. Правильная реализация требует внимания к доступности, производительности и UX.