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) قائمة مراجعة الكود


▶ مثال: الوضع الصارم يكتشف أخطاء حقيقية

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) استخدم القيم الافتراضية العامة لتقليل عدد المعلمات التي يجب تحديدها. وعادةً ما يشير وجود عدد كبير جدًا من معلمات الأنواع إلى مستوى غير مناسب من التجريد.

📖 ملخص

📝 تمارين

  1. تمرين أساسي (مستوى الصعوبة ⭐): ابحث عن مثال لنوع any قمت بكتابته أو رأيته من قبل، واستبدله بـ unknown أو أنواع عامة. تأكد من أن الوظيفة تظل كما هي بعد الاستبدال، وأن الكود أصبح أكثر أمانًا من حيث الأنواع.
  2. مشكلة متقدمة (درجة الصعوبة ⭐⭐): أعد هيكلة مجموعة من تعريفات الأنواع باستخدام مبدأ DRY — انطلاقًا من الواجهة ApiProduct، استخدم أنواع المساعدة لاشتقاق الأنواع الأربعة CreateProduct وUpdateProduct وProductSummary وProductResponse، دون كتابة أي خصائص مكررة يدويًّا.
  3. التحدي (الصعوبة: ⭐⭐⭐): اكتب وثيقة معايير البرمجة بلغة TypeScript لفريقك — بما في ذلك قواعد تسمية المتغيرات، والبدائل لـ any، وقواعد استيراد الأنواع، والإعدادات الموصى بها لـ tsconfig، وقواعد ESLint الموصى بها. اذكر الأسباب المنطقية لكل قاعدة، مع تقديم أمثلة وأمثلة مضادة.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%