6. Lazy Loading
Lazy Loading — это паттерн отложенной загрузки ресурсов до момента, когда они действительно нужны.
Нативный Lazy Loading (HTML)
Заголовок раздела «Нативный Lazy Loading (HTML)»<!-- Изображения --><img src="photo.jpg" loading="lazy" alt="Photo"><img src="hero.jpg" loading="eager" alt="Hero (выше fold — eager!)">
<!-- iframes --><iframe src="video-embed.html" loading="lazy"></iframe><iframe src="map.html" loading="lazy"></iframe>Браузер сам решает когда загружать — обычно 2000-3000px до viewport.
Intersection Observer API
Заголовок раздела «Intersection Observer API»Для контроля над lazy loading используйте IntersectionObserver:
// Базовый lazy loader для изображенийconst lazyImages = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.removeAttribute('data-src'); observer.unobserve(img); // больше не следим } });}, { rootMargin: '200px', // начинаем загрузку за 200px до viewport threshold: 0.01,});
lazyImages.forEach(img => observer.observe(img));<!-- HTML с data-src --><img data-src="photo.jpg" src="placeholder.jpg" alt="Photo" width="800" height="600">Lazy Loading компонентов (React)
Заголовок раздела «Lazy Loading компонентов (React)»import { lazy, Suspense, useState } from 'react';
// Загружается только при первом рендереconst HeavyModal = lazy(() => import('./HeavyModal'));const DataTable = lazy(() => import('./DataTable'));const RichEditor = lazy(() => import('./RichEditor'));
function App() { const [showModal, setShowModal] = useState(false); const [showEditor, setShowEditor] = useState(false);
return ( <> <button onClick={() => setShowModal(true)}>Открыть Modal</button>
{showModal && ( <Suspense fallback={<div>Загрузка...</div>}> <HeavyModal onClose={() => setShowModal(false)} /> </Suspense> )}
<button onClick={() => setShowEditor(true)}>Открыть Editor</button>
<Suspense fallback={<EditorSkeleton />}> {showEditor && <RichEditor />} </Suspense> </> );}Intersection Observer для компонентов
Заголовок раздела «Intersection Observer для компонентов»import { useRef, useState, useEffect } from 'react';
// Хук для lazy renderingfunction useLazyRender(options = {}) { const [isVisible, setIsVisible] = useState(false); const ref = useRef();
useEffect(() => { const observer = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) { setIsVisible(true); observer.disconnect(); } }, { rootMargin: options.rootMargin || '300px', threshold: options.threshold || 0, });
if (ref.current) observer.observe(ref.current); return () => observer.disconnect(); }, []);
return { ref, isVisible };}
// Использованиеfunction LazySection({ children, fallback }) { const { ref, isVisible } = useLazyRender();
return ( <div ref={ref}> {isVisible ? children : (fallback || <div style={{ height: 400 }} />)} </div> );}
// В компонентеfunction Page() { return ( <> <HeroSection />
<LazySection fallback={<Skeleton />}> <HeavyChartSection /> </LazySection>
<LazySection> <CommentsSection /> </LazySection> </> );}Lazy Loading данных
Заголовок раздела «Lazy Loading данных»// Pagination — загружаем по страницеasync function loadPage(page, pageSize = 20) { const response = await fetch(`/api/items?page=${page}&limit=${pageSize}`); return response.json();}
// Infinite Scroll с IntersectionObserverfunction InfiniteList() { const [items, setItems] = useState([]); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const loaderRef = useRef();
useEffect(() => { const observer = new IntersectionObserver(async ([entry]) => { if (entry.isIntersecting && hasMore) { const newItems = await loadPage(page); if (newItems.length === 0) { setHasMore(false); } else { setItems(prev => [...prev, ...newItems]); setPage(p => p + 1); } } });
if (loaderRef.current) observer.observe(loaderRef.current); return () => observer.disconnect(); }, [page, hasMore]);
return ( <> <ul>{items.map(item => <Item key={item.id} {...item} />)}</ul> <div ref={loaderRef}> {hasMore ? <Spinner /> : <p>Всё загружено!</p>} </div> </> );}Виртуализация списков
Заголовок раздела «Виртуализация списков»Для очень больших списков — рендерим только видимые элементы:
// react-windowimport { FixedSizeList } from 'react-window';
function Row({ index, style }) { return ( <div style={style}> Строка {index} </div> );}
function VirtualList({ items }) { return ( <FixedSizeList height={600} width={800} itemCount={items.length} itemSize={50} // высота строки > {({ index, style }) => ( <div style={style}>{items[index].title}</div> )} </FixedSizeList> );}