TypeScript עם React
React ו-TypeScript הם הצמד הנפוץ ביותר בפיתוח Frontend היום. השיעור מראה את הדפוסים שתכתבו כל יום: props, state, אירועים, refs ו-Context. הוא מניח שעברתם את קורס React.
npm create vite@latest my-app -- --template react-ts
Props
type ButtonProps = {
label: string;
variant?: 'primary' | 'secondary';
disabled?: boolean;
onClick: () => void;
};
function Button({ label, variant = 'primary', disabled = false, onClick }: ButtonProps) {
return (
<button className={`btn btn-${variant}`} disabled={disabled} onClick={onClick}>
{label}
</button>
);
}
<Button label="שמירה" onClick={save} />
<Button label="שמירה" variant="danger" onClick={save} />
// ~~~~~~~ Type '"danger"' is not assignable to type '"primary" | "secondary"'
לא צריך React.FC: פונקציה רגילה עם props מוקלדים היא הסגנון המומלץ.
children
import type { ReactNode } from 'react';
type CardProps = {
title: string;
children: ReactNode; // כל מה ש-React יודעת לרנדר
};
function Card({ title, children }: CardProps) {
return (
<section className="card">
<h2>{title}</h2>
{children}
</section>
);
}
להרחיב props של אלמנט HTML
import type { ComponentProps } from 'react';
// כל מה ש-<input> מקבל, ועוד label
type TextFieldProps = ComponentProps<'input'> & { label: string };
function TextField({ label, ...inputProps }: TextFieldProps) {
return (
<label>
{label}
<input {...inputProps} />
</label>
);
}
<TextField label="אימייל" type="email" required placeholder="[email protected]" />
useState
const [count, setCount] = useState(0); // מוסק: number
const [user, setUser] = useState<User | null>(null); // צריך להגיד: עכשיו null, אחר כך User
const [tags, setTags] = useState<string[]>([]); // מערך ריק: TypeScript לא יודעת ממה
if (user) {
console.log(user.name); // Narrowing עובד גם כאן
}
אירועים
import type { ChangeEvent, FormEvent } from 'react';
function SearchBox() {
const [query, setQuery] = useState('');
function handleChange(event: ChangeEvent<HTMLInputElement>) {
setQuery(event.target.value);
}
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
search(query);
}
return (
<form onSubmit={handleSubmit}>
<input value={query} onChange={handleChange} />
</form>
);
}
טריק: כשה-handler כתוב ישירות ב-JSX (onChange={(event) => ...}), הטיפוס של event מוסק לבד. לא צריך לזכור את השמות.
useRef
const inputRef = useRef<HTMLInputElement>(null);
function focusInput() {
inputRef.current?.focus(); // current יכול להיות null לפני הרינדור
}
return <input ref={inputRef} />;
Context עם טיפוס
type Theme = 'light' | 'dark';
type ThemeContextValue = { theme: Theme; toggle: () => void };
const ThemeContext = createContext<ThemeContextValue | null>(null);
// הוק שמגן מפני שימוש מחוץ ל-Provider
function useTheme(): ThemeContextValue {
const value = useContext(ThemeContext);
if (!value) throw new Error('useTheme חייב להיות בתוך ThemeProvider');
return value;
}
function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<Theme>('light');
const toggle = () => setTheme((current) => (current === 'light' ? 'dark' : 'light'));
return <ThemeContext value={{ theme, toggle }}>{children}</ThemeContext>;
}
קומפוננטה גנרית
type ListProps<T> = {
items: T[];
renderItem: (item: T) => ReactNode;
getKey: (item: T) => string | number;
};
function List<T>({ items, renderItem, getKey }: ListProps<T>) {
return <ul>{items.map((item) => <li key={getKey(item)}>{renderItem(item)}</li>)}</ul>;
}
<List items={products} getKey={(p) => p.id} renderItem={(p) => p.name} />
// p מוסק כ-Product, בלי לכתוב כלום
בדקו את עצמכם
נסו לענות לבד לפני שאתם פותחים את התשובה.
-
איך נותנים טיפוס ל-state שמתחיל ב-null ויהיה User?
הצגת התשובה
תשובה ג. מ-null לבד TypeScript לא יכולה לדעת מה יבוא אחר כך.
-
איזה טיפוס מתאים ל-children?
הצגת התשובה
תשובה ג. ReactNode כולל כל מה ש-React יודעת לרנדר.