frontend

Jak ogarnąłem wirtualizację listy dzięki napisaniu własnego custom hooka?

Will4_U
#react#react hooks#frontend

Siema, wróciłem do pisania blogów na własnej stronie, bo czemu kurwa nie

Nie wiem, czy wiecie, ale od stycznia tego roku mam swoje repozytorium z własnymi custom hookami o nazwie react-useful-hooks.

(jeśli nie byliście świadomi, to teraz już wiecie 😁)

I niby nie jest to coś mega wybitnego, bo już w internetach istnieją taki stronki jak usehooks.com czy nikgraf.github.io/react-hooks/, ale ja chciałem mieć swoje własne, które będę mógł rozwijać i które będą mi służyć jako swojego rodzaju notatki.

Dobra, bez pierdolenia - przejdźmy do konkretów.

Ostatnio miałem rozmowę rekrutacyjną, na której było poruszone pytanie dot. wirtualizacji list (list virtualization), czyli jak zrobić tak, żeby renderować tylko te elementy listy, które są widoczne na ekranie.

Generalnie chodziło o to, że było pokazana apka. gdzie renderowało się 100 elementów, które miały jakąś tam animację, więc to powodowało, że apka lagowała nie miłosiernie.

No i padło pytanie, w jaki sposób bym zaimplementował wirtualizację bez użycia bibliotek.

I szczerze mówiąc (albo pisząc XDD) to nie miałem kurwa pojęcia, jak w ogóle to się robi. Nigdy takich rzeczy nie robiłem własnoręcznie, bo albo nie miałem takiego case’u w projektcie, a jak już był to korzystał z gotowca aka libki np. react-window czy react-virtualized.

No i wtedy sobie pomyślałem, że to jest właśnie dobry moment, żeby zrobić własny custom hook, który będzie wirtualizował listę.

Jakoś tak uznałem, że zajmę się tym od razu po rozmowie rekrutacyjnej, bo mnie to męczyło i chciałem to zrobić.

Chciałem zobaczyć, jak w ogóle się to robi, bo nigdy nie miałem z tym do czynienia.

Na rekrutacji gadałem, że może jakimś Intersection Observerem, ale w praktyce okazało się, że nie tędy droga i trzeba to zrobić inaczej.

Tak więc na początek poszedł ChatGPT, który MOOOOOŻE podpowie mi jakieś rozwiązanie.

Więc poszło takie zapytanie:

I want to implement the virtualized list pattern without using the third party libraries like react-virtualized or react-virtual.

How I can achieve that?

Po chwili bot wypluł mi takie coś:

Implementing a virtualized list pattern from scratch can be a bit challenging, but it’s definitely doable. The main idea behind a virtualized list is to render only the items that are currently visible in the viewport, rather than rendering the entire list at once. This improves performance and reduces memory usage, especially for large lists.

Here’s a high-level overview of how you can achieve this without using third-party libraries:

  1. Calculate Viewport Metrics:

    • Determine the height of the viewport (the visible area where the list will be displayed).
    • Calculate the number of items that can be displayed in the viewport based on the average item height.
  2. Render Initial Items:

    • Render a subset of your data that corresponds to the number of items that can fit in the viewport. These items will be your initial visible items.
  3. Handle Scroll Events:

    • Add a scroll event listener to the container element that holds your list.
    • When the user scrolls, calculate the current scroll position.
    • Based on the scroll position, calculate the index of the first visible item.
  4. Render Visible Items:

    • Based on the index of the first visible item, render a subset of your data that corresponds to the items that should be visible in the viewport.
    • Update the list of rendered items whenever the user scrolls.
  5. Dynamic Data Loading:

    • Load additional data as the user scrolls, so you can keep rendering new items.
    • This can be done by calculating the index of the last visible item and loading data as needed.
  6. Virtualization Optimization:

    • To optimize rendering, use techniques like “windowing” where you render a buffer of items before and after the visible range to provide smoother scrolling.
  7. Item Recycling:

    • Instead of creating new DOM elements for every item, recycle and reuse DOM elements as the user scrolls.
  8. Item Measurements:

    • Since each item might have a different height, measure the height of each item and store the measurements to accurately calculate scroll positions and visible items.
  9. Accessibility:

    • Ensure that keyboard navigation and screen reader support are maintained.

Here’s a simple example using React to give you an idea of how you might approach this:

import React, { useState, useRef, useEffect } from 'react';

const VirtualizedList = ({ data, itemHeight }) => {
  const [startIndex, setStartIndex] = useState(0);
  const containerRef = useRef(null);
  
  const itemsInView = Math.ceil(containerRef.current.clientHeight / itemHeight);
  const endIndex = startIndex + itemsInView;
  const visibleData = data.slice(startIndex, endIndex);

  const handleScroll = () => {
    const scrollOffset = containerRef.current.scrollTop;
    const newStartIndex = Math.floor(scrollOffset / itemHeight);
    setStartIndex(newStartIndex);
  };

  useEffect(() => {
    containerRef.current.addEventListener('scroll', handleScroll);
    return () => {
      containerRef.current.removeEventListener('scroll', handleScroll);
    };
  }, []);

  return (
    <div ref={containerRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: data.length * itemHeight, position: 'relative' }}>
        {visibleData.map((item, index) => (
          <div key={index} style={{ height: itemHeight }}>
            {item}
          </div>
        ))}
      </div>
    </div>
  );
};

export default VirtualizedList;

No kurwa, ChatGPT ładnie opisał w podpunktach, jak mam to zaimplementować, ale nawet samo już coś ogarnęło! Zobaczymy czy to w ogóle działa… 🤔

I… chuj, nie działa.

Znaczy… działa, ale nie do końca tak jakbym chciał.

Mianowicie gdy scrolluje to elementy nie przesuwają się wraz ze scrollem, a to nie jest to co chcę.

Dobra, pytam się pana bota, że coś jest nie tak, daje mi niby naprawiony kod, ale to też nie działa tak jak chciałem.

No cóż, trzeba ogarnąć inny plan - spojrzę na to, jak te bibliotek to robią.

Popatrzyłem na kod źródłowy i niestety, nie jest to takie proste do zrozumienia. Wygląda to mega skomplikowanie oraz zajęło by mi to sporo czasu, aby to ogarnąć.

Tak więc uznałem, że postaram się odtworzyć API tych bibliotek na tyle, ile mogę.

W tym przypadku dużo pomogła mi instalacja biblioteki, która próbuję odtworzyć (w tym przypadku TanStack Virtual).

Pomogła mi też część logiki wygenerowanej przez GitHub Copilota, który starał się odtwarzać API tej biblioteki i na bieżąco poprawiałem, aby nakierowywać AI na to, co chcę zrobić.

Później trafiłem na to, że muszę ręcznie rerenderować listę, gdy zmieni się jej wysokość, bo inaczej nie będzie działać.

Potem trafiłem na to, że muszę dać throttle na scroll, bo inaczej będzie za często rerenderować przez co będzie lagować.

No i w końcu, po zaledwie 2h udało mi się stworzyć tego hooka o nazwie useVirtual 😎

Było to ciekawe doświadczenie, bo zamiast się poddawać i korzystać z gotowca, to postanowiłem zrobić to samemu.

Dzięki tej sytuacji z rekrutacji postanowiłem zrobić coś, czego wcześniej nie robiłem i dzięki temu nauczyłem się czegoś nowego.

No i to tyle xd

← wracaj na bloga