Middle
Tema 1 Avem un array de 10.000 de obiecte (de exemplu, "Angajați" sau "Produse"). Trebuie să implementăm un component pentru selectarea unui element care nu va "bloca" browserul și va fi convenabil pentru utilizator. Ce trebuie implementat (Cerinte): 1. Tipizarea datelor: Creați o interfață Item cu câmpurile id: string, label: string și category: string. 2. Căutare (Input): La introducerea textului, lista trebuie filtrată. 3. Lucru cu DOM și evenimente (Navigare cu tastatura): * Utilizatorul trebuie să poată naviga în listă cu săgețile Sus și Jos. 4. La apăsarea Enter, elementul selectat este "confirmat". 5. Punct important: Elementul selectat cu săgețile trebuie să fie întotdeauna vizibil (derulați programatic containerul dacă cursorul iese din limite). 6. Optimizare (Performanță): * Deoarece există multe date (10k), discutați sau implementați o "randare leneșă" simplă (doar primele 20-30 de elemente sau utilizați IntersectionObserver). Ieșirea componentului
sobes.tech AI
Răspuns de la AI
Для реализации компонента выбора из большого массива (10 000 элементов) с поиском и навигацией клавиатурой можно сделать так:
interface Item {
id: string;
label: string;
category: string;
}
const ITEMS_PER_PAGE = 30;
function ItemSelector({ items }: { items: Item[] }) {
const [query, setQuery] = React.useState('');
const [filteredItems, setFilteredItems] = React.useState<Item[]>(items);
const [highlightedIndex, setHighlightedIndex] = React.useState(0);
const containerRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
const filtered = items.filter(item =>
item.label.toLowerCase().includes(query.toLowerCase())
);
setFilteredItems(filtered);
setHighlightedIndex(0);
}, [query, items]);
// Обработка клавиш
function onKeyDown(e: React.KeyboardEvent) {
if (e.key === 'ArrowDown') {
e.preventDefault();
setHighlightedIndex(i => Math.min(i + 1, filteredItems.length - 1));
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setHighlightedIndex(i => Math.max(i - 1, 0));
} else if (e.key === 'Enter') {
e.preventDefault();
const selected = filteredItems[highlightedIndex];
if (selected) alert(`Выбран: ${selected.label}`);
}
}
// Скролл к выделенному элементу
React.useEffect(() => {
const container = containerRef.current;
if (!container) return;
const itemElements = container.querySelectorAll('.item');
const current = itemElements[highlightedIndex] as HTMLElement | undefined;
if (current) {
const containerTop = container.scrollTop;
const containerBottom = containerTop + container.clientHeight;
const elemTop = current.offsetTop;
const elemBottom = elemTop + current.offsetHeight;
if (elemTop < containerTop) {
container.scrollTop = elemTop;
} else if (elemBottom > containerBottom) {
container.scrollTop = elemBottom - container.clientHeight;
}
}
}, [highlightedIndex]);
// Ленивый рендеринг первых ITEMS_PER_PAGE элементов
const visibleItems = filteredItems.slice(0, ITEMS_PER_PAGE);
return (
<div>
<input
type="text"
value={query}
onChange={e => setQuery(e.target.value)}
onKeyDown={onKeyDown}
placeholder="Поиск..."
/>
<div
ref={containerRef}
style={{ maxHeight: 300, overflowY: 'auto', border: '1px solid #ccc' }}
tabIndex={0}
onKeyDown={onKeyDown}
>
{visibleItems.map((item, index) => (
<div
key={item.id}
className="item"
style={{
padding: '4px 8px',
backgroundColor: index === highlightedIndex ? '#bde4ff' : 'transparent',
}}
>
{item.label} ({item.category})
</div>
))}
{filteredItems.length > ITEMS_PER_PAGE && (
<div style={{ padding: 8, fontStyle: 'italic' }}>
Показано {ITEMS_PER_PAGE} из {filteredItems.length} элементов
</div>
)}
</div>
</div>
);
}
Ключевые моменты:
- Типизация через интерфейс
Item. - Фильтрация по вводу пользователя.
- Навигация стрелками и подтверждение Enter.
- Скролл к выделенному элементу, чтобы он всегда был виден.
- Ленивый рендеринг — показываем только первые 30 элементов, чтобы не перегружать DOM.
Для более продвинутой оптимизации можно использовать виртуализацию (например, react-window) или IntersectionObserver для подгрузки элементов по мере прокрутки.