לדלג לתוכן

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 ביעילות:

  1. כשהסטייט משתנה, ריאקט יוצרת עץ JSX חדש (Virtual DOM)
  2. ריאקט משווה את העץ החדש לישן (diffing)
  3. ריאקט מעדכנת רק את ההבדלים ב-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 בכל רמה