react-render-optimization
Reactのレンダリングパフォーマンス最適化パターンを提供します。不要な再レンダリングの削減、メモ化の最適化、状態設計の改善、またはReactのパフォーマンス問題の診断が必要な場合に活用してください。
description の原文を見る
Teaches React rendering performance optimization patterns. Use when reducing unnecessary re-renders, optimizing memoization, improving state design, or diagnosing React performance issues.
SKILL.md 本文
React レンダリング最適化
目次
不要なレンダリングを排除し、レンダリングコストを削減し、React UIを応答性の高い状態に保つための実践的なパターン。これらのパターンはあらゆるReactアプリケーションに適用できます。Vite、Next.js、Remix、またはカスタムセットアップのどれを使用していても同じです。
いつ使うか
以下の場合にこれらのパターンを参照してください:
- コンポーネントが予想以上に頻繁に再レンダリングされる
- 入力、スクロール、またはインタラクション中にUIが緩い
- Profilerがコンポーネントツリー内の無駄なレンダリングを示している
- パフォーマンスに敏感な機能(ダッシュボード、エディタ、リスト)を構築している
- 既存のReactコンポーネントをレビューまたはリファクタリングしている
使用方法
- コード生成、レビュー、リファクタリング中にこれらのパターンを適用してください。アンチパターンを見つけたら、修正版を説明付きで提案してください。
詳細
概要
Reactはコンポーネントの状態が変わったとき、親が再レンダリングされたとき、またはそれが消費するコンテキストが更新されたときに、コンポーネントを再レンダリングします。ほとんどの再レンダリングは無害ですが、コストの高い計算、深いツリー、またはレイアウトスラッシングをトリガーすると、ユーザーに目に見えるようになります。
以下のパターンは影響度順に並んでいます。マイクロ最適化に到達する前に、最大の効果から取り組んでください。
1. 導出値をレンダリング中に計算し、保存しない
影響度: 高 — バグと不要な状態の全カテゴリを排除します。
既存の状態またはプロパティから計算できる値を保存すると、同期の問題と余分な再レンダリングが発生します。代わりにインラインで計算してください。
避けるべき — 漂うと冗長な状態:
function ProductList({ products }: { products: Product[] }) {
const [search, setSearch] = useState('')
const [filtered, setFiltered] = useState(products)
useEffect(() => {
setFiltered(products.filter(p =>
p.name.toLowerCase().includes(search.toLowerCase())
))
}, [products, search])
return (
<>
<input value={search} onChange={e => setSearch(e.target.value)} />
{filtered.map(p => <ProductCard key={p.id} product={p} />)}
</>
)
}
推奨 — レンダリング中に導出(廉価な導出は通常のconstを使用):
function ProductList({ products }: { products: Product[] }) {
const [search, setSearch] = useState('')
// 廉価な導出 — 通常のconst、useMemoは不要
const hasSearch = search.length > 0
const normalizedSearch = search.toLowerCase()
// コストの高い導出 — 大きな配列を反復処理する場合はuseMemoが正当化される
const filtered = useMemo(
() => products.filter(p =>
p.name.toLowerCase().includes(normalizedSearch)
),
[products, normalizedSearch]
)
return (
<>
<input value={search} onChange={e => setSearch(e.target.value)} />
{hasSearch && <ClearButton />}
{filtered.map(p => <ProductCard key={p.id} product={p} />)}
</>
)
}
useMemoと通常のconstをいつ使うか:
- 通常の
const— ブール値フラグ、文字列フォーマット、シンプルな算術、オブジェクトプロパティアクセス、.lengthチェック。これらは本質的に無料で、useMemoのオーバーヘッドはそれに値しません。 useMemo— 配列のフィルタリング/ソート、データ構造の構築、JSON.parse、コストの高い変換、コレクションを反復処理または O(n) の作業を含むもの。
ルール: 式がプリミティブを返したり、単一のプロパティアクセスの場合、useMemoをスキップしてください。データを反復処理または変換する場合は、それをラップしてください。
React Compiler に関する注記: React Compiler が有効になっている場合、式を自動的にメモ化し、手動の
useMemo呼び出しをスキップできます。
2. 生の値ではなく、粗粒度の状態にサブスクライブする
影響度: 高 — 無関連な変更での再レンダリングを防ぎます。
コンポーネントが導出ブール値(例: "モバイル?")のみを気にする場合、継続的に変わる生の値にサブスクライブしないでください。
避けるべき — すべてのピクセルで再レンダリング:
function Sidebar() {
const width = useWindowWidth() // 毎回のリサイズで発火
const isMobile = width < 768
return <nav className={isMobile ? 'mobile' : 'desktop'}>...</nav>
}
推奨 — ブール値が反転したときだけ再レンダリング:
function Sidebar() {
const isMobile = useMediaQuery('(max-width: 767px)')
return <nav className={isMobile ? 'mobile' : 'desktop'}>...</nav>
}
これは広く適用されます: ユーザーオブジェクト全体ではなくisLoggedInにサブスクライブ、カート配列全体ではなくhasItemsにサブスクライブします。
3. コストの高いサブツリーをメモ化されたコンポーネントに抽出する
影響度: 高 — 早期リターンとレンダリングスキップを有効にします。
親に高速パス(ローディング、エラー、空)がある場合、コストの高い子供たちは同じコンポーネント内に存在する場合でも計算します。それらを抽出して、Reactが完全にレンダリングをスキップできるようにしましょう。
避けるべき — ローディング中でもアバター計算が実行される:
function Profile({ user, loading }: Props) {
const avatar = useMemo(() => processAvatar(user), [user])
if (loading) return <Skeleton />
return <div><img src={avatar} /></div>
}
推奨 — ローディング時に計算がスキップされる:
const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
const avatar = useMemo(() => processAvatar(user), [user])
return <img src={avatar} />
})
function Profile({ user, loading }: Props) {
if (loading) return <Skeleton />
return <div><UserAvatar user={user} /></div>
}
React Compiler に関する注記: Compiler は自動的にメモ化され、手動の
memo()ラップがそれほど必要ではなくなります。しかし、早期リターンのためにコンポーネントを抽出することは依然として価値があります。
4. 遅延状態初期化を使用する
影響度: 中 — 毎回のレンダリング時の無駄な計算を避けます。
useStateが関数呼び出しを初期値として受け取る場合、その呼び出しはレンダリングするたびに実行されますが、結果は一度だけ使用されます。代わりに関数リファレンスを渡してください。
避けるべき — buildIndex()がすべてのレンダリングで実行:
const [index, setIndex] = useState(buildSearchIndex(items))
推奨 — マウント時のみ実行:
const [index, setIndex] = useState(() => buildSearchIndex(items))
遅延初期化を使用: JSON.parse、localStorage読み込み、データ構造の構築、コストの高い変換。useState(0)やuseState(false)のようなシンプルなプリミティブはスキップしてください。
5. 安定したコールバックに関数形式のsetStateを使用する
影響度: 中 — 依存関係配列から状態変数を削除します。
コールバックが前の状態のみを必要として次の状態を計算する場合、関数形式を使用してください。これにより、状態変数が依存関係配列から削除され、安定したコールバック識別が生成されます。
避けるべき — countが変わるとコールバックが変わる:
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(count + 1), [count])
推奨 — コールバックは常に安定:
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [])
6. インタラクションロジックをEffect ではなくイベントハンドラに入れる
影響度: 中 — 無関連な依存関係の変更時にサイドエフェクトが再実行されるのを避けます。
サイドエフェクトがユーザーアクション(クリック、送信、ドラッグ)によってトリガーされる場合、イベントハンドラで実行してください。それを状態+effect としてモデル化すると、無関連な依存関係が変わるたびに再実行されます。
避けるべき — themeが変わるとEffect が再実行:
function Form() {
const [submitted, setSubmitted] = useState(false)
const theme = useContext(ThemeContext)
useEffect(() => {
if (submitted) {
post('/api/register')
showToast('Registered', theme)
}
}, [submitted, theme])
return <button onClick={() => setSubmitted(true)}>Submit</button>
}
推奨 — ハンドラ内のロジック:
function Form() {
const theme = useContext(ThemeContext)
function handleSubmit() {
post('/api/register')
showToast('Registered', theme)
}
return <button onClick={handleSubmit}>Submit</button>
}
7. 頻繁に変わる一時的な値にuseRefを使用する
影響度: 中 — 高速更新時の再レンダリングを防ぎます。
非常に頻繁に変わる値(マウス位置、スクロール時間オフセット、インターバルティック)が再レンダリングを駆動する必要がない場合、ref に置いてください。必要に応じてDOMを直接更新してください。
避けるべき — すべてのマウスムーブで再レンダリング:
function Cursor() {
const [x, setX] = useState(0)
useEffect(() => {
const handler = (e: MouseEvent) => setX(e.clientX)
window.addEventListener('mousemove', handler)
return () => window.removeEventListener('mousemove', handler)
}, [])
return <div style={{ transform: `translateX(${x}px)` }} />
}
推奨 — ゼロ再レンダリング:
function Cursor() {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const handler = (e: MouseEvent) => {
if (ref.current) {
ref.current.style.transform = `translateX(${e.clientX}px)`
}
}
window.addEventListener('mousemove', handler)
return () => window.removeEventListener('mousemove', handler)
}, [])
return <div ref={ref} />
}
8. 緊急ではない更新にstartTransitionを使用する
影響度: 中 — 緊急の更新(入力、クリック)を応答性の高い状態に保ちます。
緊急ではない状態更新をstartTransitionでラップして、Reactが緊急の作業に割り込めるようにしてください。これは特に検索フィルタリング、タブ切り替え、リストの再ソートに役立ちます。
避けるべき — リストがフィルタリングされている間に入力がブロック:
function Search({ items }: { items: Item[] }) {
const [query, setQuery] = useState('')
const [filtered, setFiltered] = useState(items)
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setQuery(e.target.value)
setFiltered(items.filter(i => i.name.includes(e.target.value)))
}
return (
<>
<input value={query} onChange={handleChange} />
<List items={filtered} />
</>
)
}
推奨 — 入力は応答性を保つ:
import { useState, useTransition } from 'react'
function Search({ items }: { items: Item[] }) {
const [query, setQuery] = useState('')
const [filtered, setFiltered] = useState(items)
const [isPending, startTransition] = useTransition()
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setQuery(e.target.value)
startTransition(() => {
setFiltered(items.filter(i => i.name.includes(e.target.value)))
})
}
return (
<>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<List items={filtered} />
</>
)
}
9. 状態の読み取りを使用ポイントに延期する
影響度: 中 — イベントハンドラ内でのみ読み取る状態へのサブスクリプションを避けます。
useSearchParams()のようなフックを呼び出す場合、値をイベントハンドラの内側でのみ読み取ります。代わりにオンデマンドで読み取ってください。
避けるべき — URL が変わるたびにコンポーネントが再レンダリング:
function ShareButton({ id }: { id: string }) {
const [searchParams] = useSearchParams()
const handleShare = () => {
const ref = searchParams.get('ref')
share(id, { ref })
}
return <button onClick={handleShare}>Share</button>
}
推奨 — オンデマンド読み取り、サブスクリプションなし:
function ShareButton({ id }: { id: string }) {
const handleShare = () => {
const params = new URLSearchParams(window.location.search)
share(id, { ref: params.get('ref') })
}
return <button onClick={handleShare}>Share</button>
}
10. デフォルトプロパティに安定したリファレンスを使用する
影響度: 中 — 子コンポーネントのmemo()をすり潰すことなく、デフォルトプロップ値として新しいオブジェクト/配列リテラルを渡すことを防ぎます。
[]または{}をデフォルトプロップ値として渡すと、毎回のレンダリングで新しいリファレンスが作成され、子コンポーネントのメモ化を破ります。
避けるべき — 毎回のレンダリングで新しい配列:
function Dashboard({ tabs = [] }: { tabs?: Tab[] }) {
return <TabBar tabs={tabs} /> {/* TabBar が毎回再レンダリング */}
}
推奨 — 安定したリファレンス:
const EMPTY_TABS: Tab[] = []
function Dashboard({ tabs = EMPTY_TABS }: { tabs?: Tab[] }) {
return <TabBar tabs={tabs} />
}
11. 長いリストに CSS content-visibilityを使用する
影響度: 高 — 長いスクロール可能なコンテンツの初期レンダリング速度が5〜10倍高速化します。
content-visibility: autoをオフスクリーン項目に適用して、ブラウザが表示までレイアウトと描画をスキップするようにしてください。
.list-item {
content-visibility: auto;
contain-intrinsic-size: 0 80px; /* 推定高さ */
}
function MessageList({ messages }: { messages: Message[] }) {
return (
<div style={{ overflowY: 'auto', height: '100vh' }}>
{messages.map(msg => (
<div key={msg.id} className="list-item">
<MessageCard message={msg} />
</div>
))}
</div>
)
}
1,000個のアイテムに対して、ブラウザは約990個のオフスクリーン項目のレイアウトと描画をスキップします。仮想化(例: react-window、@tanstack/react-virtual)と組み合わせて、本当に巨大なリストを使用してください。
12. 静的 JSX をコンポーネントの外側に持ち上げる
影響度: 低 — 同一要素の再作成を避けます。
変わることのない JSX 要素は、モジュールスコープに持ち上げることができます。Reactは複数のレンダリング間で同じオブジェクトリファレンスを再利用します。
避けるべき — 毎回のレンダリングで再作成:
function Page() {
return (
<main>
<footer>
<p>Copyright 2026 Acme Inc.</p>
</footer>
</main>
)
}
推奨 — 一度だけ作成:
const footer = (
<footer>
<p>Copyright 2026 Acme Inc.</p>
</footer>
)
function Page() {
return <main>{footer}</main>
}
大きな SVG 要素の場合、再作成はコストがかかるため、最も影響があります。
React Compiler に関する注記: Compiler は静的 JSX を自動的に持ち上げるため、この手動最適化は不要です。
13. アプリごとに一度コストの高い操作を初期化する
影響度: 低-中 — Strict Mode とリマウント時の重複初期化を避けます。
アプリ全体の初期化(分析、認証チェック、サービスワーカー)はuseEffectに置いてはいけません。開発ではコンポーネントが再マウントされ、同時実行機能でもそうです。モジュールレベルのガードを使用してください。
避けるべき — dev で2回実行、リマウント時に再度実行:
function App() {
useEffect(() => {
initAnalytics()
checkAuth()
}, [])
return <Router />
}
推奨 — アプリロードごとに一度:
let initialized = false
function App() {
useEffect(() => {
if (initialized) return
initialized = true
initAnalytics()
checkAuth()
}, [])
return <Router />
}
またはエントリファイル(main.tsx)のモジュールレベルでコンポーネント外部を初期化してください。
14. 安定したサブスクリプション用のRef にイベントハンドラを保存する
影響度: 低 — Effect の再サブスクリプションを防ぎます。
カスタムフックがイベントにサブスクライブしてコールバックを受け入れる場合、コールバックを ref に保存して、毎回のレンダリングでサブスクリプションが破棄・再作成されないようにしてください。
避けるべき — ハンドラが変わるたびに再サブスクライブ:
function useWindowEvent(event: string, handler: (e: Event) => void) {
useEffect(() => {
window.addEventListener(event, handler)
return () => window.removeEventListener(event, handler)
}, [event, handler])
}
推奨 — 安定したサブスクリプション:
function useWindowEvent(event: string, handler: (e: Event) => void) {
const saved = useRef(handler)
useEffect(() => { saved.current = handler }, [handler])
useEffect(() => {
const listener = (e: Event) => saved.current(e)
window.addEventListener(event, listener)
return () => window.removeEventListener(event, listener)
}, [event])
}
React 19+ を使用している場合、useEffectEventはこのパターンを組み込みとして提供します:
import { useEffectEvent } from 'react'
function useWindowEvent(event: string, handler: (e: Event) => void) {
const onEvent = useEffectEvent(handler)
useEffect(() => {
window.addEventListener(event, onEvent)
return () => window.removeEventListener(event, onEvent)
}, [event])
}
15. SSR ハイドレーション用のクライアント専用データのフリッカーを防ぐ
影響度: 中 — SSR ハイドレーション中の誤ったコンテンツのフラッシュを排除します。
レンダリングがクライアント専用データ(localStorage、クッキー)に依存する場合、インラインスクリプトが React がハイドレートする前に正しい値を設定できます。SSR エラーと見えるフラッシュの両方を回避します。
function ThemeRoot({ children }: { children: React.ReactNode }) {
return (
<>
<div id="app-root">{children}</div>
<script
dangerouslySetInnerHTML={{
__html: `(function(){
try {
var t = localStorage.getItem('theme') || 'light';
document.getElementById('app-root').dataset.theme = t;
} catch(e) {}
})();`,
}}
/>
</>
)
}
このアプローチは任意の SSR セットアップで機能します。Next.js、Remix、またはカスタム Vite SSR パイプライン。
16. コンポーネント内でコンポーネントを定義しない
影響度: 高 — リマウント、状態喪失、不要な DOM 作業を毎回のレンダリングで引き起こします。
別のコンポーネントのレンダリング内でコンポーネントを定義すると、Reactは毎回のレンダリングで新しいコンポーネント型を作成します。これはサブツリー全体がアンマウント・リマウントされます。すべての状態、DOM ノード、Effect クリーンアップ/セットアップが失われます。
避けるべき — Rowは毎回のレンダリングで新しい型:
function Table({ data }: { data: Item[] }) {
// これは毎回のレンダリングで NEW コンポーネント型を作成
function Row({ item }: { item: Item }) {
const [selected, setSelected] = useState(false)
return <tr onClick={() => setSelected(!selected)}>{item.name}</tr>
}
return <table>{data.map(item => <Row key={item.id} item={item} />)}</table>
}
推奨 — Rowはモジュールスコープで定義:
function Row({ item }: { item: Item }) {
const [selected, setSelected] = useState(false)
return <tr onClick={() => setSelected(!selected)}>{item.name}</tr>
}
function Table({ data }: { data: Item[] }) {
return <table>{data.map(item => <Row key={item.id} item={item} />)}</table>
}
これはuseMemo、useCallback、または他のフック内で定義されたコンポーネントにも適用されます。常にコンポーネントをモジュールスコープまたは静的プロパティとして定義してください。
17. コストの高い導出レンダリングにuseDeferredValueを使用する
影響度: 高 — UI をコストの高いサブツリーがバックグラウンドで再レンダリングしている間、応答性を保ちます。
useDeferredValueは、高速で変わる値に依存するコンポーネントの再レンダリングを遅延するようにReactに指示します。useTransition(状態更新をラップ)とは異なり、useDeferredValueは消費をラップします。状態セッターを制御しない場合に役立ちます。
避けるべき — すべてのキー入力で UI がブロック:
function SearchPage({ query }: { query: string }) {
// コスト高: 毎回のキー入力で10,000個のアイテムをフィルタリング・レンダリング
const results = filterItems(query)
return <ResultsList items={results} />
}
推奨 — 入力は応答性を保ち、結果がバックグラウンドで更新:
import { useDeferredValue, useMemo } from 'react'
function SearchPage({ query }: { query: string }) {
const deferredQuery = useDeferredValue(query)
const isStale = query !== deferredQuery
const results = useMemo(() => filterItems(deferredQuery), [deferredQuery])
return (
<div style={{ opacity: isStale ? 0.7 : 1 }}>
<ResultsList items={results} />
</div>
)
}
useDeferredValuevs useTransitionを使うとき:
useTransition— 状態セッターを制御し、startTransitionでラップできるuseDeferredValue— 値がプロパティ、親、または制御しないライブラリから来ている
18. 条件付きレンダリングで明示的なチェックを使用する
影響度: 中 — 0、NaN、または空の文字列が DOM にレンダリングされるのを防ぎます。
JSX の&&演算子は falsy 値でショートサーキットします。しかし、0、NaN、""は falsy ですが、それでも目に見えるテキストノードとして DOM にレンダリングされます。
避けるべき — count がゼロのとき0が DOM にレンダリング:
function NotificationBadge({ count }: { count: number }) {
return <div>{count && <Badge>{count}</Badge>}</div>
// count が0のとき、レンダリング: <div>0</div>
}
推奨 — 明示的なブール値チェック:
function NotificationBadge({ count }: { count: number }) {
return <div>{count > 0 && <Badge>{count}</Badge>}</div>
}
// または、明確さのための三項演算子
function NotificationBadge({ count }: { count: number }) {
return <div>{count > 0 ? <Badge>{count}</Badge> : null}</div>
}
これは0、NaN、または""の可能性がある値に適用されます。配列長、文字列値、数値プロパティ。truthiness に頼るのではなく、常に明示的なブール式(> 0、!== ''、!= null)を使用してください。
19. Effect 依存関係をプリミティブに絞る
影響度: 中 — オブジェクトのプロパティが変わるたびに Effect が再実行されるのを防ぎます。
Effect がオブジェクトから1つのプロパティのみが必要な場合、依存関係配列の前にそれを抽出してください。オブジェクト全体を渡すと、どのプロパティが変わるたびに再実行されます。
避けるべき — user.nameまたはuser.avatarが変わるたびに Effect が再実行:
function UserStatus({ user }: { user: User }) {
useEffect(() => {
updatePresence(user.id)
}, [user]) // ANY user プロパティ変更で再実行
}
推奨 — ID が変わるときだけ再実行:
function UserStatus({ user }: { user: User }) {
const { id } = user
useEffect(() => {
updatePresence(id)
}, [id])
}
これはフック戻り値にも適用されます。useQueryが{ data, status, fetchStatus }を返し、Effect がstatusのみを気にする場合、最初に分割代入してください。
20. 組み合わせたフック計算を分割する
影響度: 中 — 1つの部分のみが必要なコンシューマーの再レンダリング要求を防ぎます。
カスタムフックが複数の無関連な値を計算する場合、1つの変更により、すべてのコンシューマーの再レンダリングが強制されます。変わっていない値を読み取るだけのコンシューマーも含めて。
避けるべき — totalが変わると、averageが必要なだけのコンポーネントが再レンダリング:
function useStats(items: number[]) {
return useMemo(() => ({
total: items.reduce((a, b) => a + b, 0),
average: items.reduce((a, b) => a + b, 0) / items.length,
max: Math.max(...items),
}), [items])
}
推奨 — フォーカスされたフックに分割:
function useTotal(items: number[]) {
return useMemo(() => items.reduce((a, b) => a + b, 0), [items])
}
function useAverage(items: number[]) {
return useMemo(() => items.reduce((a, b) => a + b, 0) / items.length, [items])
}
function useMax(items: number[]) {
return useMemo(() => Math.max(...items), [items])
}
コンポーネントは必要なフックのみを呼び出します。単一のコンポーネントが3つすべてを必要とする場合、そこで組み合わせることは問題ありません。分割は不要なカップリングをフックレベルで防ぎます。
21. バッチ処理 DOM 読み取り/書き込みを使用してレイアウトスラッシングを避ける
影響度: 高 — ブロックするメインスレッドの強制的な同期レイアウトを防ぎます。
レイアウトプロパティ(例: offsetHeight、getBoundingClientRect())を読み取った後、DOM に書き込むと、ブラウザはレイアウトを同期的に再計算するよう強制されます。ループの場合、これはレイアウトスラッシングを作成します。
避けるべき — 毎回の反復でレイアウト再計算を強制:
function resizeCards(cards: HTMLElement[]) {
cards.forEach(card => {
const height = card.offsetHeight // READ (レイアウトを強制)
card.style.minHeight = `${height + 20}px` // WRITE (レイアウト無効化)
})
}
推奨 — すべての読み取り、次にすべての書き込みをバッチ処理:
function resizeCards(cards: HTMLElement[]) {
// 読み取りフェーズ
const heights = cards.map(card => card.offsetHeight)
// 書き込みフェーズ
cards.forEach((card, i) => {
card.style.minHeight = `${heights[i] + 20}px`
})
}
React では、これはuseLayoutEffectまたはuseEffectコールバック内で DOM 要素を測定・変異させるときに最も一般的に発生します。アニメーション フレーム内でレイアウトを読み取る必要がある場合、バッチ処理にrequestAnimationFrameを使用してください:
useLayoutEffect(() => {
const measurements = items.map(el => el.getBoundingClientRect())
requestAnimationFrame(() => {
items.forEach((el, i) => {
el.style.transform = `translateY(${measurements[i].top}px)`
})
})
}, [items])
22. SVG 要素ではなく SVG ラッパーをアニメ化する
影響度: 中 — 毎回のアニメーションフレームで SVG ツリー全体の再描画を避けます。
SVG 要素自体のプロパティ(例: <svg>または<path>)をアニメ化すると、SVG 全体の再描画がトリガーされます。代わりに SVG をラッパーでラップして、ラッパーをアニメ化してください。
避けるべき — SVG ツリー全体を再描画:
<motion.svg animate={{ rotate: 360 }} style={{ width: 200, height: 200 }}>
<ComplexChart />
</motion.svg>
推奨 — ラッパーのみ再描画:
<motion.div animate={{ rotate: 360 }} style={{ width: 200, height: 200 }}>
<svg viewBox="0 0 200 200">
<ComplexChart />
</svg>
</motion.div>
これは CSS アニメーションにも適用されます。SVG 属性(例: cx、cy、またはd)を直接アニメ化するのではなく、ラッパー要素でtransformを使用してください。
23. 予期されるハイドレーション不一致を抑制する
影響度: 低-中 — 既知のセーフ警告をサイレントにして、実際のバグを隠さない。
一部のコンテンツはサーバーとクライアント間で異なります。タイムスタンプ、ランダムID、ユーザーエージェント固有のレンダリング。これらの特定の要素にsuppressHydrationWarningを使用してください。
function Comment({ createdAt }: { createdAt: Date }) {
return (
<article>
<p>{comment.body}</p>
<time suppressHydrationWarning>
{formatRelativeTime(createdAt)} {/* "2 minutes ago" はサーバーとクライアント間で異なる */}
</time>
</article>
)
}
控えめに適用し、リーフ要素のみに適用してください。コンテナ要素の警告を抑制しないでください。子の実際の不一致が隠れます。
24. Vite SPA 用の React DOM リソースヒント
影響度: 高 — フレームワークのサポートなしに、ブラウザが重要なリソースを読み込み始めることができます。
React 19 はreact-domからpreload()とpreinit()を追加します。命令型リソースヒントは任意の React アプリで機能します。Vite SPA(フレームワークレベルのプリフェッチがない)では、特に価値があります。
import { preload, preinit } from 'react-dom'
function App() {
// 必要になる前にフォントをプリロード
preload('/fonts/inter-var.woff2', { as: 'font', type: 'font/woff2', crossOrigin: 'anonymous' })
// クリティカル CSS ファイルをプリイニット(読み込み+適用)
preinit('/critical.css', { as: 'style' })
return <RouterProvider router={router} />
}
ナビゲーション時 — 次のページのデータとコードをプリロード:
function ProductLink({ id }: { id: string }) {
const handleHover = () => {
// 次のページで必要なイメージをプリロード
preload(`/api/products/${id}/image.webp`, { as: 'image' })
// ルートコードをプリフェッチ
import('./pages/ProductDetail')
}
return <Link to={`/products/${id}`} onMouseEnter={handleHover}>View</Link>
}
リソースが既に読み込まれている場合、これらはno-ops です。そのため、早期に呼び出すことは安全です。Vite アプリにメタフレームワークがない場合、これはリソース優先順位化の主要なメカニズムです。
25. ルートナビゲーションにuseTransitionを使用する
影響度: 中 — 新しいルートが読み込まれている間、現在のページを対話型に保ちます。
React.lazy()ルートを備えた Vite SPA では、ナビゲーションリンクをクリックすると、UI がチャンク読み込みとコンポーネント レンダリング中にフリーズできます。ナビゲーションをstartTransitionでラップすると、新しいページの準備ができるまで古いページを表示できます。
import { useTransition } from 'react'
import { useNavigate } from 'react-router-dom'
function NavLink({ to, children }: { to: string; children: React.ReactNode }) {
const navigate = useNavigate()
const [isPending, startTransition] = useTransition()
const handleClick = (e: React.MouseEvent) => {
e.preventDefault()
startTransition(() => {
navigate(to)
})
}
return (
<a
href={to}
onClick={handleClick}
style={{ opacity: isPending ? 0.7 : 1 }}
>
{children}
</a>
)
}
これは lazy でロードされるルート間のブランク画面フラッシュを防ぎ、現在のページに微妙なローディングインジケーターを表示するためのisPendingを提供します。
出典
patterns.dev からのパターン — より広い Web エンジニアリング コミュニティ向けのフレームワーク非依存 React パフォーマンス ガイダンス。
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- patternsdev
- リポジトリ
- patternsdev/skills
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/patternsdev/skills / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。