Component Composition ב-React: איך לבנות קומפוננטות גמישות ושימושיות
סער טויטו
Software & Web Development
2025-09-01
•
12 דקות קריאה
הנה שאלת ראיון עבודה ב-React (אבל האמת, זה רלוונטי כמעט לכל פרויקט פיתוח): איך הייתם בונים כרטיס (Card) שיכול להופיע במיליון וריאציות שונות - עם תמונה או בלי, עם כפתור לייק או בלי, עם לינק או בלי… מבלי לשכפל קוד בכל פעם מחדש?
התשובה היא 👈 Component Composition.
אם עניתם "נעשה את זה דרך props" - אז אתם עדיין לא חושבים מספיק Engineering-minded - אבל לא נורא, אתם תלמדו את זה עכשיו בצורה מקצועית.
אבל למה זה לא מספיק טוב דרך props? כי אם היינו מנסים לנהל את זה עם props בלבד, היינו מוצאים את עצמנו עם עשרות props שונים, והרבה לוגיקה מסובכת וקשה לניהול. בקומפוזיציה לעומת זאת, אנחנו מפרקים את הבעיה לרכיבים קטנים וגמישים, ומרכיבים אותם יחד לפי הצורך. הרבה יותר נקי, הרבה יותר חכם.
מה זה Component Composition?
Component Composition היא תבנית עיצוב שמאפשרת לבנות קומפוננטות מורכבות על ידי שילוב של קומפוננטות קטנות ופשוטות יותר. במקום ליצור הקומפוננטה מונוליתית גדולה עם הרבה פונקציונליות, אנחנו מפרקים את הקומפוננטה לקומפוננטות קטנות, שכל אחת מהן אחראית על משימה ספציפית.
בתמונה למעלה אתם יכולים לראות בדיוק את מה שאנחנו הולכים לבנות - אותם building blocks שיוצרים 4 סוגים שונים של כרטיסים:
Basic Card - כרטיס פשוט עם תוכן
Interactive Card - כרטיס עם כפתור לייק
Linked Card - כרטיס שכל הקליק עליו מוביל לקישור
Full Composition - כרטיס עם תמונה, overlay, וכל הפונקציונליות
במקום לכתוב 4 קומפוננטות שונות או קומפוננטה אחת עם המון props, נבנה חלקים קטנים שניתן לשלב בצורות שונות. קודם כל, נבין את ההירכיה של הקבצים שלנו כדי לשמור על סדר:
נתחיל עם הקומפוננטות הבסיסיות ביותר - Card ו-CardContent. אלה הם building blocks שעליהם נבנה את שאר הפונקציונליות. הקומפוננטות האלה יהיו פשוטים מאוד - הם רק יספקו מבנה ועיצוב בסיסי.
ראשית, בואו ניצור את הקומפוננטה Card הבסיסית:
// Card Component - Basic wrapper import React from 'react'; import styles from './Card.module.scss';
שוב, הקומפוננטה אחראית רק על המבנה והעיצוב של אזור התוכן. שימו לב שהקומפוננטות האלו עושים רק את העבודה הספציפית שלהם.
הוספת פונקציונליות - LikeButton
עכשיו נוסיף קומפוננטה נוספת עם פונקציונליות ספציפית - כפתור לייק. זה יהיה הקומפוננטה הראשונה שלנו שיש לה state פנימי ולוגיקה עסקית. היא תהיה אחראית רק על פונקציונליות הלייק ושום דבר אחר.
import React, { useState } from 'react'; import { Heart } from 'lucide-react'; import styles from './LikeButton.module.scss';
הקומפוננטה הזו מדגימה עיקרון חשוב - היא מכילה את כל הלוגיקה של הלייק (state, מעבר בין מצבים, callback לחיצה), אבל היא לא תלויה בשום קומפוננטה אחרת. זה אומר שאפשר להשתמש בה בכל מקום, לא רק בכרטיסים.
תמונה עם Overlay - ImageOverlay
בואו נוסיף קומפוננטה נוספת שנצטרך - ImageOverlay. זו הקומפוננטה שמציגה תמונה עם יכולת להוסיף תוכן נוסף מעליה (כמו "Featured" בדוגמה שלנו):
import React from 'react'; import styles from './ImageOverlay.module.scss';
שוב, הקומפוננטה פשוטה וממוקדת - היא אחראית רק על הצגת תמונה עם תוכן אופציונלי מעליה. אם אין overlay, היא פשוט לא מציגה אותו.
מעטף לקישורים - LinkWrapper
לעיתים אנחנו רוצים שכל הכרטיס יהיה קישור, אבל לא תמיד. LinkWrapper היא קומפוננטה חכמה שיכולה להוסיף פונקציונליות של קישור לכל תוכן מבלי לשנות אותו. אם אין קישור - היא פשוט מחזירה את התוכן כמו שהוא.
import React from 'react'; import { ExternalLink } from 'lucide-react'; import styles from './LinkWrapper.module.scss';
זו דוגמה מעולה ל-conditional rendering ולעיקרון הגמישות. הקומפוננטה הזו מאפשרת לנו להוסיף או להסיר פונקציונליות קישור מכל תוכן בלי לשנות את הקומפוננטה המקורית.
שילוב הקומפוננטות - מפשוט למורכב
עכשיו הגיע החלק המהנה - נראה איך לשלב את הקומפוננטות האלו יחד בדרכים שונות. זה בדיוק מה שראיתם בתמונה בתחילת המאמר - אותם building blocks יוצרים 4 חוויות שונות לגמרי!
1. Basic Card - הבסיס הפשוט
זהו הכרטיס הפשוט ביותר - רק תוכן בסיסי:
// Basic card - just content <Card> <CardContent> <h3>Basic Card</h3> <p>Simple card with content</p> </CardContent> </Card>
2. Interactive Card - הוספת אינטרקטיביות
עכשיו נוסיף כפתור לייק לאותו כרטיס:
// Interactive card with like functionality <Card> <CardContent> <h3>Interactive Card</h3> <p>Card with like button</p> <LikeButton onLike={(liked) => console.log('Liked:', liked)} /> </CardContent> </Card>
3. Linked Card - כל הכרטיס כקישור
עכשיו נהפוך את כל הכרטיס לקליק עם אייקון קישור חיצוני:
// Clickable card - entire card becomes a link <LinkWrapper href="https://example.com" external> <Card> <CardContent> <h3>Linked Card</h3> <p>Click anywhere to navigate</p> </CardContent> </Card> </LinkWrapper>
שימו לב איך כל קומפוננטה שומרת על האחריות שלה, ואיך אנחנו יכולים לשלב אותן בדרכים שונות ללא שינוי בקוד הבסיסי. זה הקסם של composition!
שילוב מורכב - Full Composition
ועכשיו הדוגמה הכי מרשימה - הכרטיס המורכב ביותר שמשלב את כל מה שבנינו: תמונה עם overlay, קישור, וכפתור לייק. זו בדיוק הדוגמה הרביעית בתמונה:
// Complex composition - Image + Link + Interactive elements <LinkWrapper href="https://example.com" external> <Card> <ImageOverlay src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=200&fit=crop" alt="Mountain landscape" overlay={<div style={{ color: 'white', fontWeight: 'bold' }}>Featured</div>} /> <CardContent> <h3>Full Composition</h3> <p>Image + Link + Like button</p> {/* Prevent like button from triggering link */} <div onClick={(e) => e.preventDefault()}> <LikeButton initialLiked /> </div> </CardContent> </Card> </LinkWrapper>
הפרט החשוב כאן היא שעטפנו את ה-LikeButton ב-div עם preventDefault כדי למנוע מהקליק על הלייק לפעיל גם את הקישור. זה מדגים איך composition מאפשר לנו לפתור בעיות מורכבות של UX בצורה נקייה ומובנת.
Compound Components - דפוס מתקדם יותר
עד כה ראינו Component Composition - קומפוננטות עצמאיות שניתן לשלב. עכשיו בואו נכיר דפוס מתקדם יותר: Compound Components. זהו דפוס ספציפי בתוך עולם ה-composition שבו קומפוננטות עובדות יחד כיחידה אחת ומשתפות state או לוגיקה.
ב-Compound Components יש קומפוננטת-אב אחת והקומפוננטות ילדים "מדברים" איתה דרך Context API. בואו נראה דוגמה של Select עם Options:
// Compound Components example - Select with Options import React, { createContext, useContext, useState } from 'react';
// Create context for internal communication const SelectContext = createContext();
// Parent component that manages state function Select({ children, defaultValue, onChange }) { const [value, setValue] = useState(defaultValue);
// Attach Option as static property for cleaner API Select.Option = Option;
export { Select };
וכך נשתמש בקומפוננטות האלה:
// Usage - Notice how clean the API is <Select defaultValue="1" onChange={(value) => console.log('Selected:', value)}> <Select.Option value="1">אופציה 1</Select.Option> <Select.Option value="2">אופציה 2</Select.Option> <Select.Option value="3">אופציה 3</Select.Option> </Select>
כאן ה-Option לא עובד לבד - הוא חייב להיות בתוך <Select>. כל הילדים "מדברים" עם האב דרך context. זה מה שהופך את זה ל-compound components.
סיכום
אז איך עונים על שאלת הראיון שפתחנו איתה? בדיוק כמו שראיתם בתמונה בתחילת המאמר - Component Composition מאפשר לבנות ממשקי משתמש גמישים ושימושיים. במקום קומפוננטה אחד עם עשרות props, בנינו חלקים קטנים וגמישים שניתן לשלב בדרכים בלתי מוגבלות.
חזרו לתמונה למעלה ותראו איך 4 building blocks פשוטים יצרו 4 חוויות שונות לגמרי - וזה רק ההתחלה! אפשר לשלב את הקומפוננטות האלה עם עוד קומפוננטות ליצירת עשרות וריאציות נוספות.