6.2 קומפוננטות ופרופס הרצאה
קומפוננטות - Components¶
קומפוננטה היא אבן הבניין הבסיסית בריאקט. כל חלק ב-UI הוא קומפוננטה - כפתור, כרטיס, תפריט, עמוד שלם. קומפוננטה היא פונקציה שמקבלת קלט (props) ומחזירה JSX:
כללים בסיסיים¶
- שם הקומפוננטה חייב להתחיל באות גדולה - PascalCase (לא camelCase)
- הקומפוננטה חייבת להחזיר JSX (או
null) - כל קובץ בדרך כלל מכיל קומפוננטה אחת ומייצא אותה
// 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 מורכב מקומפוננטות פשוטות