05- الأنواع الخاصة في TypeScript مثل any و never و void و unknown

قالب اقرأ نوفمبر 19, 2025 نوفمبر 19, 2025
للقراءة
كلمة

في الدرس السابق من دورتنا النظرية المتقدمة في تعلم واتقان الـ TypeScript من الصفر، تعرفنا على الأنواع البدائية (Primitive Types) في TypeScript، وهي اللبنات الأساسية مثل string للنصوص، و number للأرقام، و boolean للقيم المنطقية (صواب أو خطأ).

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

ولكن، ماذا يحدث عندما تواجه مواقف أكثر تعقيدًا؟

ماذا لو كنت تتعامل مع بيانات قادمة من مصدر خارجي لا تعرف هيكلها مسبقًا؟

أو ماذا عن دالة (Function) لا يُفترض بها أن تُعيد أي قيمة على الإطلاق؟ أو حتى دالة لا تنتهي من تنفيذها أبدًا؟

هنا يأتي دور الأنواع الخاصة (Special Types).

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

إنها تساعد TypeScript على فهم نواياك بشكل أعمق، مما يؤدي إلى كود أكثر أمانًا ووضوحًا.

في هذا القسم، سنتعمق في أربعة من أهم هذه الأنواع الخاصة:

  1. any: الجوكر أو البطاقة الجامحة التي تتجاوز كل قواعد فحص الأنواع.
  2. unknown: الصندوق الغامض الذي يجب عليك التحقق من محتوياته قبل استخدامه.
  3. void: الفراغ الذي يدل على أن دالة ما تؤدي مهمة ولكن لا تُعيد أي نتيجة.
  4. never: الطريق المسدود الذي يشير إلى أن تنفيذ الكود لن يستمر من هذه النقطة.

فهم هذه الأنواع الأربعة هو خطوة حاسمة لتنتقل من مجرد استخدام TypeScript إلى إتقانه.

هيا بنا نبدأ رحلتنا لاستكشاف هذه الأدوات القوية واحدة تلو الأخرى.

05- الأنواع الخاصة في TypeScript مثل any و never و void و unknown

النوع any: سيف ذو حدين

تخيل أنك في بلد يطبق قواعد مرور صارمة للغاية، ولكن لديك تصريح خاص يسمح لك بتجاوز كل الإشارات والقواعد. هذا التصريح يمنحك حرية مطلقة، ولكنه في نفس الوقت يعرضك لخطر كبير. هذا بالضبط هو النوع any في عالم TypeScript.

ما هو النوع any؟

النوع any هو بمثابة مهرب (Escape Hatch) من نظام الأنواع الصارم في TypeScript. عندما تعلن عن متغير (Variable) من النوع any، فأنت تقول لمترجم TypeScript (TypeScript Compiler): "ثق بي، أنا أعرف ما أفعل. لا تقم بأي فحص للأنواع على هذا المتغير".

هذا يعني أنه يمكنك:

  • إسناد أي قيمة له: رقم، نص، كائن (Object)، مصفوفة (Array)، أي شيء على الإطلاق.
  • إعادة إسناد قيمة من نوع مختلف تمامًا له في أي وقت.
  • الوصول إلى أي خاصية (Property) عليه، حتى لو لم تكن موجودة.
  • استدعاء أي دالة (Function) عليه، حتى لو لم تكن موجودة.

مترجم TypeScript لن يعترض على أي من هذه العمليات وقت الترجمة (Compile Time).

المشكلة هي أن هذه الحرية تأتي على حساب الأمان، وقد تؤدي إلى أخطاء كارثية وقت التشغيل (Runtime)، وهو بالضبط ما نحاول تجنبه باستخدام TypeScript!

CODE
// 'any' allows you to assign any type of value
let myWildCard: any = "Hello, TypeScript!";

// No error at compile time
myWildCard = 100;
myWildCard = { name: "John Doe", age: 30 };
myWildCard = [1, 2, 3];

// TypeScript will not check any operations on an 'any' type
// This will compile without any errors...
console.log(myWildCard.toUpperCase()); // Potentially a runtime error if myWildCard is not a string
myWildCard.someNonExistentMethod();   // Potentially a runtime error
let y = myWildCard.whatever.you.want;  // Potentially a runtime error

في المثال أعلاه، آخر قيمة تم إسنادها للمتغير myWildCard هي مصفوفة. عند محاولة استدعاء .toUpperCase() (وهي دالة خاصة بالنصوص)، لن يكتشف TypeScript الخطأ. سينجح الكود في الترجمة، ولكنه سينهار عند تشغيله في المتصفح أو بيئة Node.js، وهذا يلغي الفائدة الأساسية من استخدام TypeScript.

متى قد نستخدم any؟ (مع تحذير شديد)

على الرغم من خطورته، هناك بعض الحالات المحدودة جدًا التي قد يكون فيها استخدام any مبررًا كحل مؤقت:

ترحيل كود JavaScript قديم:

عند تحويل مشروع JavaScript ضخم إلى TypeScript، قد يكون من الصعب تحديد أنواع كل شيء دفعة واحدة. يمكن استخدام any كحل مؤقت لتشغيل المشروع، على أن يتم التخلص منه تدريجيًا.

التعامل مع مكتبات خارجية بدون تعريفات أنواع:

بعض مكتبات JavaScript القديمة قد لا تحتوي على ملفات تعريف أنواع (Type Definition Files). في هذه الحالة، قد تضطر لاستخدام any للتعامل مع الكائنات التي تعيدها هذه المكتبات.

بيانات ديناميكية معقدة:

في حالات نادرة جدًا، قد تتعامل مع بيانات ديناميكية للغاية لا يمكن التنبؤ بهيكلها، مثل مخرجات من واجهة برمجة تطبيقات (API) قديمة جدًا وغير موثقة.

القاعدة الذهبية: فكر في any على أنه "دين تقني" (Technical Debt). يجب أن يكون هدفك دائمًا هو التخلص منه واستبداله بأنواع أكثر تحديدًا وأمانًا في أقرب فرصة ممكنة.

النوع unknown: الأخ الأكثر أمانًا للنوع any

إذا كان any هو التصريح الذي يسمح لك بتجاوز كل القواعد، فإن unknown يشبه استلامك لصندوق مغلق ومختوم. أنت تعلم أن هناك شيئًا بالداخل، لكن لا يمكنك استخدامه أو التعامل معه إلا بعد فتحه والتحقق من محتوياته بأمان. هذا هو جوهر النوع unknown.

ما هو النوع unknown؟

تم تقديم unknown في TypeScript 3.0 كبديل آمن للنوع any. مثله مثل any، يمكنك إسناد أي قيمة ممكنة إلى متغير من النوع unknown.

CODE
let myMysteryBox: unknown;

myMysteryBox = "A secret message";
myMysteryBox = 12345;
myMysteryBox = () => console.log("I am a function");

ولكن هنا يكمن الاختلاف الجوهري والحاسم: لا يمكنك فعل أي شيء بمتغير من النوع unknown مباشرة. لا يمكنك الوصول إلى خصائصه، أو استدعاء دواله، أو استخدامه في عمليات حسابية. يجبرك TypeScript على القيام بخطوة إضافية: التحقق من نوعه أولاً.

CODE
let myMysteryBox: unknown = "This could be anything";

// The following lines will ALL cause a COMPILE-TIME error!
// console.log(myMysteryBox.length); // Error: Object is of type 'unknown'.
// myMysteryBox.toUpperCase();         // Error: Object is of type 'unknown'.
// let trimmed = myMysteryBox.trim(); // Error: Object is of type 'unknown'.

هذا السلوك يمثل قمة الأمان. TypeScript يقول لك: "أنا لا أعرف ما هذا الشيء، ولا أنت كذلك. أثبت لي ما هو نوعه قبل أن أسمح لك باستخدامه".

كيف نتعامل مع النوع unknown؟ (تضييق الأنواع)

للاستفادة من قيمة من نوع unknown، يجب عليك تضييق نوعها (Type Narrowing) إلى نوع أكثر تحديدًا. يمكنك فعل ذلك بعدة طرق:

باستخدام typeof للمقارنة مع الأنواع البدائية:

هذه هي الطريقة الأكثر شيوعًا. يمكنك استخدام شرط if مع typeof ليعرف TypeScript أنه داخل هذا الشرط، المتغير هو من النوع الذي تحققت منه.

CODE
function processInput(input: unknown) {
  // Inside this 'if' block, TypeScript knows that 'input' is a string.
  if (typeof input === 'string') {
    console.log(input.toUpperCase()); // This is now safe and allowed!
  } else if (typeof input === 'number') {
    // Inside this block, it's a number.
    console.log(`The number is ${input.toFixed(2)}`); // Safe!
  }
}

processInput("hello world"); // Outputs: HELLO WORLD
processInput(3.14159);   // Outputs: The number is 3.14

باستخدام instanceof للكائنات:

إذا كنت تتوقع أن يكون المتغير نسخة (Instance) من فئة (Class) معينة، يمكنك استخدام instanceof.

CODE
class User {
  constructor(public name: string) {}
}

function greet(person: unknown) {
  // Check if 'person' is an instance of the User class
  if (person instanceof User) {
    // TypeScript now knows 'person' is of type User inside this block
    console.log(`Hello, ${person.name}!`); // Safe!
  }
}

greet(new User("Alice")); // Outputs: Hello, Alice!
greet({ name: "Bob" });   // Does nothing, because it's not an instance of User

باستخدام تأكيد النوع (Type Assertion):

يمكنك أن "تجبر" TypeScript على التعامل مع المتغير كنوع معين باستخدام الكلمة المفتاحية as. هذا الخيار أقل أمانًا لأنه يشبه قولك للمترجم "أنا أعرف أفضل منك"، وقد تكون مخطئًا.

CODE
let data: unknown = '{"product": "Laptop", "price": 4000}';

// We are asserting that 'data' is a string. Use with caution.
const jsonData = data as string;

// Now we can use string methods on it.
const parsedData = JSON.parse(jsonData);
console.log(parsedData.product);

متى نستخدم unknown؟

استخدم unknown دائمًا كبديل لـ any عندما لا تكون متأكدًا من نوع البيانات القادمة:

  • عند استقبال بيانات من واجهة برمجية تطبيقات (API).
  • عند قراءة بيانات من localStorage أو من ملفات المستخدم.
  • عند التعامل مع أي بيانات تأتي من خارج بيئة TypeScript الخاصة بك.

النوع unknown هو الصديق الآمن للمطور. إنه يفرض عليك كتابة كود مرن ومقاوم للأخطاء من خلال إجبارك على التعامل مع الحالات غير المتوقعة.

النوع void: للدوال التي لا تُعيد شيئًا

في حياتنا اليومية، نقوم بالعديد من الأفعال التي لا نتوقع منها نتيجة مادية. على سبيل المثال، عندما تضغط على زر لتشغيل التلفزيون، أنت لا تتوقع أن يعطيك الزر شيئًا في المقابل. لقد أدى مهمته (تشغيل التلفزيون) وانتهى الأمر. النوع void في TypeScript يمثل هذا المفهوم بالضبط.

ما هو النوع void؟

النوع void يستخدم بشكل أساسي لتحديد القيمة المعادة (Return Value) لدالة لا تُعيد أي قيمة.

عندما ترى : void بعد قائمة المعاملات (Parameters) في تعريف دالة، فهذا يعني أن هذه الدالة قد تؤدي بعض الإجراءات الجانبية (Side Effects) - مثل الطباعة في وحدة التحكم (Console)، أو تعديل متغير، أو إجراء طلب شبكي - ولكنها لا تستخدم العبارة return لإرجاع قيمة محددة.

CODE
// This function logs a message to the console. It performs an action but returns nothing.
function logMessage(message: string): void {
  console.log(message);
  // There is no 'return' statement here.
}

// This function modifies a DOM element. It also returns nothing.
function updateHeaderText(newText: string): void {
  const header = document.getElementById('main-header');
  if (header) {
    header.textContent = newText;
  }
}

إذا حاولت إرجاع قيمة من دالة تم تحديد نوعها المعاد بـ void، سيقوم TypeScript بتنبيهك بوجود خطأ (باستثناء null و undefined في بعض الإعدادات).

CODE
function invalidFunction(): void {
  // Error: Type 'string' is not assignable to type 'void'.
  return "this is not allowed"; 
}

الفرق بين void و undefined

قد يبدو void و undefined متشابهين، لكنهما يخدمان أغراضًا مختلفة:

  • undefined: هو قيمة (Value). متغير لم يتم إسناد قيمة له يكون undefined.
  • void: هو نوع (Type). يستخدم بشكل حصري تقريبًا لوصف القيمة المعادة لدالة لا تُرجع أي شيء. إنه يتعلق بتعريف الدالة (Function Signature) وليس بقيمة المتغير.

من الناحية الفنية، الدالة التي لا تحتوي على عبارة return تُعيد undefined في JavaScript. TypeScript يعرف هذا، لكن استخدام void هو طريقة أكثر وضوحًا وتعبيرًا للإشارة إلى أن القيمة المعادة، إن وجدت، لا ينبغي استخدامها.

لنفترض أنك قمت بتخزين نتيجة استدعاء دالة void في متغير. سيكون نوع هذا المتغير void، ولن تتمكن من فعل أي شيء مفيد به تقريبًا، وهو بالضبط السلوك المطلوب.

CODE
function sayHello(): void {
  console.log("Hello!");
}

const result = sayHello(); // 'result' is of type 'void'

// You can't use 'result' for much, which is the point.
// const doubled = result * 2; // Error: The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.

باختصار، عندما تكتب دالة هدفها "القيام بشيء" وليس "حساب شيء"، فإن void هو النوع الصحيح لقيمتها المعادة.

النوع never: للدوال التي لا تنتهي أبدًا

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

ما هو النوع never؟

النوع never يمثل نوع القيم التي لا يمكن أن تحدث أبدًا. يتم استخدامه للدوال التي نكون متأكدين تمامًا أنها لن تصل إلى نهايتها بشكل طبيعي. هذا يعني أن الدالة إما:

  1. دائمًا ما تطلق خطأ (Throws an Error): عندما يتم إطلاق خطأ، يتوقف التنفيذ العادي للبرنامج تمامًا.
  2. تدخل في حلقة لا نهائية (Infinite Loop): الدالة تستمر في العمل إلى الأبد ولا تصل أبدًا إلى عبارة return.

في كلتا الحالتين، لا تعود الدالة أبدًا إلى النقطة التي تم استدعاؤها منها. لذلك، نقول إنها تُعيد never.

أمثلة عملية على never

دالة تطلق خطأ:

من الشائع جدًا إنشاء دوال مساعدة (Helper Functions) للتعامل مع الأخطاء. إذا كانت وظيفة الدالة هي فقط إطلاق خطأ وإنهاء التنفيذ، فإن نوعها المعاد هو never.

CODE
// This function never successfully completes. It always throws an error.
function throwError(message: string): never {
  throw new Error(message);
}

function validateInput(input: string) {
  if (!input) {
    throwError("Input cannot be empty!");
  }
  // Because throwError is of type 'never', TypeScript knows that
  // the code here is only reachable if 'input' is not empty.
  // So 'input' must be a non-empty string from this point forward.
}

دالة ذات حلقة لا نهائية:

هذا المثال أقل شيوعًا في تطبيقات الويب العادية ولكنه يوضح المفهوم.

CODE
// This function never returns because it runs forever.
function infiniteProcess(): never {
  while (true) {
    console.log("Processing...");
    // This loop never exits
  }
}

كيف يختلف never عن void؟

هذه نقطة حاسمة يجب فهمها. الفرق يكمن في إمكانية الوصول إلى نهاية الدالة (Reachability):

  • void: الدالة تصل إلى نهايتها. يتم تنفيذ الكود بداخلها، وتنتهي، ويعود التحكم إلى المتصل (Caller). إنها ببساطة لا تُعيد قيمة يمكن استخدامها.
  • never: الدالة لا تصل أبدًا إلى نهايتها. إما أن ينهار البرنامج (بسبب خطأ) أو يظل عالقًا إلى الأبد. التحكم لا يعود أبدًا إلى المتصل.
CODE
// Returns 'void'. The function completes and execution continues.
function log(message: string): void {
  console.log(message);
  // End of function is reached here
}

// Returns 'never'. The function never completes.
function fail(message: string): never {
  throw new Error(message);
  // This point is unreachable. End of function is never reached.
}

function demo() {
  log("Starting demo"); // This line runs
  fail("Something went wrong"); // Execution stops here
  log("This message will never be logged"); // Unreachable code
}

الاستخدام المتقدم لـ never: فحص الشمولية (Exhaustiveness Checking)

أحد أقوى استخدامات النوع never هو ضمان أنك قد تعاملت مع جميع الحالات الممكنة في كودك، خاصة عند استخدام عبارات switch مع الأنواع الموحدة (Union Types)، وهو ما سنتناوله لاحقًا بالتفصيل. الفكرة هي أنه إذا قمت بتغطية جميع الحالات الممكنة، فإن الحالة الافتراضية default يجب أن تكون مستحيلة الوصول، وبالتالي يمكن تعيين نوعها إلى never. إذا أضفت نوعًا جديدًا لاحقًا ونسيت التعامل معه، سيخبرك TypeScript بوجود خطأ. هذا يجعل كودك أكثر متانة وقابلية للصيانة.

خلاصة: اختيار الأداة المناسبة للمهمة

لقد استكشفنا أربعة أنواع خاصة وقوية في TypeScript، كل منها يحل مشكلة فريدة. دعونا نلخصها:

any: تجنبه قدر الإمكان. إنه يتجاوز فحص الأنواع ويقوض سلامة الكود. استخدمه فقط كحل مؤقت للغاية عند ترحيل كود JavaScript.

unknown: البديل الآمن لـ any. استخدمه عندما تتلقى بيانات من مصدر لا تثق بنوعه. يجبرك على التحقق من النوع قبل استخدامه، مما يمنع الأخطاء وقت التشغيل.

void: للدوال التي لا تُعيد قيمة. استخدمه كنوع معاد (Return Type) للدوال التي تؤدي إجراءات ولكن لا تنتج نتيجة، مثل معالجات الأحداث (Event Handlers) أو دوال التسجيل (Logging Functions).

never: للدوال التي لا تنتهي. استخدمه كنوع معاد للدوال التي تطلق خطأ دائمًا أو تحتوي على حلقة لا نهائية. يخبر TypeScript أن تنفيذ الكود لن يستمر بعد هذه النقطة.

إن إتقان هذه الأنواع الخاصة ينقلك من مجرد كتابة كود TypeScript إلى كتابة كود آمن ومعبر وذكي. أنت الآن لا تخبر المترجم فقط عن أنواع بياناتك، بل تخبره أيضًا عن سلوك ونوايا الكود الخاص بك، وهذا هو جوهر قوة TypeScript.

شارك المقال لتنفع به غيرك

قد تُعجبك هذه المشاركات

4419914284293787151
https://www.iqraatech.net/