10.5 Performance Optimization תרגול
תרגול - אופטימיזציית ביצועים - Performance Optimization¶
תרגיל 1 - אופטימיזציית תמונות¶
נתון גלריית תמונות שטוענת 50 תמונות בבת אחת ללא אופטימיזציה:
function Gallery({ images }: { images: { src: string; title: string }[] }) {
return (
<div className="gallery">
{images.map((img, i) => (
<div key={i} className="gallery-item">
<img src={img.src} />
<p>{img.title}</p>
</div>
))}
</div>
);
}
שפרו את הקוד עם:
- שימוש ב-next/image
- lazy loading לכל התמונות מלבד 6 הראשונות
- גדלים רספונסיביים עם sizes
- placeholder מטושטש (blur placeholder)
- שמירת מקום למניעת CLS
תרגיל 2 - פיצול קוד וייבוא דינמי¶
נתונה אפליקציית דשבורד עם כמה מודולים כבדים:
import MarkdownEditor from './MarkdownEditor'; // 200KB
import ChartLibrary from './ChartLibrary'; // 150KB
import PdfExporter from './PdfExporter'; // 100KB
import DataTable from './DataTable'; // 80KB
function Dashboard() {
const [activeTab, setActiveTab] = useState('table');
return (
<div>
<nav>
<button onClick={() => setActiveTab('table')}>טבלה</button>
<button onClick={() => setActiveTab('chart')}>גרף</button>
<button onClick={() => setActiveTab('editor')}>עורך</button>
<button onClick={() => setActiveTab('export')}>ייצוא</button>
</nav>
{activeTab === 'table' && <DataTable />}
{activeTab === 'chart' && <ChartLibrary />}
{activeTab === 'editor' && <MarkdownEditor />}
{activeTab === 'export' && <PdfExporter />}
</div>
);
}
שפרו את הקוד כך שכל טאב נטען רק כשצריך. הוסיפו loading states מתאימים.
תרגיל 3 - ניתוח ושיפור Bundle¶
נתון הקוד הבא שמשתמש בספריות כבדות. שפרו את גודל ה-bundle:
import _ from 'lodash';
import moment from 'moment';
import 'moment/locale/he';
import { v4 as uuid } from 'uuid';
import axios from 'axios';
function processData(items: any[]) {
return _.chain(items)
.filter(item => moment(item.date).isAfter(moment().subtract(30, 'days')))
.sortBy('name')
.map(item => ({
...item,
id: uuid(),
formattedDate: moment(item.date).locale('he').format('DD MMMM YYYY'),
}))
.value();
}
async function fetchData() {
const response = await axios.get('/api/data');
return response.data;
}
החליפו את הספריות הכבדות בחלופות קלות או בקוד native. ציינו את החיסכון בגודל bundle לכל שינוי.
תרגיל 4 - אופטימיזציות React¶
נתונה קומפוננטת רשימת משימות עם בעיות ביצועים. כל שינוי ב-input גורם לרינדור מחדש של כל הרשימה (1000 פריטים):
'use client';
import { useState } from 'react';
interface Todo {
id: number;
text: string;
completed: boolean;
}
function TodoApp() {
const [todos, setTodos] = useState<Todo[]>(generateTodos(1000));
const [newTodo, setNewTodo] = useState('');
const [filter, setFilter] = useState('all');
const filteredTodos = todos.filter(todo => {
if (filter === 'active') return !todo.completed;
if (filter === 'completed') return todo.completed;
return true;
});
const stats = {
total: todos.length,
completed: todos.filter(t => t.completed).length,
active: todos.filter(t => !t.completed).length,
};
function toggleTodo(id: number) {
setTodos(todos.map(t => t.id === id ? { ...t, completed: !t.completed } : t));
}
function deleteTodo(id: number) {
setTodos(todos.filter(t => t.id !== id));
}
function addTodo() {
if (!newTodo.trim()) return;
setTodos([...todos, { id: Date.now(), text: newTodo, completed: false }]);
setNewTodo('');
}
return (
<div>
<h1>רשימת משימות</h1>
<div>
<input value={newTodo} onChange={e => setNewTodo(e.target.value)} />
<button onClick={addTodo}>הוסף</button>
</div>
<div>
<span>סה"כ: {stats.total}</span>
<span>הושלמו: {stats.completed}</span>
<span>פעילות: {stats.active}</span>
</div>
<div>
<button onClick={() => setFilter('all')}>הכל</button>
<button onClick={() => setFilter('active')}>פעילות</button>
<button onClick={() => setFilter('completed')}>הושלמו</button>
</div>
<ul>
{filteredTodos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>מחק</button>
</li>
))}
</ul>
</div>
);
}
שפרו עם: memo, useMemo, useCallback, וירטואליזציה (react-window).
תרגיל 5 - אסטרטגיית Caching¶
תכננו ויישמו אסטרטגיית caching מלאה לאתר חדשות:
- דף הבית - מתעדכן כל 5 דקות
- דף מאמר - מתעדכן כל שעה
- תמונות - cache ארוך (חודש)
- קבצי JS/CSS סטטיים - cache ארוך מאוד (שנה, immutable)
- API responses - cache קצר עם stale-while-revalidate
כתבו:
- הגדרות Cache-Control ב-next.config.js
- הגדרות revalidation בדפי Next.js
- הגדרות cache ב-fetch calls
תרגיל 6 - אופטימיזציית פונטים¶
נתון אתר שטוען 4 פונטים מ-Google Fonts:
- Rubik (עברית) - Regular, Bold
- Inter (אנגלית) - Regular, Medium, Bold
- Fira Code (קוד) - Regular
- Open Sans (תוכן) - Regular, Bold
הבעיות:
- כל הפונטים נטענים מ-Google Fonts CDN
- אין display strategy
- האתר מציג FOUT ו-CLS
שפרו:
- השתמשו ב-next/font
- הגדירו CSS variables
- בטלו את Open Sans (מיותר - Rubik ו-Inter מספיקים)
- הגדירו display strategy ו-fallbacks
- טענו רק את ה-subsets הנדרשים
שאלות¶
- מה ההבדל בין
loading="lazy"ל-priorityב-next/image? מתי משתמשים בכל אחד? - מה זה Tree Shaking ולמה זה לא עובד עם CommonJS?
- הסבירו את ההבדל בין
React.memo,useMemo, ו-useCallback. מתי משתמשים בכל אחד? - מה זה stale-while-revalidate ואיך זה משפר את חווית המשתמש?
- למה וירטואליזציה חשובה ומה החיסרון שלה?