الأنواع الأساسية في TypeScript
فهم نظام النوع في TypeScript
نظام النوع في TypeScript هو ما يميزه عن JavaScript. تسمح لك الأنواع بتحديد شكل بياناتك، واكتشاف الأخطاء مبكراً، وجعل الكود الخاص بك موثقاً ذاتياً. في هذا الدرس، سنستكشف الأنواع الأساسية التي تشكل أساس نظام النوع في TypeScript.
تعليقات النوع: في TypeScript، تضيف معلومات النوع باستخدام نقطتين متبوعتين باسم النوع: let variableName: typeName = value;
الأنواع الأولية (Primitive Types)
string
يمثل نوع string البيانات النصية. يمكنك استخدام علامات اقتباس مفردة أو مزدوجة أو قوالب حرفية:
let firstName: string = "John";
let lastName: string = 'Doe';
let fullName: string = `${firstName} ${lastName}`;
// استنتاج النوع - يمكن لـ TypeScript استنتاج النوع
let city = "New York"; // النوع يُستنتج كـ string
// أمثلة على الأخطاء
let age: string = 25; // Error: Type 'number' is not assignable to type 'string'
number
يمثل نوع number كلاً من الأعداد الصحيحة والأعداد العشرية. TypeScript، مثل JavaScript، لا يميز بين أنواع رقمية مختلفة:
let age: number = 30; let price: number = 99.99; let hex: number = 0xf00d; // سداسي عشري let binary: number = 0b1010; // ثنائي let octal: number = 0o744; // ثماني // قيم رقمية خاصة let notANumber: number = NaN; let infinite: number = Infinity; let negInfinite: number = -Infinity; // فواصل رقمية للقراءة (ES2020+) let million: number = 1_000_000; let billion: number = 1_000_000_000;
boolean
نوع boolean له قيمتان فقط: true و false:
let isActive: boolean = true; let hasPermission: boolean = false; // تعبيرات منطقية let isAdult: boolean = age >= 18; let canVote: boolean = isAdult && hasPermission; // مثال على خطأ let isValid: boolean = "true"; // Error: Type 'string' is not assignable to type 'boolean'
استنتاج النوع: TypeScript ذكي بما يكفي لاستنتاج الأنواع في كثير من الحالات. إذا قمت بتهيئة متغير بقيمة، فغالباً لا تحتاج إلى التصريح صراحةً بالنوع: let name = "John"; يحصل تلقائياً على نوع string.
أنواع خاصة (Special Types)
any
نوع any هو مخرج يوقف فحص النوع. يمكن لمتغير من نوع any أن يحتوي على أي قيمة ويمكنك تنفيذ أي عملية عليه:
let data: any = "hello";
data = 42; // OK
data = true; // OK
data = { x: 10 }; // OK
// يمكن استدعاء أي أسلوب دون فحص النوع
data.toUpperCase(); // لا خطأ، حتى لو لم يكن data سلسلة نصية
data.nonExistent(); // لا خطأ في وقت التجميع
// any معدٍ - العمليات مع any تُرجع any
let result = data + 5; // result هو أيضاً نوع any
تجنب any عندما يكون ذلك ممكناً: استخدام any يبطل الغرض من TypeScript. يجب استخدامه فقط عند ترحيل كود JavaScript إلى TypeScript أو عند التعامل مع بيانات ديناميكية حقاً حيث لا يمكن معرفة النوع. حتى ذلك الحين، فكر في استخدام unknown بدلاً من ذلك.
unknown
نوع unknown هو بديل آمن من النوع لـ any. مثل any، يمكن لمتغير من نوع unknown أن يحتوي على أي قيمة، لكن يجب عليك إجراء فحص النوع قبل استخدامه:
let userInput: unknown = getUserInput();
// لا يمكن استخدام قيم unknown دون فحص النوع
userInput.toUpperCase(); // Error: Object is of type 'unknown'
// يجب فحص النوع أولاً
if (typeof userInput === "string") {
console.log(userInput.toUpperCase()); // OK - يعرف TypeScript أنها سلسلة نصية
}
// تأكيدات النوع (استخدم بحذر)
let strInput = userInput as string;
// أفضل: دالة حارس النوع
function isString(value: unknown): value is string {
return typeof value === "string";
}
if (isString(userInput)) {
console.log(userInput.toUpperCase()); // OK
}
أفضل ممارسة: استخدم unknown بدلاً من any عندما يكون النوع غير معروف حقاً. هذا يجبرك على إجراء فحص النوع، مما يجعل الكود الخاص بك أكثر أماناً.
void
يمثل نوع void غياب القيمة. يُستخدم عادةً كنوع إرجاع للدوال التي لا تُرجع أي شيء:
function logMessage(message: string): void {
console.log(message);
// لا عبارة إرجاع
}
function saveData(data: any): void {
localStorage.setItem("data", JSON.stringify(data));
return; // OK - يمكن الإرجاع بدون قيمة
}
// نادراً ما يُستخدم للمتغيرات
let unusable: void = undefined; // OK
let alsoUnusable: void = null; // OK فقط مع إيقاف --strictNullChecks
ملاحظة: دالة تُرجع void لا تزال قادرة على تنفيذ عبارة return، لكن لا يمكنها إرجاع قيمة. هذا يختلف عن الدوال التي تُرجع undefined.
null و undefined
لدى TypeScript نوعان لتمثيل الغياب: null و undefined:
let nothing: null = null; let notDefined: undefined = undefined; // مع strictNullChecks: false (غير موصى به) let name: string = null; // OK let age: number = undefined; // OK // مع strictNullChecks: true (موصى به) let name: string = null; // Error let age: number = undefined; // Error // السماح صراحةً بـ null/undefined let name: string | null = null; // OK let age: number | undefined = undefined; // OK let data: string | null | undefined = null; // OK
التوصية: قم دائماً بتمكين strictNullChecks في tsconfig.json. هذا يجبرك على التعامل صراحةً مع null و undefined، مما يمنع العديد من أخطاء وقت التشغيل.
never
يمثل نوع never القيم التي لا تحدث أبداً. يُستخدم للدوال التي لا تُرجع أبداً (ترمي أخطاء أو لديها حلقات لا نهائية):
// دالة ترمي خطأ
function throwError(message: string): never {
throw new Error(message);
}
// دالة مع حلقة لا نهائية
function infiniteLoop(): never {
while (true) {
// هذا لا ينتهي أبداً
}
}
// never هو نوع الإرجاع للدوال التي لا يمكنها الاكتمال
function fail(msg: string): never {
throw new Error(msg);
}
// never مفيد في فحص النوع الشامل
type Shape = "circle" | "square";
function getArea(shape: Shape): number {
switch (shape) {
case "circle":
return Math.PI * 10 * 10;
case "square":
return 10 * 10;
default:
// إذا أضفنا شكلاً جديداً ونسينا معالجته،
// سيخطئ TypeScript هنا
const exhaustiveCheck: never = shape;
return exhaustiveCheck;
}
}
فهم never: نوع never هو النوع السفلي في نظام النوع في TypeScript. يمكن تعيينه لكل نوع، لكن لا يمكن تعيين أي نوع (باستثناء never نفسه) لـ never. هذا يجعله مفيداً لفحص الاكتمال.
استنتاج النوع وتعليقات النوع
متى نستخدم تعليقات النوع
يمكن لـ TypeScript غالباً استنتاج الأنواع تلقائياً، لكن هناك حالات يجب عليك فيها التعليق على الأنواع صراحةً:
// استنتاج النوع يعمل بشكل جيد
let name = "John"; // يُستنتج كـ string
let age = 30; // يُستنتج كـ number
let isActive = true; // يُستنتج كـ boolean
// عندما لا يعمل الاستنتاج أو ليس واضحاً
let numbers; // يُستنتج كـ any - سيء
let numbers: number[]; // مكتوب صراحةً - جيد
// معلمات الدالة تحتاج دائماً إلى أنواع
function greet(name: string) { // يجب كتابة المعلمة
return `Hello, ${name}`; // نوع الإرجاع يُستنتج كـ string
}
// أحياناً أنواع الإرجاع الصريحة تحسّن القراءة
function add(a: number, b: number): number {
return a + b;
}
توسيع النوع وتضييقه
يمكن لـ TypeScript توسيع أو تضييق الأنواع بناءً على السياق:
// توسيع النوع
let x = "hello"; // النوع هو 'string'، وليس الحرفي "hello"
const y = "hello"; // النوع هو الحرفي "hello" (const لا يمكن إعادة تعيينه)
// تضييق النوع مع حراس النوع
function processValue(value: string | number) {
// value هو string | number هنا
if (typeof value === "string") {
// value مُضيّق إلى string هنا
console.log(value.toUpperCase());
} else {
// value مُضيّق إلى number هنا
console.log(value.toFixed(2));
}
}
// تضييق الصدق
function printLength(str: string | null) {
if (str) {
// str مُضيّق إلى string (ليس null)
console.log(str.length);
} else {
console.log("No string provided");
}
}
تأكيدات النوع (Type Assertions)
في بعض الأحيان تعرف المزيد عن نوع أكثر مما يعرفه TypeScript. تسمح لك تأكيدات النوع بتجاوز استنتاج TypeScript:
// بناء جملة القوس الزاوي (غير قابل للاستخدام في ملفات JSX/TSX)
let someValue: unknown = "this is a string";
let strLength: number = (<string>someValue).length;
// بناء جملة as (مفضل، يعمل في كل مكان)
let someValue: unknown = "this is a string";
let strLength: number = (someValue as string).length;
// مثال من العالم الحقيقي: معالجة DOM
const canvas = document.getElementById("myCanvas"); // Type: HTMLElement | null
const ctx = (canvas as HTMLCanvasElement).getContext("2d");
// تأكيد مزدوج (تجنب ما لم يكن ضرورياً للغاية)
const value = "hello" as any as number; // سيء - يبطل سلامة النوع
استخدم تأكيدات النوع بعناية: تأكيدات النوع تتجاوز فحص النوع في TypeScript. استخدمها فقط عندما تكون متأكداً من النوع. فضّل حراس النوع عندما يكون ذلك ممكناً.
الأنواع الحرفية (Literal Types)
الأنواع الحرفية تسمح لك بتحديد القيم الدقيقة التي يمكن أن يحتويها متغير:
// حرفيات سلسلة نصية
let direction: "up" | "down" | "left" | "right";
direction = "up"; // OK
direction = "north"; // Error
// حرفيات رقمية
let diceRoll: 1 | 2 | 3 | 4 | 5 | 6;
diceRoll = 3; // OK
diceRoll = 7; // Error
// حرفيات منطقية (أقل شيوعاً)
let isTrue: true = true;
let isFalse: false = false;
// الجمع مع أنواع الاتحاد
type Status = "success" | "error" | "pending";
type Level = 1 | 2 | 3 | 4 | 5;
function setStatus(status: Status): void {
console.log(`Status: ${status}`);
}
setStatus("success"); // OK
setStatus("failed"); // Error
أسماء النوع المستعارة (Type Aliases)
تُنشئ أسماء النوع المستعارة أسماء جديدة للأنواع، مما يجعل الكود الخاص بك أكثر قابلية للقراءة:
// اسم نوع مستعار أساسي
type UserID = string | number;
let id1: UserID = "abc123";
let id2: UserID = 456;
// اسم نوع مستعار معقد
type Point = {
x: number;
y: number;
};
let point: Point = { x: 10, y: 20 };
// اسم نوع دالة مستعار
type MathOperation = (a: number, b: number) => number;
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
// اسم نوع اتحاد مستعار
type StringOrNumber = string | number;
type SuccessResponse = { success: true; data: any };
type ErrorResponse = { success: false; error: string };
type ApiResponse = SuccessResponse | ErrorResponse;
تمرين عملي
أنشئ ملفاً يسمى types-practice.ts وأكمل هذه المهام:
- أنشئ متغيرات من كل نوع أولي (string، number، boolean) وقم بتعيين قيم مناسبة
- أنشئ دالة تقبل معلمة من نوع
string | numberوتُرجع تمثيلها كسلسلة نصية. استخدم تضييق النوع للتعامل مع كلتا الحالتين. - أنشئ اسماً مستعاراً للنوع يسمى
Temperatureيمكن أن يكون إما Celsius أو Fahrenheit:ثم أنشئ دالة تحول درجات الحرارة بين الوحدات.type Temperature = { value: number; unit: "C" | "F"; }; - أنشئ دالة تأخذ معلمة من نوع
unknownوتطبعها بأمان على وحدة التحكم، مع فحص نوعها أولاً - أنشئ اسماً مستعاراً لحالة مستخدم يمكن أن تكون فقط "active" أو "inactive" أو "suspended"، واكتب دالة تأخذ هذه الحالة وتُرجع رسالة مناسبة
- أصلح الأخطاء في هذا الكود:
let age: number = "25"; let status: "active" | "inactive" = "pending"; let data: string = null; function greet(name) { return "Hello, " + name; }
تحدي إضافي: أنشئ آلة حاسبة آمنة من النوع تقبل فقط الأرقام وتُرجع الأرقام، مع معالجة مناسبة للأخطاء للقسمة على صفر باستخدام نوع never.
الأخطاء الشائعة التي يجب تجنبها
- استخدام
anyفي كل مكان: هذا يبطل الغرض من TypeScript. استخدمunknownبدلاً من ذلك. - عدم تمكين
strictNullChecks: قم دائماً بتمكين هذا فيtsconfig.json. - الإفراط في استخدام تأكيدات النوع: فضّل حراس النوع والكتابة المناسبة.
- تجاهل أخطاء النوع: إذا أعطاك TypeScript خطأ، فعادةً ما يكون هناك سبب وجيه.
- عدم الاستفادة من استنتاج النوع: دع TypeScript يستنتج الأنواع عندما يكون واضحاً.
الملخص
- الأنواع الأولية:
stringوnumberوbooleanهي اللبنات الأساسية - any: يعطّل فحص النوع - تجنب عندما يكون ذلك ممكناً
- unknown: بديل آمن من النوع لـ
any- يتطلب فحص النوع قبل الاستخدام - void: يُستخدم للدوال التي لا تُرجع قيماً
- null و undefined: تمثل الغياب - تعامل صراحةً مع
strictNullChecks - never: للدوال التي لا تُرجع أبداً أو الكود غير القابل للوصول
- استنتاج النوع: يمكن لـ TypeScript غالباً تحديد الأنواع تلقائياً
- تأكيدات النوع: تجاوز استنتاج النوع عندما تعرف أفضل (استخدم بحذر)
- الأنواع الحرفية: حدد القيم الدقيقة التي يمكن أن يحتويها متغير
- أسماء النوع المستعارة: أنشئ أسماء نوع مخصصة قابلة لإعادة الاستخدام
الخطوات التالية: الآن بعد أن فهمت الأنواع الأساسية، سنستكشف كيفية العمل مع مجموعات من البيانات باستخدام المصفوفات والصفوف في الدرس التالي.