Project_PerformanceDemo.jsx
React.js/lessons/16_performance/projects/Project_PerformanceDemo.jsx
/**
* Project: 效能優化展示 — React.memo / useMemo / useCallback 練習
*
* 學習重點:
* - React.memo 避免不必要的子元件渲染
* - useMemo 快取昂貴計算
* - useCallback 穩定函式參考
* - 渲染次數觀察
*
* 使用方式:將此檔案內容複製到你的 App.jsx 中
*/
import { useState, useMemo, useCallback, memo, useRef, useEffect } from 'react';
// 渲染計數器 Hook
function useRenderCount(name) {
const count = useRef(0);
useEffect(() => { count.current += 1; });
return count.current;
}
// 未優化的子元件
const UnoptimizedChild = ({ label, onClick }) => {
const renderCount = useRenderCount(label);
return (
<div style={{
padding: '12px 16px', borderRadius: '8px', backgroundColor: '#fef2f2',
border: '1px solid #fecaca', display: 'flex', justifyContent: 'space-between',
alignItems: 'center', marginBottom: '8px',
}}>
<span style={{ color: '#991b1b', fontSize: '0.9rem' }}>{label}</span>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<span style={{
padding: '2px 8px', borderRadius: '4px', backgroundColor: '#ef4444',
color: 'white', fontSize: '0.75rem', fontWeight: 'bold',
}}>
渲染 {renderCount} 次
</span>
<button onClick={onClick} style={{
padding: '4px 12px', borderRadius: '6px', border: '1px solid #fca5a5',
backgroundColor: 'white', cursor: 'pointer', fontSize: '0.8rem',
}}>點擊</button>
</div>
</div>
);
};
// 用 memo 優化的子元件
const OptimizedChild = memo(({ label, onClick }) => {
const renderCount = useRenderCount(label);
return (
<div style={{
padding: '12px 16px', borderRadius: '8px', backgroundColor: '#f0fdf4',
border: '1px solid #bbf7d0', display: 'flex', justifyContent: 'space-between',
alignItems: 'center', marginBottom: '8px',
}}>
<span style={{ color: '#166534', fontSize: '0.9rem' }}>{label}</span>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<span style={{
padding: '2px 8px', borderRadius: '4px', backgroundColor: '#22c55e',
color: 'white', fontSize: '0.75rem', fontWeight: 'bold',
}}>
渲染 {renderCount} 次
</span>
<button onClick={onClick} style={{
padding: '4px 12px', borderRadius: '6px', border: '1px solid #86efac',
backgroundColor: 'white', cursor: 'pointer', fontSize: '0.8rem',
}}>點擊</button>
</div>
</div>
);
});
OptimizedChild.displayName = 'OptimizedChild';
function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
const [numbers] = useState(() =>
Array.from({ length: 5000 }, () => Math.floor(Math.random() * 1000))
);
const parentRenderCount = useRenderCount('Parent');
// ❌ 每次渲染都建立新函式
const unstableCallback = () => alert('未優化的 callback');
// ✅ useCallback 穩定函式參考
const stableCallback = useCallback(() => alert('優化的 callback'), []);
// ❌ 每次渲染都重新計算
const expensiveResultBad = (() => {
let sum = 0;
for (const n of numbers) sum += n;
return sum;
})();
// ✅ useMemo 快取計算結果
const expensiveResultGood = useMemo(() => {
let sum = 0;
for (const n of numbers) sum += n;
return sum;
}, [numbers]);
// useMemo 搜尋範例
const filteredNumbers = useMemo(() => {
if (!text) return numbers.slice(0, 20);
return numbers.filter((n) => n.toString().includes(text)).slice(0, 20);
}, [numbers, text]);
return (
<div style={{
maxWidth: '800px', margin: '0 auto', padding: '32px 16px',
fontFamily: "'Segoe UI', 'Noto Sans TC', sans-serif",
}}>
<h1 style={{ color: '#0f172a', marginBottom: '4px' }}>React 效能優化展示</h1>
<p style={{ color: '#94a3b8', marginTop: 0 }}>
觀察不同優化方式如何影響渲染次數
</p>
<div style={{
padding: '12px 16px', borderRadius: '8px', backgroundColor: '#f1f5f9',
marginBottom: '24px', display: 'flex', gap: '16px', alignItems: 'center',
}}>
<span style={{ color: '#64748b', fontSize: '0.85rem' }}>父元件渲染次數:</span>
<span style={{
padding: '2px 8px', borderRadius: '4px', backgroundColor: '#4f46e5',
color: 'white', fontSize: '0.85rem', fontWeight: 'bold',
}}>{parentRenderCount}</span>
</div>
{/* 觸發重新渲染的控制 */}
<div style={{
padding: '20px', borderRadius: '12px', backgroundColor: 'white',
border: '1px solid #e2e8f0', marginBottom: '24px',
}}>
<h3 style={{ margin: '0 0 12px', color: '#0f172a' }}>觸發父元件重新渲染</h3>
<div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
<button
onClick={() => setCount((c) => c + 1)}
style={{
padding: '8px 20px', borderRadius: '8px', border: 'none',
backgroundColor: '#4f46e5', color: 'white', cursor: 'pointer',
}}
>
Count: {count} (點擊 +1)
</button>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="輸入文字觸發渲染..."
style={{
flex: 1, padding: '8px 12px', borderRadius: '8px',
border: '1px solid #e2e8f0', outline: 'none',
}}
/>
</div>
</div>
{/* React.memo + useCallback 比較 */}
<div style={{
padding: '20px', borderRadius: '12px', backgroundColor: 'white',
border: '1px solid #e2e8f0', marginBottom: '24px',
}}>
<h3 style={{ margin: '0 0 4px', color: '#0f172a' }}>React.memo + useCallback 比較</h3>
<p style={{ color: '#94a3b8', fontSize: '0.8rem', margin: '0 0 16px' }}>
點擊上方按鈕改變 count,觀察哪些子元件會重新渲染
</p>
<p style={{ fontWeight: '600', color: '#991b1b', fontSize: '0.85rem', margin: '0 0 4px' }}>
❌ 未優化(每次父元件渲染都跟著渲染)
</p>
<UnoptimizedChild label="無 memo 的元件" onClick={unstableCallback} />
<p style={{ fontWeight: '600', color: '#166534', fontSize: '0.85rem', margin: '16px 0 4px' }}>
✅ 已優化(memo + useCallback)
</p>
<OptimizedChild label="memo + useCallback 的元件" onClick={stableCallback} />
<p style={{ fontWeight: '600', color: '#b45309', fontSize: '0.85rem', margin: '16px 0 4px' }}>
⚠️ memo 但沒有 useCallback(函式參考每次不同,memo 失效)
</p>
<OptimizedChild label="memo 但 callback 不穩定" onClick={unstableCallback} />
</div>
{/* useMemo 範例 */}
<div style={{
padding: '20px', borderRadius: '12px', backgroundColor: 'white',
border: '1px solid #e2e8f0', marginBottom: '24px',
}}>
<h3 style={{ margin: '0 0 4px', color: '#0f172a' }}>useMemo — 快取昂貴計算</h3>
<p style={{ color: '#94a3b8', fontSize: '0.8rem', margin: '0 0 16px' }}>
對 5000 個數字求和。useMemo 只在 numbers 改變時重算,count 改變不會重算。
</p>
<div style={{ display: 'flex', gap: '16px' }}>
<div style={{ flex: 1, padding: '12px', borderRadius: '8px', backgroundColor: '#fef2f2' }}>
<p style={{ margin: '0 0 4px', fontSize: '0.8rem', color: '#991b1b' }}>❌ 無 useMemo</p>
<p style={{ margin: 0, fontWeight: 'bold', color: '#0f172a' }}>
總和:{expensiveResultBad.toLocaleString()}
</p>
<p style={{ margin: '4px 0 0', fontSize: '0.7rem', color: '#94a3b8' }}>每次渲染都重算</p>
</div>
<div style={{ flex: 1, padding: '12px', borderRadius: '8px', backgroundColor: '#f0fdf4' }}>
<p style={{ margin: '0 0 4px', fontSize: '0.8rem', color: '#166534' }}>✅ 有 useMemo</p>
<p style={{ margin: 0, fontWeight: 'bold', color: '#0f172a' }}>
總和:{expensiveResultGood.toLocaleString()}
</p>
<p style={{ margin: '4px 0 0', fontSize: '0.7rem', color: '#94a3b8' }}>只在 numbers 改變時重算</p>
</div>
</div>
</div>
{/* useMemo 搜尋 */}
<div style={{
padding: '20px', borderRadius: '12px', backgroundColor: 'white',
border: '1px solid #e2e8f0',
}}>
<h3 style={{ margin: '0 0 4px', color: '#0f172a' }}>useMemo — 搜尋過濾</h3>
<p style={{ color: '#94a3b8', fontSize: '0.8rem', margin: '0 0 12px' }}>
在上方輸入框輸入數字來搜尋(顯示前 20 筆結果)
</p>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
{filteredNumbers.map((n, i) => (
<span key={i} style={{
padding: '4px 10px', borderRadius: '6px', backgroundColor: '#f1f5f9',
fontSize: '0.85rem', color: '#475569', fontFamily: 'monospace',
}}>
{n}
</span>
))}
</div>
<p style={{ color: '#94a3b8', fontSize: '0.75rem', margin: '8px 0 0' }}>
符合條件:{text ? numbers.filter((n) => n.toString().includes(text)).length : numbers.length} 筆
</p>
</div>
</div>
);
}
export default App;
Artigos relacionados
App.js
App.js — javascript source code from the React.js learning materials (React.js/lessons/01_introduction/example/src/App.js).
Ler artigo →App.test.js
App.test.js — javascript source code from the React.js learning materials (React.js/lessons/01_introduction/example/src/App.test.js).
Ler artigo →index.js
index.js — javascript source code from the React.js learning materials (React.js/lessons/01_introduction/example/src/index.js).
Ler artigo →reportWebVitals.js
reportWebVitals.js — javascript source code from the React.js learning materials (React.js/lessons/01_introduction/example/src/reportWebVitals.js).
Ler artigo →setupTests.js
setupTests.js — javascript source code from the React.js learning materials (React.js/lessons/01_introduction/example/src/setupTests.js).
Ler artigo →Project_HelloReact.jsx
Project_HelloReact.jsx — javascript source code from the React.js learning materials (React.js/lessons/01_introduction/projects/Project_HelloReact.jsx).
Ler artigo →