بهترین شیوه ها (Best Practices)
در «بهترین شیوه های تایپ اسکریپت»، هدف ما کد تمیز و ایمن است. «ایمن از نوع (Type-safe)» یعنی خطاها زودتر گیر می افتند. بنابراین نگهداری آسان تر می شود و تیم راحت تر پیش می رود.
پیکربندی پروژه
حالت «سخت گیر (strict)» را روشن کن. سپس چند گزینه پیشنهادی را هم فعال کن.
فعال سازی Strict Mode
// tsconfig.json\n{\n \"compilerOptions\": {\n /* Enable all strict type-checking options */\n \"strict\": true,\n /* Additional recommended settings */\n \"target\": \"ES2020\",\n \"module\": \"commonjs\",\n \"moduleResolution\": \"node\",\n \"esModuleInterop\": true,\n \"skipLibCheck\": true,\n \"forceConsistentCasingInFileNames\": true\n }\n}\n
سخت گیری های بیشتر
{\n \"compilerOptions\": {\n \"noImplicitAny\": true,\n \"strictNullChecks\": true,\n \"strictFunctionTypes\": true,\n \"strictBindCallApply\": true,\n \"strictPropertyInitialization\": true,\n \"noImplicitThis\": true,\n \"alwaysStrict\": true\n }\n}\n
بهترین ها برای سیستم نوع
«استنباط نوع (Type Inference)» یعنی TS خودش نوع را حدس بزند. جاهای واضح، بگذار خودش حدس بزند.
به استنباط اعتماد کن
// Bad\nconst name: string = 'John';\n\n// Good\nconst name = 'John';\n\n// Bad\nfunction add(a: number, b: number): number {\n return a + b;\n}\n\n// Good\nfunction add(a: number, b: number) {\n return a + b;\n}\n
Annotation دقیق در APIها
// Bad\nfunction processUser(user: any) {\n return (user as any).name.toUpperCase();\n}\n\n// Good\ninterface User {\n id: number;\n name: string;\n email?: string;\n}\n\nfunction processUser(user: User): string {\n return user.name.toUpperCase();\n}\n
interface در برابر type
interface User {\n id: number;\n name: string;\n}\n\ninterface AdminUser extends User {\n permissions: string[];\n}\n\ntype UserRole = 'admin' | 'editor' | 'viewer';\n\ntype UserId = number | string;\n\ntype ReadonlyUser = Readonly<User>;\n\ntype Point = [number, number];\n
تا جای ممکن از any دوری کن
// Bad\nfunction logValue(value: any): void {\n console.log((value as any).toString());\n}\n\n// Better\nfunction logValue<T>(value: T): void {\n console.log(String(value));\n}\n\n// Best\nfunction logString(value: string): void {\n console.log(value.toUpperCase());\n}\n\nfunction logUnknown(value: unknown): void {\n if (typeof value === 'string') {\n console.log(value.toUpperCase());\n } else {\n console.log(String(value));\n }\n}\n
سازمان دهی کد
ماژول ها را تفکیک کن. سپس فایل ها را نام گذاری منظم کن.
چینش ماژول ها
// user/user.model.ts\nexport interface User {\n id: string;\n name: string;\n email: string;\n}\n\n// user/user.service.ts\nimport { User } from './user.model';\n\nexport class UserService {\n private users: User[] = [];\n\n addUser(user: User): void {\n this.users.push(user);\n }\n\n getUser(id: string): User | undefined {\n return this.users.find((user) => {\n return user.id === id;\n });\n }\n}\n\n// user/index.ts\nexport * from './user.model';\nexport * from './user.service';\n
الگوی نام گذاری فایل
// Good\n// user.service.ts\n// user.model.ts\n// user.controller.ts\n// user.component.ts\n// user.utils.ts\n// user.test.ts\n\n// Bad\n// UserService.ts\n// user_service.ts\n// userService.ts\n
توابع و متدها
ورودی ها و خروجی ها را دقیق مشخص کن. سپس از پارامترهای پیش فرض و rest کمک بگیر.
پارامترها و خروجی ها
// Bad\nfunction process(user: any, notify: any): void {\n notify((user as any).name);\n}\n\n// Good\ninterface User {\n id: string;\n name: string;\n}\n\nfunction processUser(user: User, notify: (message: string) => void): void {\n notify(`Processing user: ${user.name}`);\n}\n\nfunction createUser(name: string, role: string = 'viewer', isActive: boolean = true): {\n name: string;\n role: string;\n isActive: boolean;\n} {\n return { name: name, role: role, isActive: isActive };\n}\n\nfunction sum(...numbers: number[]): number {\n return numbers.reduce((total, num) => {\n return total + num;\n }, 0);\n}\n
کوچک سازی مسئولیت ها
// Better\ninterface UserData {\n name: string;\n email: string;\n}\n\ninterface ProcessedUserData extends UserData {\n createdAt: Date;\n}\n\nfunction validateUserData(data: unknown): UserData {\n if (!data || typeof data !== 'object') {\n throw new Error('Invalid user data');\n }\n return data as UserData;\n}\n\nfunction processUserData(userData: UserData): ProcessedUserData {\n return {\n ...userData,\n name: userData.name.trim(),\n createdAt: new Date()\n };\n}\n
الگوهای Async/Await
در ناهمگام ها، خطا را مدیریت کن. سپس درخواست های مستقل را موازی کن.
استفاده درست از Async/Await
async function fetchData<T>(url: string): Promise<T> {\n try {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n return await response.json() as T;\n } catch (error) {\n console.error('Failed to fetch data:', error);\n throw error;\n }\n}\n\nasync function fetchMultipleData<T>(urls: string[]): Promise<T[]> {\n try {\n const promises = urls.map((u) => {\n return fetchData<T>(u);\n });\n return await Promise.all(promises);\n } catch (error) {\n console.error('One or more requests failed:', error);\n throw error;\n }\n}\n\ninterface User {\n id: string;\n name: string;\n email: string;\n}\n\nasync function getUserData(userId: string): Promise<User> {\n return fetchData<User>(`/api/users/${userId}`);\n}\n
پرهیز از تو در تویی
async function processUser(userId: string): Promise<{ user: unknown; latestOrder: unknown; items: unknown[] } | null> {\n const user = await getUser(userId);\n if (!user) {\n return null;\n }\n const orders = await getOrders(userId);\n if (orders.length === 0) {\n return { user: user, latestOrder: null, items: [] };\n }\n const latestOrder = orders[0];\n const items = await getOrderItems(latestOrder.id);\n return { user: user, latestOrder: latestOrder, items: items };\n}\n
تست و کیفیت
وابستگی ها را تزریق کن. سپس توابع خالص بساز تا تست پذیر شوند.
کد تست پذیر
interface PaymentGateway {\n charge(amount: number): Promise<boolean>;\n}\n\nclass PaymentProcessor {\n constructor(private paymentGateway: PaymentGateway) {}\n\n async processPayment(amount: number): Promise<boolean> {\n if (amount <= 0) {\n throw new Error('Amount must be greater than zero');\n }\n return this.paymentGateway.charge(amount);\n }\n}\n
Type Testing نمونه ای
// @ts-expect-error\nconst invalidUser: { id: number; name: string } = { id: -1, name: 'Test' };\n\nfunction assertIsString(value: unknown): asserts value is string {\n if (typeof value !== 'string') {\n throw new Error('Not a string');\n }\n}\n\ntype IsString<T> = T extends string ? true : false;\ntype Test1 = IsString<string>;\ntype Test2 = IsString<number>;\n
نکات عملکردی
«واردات فقط-نوع (Type-only imports)» به درخت بُری کمک می کند. همچنین کُد سبک تر می شود.
type-only import/export
import type { User } from './api';\nimport { fetchUser } from './api';\n\nexport type { User };\nexport { fetchUser };\n\n// tsconfig: \"isolatedModules\": true\n
از نوع های بیش ازحد پیچیده دوری کن
// Better: استفاده از Utility Types\ninterface UserProfile {\n name: string;\n email: string;\n}\n\ninterface UserPreferences {\n notifications: boolean;\n}\n\ninterface User {\n id: string;\n profile: UserProfile;\n preferences?: UserPreferences;\n}\n\nconst updateUser = (updates: Partial<User>): void => {\n // ...\n};\n
const assertion برای نوع های دقیق
const colors = ['red', 'green', 'blue'] as const;\ntype Color = typeof colors[number];\n\nconst config = {\n apiUrl: 'https://api.example.com',\n timeout: 5000,\n features: ['auth', 'notifications']\n} as const;\n
خطاهای رایج
چند اشتباه معمول را ببین. سپس از آن ها دوری کن.
زیاده روی در any
// Bad\nfunction process(data: any): unknown {\n return (data as any).map((item: any) => {\n return item.name;\n });\n}\n\n// Better\nfunction process<T extends { name: string }>(items: T[]): string[] {\n return items.map((item) => {\n return item.name;\n });\n}\n
نادیده گرفتن Strict Mode
{\n \"compilerOptions\": {\n \"strict\": true,\n \"noImplicitAny\": true,\n \"strictNullChecks\": true,\n \"strictFunctionTypes\": true,\n \"strictBindCallApply\": true,\n \"strictPropertyInitialization\": true,\n \"noImplicitThis\": true,\n \"alwaysStrict\": true\n }\n}\n
بی توجهی به نگهبان نوع
function isString(value: unknown): value is string {\n return typeof value === 'string';\n}\n\nfunction process(input: string | number): string {\n if (isString(input)) {\n return input.toUpperCase();\n } else {\n return input.toFixed(2);\n }\n}\n\nif (typeof value === 'string') {\n // value is string\n}\n\nif (value instanceof Date) {\n // value is Date\n}\n\nif ('id' in (user as object)) {\n // user has id property\n}\n
عدم مدیریت null/undefined
function getLength(str: string | null): number {\n if (str === null) {\n return 0;\n }\n return str.length;\n}\n\nfunction getLengthSafe(str: string | null): number {\n return str?.length ?? 0;\n}\n\ninterface User {\n profile?: {\n name?: string;\n };\n}\n\nconst user: User = {};\nconst name = user.profile?.name ?? 'Anonymous';\n
جمع بندی سریع
- strict را روشن نگه دار.
- از any فاصله بگیر.
- استنباط نوع را جدی بگیر.
- async را با خطا مدیریت کن.
- ماژول ها را تمیز بچین.