Comment implémenter le défilement infini avec Claude Code
Découvrez comment implémenter le défilement infini avec Claude Code. Conseils pratiques et exemples de code inclus.
無限スクロールの仕組み
無限スクロールは、ユーザーがページ末尾に近づくと自動的に次のデータを読み込むUIパターンです。SNSやニュースアプリでおなじみですが、パフォーマンスやアクセシビリティを考慮した実装は意外と難しいものです。Claude Codeなら、ベストプラクティスに沿った実装を素早く構築できます。
Intersection Observerを使った基本実装
> Intersection Observerを使った無限スクロールのReactフックを作って。
> ローディング状態とエラーハンドリングも含めて。
import { useEffect, useRef, useState, useCallback } from 'react';
interface UseInfiniteScrollOptions<T> {
fetchFn: (page: number) => Promise<{ data: T[]; hasMore: boolean }>;
initialPage?: number;
threshold?: number;
}
function useInfiniteScroll<T>({ fetchFn, initialPage = 1, threshold = 0.8 }: UseInfiniteScrollOptions<T>) {
const [items, setItems] = useState<T[]>([]);
const [page, setPage] = useState(initialPage);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const [error, setError] = useState<Error | null>(null);
const observerRef = useRef<IntersectionObserver | null>(null);
const lastElementRef = useCallback(
(node: HTMLElement | null) => {
if (loading) return;
if (observerRef.current) observerRef.current.disconnect();
observerRef.current = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && hasMore) {
setPage((prev) => prev + 1);
}
},
{ threshold }
);
if (node) observerRef.current.observe(node);
},
[loading, hasMore, threshold]
);
useEffect(() => {
let cancelled = false;
setLoading(true);
setError(null);
fetchFn(page)
.then((result) => {
if (!cancelled) {
setItems((prev) => [...prev, ...result.data]);
setHasMore(result.hasMore);
}
})
.catch((err) => { if (!cancelled) setError(err); })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, [page, fetchFn]);
return { items, loading, hasMore, error, lastElementRef };
}
コンポーネントでの使用例
function ArticleList() {
const fetchArticles = useCallback(async (page: number) => {
const res = await fetch(`/api/articles?page=${page}&limit=20`);
const data = await res.json();
return { data: data.articles, hasMore: data.hasMore };
}, []);
const { items, loading, hasMore, error, lastElementRef } = useInfiniteScroll({
fetchFn: fetchArticles,
});
return (
<div role="feed" aria-busy={loading} aria-label="記事一覧">
{items.map((article, index) => (
<article
key={article.id}
ref={index === items.length - 1 ? lastElementRef : null}
aria-posinset={index + 1}
aria-setsize={hasMore ? -1 : items.length}
>
<h2>{article.title}</h2>
<p>{article.summary}</p>
</article>
))}
{loading && <div aria-live="polite">読み込み中...</div>}
{error && <div role="alert">エラーが発生しました。再試行してください。</div>}
{!hasMore && <p>すべての記事を表示しました。</p>}
</div>
);
}
バックエンドのカーソルベースページネーション
// オフセットベースよりもカーソルベースの方がパフォーマンスが良い
app.get('/api/articles', async (req, res) => {
const limit = Math.min(Number(req.query.limit) || 20, 100);
const cursor = req.query.cursor as string | undefined;
const where = cursor ? { id: { gt: cursor } } : {};
const articles = await prisma.article.findMany({
where,
take: limit + 1,
orderBy: { id: 'asc' },
});
const hasMore = articles.length > limit;
const data = hasMore ? articles.slice(0, -1) : articles;
res.json({
data,
hasMore,
nextCursor: hasMore ? data[data.length - 1].id : null,
});
});
Summary
Claude Codeを使えば、Intersection Observerベースの無限スクロールからカーソルベースAPIまで一貫して構築できます。大量データの描画パフォーマンスには仮想スクロール実装を、ページネーション方式の比較はページネーション実装を参照してください。
Intersection Observer APIの詳細はMDN Web Docsをご覧ください。
Related Posts
10 astuces pour tripler votre productivité avec Claude Code
Découvrez 10 astuces pratiques pour tirer le meilleur parti de Claude Code. Des stratégies de prompts aux raccourcis de workflow, ces techniques amélioreront votre efficacité dès aujourd'hui.
Optimisation Canvas/WebGL avec Claude Code
Découvrez l'optimisation Canvas/WebGL avec Claude Code. Conseils pratiques et exemples de code inclus.
Traitement Markdown avec Claude Code
Découvrez traitement Markdown avec Claude Code. Conseils pratiques et exemples de code inclus.