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:
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:
Calculate Viewport Metrics:
Render Initial Items:
Handle Scroll Events:
Render Visible Items:
Dynamic Data Loading:
Virtualization Optimization:
Item Recycling:
Item Measurements:
Accessibility:
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