يقبل any وunknown كل القيم. الفرق فيما يمكنك فعله بالقيمة بعد ذلك: يسمح لك any بفعل أي شيء ولا يفحص شيئًا، بينما لا يسمح لك unknown بفعل شيء تقريبًا حتى تثبت ما هي القيمة.
دون سطر @ts-expect-error يكون u.toUpperCase() خطأ الترجمة TS18046. يضيّق فحص typeof المتغير u إلى string، وداخل تلك الكتلة تتاح كل دوال النصوص.
any مقابل unknown في لمحة
any | unknown | |
|---|---|---|
| يقبل أي قيمة | نعم | نعم |
إسناده إلى string أو number أو غيرهما | نعم، دون فحص | لا (TS2322) |
| قراءة خاصية أو استدعاء دالة | نعم، دون فحص | لا (TS18046) |
| استدعاؤه كدالة | نعم | لا |
العمليات الحسابية والمقارنة (x * 2 وx + 1 وx < 5) | نعم | لا (TS18046) |
| يحتاج إلى فحص قبل الاستخدام | لا | نعم (typeof أو instanceof أو in أو type guard) |
| أثره على فحص الأنواع | يتوقف لتلك القيمة ولكل ما تلمسه | يبقى مفعّلًا |
بلغة نظرية الأنواع، unknown هو النوع الأعلى (top type): كل نوع يمكن إسناده إليه، ولا يمكن إسناده إلا إلى unknown وany. أما any فمخرج طوارئ يمكن إسناده في الاتجاهين، إلى كل شيء ما عدا never.
any يوقف فحص الأنواع
القيمة التي نوعها any موثوق بها ثقة عمياء. يقبل المترجم الأخطاء الإملائية والأنواع الخاطئة والخصائص الغائبة، فتظهر الأخطاء وقت التشغيل بدلًا من ذلك.
يُظهر الناتج المشكلة: متغير نوعه المعلن number يحمل نصًا، والسطر الأخير يرمي Cannot read properties of undefined (reading 'city'). وينتشر any أيضًا. user.name نوعه any، فكل قيمة محسوبة منه تكون any، وقيمة واحدة بلا نوع يمكن أن توقف الفحص بعيدًا عن المكان الذي دخلت منه.
unknown يجبرك على الفحص أولًا
مع unknown يرفض المترجم كل عملية حتى يضيّق الكود القيمة. يستخدم التضييق فحوص JavaScript العادية، وداخل كل فرع يكون للقيمة النوع الذي فُحص.
يجب أن يأتي فحص value === null قبل فحص الكائن لأن typeof null يساوي "object". بعد "id" in value تعرف TypeScript أن للكائن خاصية id نوعها unknown، لأن لا شيء يقول ما الذي تحمله. يحوّلها String() إلى نص صراحة؛ ولاستخدامها كرقم عليك فحصها بـ typeof أولًا.
يمكنك أيضًا تخطي الفحص بتأكيد نوع، value as string، فيقبله المترجم. هذا وعد لا يقف وراءه أي فحص وقت التشغيل، لذلك فضّل فحصًا حقيقيًا؛ انظر تأكيدات الأنواع.
التحقق من JSON باستخدام unknown
تُعرَّف JSON.parse على أنها تعيد any، فيوقف ناتجها الفحص بصمت. حدد نوع الناتج بـ unknown واكتب type guard يفحص الشكل قبل أن يثق به بقية الكود.
يطبع المدخل الأول dark at 14px، ويُرفض الثاني لأن fontSize غائبة. مع any كان المدخل الثاني سيمر كـ Settings قيمة fontSize فيه undefined. ومع المخططات الكبيرة، تؤدي مكتبة تحقق المهمة نفسها وتشتق النوع لك.
noImplicitAny
لا يأتي any فقط من كتابة الناس له. المعامل الذي بلا تعليق نوع ولا سياق يُستنتج منه سيكون any أيضًا. والخيار noImplicitAny، الذي يفعّله strict، يبلّغ عن ذلك كخطأ:
index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type.
الحل تعليق نوع، function double(x: number). وكتابة x: any صراحة تُترجم أيضًا، وهذا هو المقصود: يبقى any ظاهرًا في الكود ويمكن البحث عنه ومراجعته.
أين يتسلل any رغم ذلك
حتى تحت strict، تنتج هذه المصادر any دون أن تظهر الكلمة في كودك:
| المصدر | ما تحصل عليه | ما العمل |
|---|---|---|
JSON.parse(text) | any | حدد النوع بـ unknown ثم تحقق |
response.json() مع أنواع المتصفح (DOM) | Promise<any> | كما مع JSON.parse (أنواع fetch الخاصة بـ Node تعيد Promise<unknown> أصلًا) |
| حزمة بلا تعريفات أنواع | any لما تستورده (مع خطأ ما لم تضف تصريحًا) | ثبّت @types/... أو اكتب .d.ts |
value as any | any | استخدم type guard أو تأكيدًا دقيقًا |
catch (e) مع تعطيل useUnknownInCatchVariables | any | يجعله strict من نوع unknown؛ أبقِه كذلك |
متغير catch نوعه unknown تحت strict لأن أي شيء يمكن رميه، لا كائنات Error فقط. ضيّقه بـ e instanceof Error قبل قراءة e.message.
متى يكون any مقبولًا
any ليس ممنوعًا، لكن كل استخدام له موضع لم يعد المترجم يحميه. استخدامات معقولة:
- ترحيل قاعدة كود JavaScript، حيث يعلّم
anyما لم تُحدد أنواعه بعد. - كود لا يستطيع نظام الأنواع التعبير عنه جيدًا، يُبقى صغيرًا خلف توقيع دالة له أنواع.
- كود الاختبارات الذي يمرر بيانات خاطئة عمدًا.
ولكل ما عدا ذلك، يغطي unknown حالة «لا أعرف هذا النوع» نفسها مع الإبقاء على الفحوص. وتفرض فرق كثيرة ذلك بقاعدة الفحص @typescript-eslint/no-explicit-any. والنوع القريب Record<string, unknown> هو الخيار المعتاد لـ «كائن ما بقيم غير معروفة».
الأسئلة الشائعة
ما الفرق بين any و unknown في TypeScript؟
كلاهما يقبل أي قيمة. مع any يمكنك فعل أي شيء بالقيمة (قراءة الخصائص، واستدعاؤها، وإسنادها إلى number) ولا يفحص المترجم شيئًا. ومع unknown لا يمكنك فعل شيء تقريبًا حتى تضيّقها بفحص مثل typeof x === "string". يُبقي unknown فحص الأنواع مفعّلًا، ولهذا هو الخيار الأكثر أمانًا.
متى أستخدم unknown بدل any؟
كلما كان نوع القيمة غير معروف وقت الترجمة: JSON المحلَّل، وبيانات استجابة الشبكة، والخطأ الملتقط، ومدخل دالة التحقق. حدد نوعها بـ unknown وضيّقها. ولا تلجأ إلى any إلا لعمل ترحيل مؤقت أو لكود لا يستطيع نظام الأنواع وصفه.
ماذا تعني الرسالة "Object is of type 'unknown'"؟
الخطآن TS18046 ('x' is of type 'unknown') وTS2571 (Object is of type 'unknown'، ويُستخدم عندما لا تكون القيمة اسمًا بسيطًا، مثل load().id) يعنيان أنك استخدمت قيمة unknown كما لو كان لها نوع محدد، مثل قراءة خاصية أو استدعاء دالة. افحص النوع أولًا (typeof أو instanceof أو Array.isArray أو in أو دالة type guard)، واستخدم القيمة داخل الفرع المضيَّق.
لماذا تعيد JSON.parse النوع any؟
لأن تعريفها في المكتبة المعيارية يقول parse(text: string, ...): any، فالمترجم لا يستطيع معرفة ما يحتويه النص. ويوقف الناتج الفحص بصمت عن كل ما يلمسه. حدد نوعه بدلًا من ذلك: const data: unknown = JSON.parse(text)، ثم تحقق منه قبل الاستخدام.
ما هو noImplicitAny؟
خيار في المترجم، وجزء من strict، يبلّغ عن خطأ عندما يحصل تصريح بصمت على النوع any لأنه بلا تعليق نوع ولا شيء يُستنتج منه: TS7006 للمعامل، وTS7005 أو TS7034 للمتغير الذي لا يمكن تحديد نوعه. يمنع ظهور any دون أن يكتبه أحد. أما any الصريح فما زال مسموحًا.