לדלג לתוכן

6.2 קומפוננטות ופרופס הרצאה

קומפוננטות - Components

קומפוננטה היא אבן הבניין הבסיסית בריאקט. כל חלק ב-UI הוא קומפוננטה - כפתור, כרטיס, תפריט, עמוד שלם. קומפוננטה היא פונקציה שמקבלת קלט (props) ומחזירה JSX:

function Button() {
    return <button>Click me</button>;
}

כללים בסיסיים

  • שם הקומפוננטה חייב להתחיל באות גדולה - PascalCase (לא camelCase)
  • הקומפוננטה חייבת להחזיר JSX (או null)
  • כל קובץ בדרך כלל מכיל קומפוננטה אחת ומייצא אותה
// Button.tsx
function Button() {
    return <button>Click me</button>;
}

export default Button;
// App.tsx
import Button from "./Button";

function App() {
    return (
        <div>
            <h1>My App</h1>
            <Button />
        </div>
    );
}

קומפוננטה כאלמנט

כשמשתמשים בקומפוננטה, כותבים אותה כתגית JSX. אם אין לה תוכן פנימי, אפשר לסגור אותה עצמאית:

// self-closing tag (no children)
<Button />

// with children (we'll learn about this soon)
<Card>
    <h2>Title</h2>
</Card>

פרופס - Props

פרופס (properties) הם הדרך להעביר נתונים מקומפוננטת אב לקומפוננטת בן. הם עובדים כמו אטריביוטים ב-HTML:

// passing props
<Greeting name="Alice" age={30} />

// receiving props
function Greeting(props: { name: string; age: number }) {
    return (
        <h1>
            Hello, {props.name}! You are {props.age} years old.
        </h1>
    );
}

פירוק - Destructuring

במקום לגשת ל-props.name, props.age וכו׳, מקובל לפרק את ה-props ישירות:

function Greeting({ name, age }: { name: string; age: number }) {
    return (
        <h1>
            Hello, {name}! You are {age} years old.
        </h1>
    );
}

הגדרת טיפוס ל-Props

הדרך המומלצת היא להגדיר interface או type עבור ה-props:

interface GreetingProps {
    name: string;
    age: number;
}

function Greeting({ name, age }: GreetingProps) {
    return (
        <h1>
            Hello, {name}! You are {age} years old.
        </h1>
    );
}

זה נותן השלמה אוטומטית וזיהוי שגיאות במקום השימוש:

// OK
<Greeting name="Alice" age={30} />

// ERROR: Property 'age' is missing
<Greeting name="Alice" />

// ERROR: Type 'string' is not assignable to type 'number'
<Greeting name="Alice" age="thirty" />

ערכי ברירת מחדל - Default Props

אפשר להגדיר prop כאופציונלי עם ? ולתת ערך ברירת מחדל:

interface ButtonProps {
    label: string;
    color?: string;    // optional
    size?: number;     // optional
}

function Button({ label, color = "blue", size = 16 }: ButtonProps) {
    return (
        <button style={{ backgroundColor: color, fontSize: `${size}px` }}>
            {label}
        </button>
    );
}

// usage
<Button label="Click" />                    // color="blue", size=16
<Button label="Click" color="red" />        // color="red", size=16
<Button label="Click" color="green" size={20} />  // color="green", size=20

פרופס הם קריאה-בלבד - Read-Only

פרופס הם immutable - הקומפוננטה לא יכולה לשנות את ה-props שקיבלה:

function Greeting({ name }: { name: string }) {
    // WRONG - never mutate props!
    // name = "Bob";

    return <h1>Hello, {name}!</h1>;
}

הנתונים זורמים בכיוון אחד - מלמעלה למטה (one-way data flow). קומפוננטת אב שולטת בנתונים ומעבירה אותם לקומפוננטות בנות.

ילדים - children

כשעוטפים תוכן בין תגית פתיחה לסגירה של קומפוננטה, התוכן מגיע ב-prop מיוחד בשם children:

interface CardProps {
    title: string;
    children: React.ReactNode;
}

function Card({ title, children }: CardProps) {
    return (
        <div className="card">
            <h2>{title}</h2>
            <div className="card-body">{children}</div>
        </div>
    );
}

// usage
function App() {
    return (
        <Card title="Welcome">
            <p>This is the card content.</p>
            <p>It can contain any JSX.</p>
        </Card>
    );
}

React.ReactNode הוא הטיפוס שמתאר כל דבר שאפשר לרנדר - מחרוזת, מספר, JSX, מערך שלהם, או null.

פרופס כ-callback - קריאה חוזרת

פרופס יכולים להיות גם פונקציות. זה מאפשר לקומפוננטת בן לתקשר חזרה עם קומפוננטת האב:

interface ButtonProps {
    label: string;
    onClick: () => void;
}

function Button({ label, onClick }: ButtonProps) {
    return <button onClick={onClick}>{label}</button>;
}

function App() {
    const handleClick = () => {
        console.log("Button was clicked!");
    };

    return <Button label="Click me" onClick={handleClick} />;
}

דוגמה עם העברת נתונים מהבן לאב:

interface SearchBoxProps {
    onSearch: (query: string) => void;
}

function SearchBox({ onSearch }: SearchBoxProps) {
    return (
        <input
            type="text"
            placeholder="Search..."
            onChange={(e) => onSearch(e.target.value)}
        />
    );
}

function App() {
    const handleSearch = (query: string) => {
        console.log("Searching for:", query);
    };

    return <SearchBox onSearch={handleSearch} />;
}

הרכבה - Composition

ריאקט מעודדת הרכבה (composition) - בניית קומפוננטות מורכבות מקומפוננטות פשוטות:

interface AvatarProps {
    src: string;
    alt: string;
}

function Avatar({ src, alt }: AvatarProps) {
    return <img className="avatar" src={src} alt={alt} />;
}

interface UserInfoProps {
    name: string;
    email: string;
}

function UserInfo({ name, email }: UserInfoProps) {
    return (
        <div>
            <h3>{name}</h3>
            <p>{email}</p>
        </div>
    );
}

interface UserCardProps {
    name: string;
    email: string;
    avatarUrl: string;
}

function UserCard({ name, email, avatarUrl }: UserCardProps) {
    return (
        <div className="user-card">
            <Avatar src={avatarUrl} alt={name} />
            <UserInfo name={name} email={email} />
        </div>
    );
}

כל קומפוננטה אחראית על חלק אחד. UserCard מרכיבה אותן ביחד.

חשוב - מתי לפצל לקומפוננטה נפרדת

  • כשחלק מה-UI חוזר על עצמו (DRY)
  • כשקומפוננטה גדלה מדי ונהיית קשה לקריאה
  • כשחלק מה-UI הוא יחידה לוגית עצמאית
  • כשרוצים לשתף קוד UI בין מסכים שונים

אין צורך לפצל כל div לקומפוננטה נפרדת. מתחילים עם קומפוננטות גדולות ומפצלים כשזה הופך לנחוץ.

סיכום

  • קומפוננטה היא פונקציה שמחזירה JSX, עם שם ב-PascalCase
  • פרופס מעבירים נתונים מאב לבן - כמו אטריביוטים ב-HTML
  • מגדירים טיפוס ל-props עם interface כדי לקבל בדיקת טיפוסים והשלמה אוטומטית
  • פרופס הם immutable - הקומפוננטה לא משנה אותם
  • children הוא prop מיוחד שמכיל את התוכן שעטוף בקומפוננטה
  • פרופס יכולים להיות פונקציות (callbacks) לתקשורת מבן לאב
  • הרכבה (composition) - בניית UI מורכב מקומפוננטות פשוטות