לדלג לתוכן

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>
    );
}

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

כשמשתמשים בקומפוננטה, כותבים אותה כtag 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). קומפוננטת אב שולטת בנתונים ומעבירה אותם לקומפוננטות בנות.

תרשים המראה כיצד קומפוננטת האב App מעבירה את הפרופס לקומפוננטת הבן Greeting בכיוון אחד, וכשהאב משנה את הערך הבן מרנדר מחדש עם הערך החדש

ילדים - children

כשעוטפים תוכן בין tag פתיחה לסגירה של קומפוננטה, התוכן מגיע ב-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 מורכב מקומפוננטות פשוטות