デジタル庁デザインシステム:デバウンスとスロットリングの実装ベストプラクティス
はじめに:なぜデバウンスとスロットリングが必要なのか?
ユーザーインターフェースのパフォーマンス最適化において、デバウンス(Debounce)とスロットリング(Throttling)は不可欠なテクニックです。デジタル庁デザインシステムのような大規模なデザインシステムでは、これらの技術を適切に実装することで、以下のメリットが得られます:
- パフォーマンス向上: 不要なレンダリングやAPI呼び出しを削減
- ユーザー体験の改善: 入力の応答性を維持しながら過剰な処理を防止
- リソース効率化: サーバー負荷の軽減とバッテリー消費の最適化
デバウンスとスロットリングの基本概念
デバウンス(Debounce)
スロットリング(Throttling)
デジタル庁デザインシステムにおける実装パターン
カスタムフックによる実装
import { useCallback, useRef } from 'react';
// デバウンスフック
export function useDebounce<T extends (...args: any[]) => void>(
callback: T,
delay: number
): T {
const timeoutRef = useRef<NodeJS.Timeout>();
return useCallback((...args: Parameters<T>) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
callback(...args);
}, delay);
}, [callback, delay]) as T;
}
// スロットリングフック
export function useThrottle<T extends (...args: any[]) => void>(
callback: T,
delay: number
): T {
const lastExecutedRef = useRef<number>(0);
const timeoutRef = useRef<NodeJS.Timeout>();
return useCallback((...args: Parameters<T>) => {
const now = Date.now();
const timeSinceLastExecution = now - lastExecutedRef.current;
if (timeSinceLastExecution >= delay) {
lastExecutedRef.current = now;
callback(...args);
} else {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
lastExecutedRef.current = Date.now();
callback(...args);
}, delay - timeSinceLastExecution);
}
}, [callback, delay]) as T;
}
入力コンポーネントでの実装例
import { useState, useCallback } from 'react';
import { Input } from './Input';
import { useDebounce } from '../hooks/useDebounce';
export function SearchInput() {
const [value, setValue] = useState('');
const [searchResults, setSearchResults] = useState<string[]>([]);
// デバウンスされた検索関数
const debouncedSearch = useDebounce(async (searchTerm: string) => {
if (searchTerm.length < 2) {
setSearchResults([]);
return;
}
// API呼び出しやフィルタリング処理
const results = await performSearch(searchTerm);
setSearchResults(results);
}, 300);
const handleInputChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value;
setValue(newValue);
debouncedSearch(newValue);
}, [debouncedSearch]);
return (
<div>
<Input
value={value}
onChange={handleInputChange}
placeholder="検索..."
blockSize="lg"
/>
{searchResults.length > 0 && (
<ul className="mt-2 border rounded-8 bg-white">
{searchResults.map((result, index) => (
<li key={index} className="px-4 py-2 border-b last:border-b-0">
{result}
</li>
))}
</ul>
)}
</div>
);
}
最適な遅延時間の選択基準
| ユースケース | 推奨遅延時間 | 理由 |
|---|---|---|
| 検索入力 | 300-500ms | ユーザーの入力ペースを考慮 |
| ウィンドウリサイズ | 250ms | スムーズなレスポンシブ対応 |
| スクロールイベント | 100ms | 滑らかなスクロール体験 |
| ドラッグ操作 | 16ms (60fps) | アニメーションの滑らかさ |
パフォーマンス比較表
| 手法 | 実行回数 | メモリ使用量 | 応答性 | 適したユースケース |
|---|---|---|---|---|
| デバウンス | 低 | 中 | 高 | 検索、フォーム検証 |
| スロットリング | 中 | 低 | 中 | スクロール、リサイズ |
| 未最適化 | 高 | 高 | 低 | 基本的に推奨しない |
実装上の注意点とベストプラクティス
1. メモリリークの防止
useEffect(() => {
return () => {
// コンポーネントアンマウント時にクリーンアップ
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
2. TypeScript型安全性の確保
function useDebounce<T extends (...args: any[]) => void>(
callback: T,
delay: number
): (...args: Parameters<T>) => void {
// 実装...
}
3. テスト容易性の考慮
// テスト用のユーティリティ
export const TEST_UTILS = {
advanceTimersByTime: (ms: number) => {
jest.advanceTimersByTime(ms);
},
useFakeTimers: () => {
jest.useFakeTimers();
},
useRealTimers: () => {
jest.useRealTimers();
}
};
実際のユースケース例
フォーム検証のデバウンス
export function EmailInput() {
const [email, setEmail] = useState('');
const [isValid, setIsValid] = useState(true);
const validateEmail = useDebounce((value: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
setIsValid(emailRegex.test(value));
}, 500);
const handleChange = (value: string) => {
setEmail(value);
validateEmail(value);
};
return (
<Input
value={email}
onChange={(e) => handleChange(e.target.value)}
isError={!isValid && email.length > 0}
placeholder="メールアドレス"
/>
);
}
無限スクロールのスロットリング
export function InfiniteScrollList() {
const [items, setItems] = useState<string[]>([]);
const [isLoading, setIsLoading] = useState(false);
const loadMore = useThrottle(async () => {
if (isLoading) return;
setIsLoading(true);
try {
const newItems = await fetchMoreItems();
setItems(prev => [...prev, ...newItems]);
} finally {
setIsLoading(false);
}
}, 1000);
useEffect(() => {
const handleScroll = () => {
const scrollTop = window.scrollY;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
if (scrollTop + windowHeight >= documentHeight - 100) {
loadMore();
}
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [loadMore]);
return (
<div>
{items.map((item, index) => (
<div key={index} className="p-4 border-b">
{item}
</div>
))}
{isLoading && <div className="p-4 text-center">読み込み中...</div>}
</div>
);
}
パフォーマンス計測と最適化
// パフォーマンス計測デコレータ
function measurePerformance<T extends (...args: any[]) => any>(
fn: T,
label: string
): T {
return ((...args: Parameters<T>) => {
const start = performance.now();
const result = fn(...args);
const end = performance.now();
console.log(`${label} executed in ${end - start}ms`);
return result;
}) as T;
}
// 使用例
const optimizedSearch = measurePerformance(
useDebounce(searchFunction, 300),
'Debounced Search'
);
まとめ:デジタル庁デザインシステムでの実装指針
- 適切な技術の選択: ユースケースに応じてデバウンスとスロットリングを使い分ける
- パフォーマンス計測: 実際のユーザー環境で計測し、最適な遅延時間を決定する
- メモリ管理: クリーンアップ処理を確実に実装する
- 型安全性: TypeScriptを活用して実行時エラーを防止する
- テスト容易性: ユニットテストと統合テストを容易にする設計を心がける
デバウンスとスロットリングを適切に実装することで、デジタル庁デザインシステムのコンポーネントは高いパフォーマンスと優れたユーザー体験を提供できます。これらのテクニックは、大規模なアプリケーションにおいて特に重要であり、システム全体の応答性と効率性に大きく貢献します。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



