Sobes.tech
Middle

Vəzifə 1 Bizdə 10 000 obyektli massiv var (məsələn, "İşçilər" və ya "Məhsullar"). Brauzeri "dondurmayacaq" və istifadəçi üçün rahat olacaq element seçimi komponentini həyata keçirməliyik. Nə həyata keçirilməlidir (Tələblər): 1. Məlumatların tiplenməsi: id: string, label: string və category: string sahələrinə malik Item interfeysini yaradın. 2. Axtarış (Input): Mətn daxil edilərkən siyahı filtrlənməlidir. 3. DOM və hadisələrlə işləmək (Klaviatura Nəzarəti): * İstifadəçi siyahıda Yuxarı və Aşağı oxlar ilə hərəkət edə bilməlidir. 4. Enter düyməsini basdıqda, seçilmiş element "təsdiqlənir". 5. Vacib nöqtə: Oxlar ilə seçilmiş element hər zaman görünməlidir (konteyneri proqramlı şəkildə sürüşdürün, əgər kursor sərhədlərdən çıxarsa). 6. Optimizasiya (Performans): * Məlumat çoxdur (10k), sadə "tələskən render" (yalnız ilk 20-30 elementi render et və ya IntersectionObserver istifadə et) müzakirə edin və ya həyata keçirin. Komponentin çıxışı

sobes.tech Süni İntellekt

AI-dan cavab

Для реализации компонента выбора из большого массива (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 для подгрузки элементов по мере прокрутки.