לדלג לתוכן

6.3 טיפוסים בריאקט הרצאה

טיפוסים בריאקט - TypeScript with React

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

טיפוסי Props

כבר ראינו את הדרך הבסיסית להגדיר props עם interface:

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

props אופציונליים

interface CardProps {
    title: string;
    subtitle?: string;       // optional - can be string or undefined
    maxWidth?: number;        // optional with default value
}

function Card({ title, subtitle, maxWidth = 400 }: CardProps) {
    return (
        <div style={{ maxWidth: `${maxWidth}px` }}>
            <h2>{title}</h2>
            {subtitle && <p>{subtitle}</p>}
        </div>
    );
}

union types ב-props

interface AlertProps {
    message: string;
    severity: "info" | "warning" | "error" | "success";
}

function Alert({ message, severity }: AlertProps) {
    const colors: Record<string, string> = {
        info: "#2196f3",
        warning: "#ff9800",
        error: "#f44336",
        success: "#4caf50",
    };

    return (
        <div style={{ backgroundColor: colors[severity], color: "white", padding: "12px" }}>
            {message}
        </div>
    );
}

// usage
<Alert message="Operation successful" severity="success" />
<Alert message="Something went wrong" severity="error" />

ReactNode - הטיפוס לתוכן שניתן לרנדור

React.ReactNode הוא הטיפוס שמתאר כל דבר שריאקט יכולה לרנדר:

// ReactNode includes:
// - string
// - number
// - boolean (renders nothing)
// - null / undefined (renders nothing)
// - JSX elements
// - arrays of the above

interface ContainerProps {
    children: React.ReactNode;
}

function Container({ children }: ContainerProps) {
    return <div className="container">{children}</div>;
}

// all of these are valid
<Container>Hello</Container>              // string
<Container>{42}</Container>               // number
<Container><p>Paragraph</p></Container>   // JSX element
<Container>{null}</Container>             // null - renders nothing

ReactElement לעומת ReactNode

  • React.ReactNode - כל דבר שאפשר לרנדר (כולל string, number, null)
  • React.ReactElement - רק אלמנטי JSX (מגביל יותר)

ברוב המקרים נשתמש ב-ReactNode כי הוא גמיש יותר.

טיפוסי אירועים - Event Types

כשמטפלים באירועים בריאקט, כל אירוע מגיע עם טיפוס מתאים:

function Form() {
    // click event
    const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
        console.log("clicked at", e.clientX, e.clientY);
    };

    // change event (input)
    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        console.log("value:", e.target.value);
    };

    // form submit event
    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        console.log("form submitted");
    };

    // keyboard event
    const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
        if (e.key === "Enter") {
            console.log("Enter pressed");
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <input
                onChange={handleChange}
                onKeyDown={handleKeyDown}
            />
            <button onClick={handleClick}>Submit</button>
        </form>
    );
}

טיפוסי אירועים נפוצים

אירוע טיפוס
onClick React.MouseEvent<HTMLElement>
onChange React.ChangeEvent<HTMLInputElement>
onSubmit React.FormEvent<HTMLFormElement>
onKeyDown / onKeyUp React.KeyboardEvent<HTMLElement>
onFocus / onBlur React.FocusEvent<HTMLElement>
onMouseEnter / onMouseLeave React.MouseEvent<HTMLElement>

הטיפוס הגנרי (כמו HTMLButtonElement) מציין את סוג האלמנט שעליו האירוע מתרחש. זה נותן גישה מטויפסת ל-properties הספציפיים של האלמנט.

טיפוסי סגנון - CSSProperties

כשמעבירים אובייקט style ב-JSX, הטיפוס הוא React.CSSProperties:

interface BoxProps {
    style?: React.CSSProperties;
    children: React.ReactNode;
}

function Box({ style, children }: BoxProps) {
    const defaultStyle: React.CSSProperties = {
        padding: "16px",
        borderRadius: "8px",
        border: "1px solid #ddd",
    };

    return <div style={{ ...defaultStyle, ...style }}>{children}</div>;
}

// usage - TypeScript will autocomplete and validate CSS properties
<Box style={{ backgroundColor: "lightblue", fontWeight: "bold" }}>
    Content
</Box>

// ERROR: 'colour' does not exist in type 'CSSProperties'
<Box style={{ colour: "red" }}>Content</Box>

CSSProperties נותן השלמה אוטומטית לכל מאפייני ה-CSS ותופס שגיאות כתיב.

קומפוננטות גנריות - Generic Components

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

interface ListProps<T> {
    items: T[];
    renderItem: (item: T) => React.ReactNode;
}

function List<T>({ items, renderItem }: ListProps<T>) {
    return (
        <ul>
            {items.map((item, index) => (
                <li key={index}>{renderItem(item)}</li>
            ))}
        </ul>
    );
}

// usage with strings
<List
    items={["apple", "banana", "cherry"]}
    renderItem={(fruit) => <span>{fruit}</span>}
/>

// usage with objects
interface User {
    name: string;
    age: number;
}

<List<User>
    items={[{ name: "Alice", age: 30 }, { name: "Bob", age: 25 }]}
    renderItem={(user) => <span>{user.name} ({user.age})</span>}
/>

בדוגמה הראשונה TS מסיקה ש-T הוא string. בדוגמה השנייה אנחנו מציינים במפורש List<User>.

דוגמה נוספת - Select גנרי

interface SelectProps<T> {
    options: T[];
    getLabel: (option: T) => string;
    getValue: (option: T) => string;
    onChange: (option: T) => void;
}

function Select<T>({ options, getLabel, getValue, onChange }: SelectProps<T>) {
    return (
        <select onChange={(e) => {
            const selected = options.find(opt => getValue(opt) === e.target.value);
            if (selected) onChange(selected);
        }}>
            {options.map((option) => (
                <option key={getValue(option)} value={getValue(option)}>
                    {getLabel(option)}
                </option>
            ))}
        </select>
    );
}

// usage
interface Country {
    code: string;
    name: string;
}

const countries: Country[] = [
    { code: "IL", name: "Israel" },
    { code: "US", name: "United States" },
];

<Select
    options={countries}
    getLabel={(c) => c.name}
    getValue={(c) => c.code}
    onChange={(country) => console.log(country.name)}
/>

טיפוסים שימושיים נוספים

HTMLAttributes - הרחבת אטריביוטי HTML

כשרוצים שקומפוננטה תתמוך בכל אטריביוטי HTML הסטנדרטיים:

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
    variant?: "primary" | "secondary";
}

function Button({ variant = "primary", children, ...rest }: ButtonProps) {
    return (
        <button className={`btn-${variant}`} {...rest}>
            {children}
        </button>
    );
}

// now supports all HTML button attributes
<Button variant="primary" disabled type="submit" aria-label="Save">
    Save
</Button>

סיכום

  • מגדירים props עם interface, כולל שדות אופציונליים (?) ו-union types
  • React.ReactNode - הטיפוס לכל דבר שניתן לרנדור (children וכדומה)
  • לאירועים יש טיפוסים ייעודיים כמו React.MouseEvent, React.ChangeEvent
  • React.CSSProperties מטפס אובייקטי style עם השלמה אוטומטית
  • קומפוננטות גנריות משתמשות ב-generics כדי לעבוד עם כל טיפוס
  • אפשר להרחיב אטריביוטי HTML עם extends React.HTMLAttributes