רשימות

מבוא

כמעט כל ממשק מציג נתונים בצורת רשימה - מוצרים, הודעות, משתמשים ועוד. ב-React ממירים מערך נתונים לרשימת אלמנטים בעזרת המתודה map(), שיוצרת אלמנט JSX עבור כל פריט במערך:

const fruits = ['תפוח', 'בננה', 'תפוז'];

function FruitList() {
    return (
        <ul>
            {fruits.map(fruit => (
                <li key={fruit}>{fruit}</li>
            ))}
        </ul>
    );
}

חשיבות ה-key

כל פריט ברשימה חייב key - מזהה ייחודי ויציב שעוזר ל-React לעקוב אחר הפריטים בין רינדורים ולזהות איזה מהם השתנה, נוסף או נמחק. בזכות ה-key, React מעדכנת רק את מה שהשתנה במקום לבנות את כל הרשימה מחדש. המזהה הטבעי ביותר הוא בדרך כלל ה-id שמגיע מהנתונים:

✅ נכון - key ייחודי

const users = [
    { id: 1, name: 'דני' },
    { id: 2, name: 'רונית' },
    { id: 3, name: 'משה' }
];

function UserList() {
    return (
        <ul>
            {users.map(user => (
                <li key={user.id}>{user.name}</li>
            ))}
        </ul>
    );
}

❌ בעייתי - index כ-key

// לא מומלץ! עלול לגרום לבאגים כשהרשימה משתנה
{items.map((item, index) => (
    <li key={index}>{item}</li>
))}

כללים ל-key

  1. חייב להיות ייחודי בין אחים
  2. חייב להיות יציב - לא להשתנות בין רינדורים
  3. לא להשתמש ב-Math.random() או Date.now()
  4. index מתאים רק לרשימות סטטיות שלא משתנות

רינדור רשימות

רשימה פשוטה

function ColorList() {
    const colors = ['אדום', 'ירוק', 'כחול', 'צהוב'];

    return (
        <ul>
            {colors.map((color, index) => (
                <li key={index} style={{ color }}>
                    {color}
                </li>
            ))}
        </ul>
    );
}

רשימת אובייקטים

function ProductList() {
    const products = [
        { id: 1, name: 'מקלדת', price: 150 },
        { id: 2, name: 'עכבר', price: 80 },
        { id: 3, name: 'מסך', price: 1200 }
    ];

    return (
        <div className="products">
            {products.map(product => (
                <div key={product.id} className="product-card">
                    <h3>{product.name}</h3>
                    <p>מחיר: ₪{product.price}</p>
                </div>
            ))}
        </div>
    );
}

רשימה עם קומפוננטה

function ProductCard({ product }) {
    return (
        <div className="product-card">
            <h3>{product.name}</h3>
            <p>מחיר: ₪{product.price}</p>
            <button>הוסף לסל</button>
        </div>
    );
}

function ProductList({ products }) {
    return (
        <div className="products">
            {products.map(product => (
                <ProductCard key={product.id} product={product} />
            ))}
        </div>
    );
}

פעולות על רשימות

סינון (Filter)

function FilteredList() {
    const [filter, setFilter] = useState('');
    const items = ['תפוח', 'בננה', 'תפוז', 'אבטיח'];

    const filteredItems = items.filter(item =>
        item.includes(filter)
    );

    return (
        <div>
            <input 
                value={filter}
                onChange={(e) => setFilter(e.target.value)}
                placeholder="חפש..."
            />
            <ul>
                {filteredItems.map(item => (
                    <li key={item}>{item}</li>
                ))}
            </ul>
        </div>
    );
}

מיון (Sort)

function SortedList() {
    const [sortOrder, setSortOrder] = useState('asc');
    const items = [
        { id: 1, name: 'ב' },
        { id: 2, name: 'א' },
        { id: 3, name: 'ג' }
    ];

    const sortedItems = [...items].sort((a, b) => {
        if (sortOrder === 'asc') {
            return a.name.localeCompare(b.name, 'he');
        }
        return b.name.localeCompare(a.name, 'he');
    });

    return (
        <div>
            <button onClick={() => setSortOrder(o => o === 'asc' ? 'desc' : 'asc')}>
                מיון: {sortOrder === 'asc' ? '⬆️' : '⬇️'}
            </button>
            <ul>
                {sortedItems.map(item => (
                    <li key={item.id}>{item.name}</li>
                ))}
            </ul>
        </div>
    );
}

הוספה ומחיקה

function TodoList() {
    const [todos, setTodos] = useState([
        { id: 1, text: 'לקנות חלב' },
        { id: 2, text: 'לשלם חשבונות' }
    ]);
    const [newTodo, setNewTodo] = useState('');

    // הוספה
    const addTodo = () => {
        if (!newTodo.trim()) return;
        setTodos(prev => [
            ...prev,
            { id: Date.now(), text: newTodo }
        ]);
        setNewTodo('');
    };

    // מחיקה
    const deleteTodo = (id) => {
        setTodos(prev => prev.filter(todo => todo.id !== id));
    };

    return (
        <div>
            <div>
                <input 
                    value={newTodo}
                    onChange={(e) => setNewTodo(e.target.value)}
                    onKeyPress={(e) => e.key === 'Enter' && addTodo()}
                />
                <button onClick={addTodo}>הוסף</button>
            </div>
            <ul>
                {todos.map(todo => (
                    <li key={todo.id}>
                        {todo.text}
                        <button onClick={() => deleteTodo(todo.id)}>❌</button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

טבלאות

function UserTable() {
    const users = [
        { id: 1, name: 'דני', age: 25, city: 'תל אביב' },
        { id: 2, name: 'רונית', age: 30, city: 'ירושלים' },
        { id: 3, name: 'משה', age: 28, city: 'חיפה' }
    ];

    return (
        <table>
            <thead>
                <tr>
                    <th>שם</th>
                    <th>גיל</th>
                    <th>עיר</th>
                </tr>
            </thead>
            <tbody>
                {users.map(user => (
                    <tr key={user.id}>
                        <td>{user.name}</td>
                        <td>{user.age}</td>
                        <td>{user.city}</td>
                    </tr>
                ))}
            </tbody>
        </table>
    );
}

רשימות מקוננות

function CategoryList() {
    const categories = [
        {
            id: 1,
            name: 'פירות',
            items: ['תפוח', 'בננה', 'תפוז']
        },
        {
            id: 2,
            name: 'ירקות',
            items: ['מלפפון', 'עגבנייה', 'גזר']
        }
    ];

    return (
        <div>
            {categories.map(category => (
                <div key={category.id}>
                    <h3>{category.name}</h3>
                    <ul>
                        {category.items.map((item, index) => (
                            <li key={index}>{item}</li>
                        ))}
                    </ul>
                </div>
            ))}
        </div>
    );
}

סיכום

פעולה מתודה
הצגת רשימה array.map()
סינון array.filter()
מיון [...array].sort()
הוספה [...array, newItem]
מחיקה array.filter(x => x.id !== id)
עדכון array.map(x => x.id === id ? {...x, ...update} : x)

הקודם: ← רינדור מותנה
הבא: תרגילים →

בדקו את עצמכם

נסו לענות לבד לפני שאתם פותחים את התשובה.

  1. למה כל איבר ברשימה צריך key?

    1. כדי למיין
    2. זו רק אזהרה
    3. כדי ש-React תזהה איזה איבר השתנה, נוסף או נמחק
    4. עיצוב
    הצגת התשובה

    תשובה ג. key יציב מאפשר עדכון נכון ויעיל.

  2. מה עדיף כ-key?

    1. שם הקומפוננטה
    2. Math.random()
    3. האינדקס במערך
    4. מזהה ייחודי ויציב מהנתונים, כמו id
    הצגת התשובה

    תשובה ד. אינדקס משתנה כשמוחקים או ממיינים, ו-random משתנה בכל רינדור.