Menu
flag Ar iconالعربيةdown icon

علامة التعجب في TypeScript: تأكيد عدم الفراغ (!)

علامة التعجب بعد قيمة، مثل user!، هي عامل تأكيد عدم الفراغ (non-null assertion): تحذف null وundefined من النوع دون أي فحص وقت التشغيل. تعرّف على ما يفعله x!، وصيغتي تأكيد الإسناد let x!: T وprop!: T، ولماذا هما خطرتان، والبدائل الأكثر أمانًا.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

علامة التعجب بعد تعبير، value!، هي عامل تأكيد عدم الفراغ (non-null assertion). تحذف null وundefined من النوع، فيمكن استخدام number | undefined كـ number. إنها وعد للمترجم لا فحص: لا يحدث شيء وقت التشغيل.

دون ! يكون tea.toFixed(2) هو الخطأ TS18048، 'tea' is possibly 'undefined'. ومعها يُترجم الكود لأنك أخبرت المترجم أن المفتاح موجود.

ما الذي يفعله x! وقت التشغيل: لا شيء

تُمحى ! من الناتج. كود JavaScript المترجم لـ prices.get("coffee")! هو مجرد prices.get("coffee"). وإذا كان التأكيد خاطئًا، يظهر الخطأ لاحقًا، في أول موضع تُستخدم فيه القيمة الغائبة:

يطبع البرنامج Cannot read properties of undefined (reading 'toFixed'). يحدث الانهيار في السطر الذي يلي !، وفي الكود الحقيقي قد يكون أبعد بكثير: قد تُخزَّن undefined في كائن وتنفجر في ملف آخر. هذه المسافة هي سبب خطورة !.

الإسناد المؤكد: let x!: T

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

index.ts(9,13): error TS2454: Variable 'config' is used before being assigned.

let config!: { port: number }; تأكيد إسناد مؤكد: «سيُسند هذا قبل أي قراءة». يصلح الخطأ، مع المشكلة نفسها التي في x!: إذا تُخطي init() يومًا، تحصل القراءة على undefined. وإعادة الهيكلة أفضل عادة، مثل const config = init(); مع دالة init تعيد الكائن.

خصائص الأصناف: prop!: T

مع strictPropertyInitialization (جزء من strict)، يجب تهيئة كل خاصية في الصنف عند تصريحها أو في المُنشئ. وإلا تحصل على TS2564: Property 'socket' has no initializer and is not definitely assigned in the constructor. وعندما تُضبط الخاصية لاحقًا بدالة أو بإطار عمل، يخبر prop!: T المترجم بقبولها:

يُظهر أول console.log الفجوة: يقول النوع إن socket موجود دائمًا، لكنه قبل open() يساوي undefined. استدعاء c.socket.send في تلك اللحظة يُترجم ثم يرمي خطأ. إذا كانت الخاصية قد تغيب فعلًا، فعرّفها socket?: ... وافحصها، أو أنشئ الكائن في المُنشئ. الموضع الرئيسي الذي يكون فيه prop! ممارسة معيارية هو إطار عمل يملأ خاصية بعد الإنشاء: @ViewChild(...) child!: ChildDirective في Angular (يُضبط قبل تشغيل ngAfterViewInit)، أو أصناف كيانات ORM التي تملأ المكتبة أعمدتها عند تحميل صف (يكتب توثيق MikroORM @Property() title!: string).

بدائل أكثر أمانًا

يمكن استبدال معظم علامات ! بشيء يفحصه المترجم، أو بفحص يفشل بوضوح في المكان الصحيح:

تضيّق الدالتان المساعدتان النوع كما تفعل !، لكن الافتراض الخاطئ ينتج missing HOST في مكانه بدل TypeError في مكان آخر. assertDefined دالة تأكيد (asserts value is ...): بعد الاستدعاء يعامل المترجم host على أنه string. مزيد من الأنماط في type guards.

بدلًا مناكتبما يحدث عندما تغيب القيمة
user!.nameif (user) { user.name }تُتخطى الكتلة
user!.nameuser?.nameundefined
count!count ?? 0تُستخدم القيمة الافتراضية
map.get(k)!must(map.get(k), "k")خطأ واضح في ذلك السطر
let x!: Tconst x = compute()لا شيء يمكن أن يسوء

علامات التعجب الأخرى

تعني ! أشياء مختلفة بحسب موضعها:

الكودالمعنى
value! (بعد تعبير)تأكيد عدم الفراغ، في TypeScript فقط
let x!: T وprop!: Tتأكيد الإسناد المؤكد، في TypeScript فقط
!value (قبل تعبير)النفي المنطقي NOT، JavaScript عادية
!!valueالتحويل إلى boolean، JavaScript عادية
a !== b وa != bعدم المساواة، JavaScript عادية

الأوليان فقط تُمحيان وقت الترجمة. أما !value و!!value فتعملان وقت التشغيل وتعيدان boolean.

الأسئلة الشائعة

ماذا تعني علامة التعجب بعد متغير في TypeScript؟

value! هو عامل تأكيد عدم الفراغ. يخبر المترجم بأن value ليست null ولا undefined، فيفقد نوعها هذين العضوين: string | undefined يصبح string. يُحذف من كود JavaScript الناتج ولا يضيف أي فحص وقت التشغيل، فإذا كنت مخطئًا يفشل البرنامج لاحقًا بـ TypeError.

ما الفرق بين ! و ? في TypeScript؟

يؤكد x! أن القيمة موجودة ويعطيك النوع غير الفارغ دون فحص. أما x?.y فيفحص وقت التشغيل: إذا كانت x هي null أو undefined يتوقف ويعيد undefined. وفي التصريح، تجعل name?: string الخاصية اختيارية، بينما تقول name!: string إن خاصية إلزامية ستُسند في مكان لا يراه المترجم.

ماذا تعني let x!: string؟

إنها تأكيد إسناد مؤكد (definite assignment assertion). تخبر المترجم بأن المتغير سيُسند قبل قراءته، رغم أن المترجم لا يستطيع إثبات ذلك (مثلًا يحدث الإسناد داخل دالة أخرى). دونها تكون قراءة المتغير الخطأ TS2454، Variable 'x' is used before being assigned.

كيف أصلح الخطأ "has no initializer and is not definitely assigned in the constructor"؟

هذا هو الخطأ TS2564 من strictPropertyInitialization. أعطِ الخاصية قيمة ابتدائية، أو أسندها في المُنشئ، أو اجعلها اختيارية (prop?: T)، أو إذا كان إطار عمل أو دالة تهيئة يضبطها فعلًا قبل الاستخدام، فاكتب prop!: T. علامة ! هي الخيار الأخير لأن لا شيء يتحقق من هذا الوعد.

هل استخدام عامل تأكيد عدم الفراغ ممارسة سيئة؟

ليس خطأ، لكن كل ! ادعاء غير مفحوص. إعدادات الفحص مثل @typescript-eslint/no-non-null-assertion تعلّمه. فضّل فحصًا يضيّق النوع (if (x) وx ?? fallback وx?.y) أو دالة مساعدة ترمي خطأ واضحًا. واحتفظ بـ ! للمواضع التي تكون فيها القيمة مضمونة بمنطق لا يستطيع المترجم تتبعه.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن