TypeScript: أفضل الممارسات في TypeScript
آخر تحديث: 2026-08-26
إتقان القواعد النحوية ما هو إلا البداية — فلكي تكتب كودًا جيدًا بلغة TypeScript، عليك اتباع مجموعة من أفضل الممارسات. يلخص هذا الدرس أهم الأفكار والمبادئ المستمدة من مشاريع حقيقية.
1. قواعد تسمية الملفات
(1) تسمية الأنواع
| الفئة | المواصفات | مثال |
|---|---|---|
| الواجهة | PascalCase | UserService، ApiResponse |
| الاسم المستعار | PascalCase | Status، EventHandler |
| المعلمة العامة | حرف واحد أو أحرف كبيرة وصغيرة | T، K، TItem |
| التعداد | PascalCase (قد تكون القيم بأحرف كبيرة) | HttpStatus، COLOR_RED |
| أعضاء التعداد | PascalCase | HttpStatus.Ok |
(2) تسمية الملفات
| النوع | المواصفات | مثال |
|---|---|---|
| الوحدة النمطية العادية | camelCase | userService.ts |
| نوع الملف | PascalCase | UserController.ts |
| ملف الإعلان | يحمل نفس اسم الوحدة النمطية | lodash.d.ts |
| ملف الاختبار | اسم الوحدة.test | userService.test.ts |
(3) مواصفات التصدير
TYPESCRIPT
// ✅ Recommendations——Prioritize naming and exporting
export function addUser(user: User): void { }
export class UserController { }
export type Status = "active" | "inactive";
// ⚠️ Use Default Export with Caution——It's easy to make mistakes when renaming files
export default class UserController { }
// ✅ Library/Framework recommended — both options are available
export class UserController { }
export default UserController;
2. مبادئ تصميم الخطوط
(1) المبدأ الأول: الدقة قبل الشمولية
TYPESCRIPT
// ❌ Broad——Type information lost
function process(value: any): any { }
// ❌ Slightly better, but still too broad
function process(value: string | number): string | number { }
// ✅ Accurate——Generics Preserve Type Information
function process<T extends string | number>(value: T): T { }
(2) المبدأ الثاني: الحساب أفضل من الكتابة بخط اليد
TYPESCRIPT
// ❌ Manually maintain two locations——Prone to desynchronization
interface User { id: number; name: string; email: string; }
type UserKeys = "id" | "name" | "email";
// ✅ Inference Based on Source Type——Automatic Synchronization
type UserKeys2 = keyof User; // "id" | "name" | "email"
type UserValues = User[keyof User]; // number | string
(3) المبدأ 3: التكوين قبل التوريث
TYPESCRIPT
// ❌ Deep Inheritance——The Problem with Fragile Base Classes
class BaseEntity { id: number; }
class TimestampedEntity extends BaseEntity { createdAt: Date; }
class FullEntity extends TimestampedEntity { createdBy: string; }
// ✅ Type Combinations——Flexible and decoupled
type WithId = { id: number };
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type WithAudit = { createdBy: string; updatedBy: string };
type FullEntity2 = WithId & WithTimestamps & WithAudit;
type SimpleEntity = WithId; // Customizable Combinations
▶ مثال: إعادة هيكلة كلمة "any" لتكون آمنة من حيث النوع
TYPESCRIPT
// ❌ Before the refactoring——everywhere any,Zero Type Safety
function processRequest(req: any): any {
let user = req.body.user; // any
let result = validate(user); // any
return { status: 200, data: result };
}
// ✅ After the refactoring——Type safety at every step
interface User { id: number; name: string; email: string; }
interface Request2 { body: { user: User } }
interface ValidationResult { valid: boolean; errors?: string[] }
interface Response2<T> { status: number; data: T }
function processRequest2(req: Request2): Response2<ValidationResult> {
let user: User = req.body.user; // ✅ User Type
let result: ValidationResult = validate2(user); // ✅ ValidationResult
return { status: 200, data: result };
}
function validate2(user: User): ValidationResult {
if (!user.email.includes("@")) {
return { valid: false, errors: ["Invalid email address"] };
}
return { valid: true };
}
الناتج:
TEXT
📖 للعرض فقط
// Executed successfully
3. البدائل لـ any
(1) غير معروف كبديل لأي شيء
TYPESCRIPT
// ❌ any——Turn off type checking
function process(value: any) {
return value.toUpperCase(); // Do not check,May crash during runtime
}
// ✅ unknown——You must narrow it first before you can use it.
function process2(value: unknown) {
if (typeof value === "string") {
return value.toUpperCase(); // ✅ Safe Use After Narrowing
}
throw new Error("Expectations string Type");
}
(2) الأدوية الجنيسة كبديل لـ any
TYPESCRIPT
// ❌ any——Missing Type Information
function first(arr: any[]): any {
return arr[0];
}
// ✅ Generics——Preserve Type Information
function first2<T>(arr: T[]): T {
return arr[0];
}
(3) استخدام أنواع الاتحاد بدلاً من any
TYPESCRIPT
// ❌ any
let value: any;
// ✅ Composite Types——Clearly list the possible types
let value2: string | number | boolean;
(4) استبدال كائنات any بتوقيعات الفهرس
TYPESCRIPT
// ❌ any Object
let config: any = { host: "localhost" };
// ✅ Index Signature
let config2: Record<string, string | number> = { host: "localhost" };
4. مبدأ DRY (لا تكرر نفسك)
(1) استخدم keyof وtypeof وأنواع المرافق لتجنب التكرار
TYPESCRIPT
const THEMES = {
light: { bg: "#fff", text: "#333" },
dark: { bg: "#1a1a1a", text: "#e0e0e0" }
} as const;
// Type Inference from Values——No duplicate definitions
type ThemeName = keyof typeof THEMES; // "light" | "dark"
type ThemeColors = typeof THEMES["light"]; // { readonly bg: "..."; readonly text: "..." }
function getTheme(name: ThemeName): ThemeColors {
return THEMES[name];
}
(2) التحويلات المجمعة باستخدام أنواع التعيين
TYPESCRIPT
interface ApiUser {
id: number;
name: string;
email: string;
role: string;
}
// No handwriting required——Derivation Using Tool Types
type CreateUserDTO = Omit<ApiUser, "id">;
type UpdateUserDTO = Partial<Omit<ApiUser, "id">>;
type UserSummary = Pick<ApiUser, "id" | "name">;
type UserResponse = Readonly<ApiUser>;
5. استراتيجيات تضييق نطاق الأنواع
(1) قم بالتضييق في أقرب وقت ممكن
TYPESCRIPT
// ❌ The gap is narrowing——Check each location before use
function process(value: string | number) {
console.log(value.toString()); // Only shared methods can be used
if (typeof value === "string") {
console.log(value.toUpperCase());
}
// Further checks will be needed later....
}
// ✅ Narrow as soon as possible——Use directly within the branch
function process2(value: string | number) {
if (typeof value === "string") {
// The entire branch is string
console.log(value.toUpperCase());
console.log(value.trim());
return;
}
// This must be number
console.log(value.toFixed(2));
}
(2) إعادة استخدام منطق التضييق في شروط الحماية الخاصة بالأنواع المخصصة
TYPESCRIPT
// The Complex Logic Behind Narrowing——Extract as a type guard
function isValidUser(obj: any): obj is User {
return obj
&& typeof obj.id === "number"
&& typeof obj.name === "string"
&& typeof obj.email === "string";
}
// Reuse in Multiple Places
function processUser(data: unknown) {
if (isValidUser(data)) {
console.log(data.name); // ✅ Type Safety
}
}
6. إرشادات للتعاون بين أعضاء الفريق
(1) التكوين الموحد لملف tsconfig
JSON
{
"compilerOptions": {
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
تُطبق القواعد نفسها تلقائيًا على بيئات تطوير البرامج (IDE) الخاصة بجميع أعضاء الفريق — دون الحاجة إلى أي إعداد يدوي.
(2) قواعد ESLint الخاصة بلغة TypeScript
JSON
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/explicit-function-return-type": "warn"
}
}
(3) قائمة مراجعة الكود
- [ ] لا يوجد نوع
any(ما لم يوضح تعليق ما السبب) - [ ] تحتوي معلمات الدالة وقيمها المرجعة على تعليقات توضيحية للنوع
- [ ] يتحقق من وجود قيم قد تكون فارغة أو غير محددة
- لم يتم استخدام [ ]
@ts-ignore(تم استبداله بـ@ts-expect-error) - [ ] تتضمن واجهة برمجة التطبيقات العامة تعليقات JSDoc
- [ ] يستخدم استيراد النوع
import type
▶ مثال: الوضع الصارم يكتشف أخطاء حقيقية
TYPESCRIPT
// strict: true catches null/undefined bugs at compile time
interface SearchResult { items: string[]; total: number; }
function search(query: string): SearchResult | null {
if (!query.trim()) return null;
return { items: [`Result for ${query}`], total: 1 };
}
// Without strict: compiles but crashes at runtime
// let result = search("");
// console.log(result.items.length); // TypeError at runtime
// With strict: compiler forces null check
let result = search("");
if (result) {
console.log(result.items.length); // ✅ safe after narrowing
} else {
console.log("No query provided");
}
الناتج:
TEXT
📖 للعرض فقط
No query provided
▶ مثال: القضاء على any باستخدام الأنواع العامة
TYPESCRIPT
// ❌ Before: loose types with any
function getProp(obj: any, key: string): any {
return obj[key];
}
let user: any = { name: "Alice", age: 30 };
let name2 = getProp(user, "name"); // any — no autocomplete
// ✅ After: generics preserve type information
function getProp2<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
let user2 = { name: "Alice", age: 30 } as const;
let name3 = getProp2(user2, "name"); // "Alice" — exact literal type
let age = getProp2(user2, "age"); // 30 — exact literal type
// getProp2(user2, "email"); // ❌ not assignable to keyof
الناتج:
TEXT
📖 للعرض فقط
Compile error — Argument of type "email" is not assignable to keyof
❓ أسئلة شائعة
س هل ينبغي حظر استخدام
any تمامًا في المشروع؟ج هذا غير واقعي. اضبط ESLint على
no-explicit-any: warn بدلاً من error — فهذا يسمح بعدد قليل من حالات استخدام any ولكنه يستدعي إجراء مراجعة. يُعد استخدام any معقولًا في حالات مثل المكتبات الخارجية غير المُصنَّفة، وتحويلات الأنواع المعقدة، والنماذج الأولية السريعة. المفتاح هو تضمين تعليقات تشرح السبب ووضع خطة للاستبدال.س هل يجب وضع تعريفات الأنواع في ملف منفصل أم داخل الملف الذي تُستخدم فيه؟
ج يجب وضع الأنواع العامة/المشتركة في الدليل
types/ أو models/؛ أما الأنواع التي تُستخدم فقط داخل ملف واحد فيجب تعريفها مباشرةً داخل ذلك الملف. القاعدة: إذا تمت الإشارة إلى نوع ما في ثلاثة ملفات أو أكثر، فيجب استخراجه إلى ملف أنواع عام؛ وإلا، فيجب تعريفه داخل الملف الذي يُستخدم فيه.س هل يجب عليّ استخدام ESLint في مشروع TypeScript؟
ج نعم. يتولى tsc مهمة التحقق من الأنواع، بينما يتولى ESLint مهمة فحص جودة الكود — وهما يكملان بعضهما البعض بدلاً من أن يحل أحدهما محل الآخر. يوفر المكون الإضافي
@typescript-eslint قواعد خاصة بـ TypeScript (مثل no-explicit-any وconsistent-type-imports)، مما يجعله الخيار القياسي لمشاريع TypeScript.س ماذا أفعل إذا كان هناك عدد كبير جدًا من معلمات الأنواع العامة؟
ج إذا كان هناك أكثر من ثلاثة معلمات أنواع، ففكر في ما يلي: (1) استبدل الأنواع العامة المتعددة بمعلمات كائنات؛ (2) افصل بعض الأنواع إلى واجهات منفصلة؛ (3) استخدم القيم الافتراضية العامة لتقليل عدد المعلمات التي يجب تحديدها. وعادةً ما يشير وجود عدد كبير جدًا من معلمات الأنواع إلى مستوى غير مناسب من التجريد.
📖 ملخص
- قواعد التسمية: استخدم أسلوب «PascalCase» للواجهات والأنواع والفئات؛ واستخدم حرفًا واحدًا أو أسلوب «PascalCase» للأنواع العامة
- تصميم الخطوط: الدقة قبل العمومية، الاشتقاق قبل البرمجة اليدوية، التركيب قبل الوراثة
- البدائل لـ
any:unknown(الخيار الاحتياطي الآمن)، الأنواع العامة (الحفاظ على النوع)، أنواع الاتحاد (التعداد الصريح) - النوع «DRY»: مشتق من المصدر باستخدام
keyofأوtypeofأو أنواع الأدوات المساعدة؛ ولا توجد تعريفات مكررة - تضييق النوع: قم بالتضييق في أقرب وقت ممكن؛ وأعد استخدام شروط التحقق من النوع
- إرشادات الفريق: ملف tsconfig موحد، وقواعد ESLint، وقوائم مراجعة الكود
📝 تمارين
- تمرين أساسي (مستوى الصعوبة ⭐): ابحث عن مثال لنوع
anyقمت بكتابته أو رأيته من قبل، واستبدله بـunknownأو أنواع عامة. تأكد من أن الوظيفة تظل كما هي بعد الاستبدال، وأن الكود أصبح أكثر أمانًا من حيث الأنواع. - مشكلة متقدمة (درجة الصعوبة ⭐⭐): أعد هيكلة مجموعة من تعريفات الأنواع باستخدام مبدأ DRY — انطلاقًا من الواجهة
ApiProduct، استخدم أنواع المساعدة لاشتقاق الأنواع الأربعةCreateProductوUpdateProductوProductSummaryوProductResponse، دون كتابة أي خصائص مكررة يدويًّا. - التحدي (الصعوبة: ⭐⭐⭐): اكتب وثيقة معايير البرمجة بلغة TypeScript لفريقك — بما في ذلك قواعد تسمية المتغيرات، والبدائل لـ
any، وقواعد استيراد الأنواع، والإعدادات الموصى بها لـtsconfig، وقواعد ESLint الموصى بها. اذكر الأسباب المنطقية لكل قاعدة، مع تقديم أمثلة وأمثلة مضادة.