Sobes.tech
Middle

Aufgabe 1 Wir haben ein Array von 10.000 Objekten (z.B. "Mitarbeiter" oder "Produkte"). Wir müssen eine Komponente implementieren, die ein Element auswählt, das den Browser nicht "aufhängt" und für den Benutzer bequem ist. Was implementiert werden muss (Anforderungen): 1. Datentypisierung: Erstellen Sie eine Schnittstelle Item mit den Feldern id: string, label: string und category: string. 2. Suche (Input): Beim Eingeben von Text soll die Liste gefiltert werden. 3. Arbeit mit DOM und Ereignissen (Tastaturnavigation): * Der Benutzer soll die Möglichkeit haben, sich mit den Pfeiltasten Hoch und Runter durch die Liste zu bewegen. 4. Beim Drücken von Enter wird das ausgewählte Element "bestätigt". 5. Wichtiger Punkt: Das durch die Pfeiltasten ausgewählte Element muss immer sichtbar sein (den Container programmatisch scrollen, wenn der Cursor die Grenzen verlässt). 6. Optimierung (Leistung): * Da viele Daten (10k) vorhanden sind, diskutieren oder implementieren Sie eine einfache "Lazy Rendering" (nur die ersten 20-30 Elemente rendern oder IntersectionObserver verwenden). Ausgabe des Komponenten

sobes.tech KI

Antwort von 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 для подгрузки элементов по мере прокрутки.