TypeScript: ترحيل مشروع TypeScript JS إلى TS

آخر تحديث: 2026-08-26

يُعد ترحيل مشروع جافا سكريبت قائم إلى TypeScript السيناريو الأكثر شيوعًا وعمليًّا — يشرح هذا الدرس كيفية إتمام عملية الترحيل بأمان وبشكل تدريجي.

1. نظرة عامة على استراتيجيات الهجرة

(1) ثلاث استراتيجيات للهجرة

الإستراتيجية السرعة المخاطرة السيناريوهات المناسبة
ترحيل كامل لمرة واحدة سريع عالي مشاريع صغيرة (< 20 ملفًا)
الترحيل التدريجي ملفًا تلو الآخر متوسط منخفض المشاريع المتوسطة إلى الكبيرة (موصى به)
الترحيل التدريجي لـ JSDoc بطيء منخفض جدًا المشاريع الكبيرة/الحاسمة
📌 توصية: الترحيل التدريجي، ملفًا تلو الآخر. أولاً، اجعل مُجمِّع TS يقبل ملفات JS، ثم قم بتغيير امتدادات الملفات من .js إلى .ts واحدًا تلو الآخر، مع التأكد من نجاح عملية التجميع بعد تغيير كل ملف.

(2) نظرة عامة على خطوات الترحيل

TEXT 📖 للعرض فقط
1. Initialization tsconfig.json(Relaxed Mode)
2. Install @types packages
3. Open allowJs——TS and JS Coexistence
4. File by file .js → .ts(Starting with the leaf file)
5. Gradually Enable Strict Options
6. Finally Unlocked strict


2. الخطوة 1: تهيئة الإعدادات

(1) إنشاء ملف tsconfig.json

BASH
tsc --init

(2) التكوين الأولي الأساسي — الوضع المريح

JSON
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "allowJs": true,
    "checkJs": false,
    "noImplicitAny": false,
    "strict": false,
    "outDir": "./dist",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
💡 نقطة أساسية: لا تقم بتمكين strict أو noImplicitAny في بداية عملية الترحيل — تأكد أولاً من أن المشروع يتم ترجمته بنجاح، ثم قم بتشديد القيود تدريجيًّا.

(3) تحديد نوع التركيب

BASH
# Install the type declarations required by the project
npm install @types/node @types/express @types/lodash --save-dev


3. الخطوة 2: استخدم allowJs لتمكين التوافق بين JS و TS

(1) تمكين allowJs

JSON
{
  "compilerOptions": {
    "allowJs": true
  }
}

تسمح ميزة allowJs لمُجمِّع TypeScript بقبول ملفات .js — وبذلك يمكن أن تحتوي المشاريع على ملفات JS وTS معًا دون أن يؤثر كل منهما على الآخر.

(2) التوافق مع ملفات الاستيراد

TYPESCRIPT
// main.ts —— Can be imported .js Documents
import { helper } from "./utils";  // utils.js Just being there is enough

// utils.js —— JS Documents,Untyped Annotations
function helper(value) {
  return value.toString();
}

(3) checkJs — التحقق الاختياري من أنواع جافا سكريبت

JSON
{
  "compilerOptions": {
    "checkJs": true
  }
}

عند تمكين checkJs، سيقوم TypeScript أيضًا بالتحقق من وجود أخطاء في الأنواع في ملفات .js (استنادًا إلى تعليقات JSDoc واستنتاج الأنواع). يُنصح بإبقائه معطلاً خلال مرحلة الترحيل الأولية — ولا تقم بتمكينه إلا بعد تحويل ملفات JS تدريجيًّا إلى TS.



4. الخطوة 3: الترحيل ملفًا تلو الآخر

(1) تسلسل الهجرة

ابدأ بالملف الذي يحتوي على أقل عدد من التبعيات — أي الملف «الورقي» (الذي يحتوي على دوال مساعدة وثوابت وما إلى ذلك، دون استيراد ملفات من مشاريع أخرى):

TEXT 📖 للعرض فقط
Recommended Migration Order:
1. Constants File(config.js → config.ts)
2. Utility Functions(utils.js → utils.ts)
3. Type Definitions(types.js → types.ts)
4. Data Model(models.js → models.ts)
5. Service Layer(services.js → services.ts)
6. Controller/Routing(controllers.js → controllers.ts)
7. Input File(index.js → index.ts)

(2) خطوات ترحيل ملف واحد

TYPESCRIPT
// ── Before the Migration:utils.js ──
function formatPrice(price, currency) {
  return currency + price.toFixed(2);
}

function clamp(value, min, max) {
  return Math.min(Math.max(value, min), max);
}
TYPESCRIPT
// ── After the migration:utils.ts ──
function formatPrice(price: number, currency: string = "¥"): string {
  return currency + price.toFixed(2);
}

function clamp(value: number, min: number, max: number): number {
  return Math.min(Math.max(value, min), max);
}

(3) تقنيات الترحيل — ابدأ بـ any ثم قم بالتحسين

TYPESCRIPT
// Step 1:Add a type annotation,Uncertain usage any
function process(data: any, options: any): any {
  return data.filter(item => item.active);
}

// Step 2:Gradual Replacement any For a specific type
interface DataItem {
  id: number;
  name: string;
  active: boolean;
}

interface Options {
  limit?: number;
  sort?: "asc" | "desc";
}

function process(data: DataItem[], options: Options = {}): DataItem[] {
  let result = data.filter(item => item.active);
  if (options.sort === "desc") result.reverse();
  if (options.limit) result = result.slice(0, options.limit);
  return result;
}

▶ مثال: ترحيل ملف مسار Express

TYPESCRIPT
// ── Before the Migration:users.js ──
const express = require("express");
const router = express.Router();

router.get("/", async (req, res) => {
  const users = await User.findAll();
  res.json(users);
});

router.post("/", async (req, res) => {
  const { name, email } = req.body;
  const user = await User.create({ name, email });
  res.status(201).json(user);
});

module.exports = router;
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
// API endpoint response (status 200)
// Returns JSON data
TYPESCRIPT
// ── After the migration:users.ts ──
import { Router, Request, Response } from "express";

const router = Router();

interface CreateUserBody {
  name: string;
  email: string;
}

router.get("/", async (_req: Request, res: Response) => {
  const users = await User.findAll();
  res.json(users);
});

router.post("/", async (req: Request<{}, {}, CreateUserBody>, res: Response) => {
  const { name, email } = req.body;
  const user = await User.create({ name, email });
  res.status(201).json(user);
});

export default router;


5. تعليقات الأنواع في JSDoc

إذا كنت لا ترغب في تغيير امتداد الملف، فيمكنك استخدام JSDoc لإضافة نوع إلى ملف JS الخاص بك:

(1) التعليقات التوضيحية للأنواع الأساسية

JAVASCRIPT
// utils.js — Use JSDoc to add types
/**
 * Pricing Format
 * @param {number} price - Price
 * @param {string} [currency="¥"] - Currency Symbol
 * @returns {string} Formatted Price
 */
function formatPrice(price, currency = "¥") {
  return currency + price.toFixed(2);
}

/**
 * @typedef {Object} User
 * @property {number} id
 * @property {string} name
 * @property {string} email
 */

/**
 * Search for a User
 * @param {number} id
 * @returns {Promise<User>}
 */
async function findUser(id) {
  // ...
}

module.exports = { formatPrice, findUser };

(2) الإشارة إلى أنواع JSDoc في ملفات TS

TYPESCRIPT
// main.ts
import { findUser } from "./utils";  // utils.js has JSDoc types

let user = await findUser(1);  // ✅ Type inference is correct(From JSDoc)

(3) علامات أنواع JSDoc الشائعة

العلامة الغرض مثال
@type نوع المتغير @type {string}
@param نوع المعلمة @param {number} x
@returns نوع الإرجاع @returns {string}
@typedef تعريف النوع @typedef {Object} User
@property خصائص الكائن @property {string} name
@template المعلمات العامة @template T
@callback نوع الاستدعاء المرتد @callback Handler


6. العقبات الشائعة في عملية الترحيل

(1) المأزق الأول: الانفجار الضمني any

TYPESCRIPT
// A large number of implicit variables after migration any——Don't rush to start it noImplicitAny
function process(data) {  // data Implicit any
  return data.map(item => item.name);  // item Me too any
}

الحل: أولاً، احرص على تجميع المشروع بنجاح، ثم أضف تعليقات الأنواع واحدة تلو الأخرى، وأخيرًا قم بتعطيل noImplicitAny.

(2) المأزق الثاني: التضارب بين module.exports وimport

TYPESCRIPT
// JS For use with documents module.exports
module.exports = function() { /* ... */ };

// TS Import Requirements esModuleInterop
import fn from "./legacy";  // Required esModuleInterop: true

(3) المأزق الثالث: المكتبات الخارجية غير المُصنَّفة

TYPESCRIPT
import untypedLib from "untyped-lib";  // ❌ Cannot find the declaration file

// Temporary solution——Create shim.d.ts
declare module "untyped-lib" {
  const lib: any;
  export default lib;
}

(4) المأزق الرابع: فقدان النوع this

TYPESCRIPT
// JS in this Dynamic Binding
const obj = {
  name: "Charlie",
  greet() {
    console.log(this.name);  // ✅ JS in OK
  }
};

// TS in this Needs annotation
const obj2 = {
  name: "Charlie",
  greet(this: { name: string }) {
    console.log(this.name);  // ✅ TS Needed in this Parameters
  }
};

▶ مثال: إضافة أنواع إلى كائنات JavaScript البسيطة

TYPESCRIPT
// ── Before: shapes.js ──
const shapes = [
  { type: "circle", radius: 5 },
  { type: "rectangle", width: 10, height: 20 },
  { type: "circle", radius: 3 }
];

function totalArea(shapes) {
  return shapes.reduce((sum, s) => {
    if (s.type === "circle") return sum + Math.PI * s.radius * s.radius;
    return sum + s.width * s.height;
  }, 0);
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
// Executed successfully
TYPESCRIPT
// ── After: shapes.ts ──
interface Circle { type: "circle"; radius: number; }
interface Rectangle { type: "rectangle"; width: number; height: number; }
type Shape = Circle | Rectangle;

const shapes: Shape[] = [
  { type: "circle", radius: 5 },
  { type: "rectangle", width: 10, height: 20 },
  { type: "circle", radius: 3 }
];

function totalArea(shapes: Shape[]): number {
  return shapes.reduce((sum, s) => {
    if (s.type === "circle") return sum + Math.PI * s.radius * s.radius;
    return sum + s.width * s.height;
  }, 0);
}

▶ مثال: الترحيل من JSDoc إلى TypeScript

JAVASCRIPT
// ── Before: math.js (JSDoc-typed) ──
/**
 * @template T
 * @param {T[]} arr
 * @param {(item: T) => boolean} predicate
 * @returns {T[]}
 */
function filter(arr, predicate) {
  return arr.filter(predicate);
}

/**
 * @typedef {Object} Point
 * @property {number} x
 * @property {number} y
 */

/**
 * @param {Point} a
 * @param {Point} b
 * @returns {number}
 */
function distance(a, b) {
  return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
// Executed successfully
TYPESCRIPT
// ── After: math.ts (native TS types) ──
function filter<T>(arr: T[], predicate: (item: T) => boolean): T[] {
  return arr.filter(predicate);
}

interface Point {
  x: number;
  y: number;
}

function distance(a: Point, b: Point): number {
  return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
}

❓ أسئلة شائعة

س كم من الوقت يستغرق ترحيل مشروع كبير؟
ج يعتمد ذلك على حجم الكود والفريق. بالنسبة للمشاريع الصغيرة إلى المتوسطة الحجم (10,000 صف أو أقل)، يستغرق الأمر حوالي 1–2 أسبوع. أما المشاريع الكبيرة (100,000 صف أو أكثر)، فقد تتطلب 1–3 أشهر من الترحيل التدريجي. المفتاح هو عدم إجراء جميع التغييرات دفعة واحدة — قم بترحيل ملف واحد في كل مرة، مع التأكد من نجاح ترجمة الكود بعد كل خطوة.
س أيهما أفضل، تعليقات الأنواع في JSDoc أم تعليقات الأنواع في TypeScript؟
ج تعليقات الأنواع في TypeScript هي الأفضل — فهي توفر صيغة أكثر إيجازًا، وتتمتع بقوة أكبر، وتتمتع بدعم أفضل من المحرر. أما JSDoc فهي حل وسط لا يتطلب تغيير امتداد الملف، مما يجعلها مناسبة للحالات التي لا يمكنك فيها تعديل اسم الملف. ويظل الهدف النهائي هو تحويل ملفات JS إلى TypeScript.
س ماذا عن التكامل المستمر (CI) أثناء عملية الترحيل؟
ج أضف tsc --noEmit إلى التكامل المستمر (CI) من أجل فحص الأنواع (دون إخراج ملفات). خلال المراحل المبكرة من عملية الترحيل، يُسمح باستخدام --noImplicitAny false لضمان عدم فشل التكامل المستمر (CI) بسبب مشكلات متعلقة بالأنواع. ومع تشديد المعايير تدريجيًّا، يصبح فحص الأنواع في التكامل المستمر (CI) أكثر صرامةً.
س أيهما يجب أن أستخدم، ts-ignore أم ts-expect-error؟
ج استخدم @ts-expect-error أولاً — فسيُظهر خطأً إذا لم يكن هناك خطأ في النوع في السطر التالي (لمنع نسيان إزالة التعليق بعد الإصلاح). @ts-ignore يتجاهل الأخطاء دون قيد أو شرط، مما قد يخفي الأخطاء التي تم إصلاحها بالفعل. كلاهما حلان مؤقتان؛ وفي النهاية، يجب عليك إصلاح مشكلات الأنواع.

📖 ملخص

📝 تمارين

  1. تمرين أساسي (مستوى الصعوبة ⭐): قم بترحيل ملف أداة بسيط مكتوب بلغة جافا سكريبت (يحتوي على 3–5 دوال) إلى لغة تايب سكريبت — أضف تعليقات الأنواع للمعلمات والقيم المرجعة لكل دالة، وتأكد من نجاح عملية الترجمة.
  2. تمرين متقدم (مستوى الصعوبة: ⭐⭐): قم بتكوين tsconfig.json لدعم المشاريع المختلطة بين JS و TS — قم بتمكين allowJs، واستخدم JSDoc لإضافة أنواع إلى ملف JS، ثم قم باستيراده واستخدامه في ملف TS.
  3. التحدي (الصعوبة: ⭐⭐⭐): قم بمحاكاة عملية ترحيل مشروع Express — قم بتكوين tsconfig، وأضف الأنواع إلى طلبات واستجابات Express، وتعامل مع أمان الأنواع لـ req.body (حدد واجهة body)، وتعامل مع الأنواع لـ req.params.
Web-Tutorial.com

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

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

100%