6.6 רשימות ומפתחות הרצאה
רינדור רשימות - Lists¶
בריאקט מציגים רשימות באמצעות map() - ממפים מערך של נתונים למערך של אלמנטי JSX:
function FruitList() {
const fruits = ["Apple", "Banana", "Cherry", "Date"];
return (
<ul>
{fruits.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
}
map() עובר על כל איבר במערך, מפעיל את הפונקציה, ומחזיר מערך חדש של אלמנטי JSX.
רשימות של אובייקטים¶
interface User {
id: number;
name: string;
email: string;
}
function UserList() {
const users: User[] = [
{ id: 1, name: "Alice", email: "alice@example.com" },
{ id: 2, name: "Bob", email: "bob@example.com" },
{ id: 3, name: "Charlie", email: "charlie@example.com" },
];
return (
<div>
{users.map((user) => (
<div key={user.id} style={{ border: "1px solid #ddd", padding: "8px", margin: "4px 0" }}>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
))}
</div>
);
}
חילוץ לקומפוננטה נפרדת¶
כשהפריט ברשימה מורכב, חלצו אותו לקומפוננטה:
interface UserCardProps {
user: User;
}
function UserCard({ user }: UserCardProps) {
return (
<div style={{ border: "1px solid #ddd", padding: "8px", margin: "4px 0" }}>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
function UserList() {
const users: User[] = [
{ id: 1, name: "Alice", email: "alice@example.com" },
{ id: 2, name: "Bob", email: "bob@example.com" },
];
return (
<div>
{users.map((user) => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}
שימו לב: ה-key נמצא על <UserCard> (האלמנט שמוחזר מ-map), לא בתוך UserCard.
מפתחות - Keys¶
key הוא prop מיוחד שריאקט צריכה כדי לזהות כל פריט ברשימה. בלי key, ריאקט מציגה אזהרה בקונסולה.
למה צריך key¶
ריאקט משתמשת ב-key כדי להבין מה השתנה ברשימה בין רינדורים:
// before
<li key="1">Alice</li>
<li key="2">Bob</li>
// after (added Charlie at the beginning)
<li key="3">Charlie</li>
<li key="1">Alice</li>
<li key="2">Bob</li>
בזכות ה-keys, ריאקט יודעת ש-Alice ו-Bob לא השתנו - רק Charlie חדש. בלי keys, ריאקט הייתה צריכה לעדכן את כל הפריטים.
כללים לבחירת key¶
key טוב:
- מזהה ייחודי מהנתונים (id מ-database, UUID)
- ערך ייחודי טבעי (קוד מדינה, כתובת אימייל)
// good - stable, unique ID
users.map((user) => <UserCard key={user.id} user={user} />)
// good - unique natural value
countries.map((country) => <Option key={country.code} value={country.code} />)
key רע:
- אינדקס (
index) - משתנה כשמוסיפים/מוחקים/ממיינים - ערך אקראי (
Math.random()) - משתנה בכל רינדור - ערך לא ייחודי - שני פריטים עם אותו key
// bad - index changes when items are reordered
items.map((item, index) => <Item key={index} item={item} />)
// bad - creates new key every render, destroying and recreating all components
items.map((item) => <Item key={Math.random()} item={item} />)
מתי אפשר להשתמש באינדקס¶
אינדקס כ-key מתאים רק כשכל התנאים מתקיימים:
- הרשימה סטטית ולא משתנה
- הפריטים לא ממוינים מחדש
- אין הוספה או מחיקה של פריטים
// OK - static list that never changes
const menuItems = ["Home", "About", "Contact"];
menuItems.map((item, index) => <li key={index}>{item}</li>);
בכל מקרה אחר, השתמשו ב-id ייחודי.
התאמה - Reconciliation¶
ריאקט משתמשת באלגוריתם שנקרא reconciliation כדי לעדכן את ה-DOM ביעילות:
- כשהסטייט משתנה, ריאקט יוצרת עץ JSX חדש (Virtual DOM)
- ריאקט משווה את העץ החדש לישן (diffing)
- ריאקט מעדכנת רק את ההבדלים ב-DOM האמיתי
ה-key עוזר ל-reconciliation:
- אם key קיים בשני העצים - ריאקט מעדכנת את הקומפוננטה (אם ה-props השתנו)
- אם key חדש - ריאקט יוצרת קומפוננטה חדשה
- אם key נעלם - ריאקט מוחקת את הקומפוננטה
דוגמה מעשית - למה index הוא key רע¶
import { useState } from "react";
interface Todo {
id: number;
text: string;
}
function TodoList() {
const [todos, setTodos] = useState<Todo[]>([
{ id: 1, text: "Learn React" },
{ id: 2, text: "Build a project" },
{ id: 3, text: "Deploy to production" },
]);
const removeFirst = () => {
setTodos(todos.slice(1)); // remove the first item
};
return (
<div>
<button onClick={removeFirst}>Remove first item</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<input type="text" defaultValue={todo.text} />
</li>
))}
</ul>
</div>
);
}
עם key={todo.id}, כשמוחקים את הפריט הראשון, ריאקט מוחקת את ה-input הראשון. עם key={index}, ריאקט "זזה" את הערכים ויכולה להציג ערכים לא נכונים ב-inputs.
סינון ומיון רשימות¶
אפשר לשלב filter ו-sort עם map:
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}
function ProductList() {
const products: Product[] = [
{ id: 1, name: "Laptop", price: 999, inStock: true },
{ id: 2, name: "Phone", price: 699, inStock: false },
{ id: 3, name: "Tablet", price: 449, inStock: true },
{ id: 4, name: "Watch", price: 299, inStock: true },
];
// filter first, then sort, then map to JSX
const availableProducts = products
.filter((p) => p.inStock)
.sort((a, b) => a.price - b.price);
return (
<ul>
{availableProducts.map((product) => (
<li key={product.id}>
{product.name} - ${product.price}
</li>
))}
</ul>
);
}
חשוב: חשבו על הסדר - filter לפני sort לפני map. זה שומר על הקוד קריא ויעיל.
רשימות מקוננות¶
כשיש נתונים מקוננים (למשל קטגוריות עם פריטים), משתמשים ב-map בתוך map:
interface Category {
id: number;
name: string;
items: string[];
}
function CategoryList() {
const categories: Category[] = [
{ id: 1, name: "Fruits", items: ["Apple", "Banana", "Cherry"] },
{ id: 2, name: "Vegetables", items: ["Carrot", "Broccoli"] },
];
return (
<div>
{categories.map((category) => (
<div key={category.id}>
<h3>{category.name}</h3>
<ul>
{category.items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
))}
</div>
);
}
סיכום¶
- מציגים רשימות עם
map()- ממפים נתונים לאלמנטי JSX - כל פריט ברשימה חייב
key- מזהה ייחודי ויציב - key טוב: id מהנתונים. key רע: index (ברשימות דינמיות) או Math.random()
- ה-key עוזר לריאקט ב-reconciliation - זיהוי מה השתנה, נוסף, או נמחק
- אינדקס כ-key מתאים רק לרשימות סטטיות שלא משתנות
- סינון עם filter, מיון עם sort, ואז map ל-JSX
- רשימות מקוננות - map בתוך map, עם keys בכל רמה