שיטות עבודה מומלצות
הכרתם את הכלים. השיעור האחרון מרכז את ההרגלים שמבדילים בין קוד TypeScript שנלחמים בו לבין קוד שהטיפוסים שלו באמת עובדים בשבילכם.
1. strict תמיד
פרויקט בלי "strict": true מפספס את רוב הערך, בעיקר את בדיקת null. בפרויקט ישן שעובר ל-TypeScript אפשר להדליק בהדרגה, אבל היעד הוא strict מלא.
2. בלי any
// ❌ any: הבדיקה כבויה, ובאג מתפשט לכל מקום שהערך מגיע אליו
function handle(data: any) { return data.user.name; }
// ✅ unknown: חייבים לבדוק לפני שימוש
function handle(data: unknown) { /* בדיקה, ואז שימוש */ }
ההגדרה noImplicitAny (חלק מ-strict) מונעת any שנוצר בטעות. כש-any נחוץ באמת, הוסיפו הערה למה.
3. לבדוק בגבולות, לסמוך בפנים
טיפוסים לא קיימים בזמן ריצה. כל מה שנכנס מבחוץ: תשובת API, req.body, localStorage, JSON.parse, משתני סביבה, צריך בדיקה אמיתית בנקודת הכניסה:
const productSchema = z.object({ id: z.number(), name: z.string(), price: z.number() });
type Product = z.infer<typeof productSchema>;
async function getProducts(): Promise<Product[]> {
const response = await fetch('/api/products');
return z.array(productSchema).parse(await response.json()); // בדיקה, לא הבטחה
}
אחרי שהנתון עבר את הגבול, שאר הקוד יכול לסמוך על הטיפוס בלי לבדוק שוב.
4. להימנע מ-as ומ-!
const user = data as User; // ❌ הבטחה בלי בדיקה
const name = user!.name; // ❌ "אני בטוח שזה לא null"
if (isUser(data)) { /* ... */ } // ✅ Type Guard
const name = user?.name ?? 'אורח'; // ✅ טיפול במקרה החסר
5. לתת להסקה לעבוד
// ❌ רעש
const count: number = items.length;
const names: string[] = users.map((user: User): string => user.name);
// ✅ הטיפוסים זהים, הקוד נקי
const count = items.length;
const names = users.map((user) => user.name);
כותבים טיפוסים בגבולות: פרמטרים של פונקציות, ערכי החזרה של פונקציות מיוצאות, ומבני נתונים מרכזיים.
6. לתאר מצבים, לא דגלים
// ❌ אפשר להיות גם loading וגם error. data תמיד "אולי"
type State = { loading: boolean; error?: string; data?: Product[] };
// ✅ Discriminated Union: רק מצבים שקיימים באמת
type State =
| { status: 'loading' }
| { status: 'error'; error: string }
| { status: 'success'; data: Product[] };
7. לגזור טיפוסים, לא להעתיק
type User = { id: number; name: string; email: string; passwordHash: string };
type PublicUser = Omit<User, 'passwordHash'>; // ✅ מתעדכן לבד
type UserId = User['id']; // ✅ number, מקור אחד
מקור אמת אחד לכל צורה. עדיף עוד: לגזור את הטיפוס מסכמת Zod או ממסד הנתונים, כך שהטיפוס והבדיקה לא יכולים להתרחק.
8. בדיקת טיפוסים ב-CI
Vite ו-Node.js רק מסירים טיפוסים ולא בודקים אותם, כך שקוד עם שגיאות טיפוס ירוץ בשקט. tsc --noEmit חייב לרוץ לפני כל מיזוג, יחד עם הבדיקות ו-lint:
{
"scripts": {
"typecheck": "tsc --noEmit",
"lint": "eslint .",
"test": "node --test",
"check": "npm run typecheck && npm run lint && npm test"
}
}
ל-lint מומלץ typescript-eslint, שמוסיף כללים שמבינים טיפוסים (למשל Promise ששכחתם לחכות לו).
מה הלאה
- תרגלו: קחו פרויקט JavaScript קטן שכתבתם והמירו אותו, קובץ אחר קובץ.
- TypeScript Handbook - המדריך הרשמי.
- Type Challenges - תרגילים למי שרוצה להעמיק בטיפוסים מתקדמים.
בדקו את עצמכם
נסו לענות לבד לפני שאתם פותחים את התשובה.
-
איפה חייבים בדיקה בזמן ריצה?
הצגת התשובה
תשובה ב. אחרי שהנתון עבר את הגבול, שאר הקוד יכול לסמוך על הטיפוס.
-
למה להריץ
tsc --noEmitב-CI?הצגת התשובה
תשובה ד. הבדיקה היא השלב היחיד שבאמת אוכף את הטיפוסים.