الدليل العربي الشامل للغة TypeScript من الأساسيات وحتى المفاهيم المتقدمة
تايب سكريبت (TypeScript) هي لغة برمجة مفتوحة المصدر طوّرتها شركة مايكروسوفت. وهي لغة نصية قوية تبنى فوق جافا سكريبت، وتضيف ميزات التحقق الثابت من الأنواع والواجهات البرمجية. يتم تحويل كود تايب سكريبت إلى جافا سكريبت لتنفيذه في المتصفحات أو بيئات تشغيل جافا سكريبت الأخرى.
الميزة | جافا سكريبت | تايب سكريبت |
---|---|---|
التحقق من الأنواع | ديناميكي (وقت التشغيل) | ثابت (وقت التطوير) |
الواجهات البرمجية | غير مدعومة | مدعومة |
الفئات (ES6) | مدعومة | مدعومة مع خصائص إضافية |
العموميات (Generics) | غير مدعومة | مدعومة |
الترجمة | لا يحتاج | يترجم إلى جافا سكريبت |
يمكنك تثبيت تايب سكريبت باستخدام مدير الحزم npm (Node Package Manager). يتطلب ذلك تثبيت Node.js أولاً.
npm install -g typescript
يمكنك التحقق من نجاح التثبيت والإصدار المثبت بالأمر التالي:
tsc --version
npm install --save-dev typescript
بعد تثبيت TypeScript، يمكنك إنشاء ملف تكوين TypeScript (tsconfig.json) باستخدام الأمر التالي:
tsc --init
tsc filename.ts
عند ترجمة ملف TypeScript، سينتج ملف JavaScript بنفس الاسم ولكن بامتداد .js
تايب سكريبت يوفر العديد من الأنواع الأساسية التي يمكن استخدامها لتحديد نوع البيانات. إليك الأنواع الأساسية المدعومة:
يمثل قيمة منطقية (true أو false)
let isDone: boolean = false;
يمثل القيم العددية الصحيحة والعشرية
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
يمثل النصوص والسلاسل النصية
let color: string = "blue";
let fullName: string = `Mohamed Ali`;
يمثل المصفوفات (هناك طريقتان للتعريف)
let list1: number[] = [1, 2, 3];
let list2: Array = [1, 2, 3];
يمثل مصفوفة بعدد معروف من العناصر ذات أنواع محددة
let x: [string, number];
x = ["hello", 10]; // صحيح
// x = [10, "hello"]; // خطأ
مجموعة من الثوابت المسماة
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
يمثل أي نوع، يستخدم عند عدم معرفة النوع مسبقًا
let notSure: any = 4;
notSure = "يمكن أن تكون سلسلة نصية";
notSure = false; // أو قيمة منطقية
يمثل عدم وجود نوع، غالبًا يستخدم كنوع إرجاع للدوال
function warnUser(): void {
console.log("هذا تحذير");
}
تمثل قيم null و undefined، وهي أنواع فرعية لجميع الأنواع الأخرى
let u: undefined = undefined;
let n: null = null;
يمثل نوع القيم التي لا تحدث أبدًا
function error(message: string): never {
throw new Error(message);
}
يمثل قيمة غير أولية (non-primitive)
let obj: object = {
name: "علي",
age: 30
};
مثل any، لكن أكثر أمانًا لأنه يتطلب فحص النوع قبل الاستخدام
let value: unknown = "مرحبا";
if (typeof value === "string") {
console.log(value.toUpperCase());
}
تايب سكريبت يمكنه استنتاج نوع المتغير تلقائيًا بناءً على قيمته الأولية:
// لا حاجة لتحديد النوع، سيستنتج TypeScript أنه 'number'
let x = 10;
// سيستنتج أنه 'string'
let name = "أحمد";
في تايب سكريبت، يمكن تعريف المتغيرات باستخدام الكلمات المفتاحية var، let، و const، مع إمكانية تحديد نوع البيانات بشكل واضح.
// تعريف متغير مع تحديد النوع
let counter: number = 0;
// تعريف متغير مع استنتاج النوع تلقائيًا
let name = "علي"; // سيستنتج أنه من نوع string
// تعريف ثابت (لا يمكن تغيير قيمته)
const PI: number = 3.14159;
// تعريف متغير يمكن أن يحمل قيمًا من نوعين مختلفين
let id: number | string;
id = 101; // صحيح
id = "abc"; // صحيح
// المتغيرات المعرفة باستخدام var لها نطاق الدالة
function varExample() {
var x = 10;
if (true) {
var x = 20; // نفس المتغير
console.log(x); // 20
}
console.log(x); // 20 (تغيرت قيمة x)
}
// المتغيرات المعرفة باستخدام let لها نطاق الكتلة
function letExample() {
let x = 10;
if (true) {
let x = 20; // متغير مختلف
console.log(x); // 20
}
console.log(x); // 10 (لم تتغير قيمة x الخارجية)
}
يفضل استخدام let و const بدلاً من var في كود تايب سكريبت الحديث لتجنب مشاكل النطاق.
// تعريف مصفوفة
let numbers: number[] = [1, 2, 3, 4, 5];
// تعريف كائن
let user: { name: string, age: number } = {
name: "سامي",
age: 25
};
// تعريف كائن باستخدام النوع المسجل مسبقًا (type)
type User = {
name: string;
age: number;
email?: string; // الخاصية اختيارية (؟)
};
let admin: User = {
name: "خالد",
age: 30
};
الواجهات في تايب سكريبت تحدد العقود التي يجب على الكائنات اتباعها. وهي طريقة لتعريف "شكل" الكائن.
interface Person {
firstName: string;
lastName: string;
age: number;
}
// استخدام الواجهة
let user: Person = {
firstName: "أحمد",
lastName: "محمد",
age: 28
};
interface Vehicle {
brand: string;
model: string;
year: number;
color?: string; // خاصية اختيارية
}
// استخدام الواجهة مع خاصية اختيارية
let car: Vehicle = {
brand: "تويوتا",
model: "كامري",
year: 2020
// يمكن حذف color لأنها اختيارية
};
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
// p1.x = 5; // خطأ! لا يمكن تعديل قيمة الخاصية للقراءة فقط
interface Animal {
name: string;
age: number;
}
interface Cat extends Animal {
breed: string;
meow(): void;
}
// استخدام الواجهة الموسعة
let cat: Cat = {
name: "قطقوط",
age: 3,
breed: "سيامي",
meow() {
console.log("مياو!");
}
};
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src, sub) {
return src.indexOf(sub) > -1;
};
interface StringArray {
[index: number]: string;
}
let myArray: StringArray = ["أبجد", "هوز", "حطي"];
let myStr: string = myArray[0];
تدعم تايب سكريبت البرمجة كائنية التوجه من خلال الفئات، وتضيف ميزات إضافية متعلقة بتحديد الأنواع.
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): void {
console.log(`مرحباً، اسمي ${this.name} وعمري ${this.age} سنة.`);
}
}
// إنشاء كائن من الفئة
const person = new Person("أحمد", 30);
person.greet(); // مرحباً، اسمي أحمد وعمري 30 سنة.
توفر تايب سكريبت ثلاثة معدلات وصول للخصائص والطرق في الفئات:
class Employee {
public name: string; // يمكن الوصول إليها من أي مكان
private salary: number; // يمكن الوصول إليها فقط من داخل الفئة
protected department: string; // يمكن الوصول إليها من داخل الفئة والفئات الوراثية
constructor(name: string, salary: number, department: string) {
this.name = name;
this.salary = salary;
this.department = department;
}
public displayInfo(): void {
console.log(`الاسم: ${this.name}, القسم: ${this.department}`);
this.displaySalary(); // يمكن استدعاء الطرق الخاصة من داخل الفئة
}
private displaySalary(): void {
console.log(`الراتب: ${this.salary}`);
}
}
const emp = new Employee("محمد", 5000, "هندسة");
emp.displayInfo(); // الاسم: محمد, القسم: هندسة
// emp.salary; // خطأ: لا يمكن الوصول للخاصية الخاصة
// emp.displaySalary(); // خطأ: لا يمكن استدعاء الطريقة الخاصة
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
move(distance: number = 0): void {
console.log(`${this.name} تحرك ${distance} متر.`);
}
}
class Dog extends Animal {
constructor(name: string) {
super(name); // استدعاء منشئ الفئة الأب
}
bark(): void {
console.log("هاو هاو!");
}
// تجاوز الطريقة في الفئة الأب
move(distance: number = 5): void {
console.log("يركض...");
super.move(distance); // استدعاء طريقة الفئة الأب
}
}
const dog = new Dog("ريكس");
dog.bark(); // هاو هاو!
dog.move(10); // يركض... ريكس تحرك 10 متر.
class MathHelper {
// خاصية ثابتة
static PI: number = 3.14159;
// طريقة ثابتة
static calculateCircleArea(radius: number): number {
return MathHelper.PI * radius * radius;
}
}
// يمكن استخدام الخصائص والطرق الثابتة مباشرة دون إنشاء كائن
console.log(MathHelper.PI); // 3.14159
console.log(MathHelper.calculateCircleArea(5)); // 78.53975
abstract class Shape {
color: string;
constructor(color: string) {
this.color = color;
}
abstract calculateArea(): number; // طريقة مجردة - يجب تنفيذها في الفئات الوراثية
displayColor(): void { // طريقة عادية
console.log(`لون الشكل هو ${this.color}`);
}
}
class Circle extends Shape {
radius: number;
constructor(color: string, radius: number) {
super(color);
this.radius = radius;
}
// تنفيذ الطريقة المجردة
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
}
// لا يمكن إنشاء كائن من فئة مجردة
// const shape = new Shape("أحمر"); // خطأ!
const circle = new Circle("أزرق", 5);
circle.displayColor(); // لون الشكل هو أزرق
console.log(circle.calculateArea()); // 78.53981633974483
class Product {
private _price: number = 0;
get price(): number {
return this._price;
}
set price(value: number) {
if (value < 0) {
throw new Error("السعر لا يمكن أن يكون سالبًا");
}
this._price = value;
}
}
const product = new Product();
product.price = 100; // استدعاء الـ setter
console.log(product.price); // 100 - استدعاء الـ getter
// product.price = -10; // خطأ: السعر لا يمكن أن يكون سالبًا
الدوال هي اللبنات الأساسية في تايب سكريبت. تايب سكريبت يضيف ميزات التحقق من الأنواع ويحسن تجربة التطوير.
// دالة بسيطة مع تحديد نوع المعاملات ونوع القيمة المرجعة
function add(x: number, y: number): number {
return x + y;
}
// تعبير الدالة (Function Expression)
const multiply = function(x: number, y: number): number {
return x * y;
};
// السهم الدالة (Arrow Function)
const divide = (x: number, y: number): number => x / y;
// معامل اختياري (بإضافة علامة ?)
function buildName(firstName: string, lastName?: string): string {
if (lastName) {
return `${firstName} ${lastName}`;
} else {
return firstName;
}
}
console.log(buildName("أحمد")); // أحمد
console.log(buildName("أحمد", "محمد")); // أحمد محمد
// معامل بقيمة افتراضية
function greet(name: string, greeting: string = "مرحبًا"): string {
return `${greeting}, ${name}!`;
}
console.log(greet("علي")); // مرحبًا, علي!
console.log(greet("خالد", "أهلاً")); // أهلاً, خالد!
// استخدام معاملات متبقية (...)
function sum(...numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2)); // 3
console.log(sum(1, 2, 3, 4, 5)); // 15
// تعريف نوع دالة
type MathOperation = (x: number, y: number) => number;
// متغير من نوع دالة
let operation: MathOperation;
// تعيين دالة متوافقة مع النوع
operation = (a, b) => a + b;
console.log(operation(5, 3)); // 8
// يمكن استخدام نوع الدالة كمعامل
function applyOperation(x: number, y: number, op: MathOperation): number {
return op(x, y);
}
console.log(applyOperation(10, 5, (a, b) => a * b)); // 50
interface Person {
name: string;
greet(this: Person): void;
}
const person: Person = {
name: "أحمد",
greet() {
console.log(`مرحباً، اسمي ${this.name}`);
}
};
person.greet(); // مرحباً، اسمي أحمد
// تايب سكريبت سيتحقق من استخدام this بشكل صحيح
// const badGreet = person.greet; // إذا تم استدعاء هذا، سيظهر خطأ عند التشغيل
// تعريف عدة توقيعات للدالة
function processInput(input: string): string;
function processInput(input: number): number;
function processInput(input: boolean): boolean;
// التنفيذ الفعلي للدالة
function processInput(input: string | number | boolean): string | number | boolean {
if (typeof input === "string") {
return input.toUpperCase();
} else if (typeof input === "number") {
return input * 2;
} else {
return !input;
}
}
console.log(processInput("hello")); // "HELLO"
console.log(processInput(10)); // 20
console.log(processInput(true)); // false
العموميات في تايب سكريبت تتيح إنشاء مكونات قابلة لإعادة الاستخدام مع مجموعة متنوعة من الأنواع، مع الحفاظ على سلامة النوع.
// دالة عمومية مع نوع T
function identity(arg: T): T {
return arg;
}
// استخدام الدالة العمومية مع تحديد النوع صراحة
let result1 = identity("مرحبًا");
// أو ترك تايب سكريبت ليستنتج النوع تلقائيًا
let result2 = identity(42); // سيستنتج أن T هي number
// واجهة عمومية
interface GenericIdentity {
(arg: T): T;
}
// تنفيذ الواجهة
let myIdentity: GenericIdentity = (arg) => arg;
// فئة عمومية
class Box {
private content: T;
constructor(value: T) {
this.content = value;
}
getValue(): T {
return this.content;
}
}
// إنشاء مثيلات للفئة مع أنواع مختلفة
const numberBox = new Box(123);
console.log(numberBox.getValue()); // 123
const stringBox = new Box("مرحبًا");
console.log(stringBox.getValue()); // "مرحبًا"
// قيد العمومية باستخدام extends
interface Lengthwise {
length: number;
}
// دالة عمومية مع قيد: T يجب أن تنفذ واجهة Lengthwise
function getLength(arg: T): number {
return arg.length;
}
// يمكن استدعاؤها مع أنواع تحتوي على خاصية length
console.log(getLength("مرحبًا")); // 5 (النص له خاصية length)
console.log(getLength([1, 2, 3])); // 3 (المصفوفة لها خاصية length)
// وهذا سيسبب خطأ في وقت التحويل البرمجي
// console.log(getLength(123)); // خطأ: الرقم ليس له خاصية length
// دالة مع عدة أنواع عمومية
function merge(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
const result = merge({ name: "أحمد" }, { age: 30 });
console.log(result); // { name: "أحمد", age: 30 }
console.log(result.name); // "أحمد"
console.log(result.age); // 30
// تعريف واجهات للقيود
interface Printable {
print(): void;
}
interface Loggable {
log(): void;
}
// دالة مع نوع عمومي مقيد بعدة واجهات
function createObject(factory: () => T): T {
const obj = factory();
obj.print();
obj.log();
return obj;
}
// استخدام الدالة
class PrintableLoggable implements Printable, Loggable {
print() {
console.log("طباعة...");
}
log() {
console.log("تسجيل...");
}
}
const obj = createObject(() => new PrintableLoggable());
التعدادات في تايب سكريبت توفر طريقة لتعريف مجموعة من الثوابت المسماة. يسهل استخدام التعدادات التعامل مع مجموعات القيم المتوقعة.
// تعداد رقمي بسيط
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
// استخدام التعداد
let dir: Direction = Direction.Down;
console.log(dir); // 1
// يمكن أيضًا الوصول للقيمة عن طريق الاسم
console.log(Direction[2]); // "Left"
// تعداد مع قيم بداية مخصصة
enum StatusCode {
OK = 200,
Created = 201,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
ServerError = 500
}
// استخدام التعداد
function handleResponse(status: StatusCode) {
switch (status) {
case StatusCode.OK:
console.log("تمت العملية بنجاح");
break;
case StatusCode.NotFound:
console.log("الصفحة غير موجودة");
break;
default:
console.log("حالة غير معروفة: " + status);
}
}
handleResponse(StatusCode.OK); // تمت العملية بنجاح
// تعداد نصي
enum MediaType {
JSON = "application/json",
XML = "application/xml",
FORM = "application/x-www-form-urlencoded"
}
// استخدام التعداد النصي
function setContentType(type: MediaType) {
console.log(`Content-Type: ${type}`);
}
setContentType(MediaType.JSON); // Content-Type: application/json
// تعدادات ثوابت وحسابية
enum FileAccess {
// ثوابت
None = 0,
Read = 1,
Write = 2,
// قيم حسابية
ReadWrite = Read | Write,
All = ReadWrite | 4
}
// استخدام التعداد
let access = FileAccess.Read | FileAccess.Write;
console.log(access); // 3
console.log(access === FileAccess.ReadWrite); // true
// استخدام العمليات المنطقية للتحقق
function checkAccess(access: FileAccess): void {
if (access & FileAccess.Read) {
console.log("لديك صلاحية القراءة");
}
if (access & FileAccess.Write) {
console.log("لديك صلاحية الكتابة");
}
}
checkAccess(FileAccess.ReadWrite);
// لديك صلاحية القراءة
// لديك صلاحية الكتابة
// التعداد المعرّف في ملفين مختلفين
enum Colors {
Red = 1,
Green,
Blue
}
// يمكن استكمال التعداد
enum Colors {
Yellow = 10,
Purple,
Orange
}
// استخدام التعداد
console.log(Colors.Green); // 2
console.log(Colors.Purple); // 11
// تعداد const يولد كود أكثر كفاءة عند التحويل
const enum Day {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
}
// استخدام التعداد
let weekend = [Day.Saturday, Day.Sunday];
استخدم const enum عندما لا تحتاج إلى الوصول إلى قيم التعداد عن طريق اسم المفتاح (مثل Day[0])، لأنه ينتج كود أكثر كفاءة.
تأكيدات النوع (Type Assertions) هي طريقة لإخبار مترجم تايب سكريبت أنك تعرف أكثر عن نوع قيمة ما. هذا يشبه التحويل في لغات أخرى، لكنه لا يقوم بتحويل البيانات فعليًا.
// صيغة "as"
let someValue: any = "هذا نص";
let strLength: number = (someValue as string).length;
// صيغة علامات زاوية (أقل استخدامًا وغير مدعومة في JSX)
let otherValue: any = "نص آخر";
let otherLength: number = (<string>otherValue).length;
interface User {
name: string;
email: string;
}
// بيانات من مصدر خارجي (مثل استجابة API)
const data: any = {
name: "أحمد",
email: "[email protected]",
// قد تحتوي على خصائص أخرى غير معلنة في الواجهة
};
// تأكيد نوع الكائن
const user = data as User;
// الآن يمكن الوصول إلى خصائص المستخدم بأمان النوع
console.log(user.name); // أحمد
// تحويل بين نوعين غير متوافقين
let value: string = "42";
// التحويل المباشر سيسبب خطأ
// let num: number = value as number; // خطأ!
// التحويل المزدوج باستخدام unknown
let num: number = (value as unknown) as number;
استخدم تأكيدات النوع بحذر! أنت تخبر مترجم TypeScript بتجاوز فحص النوع، مما قد يؤدي إلى أخطاء في وقت التشغيل إذا كان تأكيدك غير صحيح.
// بدلاً من تأكيدات النوع، يمكن استخدام فحوصات النوع
function example(value: any) {
// استخدام typeof للأنواع الأساسية
if (typeof value === "string") {
console.log(value.toUpperCase());
}
// استخدام instanceof للكائنات
if (value instanceof Date) {
console.log(value.toISOString());
}
}
// في حالة الواجهات، يمكن فحص وجود خاصية
function isUser(obj: any): obj is User {
return obj && typeof obj.name === "string" && typeof obj.email === "string";
}
function processUser(input: any) {
if (isUser(input)) {
// الآن تايب سكريبت يعرف أن input هو User
console.log(`اسم المستخدم: ${input.name}`);
} else {
console.log("البيانات غير صالحة");
}
}
// non-null assertion operator (!)
function process(value: string | null | undefined) {
// استخدام ! لإخبار المترجم أن القيمة لن تكون null أو undefined
let length: number = value!.length;
}
// definite assignment assertion (!)
let name!: string; // إخبار المترجم أن المتغير سيتم تعيينه قبل استخدامه
initialize();
console.log(name.length); // لن يتسبب بخطأ
function initialize() {
name = "أحمد";
}
تايب سكريبت يوفر العديد من الميزات المتقدمة للتعامل مع الأنواع، والتي تسمح بإنشاء أنظمة أنواع معقدة ومرنة.
// يمكن للمتغير أن يتقبل قيمة من نوعين أو أكثر
let id: string | number;
id = 101; // صحيح
id = "abc"; // صحيح
// id = true; // خطأ
// يمكن أيضًا استخدامها في المعاملات
function processId(id: string | number) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id * 2);
}
}
// الأنواع التقاطعية تجمع خصائص نوعين أو أكثر
interface User {
id: number;
name: string;
}
interface Employee {
companyId: number;
role: string;
}
// نوع جديد يجمع خصائص النوعين
type EmployeeUser = User & Employee;
// يجب أن تشتمل الكائنات على خصائص كلا النوعين
const empUser: EmployeeUser = {
id: 1,
name: "سامي",
companyId: 101,
role: "مطور"
};
// الأنواع المحرسة تساعد في تضييق نطاق النوع
function isPerson(obj: any): obj is { name: string; age: number } {
return obj && typeof obj.name === "string" && typeof obj.age === "number";
}
function greet(obj: any) {
if (isPerson(obj)) {
// الآن يعرف TypeScript أن obj له خصائص name و age
console.log(`مرحبًا ${obj.name}, عمرك ${obj.age} سنة`);
} else {
console.log("مرحبًا غريب");
}
}
// باستخدام typeof و instanceof
function process(value: string | number | Date) {
if (typeof value === "string") {
console.log(value.toUpperCase());
} else if (typeof value === "number") {
console.log(value.toFixed(2));
} else if (value instanceof Date) {
console.log(value.toISOString());
}
}
// الأنواع المتميزة تستخدم خاصية مشتركة للتمييز
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
// اتحاد الأنواع
type Shape = Circle | Square;
// استخدام خاصية kind للتمييز
function calculateArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
// TypeScript يعرف هنا أن shape هو Circle
return Math.PI * shape.radius ** 2;
case "square":
// TypeScript يعرف هنا أن shape هو Square
return shape.sideLength ** 2;
}
}
interface User {
id: number;
name: string;
email: string;
}
// keyof User سيكون "id" | "name" | "email"
function getProperty(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = {
id: 1,
name: "محمد",
email: "[email protected]"
};
const userName = getProperty(user, "name"); // محمد
// const invalid = getProperty(user, "age"); // خطأ: "age" ليس مفتاحًا في User
// أنواع المهام تنشئ أنواعًا جديدة بناءً على أنواع موجودة
interface Person {
name: string;
age: number;
}
// جعل كل الخصائص اختيارية
type PartialPerson = {
[K in keyof Person]?: Person[K];
};
// جعل كل الخصائص للقراءة فقط
type ReadonlyPerson = {
readonly [K in keyof Person]: Person[K];
};
// تحويل نوع كل خاصية
type PersonWithStringValues = {
[K in keyof Person]: string;
};
// الأنواع الشرطية تعتمد على علاقة بين الأنواع
type IsNumber = T extends number ? "yes" : "no";
type WithNumber = IsNumber; // "yes"
type WithString = IsNumber; // "no"
// مثال أكثر فائدة: استخراج نوع العنصر من مصفوفة
type ElementType = T extends (infer U)[] ? U : never;
type NumberArray = number[];
type ExtractedType = ElementType; // number
// أنواع مفيدة متوفرة في TypeScript
interface User {
id: number;
name: string;
email: string;
role: "admin" | "user";
}
// Partial - يجعل كل الخصائص اختيارية
type PartialUser = Partial;
// مثل: { id?: number; name?: string; email?: string; role?: "admin" | "user"; }
// Required - يجعل كل الخصائص مطلوبة
type RequiredUser = Required;
// Readonly - يجعل كل الخصائص للقراءة فقط
type ReadonlyUser = Readonly;
// Pick - يختار خصائص محددة
type UserCredentials = Pick;
// مثل: { email: string; role: "admin" | "user"; }
// Omit - يستبعد خصائص محددة
type PublicUser = Omit;
// مثل: { name: string; role: "admin" | "user"; }
// ReturnType - يحصل على نوع القيمة المرجعة من الدالة
function createUser() { return { name: "أحمد", age: 30 }; }
type UserObject = ReturnType;
// مثل: { name: string; age: number; }
الوحدات في تايب سكريبت تسمح بتنظيم الكود في ملفات منفصلة، مما يجعله أكثر قابلية للصيانة وإعادة الاستخدام.
// math.ts - ملف يصدّر دوال وثوابت
// تصدير ثابت
export const PI = 3.14159;
// تصدير دالة
export function add(x: number, y: number): number {
return x + y;
}
// تصدير كلاس
export class Calculator {
multiply(x: number, y: number): number {
return x * y;
}
}
// تصدير نوع أو واجهة
export interface Shape {
area(): number;
}
// تعريف دالة بدون تصديرها مباشرة
function subtract(x: number, y: number): number {
return x - y;
}
// تصدير الدالة لاحقًا
export { subtract };
// تصدير مع إعادة تسمية
export { subtract as minus };
// app.ts - ملف يستورد من math.ts
// استيراد عناصر محددة
import { add, PI } from './math';
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159
// استيراد مع إعادة تسمية
import { Calculator as Calc } from './math';
const calc = new Calc();
console.log(calc.multiply(2, 3)); // 6
// استيراد كل الصادرات ككائن
import * as MathUtils from './math';
console.log(MathUtils.subtract(5, 2)); // 3
console.log(MathUtils.PI); // 3.14159
// استيراد أنواع
import { Shape } from './math';
class Circle implements Shape {
radius: number;
constructor(radius: number) {
this.radius = radius;
}
area(): number {
return MathUtils.PI * this.radius * this.radius;
}
}
// user.ts - ملف به تصدير افتراضي
export default class User {
constructor(public name: string, public age: number) {}
greet() {
return `مرحبًا، اسمي ${this.name}`;
}
}
// استيراد الصادرات الافتراضية
// profile.ts
import User from './user';
const user = new User("أحمد", 30);
console.log(user.greet()); // مرحبًا، اسمي أحمد
// index.ts - ملف يجمع صادرات من ملفات متعددة
// إعادة تصدير كل الصادرات
export * from './math';
export * from './user';
// إعادة تصدير عناصر محددة
export { add, subtract } from './math';
// إعادة تصدير مع إعادة تسمية
export { default as UserClass } from './user';
// استيراد أنواع فقط بدون أي تنفيذ للكود
import type { Shape, Calculator } from './math';
// يمكن أيضاً استيراد أنواع وقيم معًا
import { add, type Shape } from './math';
// استيراد ديناميكي (Lazy Loading) للوحدات
async function loadModule() {
try {
// سينتظر تحميل الوحدة
const math = await import('./math');
console.log(math.add(2, 3)); // 5
} catch (error) {
console.error("فشل تحميل الوحدة", error);
}
}
يدعم تايب سكريبت أنظمة وحدات مختلفة: ES Modules, CommonJS, AMD, UMD, وغيرها. يمكن تحديد نظام الوحدات المستخدم في ملف التكوين tsconfig.json.
مساحات الأسماء (Namespaces) هي طريقة أخرى لتنظيم الكود في تايب سكريبت. على عكس الوحدات، فإن مساحات الأسماء تجمع عدة مكونات ذات صلة تحت اسم واحد.
في مشاريع تايب سكريبت الحديثة، يُفضل استخدام الوحدات (Modules) على مساحات الأسماء (Namespaces). ومع ذلك، قد تجد مساحات الأسماء في مشاريع قديمة.
// تعريف مساحة أسماء
namespace Validation {
// واجهة داخلية
export interface StringValidator {
isValid(s: string): boolean;
}
// فئة داخلية مصدرة
export class EmailValidator implements StringValidator {
isValid(s: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(s);
}
}
// دالة داخلية مصدرة
export function validate(validator: StringValidator, value: string): boolean {
return validator.isValid(value);
}
// متغير داخلي غير مصدر (لا يمكن الوصول إليه خارج مساحة الأسماء)
const defaultEmail = "[email protected]";
}
// استخدام مساحة الأسماء
let emailValidator = new Validation.EmailValidator();
let isValid = Validation.validate(emailValidator, "[email protected]");
console.log(isValid); // true
// استخدام الواجهة المصدرة
class PhoneValidator implements Validation.StringValidator {
isValid(s: string): boolean {
const phoneRegex = /^\d{10}$/;
return phoneRegex.test(s);
}
}
// مساحات أسماء متداخلة
namespace Utils {
export namespace Validation {
export class DateValidator {
isValid(date: Date): boolean {
return !isNaN(date.getTime());
}
}
}
export namespace Formatting {
export function formatDate(date: Date): string {
return date.toLocaleDateString('ar-EG');
}
}
}
// استخدام مساحات الأسماء المتداخلة
const dateValidator = new Utils.Validation.DateValidator();
const date = new Date();
if (dateValidator.isValid(date)) {
console.log(Utils.Formatting.formatDate(date));
}
// إنشاء اختصار لمساحة الأسماء
import Validators = Utils.Validation;
const myDateValidator = new Validators.DateValidator();
يمكن تقسيم مساحة الأسماء عبر عدة ملفات، ويمكن جمعها باستخدام التضمين المرجعي (/// <reference path="..." />).
// validators.ts
namespace Validation {
export interface StringValidator {
isValid(s: string): boolean;
}
}
// email-validator.ts
/// <reference path="validators.ts" />
namespace Validation {
export class EmailValidator implements StringValidator {
isValid(s: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(s);
}
}
}
// استخدام الفئات من المساحة نفسها
// main.ts
/// <reference path="validators.ts" />
/// <reference path="email-validator.ts" />
let validator = new Validation.EmailValidator();
عند ترجمة مساحات الأسماء، يمكن تحديد نمط التحويل باستخدام خيار --outFile للجمع أو --module لتحويلها إلى وحدات.
# تجميع عدة ملفات في ملف واحد مع مساحات الأسماء
tsc --outFile output.js file1.ts file2.ts
المزخرفات هي ميزة تجريبية في تايب سكريبت (وأصبحت قياسية في ECMAScript) تسمح بإضافة وظائف إضافية إلى الفئات والطرق والخصائص والمعاملات المعرفة.
لاستخدام المزخرفات، يجب تفعيل خيار experimentalDecorators في ملف tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true
}
}
// مزخرف فئة بسيط
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
// استخدام المزخرف
@sealed
class Person {
constructor(public name: string) {}
}
// مزخرف فئة مع معاملات
function logger(logString: string) {
return function(constructor: Function) {
console.log(logString);
console.log(constructor);
};
}
@logger('تسجيل عن فئة الموظف')
class Employee {
constructor(public name: string, public department: string) {}
}
// مزخرف طريقة
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// الدالة الأصلية
const originalMethod = descriptor.value;
// تعديل الدالة لإضافة السجل
descriptor.value = function(...args: any[]) {
console.log(`استدعاء ${propertyKey} مع المعاملات: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`${propertyKey} أرجع: ${JSON.stringify(result)}`);
return result;
};
return descriptor;
}
class Calculator {
@log
add(x: number, y: number) {
return x + y;
}
}
// مزخرف خاصية
function defaultValue(value: any) {
return function(target: any, propertyKey: string) {
let val = value;
// تعريف الواصف (getter/setter)
const getter = function() {
return val;
};
const setter = function(newVal: any) {
val = newVal;
};
// استبدال الخاصية بواصف جديد
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
class Product {
@defaultValue(0)
price: number;
constructor(public name: string) {}
}
// مزخرف معامل
function required(target: Object, propertyKey: string, parameterIndex: number) {
// احفظ المعلمات المطلوبة
const requiredParams: number[] = Reflect.getOwnMetadata('required', target, propertyKey) || [];
requiredParams.push(parameterIndex);
Reflect.defineMetadata('required', requiredParams, target, propertyKey);
}
// مزخرف لفحص المعاملات المطلوبة
function validate(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
descriptor.value = function(...args: any[]) {
const requiredParams: number[] = Reflect.getOwnMetadata('required', target, propertyName) || [];
for (const index of requiredParams) {
if (args[index] === undefined || args[index] === null) {
throw new Error(`المعامل في الموضع ${index} مطلوب للدالة ${propertyName}`);
}
}
return method.apply(this, args);
};
return descriptor;
}
class User {
@validate
updateProfile(@required name: string, email: string, @required age: number) {
console.log(`تحديث الملف الشخصي: ${name}, ${email}, ${age}`);
}
}
// مزخرف الواصف
function enumerable(value: boolean) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
class Person {
private _name: string;
constructor(name: string) {
this._name = name;
}
@enumerable(false)
get name() {
return this._name;
}
set name(value: string) {
this._name = value;
}
}
// عند وجود عدة مزخرفات، يكون ترتيب التنفيذ كالتالي:
// 1. مزخرفات المعامل (من اليسار إلى اليمين)
// 2. مزخرفات الطريقة
// 3. مزخرفات الواصف (getter/setter)
// 4. مزخرفات الخاصية
// 5. مزخرفات الفئة
// مثال على تسلسل المزخرفات
function first() {
console.log("أولاً");
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("تنفيذ المزخرف الأول");
};
}
function second() {
console.log("ثانيًا");
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("تنفيذ المزخرف الثاني");
};
}
class Example {
@first()
@second()
method() {}
}
// النتيجة:
// ثانيًا
// أولاً
// تنفيذ المزخرف الثاني
// تنفيذ المزخرف الأول
ملف tsconfig.json يحدد خيارات المترجم ومعلومات المشروع لمشروع تايب سكريبت. يساعد على توحيد إعدادات التحويل البرمجي وضمان سلوك متسق.
# إنشاء ملف تكوين جديد
tsc --init
{
"compilerOptions": {
"target": "es2016", /* تحديد إصدار ECMAScript المستهدف */
"module": "commonjs", /* تحديد نظام الوحدات: 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"outDir": "./dist", /* تحديد مجلد الإخراج */
"rootDir": "./src", /* تحديد مجلد المصدر */
"strict": true, /* تفعيل جميع فحوصات النوع الصارمة */
"esModuleInterop": true, /* تفعيل التوافق بين أنظمة الوحدات المختلفة */
"skipLibCheck": true, /* تخطي فحص نوع ملفات .d.ts */
"forceConsistentCasingInFileNames": true /* ضمان اتساق حالة أحرف أسماء الملفات */
},
"include": ["src/**/*"], /* أنماط الملفات المراد تضمينها */
"exclude": ["node_modules", "**/*.spec.ts"] /* أنماط الملفات المراد استبعادها */
}
الخيار | الوصف | |
---|---|---|
target | تحديد إصدار JavaScript المراد إنتاجه (es5, es6, es2016, es2020, esnext) | |
module | نظام الوحدات المستخدم (commonjs, amd, es2015, esnext) | |
outDir | مسار مجلد الإخراج للملفات المترجمة | |
rootDir | مسار مجلد المصدر للملفات | |
strict | تفعيل جميع خيارات الصرامة في النوع | |
lib | المكتبات المدمجة التي سيتم تضمينها (مثل dom, es2018) | |
declaration | إنشاء ملفات .d.ts التي تصف الأنواع | |
sourceMap | إنشاء ملفات sourceMap للتصحيح | |
noImplicitAny | noImplicitAny | رفض التعبيرات التي يستنتج لها المترجم النوع any |
strictNullChecks | التعامل مع null و undefined كنوع منفصل | |
noUnusedLocals | إظهار خطأ للمتغيرات المحلية غير المستخدمة | |
noUnusedParameters | إظهار خطأ لمعاملات الدوال غير المستخدمة | |
allowJs | السماح بمعالجة ملفات جافا سكريبت | |
jsx | كيفية معالجة ملفات JSX (preserve, react) | |
experimentalDecorators | تفعيل دعم المزخرفات |
{
"compilerOptions": {
// خيارات المترجم
},
"include": [
"src/**/*" // تضمين جميع الملفات في مجلد src
],
"exclude": [
"node_modules", // استبعاد مجلد node_modules
"**/*.test.ts" // استبعاد ملفات الاختبار
],
"files": [
"core.ts", // تضمين ملفات محددة فقط
"utils.ts"
],
"extends": "./tsconfig.base.json" // توريث الإعدادات من ملف آخر
}
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@app/*": ["src/app/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"]
}
}
}
يمكنك إنشاء ملفات تكوين متعددة للبيئات المختلفة:
# هيكل الملفات
tsconfig.json # التكوين الأساسي
tsconfig.dev.json # تكوين بيئة التطوير
tsconfig.prod.json # تكوين بيئة الإنتاج
# استخدام ملف تكوين محدد
tsc -p tsconfig.prod.json
إليك مجموعة من أفضل الممارسات لكتابة كود تايب سكريبت أفضل وأكثر أمانًا واحترافية.
// في ملف tsconfig.json
{
"compilerOptions": {
"strict": true
}
}
يفعل هذا الخيار مجموعة من الفحوصات الصارمة التي تساعد في اكتشاف المزيد من الأخطاء المحتملة.
// بدلاً من
function getData(data: any) {
return data.name;
}
// استخدم
interface Data {
name: string;
[key: string]: any; // للخصائص الإضافية إذا لزم الأمر
}
function getData(data: Data) {
return data.name;
}
// تعريف بنية البيانات باستخدام واجهات أو أنواع
interface User {
id: number;
name: string;
email: string;
}
// أو باستخدام type
type User = {
id: number;
name: string;
email: string;
};
// استخدام التعريف
function sendEmail(user: User) {
console.log(`إرسال بريد إلى ${user.name} على العنوان ${user.email}`);
}
// الاعتماد على استنتاج النوع عندما يكون واضحًا
// بدلاً من
const numbers: number[] = [1, 2, 3];
const result: number = add(1, 2);
// استخدم
const numbers = [1, 2, 3]; // يستنتج أنه number[]
const result = add(1, 2); // يستنتج أنه number إذا كانت الدالة تحدد نوع الإرجاع
// استخدام readonly للخصائص والمصفوفات
interface Config {
readonly apiKey: string;
readonly baseUrl: string;
}
function processData(data: readonly number[]) {
// لا يمكن تعديل data داخل الدالة
// data.push(1); // خطأ!
}
// استخدام الأنواع المتميزة للتعامل مع الحالات المختلفة
type Success = {
status: 'success';
data: string[];
};
type Error = {
status: 'error';
message: string;
};
type Response = Success | Error;
function handleResponse(response: Response) {
if (response.status === 'success') {
// TypeScript يعرف أن response هنا من نوع Success
console.log(`تم استرجاع ${response.data.length} عنصر`);
} else {
// TypeScript يعرف أن response هنا من نوع Error
console.log(`حدث خطأ: ${response.message}`);
}
}
// models/user.ts
export interface User {
id: number;
name: string;
}
// services/api.ts
import { User } from '../models/user';
export async function getUsers(): Promise {
// ...
}
// app.ts
import { getUsers } from './services/api';
async function initialize() {
const users = await getUsers();
// ...
}
// في بعض الحالات، يمكن استخدام union types بدلًا من enums
// بدلًا من
enum Color {
Red,
Green,
Blue
}
// يمكن استخدام
type Color = 'red' | 'green' | 'blue';
// وهذا يمنع الأخطاء عند تعيين قيم غير متوقعة
function setColor(color: Color) {
// ...
}
// تأكد من فعّل خيار strictNullChecks في tsconfig.json
function processValue(value: string | null | undefined) {
// يجب التحقق قبل استخدام value
if (value) {
console.log(value.toUpperCase());
}
// أو استخدام اختصار التهاون (nullish coalescing)
const safeValue = value ?? "قيمة افتراضية";
// أو استخدام اختصار الاستدعاء المتهاون (optional chaining)
const length = value?.length;
}
/**
* تقوم بحساب المجموع الكلي للمصفوفة
* @param numbers مصفوفة الأرقام المراد جمعها
* @returns مجموع كل الأرقام
*/
function sum(numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
}
// استخدام ملفات .d.ts لتعريف الأنواع
// types.d.ts
declare namespace App {
interface User {
id: number;
name: string;
}
interface Config {
apiUrl: string;
timeout: number;
}
}
استخدم ESLint مع قواعد TypeScript لضمان جودة الكود واتباع الممارسات المثلى:
// .eslintrc.json
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
]
}