يتحقق value satisfies Type من أن value تطابق Type وقت الترجمة، ثم يترك نوع القيمة نفسها، الأكثر دقة، دون تغيير. توصيف النوع كان سيستبدل هذا النوع الدقيق بـ Type؛ أما satisfies فيتحقق دون توسيع.
يظل satisfies يقوم بالفحص: لون ناقص، أو مفتاح مكتوب خطأً مثل bleu، أو قيمة مثل true، كلها خطأ ترجمة في ذلك السطر. وهو موجود منذ TypeScript 4.9، ويُحذف من JavaScript الناتج مثل أي توصيف نوع.
المشكلة التي يحلها satisfies
مع توصيف النوع يكون نوع المتغير هو التوصيف. ينسى المترجم ما رآه في الـ literal. هنا اللوحة نفسها بتوصيف نوع بدلًا من ذلك، ولم تعد TypeScript تعرف أن green نص:
يبلّغ المترجم:
index.ts(11,27): error TS2339: Property 'toUpperCase' does not exist on type 'Color'.
Property 'toUpperCase' does not exist on type '[number, number, number]'.
قبل TypeScript 4.9 كانت الخيارات: إما التوصيف ثم التضييق يدويًا في كل مكان (typeof palette.green === "string")، وإما حذف التوصيف وخسارة الفحص. يعطيك satisfies الاثنين. غيّر : Record<ColorName, Color> إلى satisfies Record<ColorName, Color> بعد القوس المعقوف الأخير فيعمل الكود.
satisfies مقابل توصيف النوع مقابل as
كائن الإعدادات نفسه مكتوبًا بثلاث طرق:
سمحت as بمرور lang الناقصة، وasserted.lang قيمتها undefined وقت التشغيل بينما يقول نوعها string. احذف lang من السطرين الآخرين فيفشل كلاهما بالخطأ TS2741، Property 'lang' is missing in type ....
توصيف النوع const x: T = v | التأكيد v as T | v satisfies T | |
|---|---|---|---|
| الخصائص الناقصة | خطأ | مسموحة | خطأ |
| الخصائص الزائدة (كائن literal) | خطأ | مسموحة | خطأ |
| نوع خاصية خاطئ | خطأ | فقط إن لم يتداخل النوعان | خطأ |
نوع x بعد ذلك | T | T | النوع المستنتج لـ v |
الأنواع الحرفية ("dark"، 8080) | تُوسَّع إلى T | تُوسَّع إلى T | تُحفظ حيث يسمح T بها |
مفاتيح Record<string, ...> | أي نص (الأخطاء الإملائية تُترجم) | أي نص | المفاتيح المكتوبة بالضبط |
| الأثر وقت التشغيل | لا شيء | لا شيء | لا شيء |
قاعدة عملية: استخدم توصيف النوع حين تريد أن يحمل المتغير النوع المُعلن (قيمة ستعيد إسنادها، أو واجهة برمجية عامة)، واستخدم satisfies حين تريد الفحص لكن نوع القيمة نفسها أنفع.
اكتشاف الأخطاء في كائنات الـ Literal
يُجري satisfies فحص قابلية الإسناد كاملًا، بما في ذلك فحص الخصائص الزائدة، فتصبح الأخطاء الإملائية في المفاتيح أخطاء ترجمة:
type Route = { path: string; method: "GET" | "POST" };
const home = { path: "/", metod: "GET" } satisfies Route;
// error TS2561: Object literal may only specify known properties, but 'metod' does not exist in type 'Route'. Did you mean to write 'method'?
يعطي الفحص الـ literal أيضًا نوعًا سياقيًا (contextual type)، تمامًا كما يفعل توصيف النوع. ولهذا أثران. تبقى النصوص الحرفية أنواعًا حرفية حين يتوقعها النوع الهدف: { path: "/", method: "GET" } satisfies Route يحمل method: "GET"، بينما الكائن نفسه دون توصيف سيُستنتج method: string. وتُستنتج معاملات الـ callbacks من النوع الهدف:
مفاتيح Record تبقى معروفة
من الاستخدامات الشائعة جدول البحث. حين يُوصَف بـ Record<string, T> يصبح كل نص مفتاحًا صالحًا ويُترجم أي خطأ إملائي، فيعيد undefined وقت التشغيل. ومع satisfies تظل القيم مفحوصة مقابل T، لكن نوع المتغير يسرد المفاتيح التي كتبتها بالضبط:
keyof typeof endpoints مفيد فقط لأن المفاتيح بقيت. مع توصيف النوع سيكون مجرد string.
لفرض مجموعة ثابتة من المفاتيح، طابق Record على union: يبلّغ satisfies Record<"dev" | "prod", string> عن prod الناقصة بالخطأ TS2741 وعن staging غير المعروفة بالخطأ TS2353.
as const satisfies
يجتمع as const وsatisfies. اكتب as const أولًا: تجعل القيمة readonly بعمق مع أنواع حرفية، ثم يفحص satisfies تلك القيمة بالضبط.
كل مسار يُفحص مقابل Route (القيمة method: "PUT" ستكون خطأ)، ويبقى الـ tuple من الأنواع الحرفية متاحًا، فيكون Path union من المسارات الحقيقية. استخدم readonly Route[] (أو ReadonlyArray<Route>) نوعًا هدفًا، لأن مصفوفة as const للقراءة فقط.
كائنات الإعدادات
الإعدادات هي المكان الذي يثبت فيه satisfies قيمته: يجب أن يكون الشكل صحيحًا، والكود في مواضع أخرى يريد القيم الدقيقة.
انسَ العنصر production، أو اكتب logLevel خطأً، أو اكتب logLevel: "verbose"، فيشير المترجم إلى السطر بالضبط. النمط نفسه يناسب ملفات *.config.ts: يفحص export default { ... } satisfies SomeConfig الملف كله بينما يحتفظ الكائن المُصدَّر بقيمه الحرفية.
متى لا تستخدم satisfies
- سيُعاد إسناد المتغير. يعطي
let cfg = { port: 3000 } satisfies { port: number | string }المتغيرcfgالنوع{ port: number }، فيفشل لاحقًاcfg = { port: "80" }(TS2322). استخدم توصيف النوع للمتغيرات التي تنوي تغييرها. - تريد النوع المُعلن عن قصد. لقيمة مُعادة من دالة أو ثابت مُصدَّر يشكّل جزءًا من واجهة برمجية، نوع التوصيف هو العقد، وتسريب النوع الحرفي الدقيق قد يجعل التغييرات اللاحقة كاسرة للتوافق.
- القيمة ليست literal. يتألق
satisfiesمع كائنات ومصفوفات الـ literal. أما على متغير أو نتيجة استدعاء فهو مجرد فحص قابلية إسناد، يعطيك إياه توصيف النوع أصلًا.
الأسئلة الشائعة
ماذا يفعل satisfies في TypeScript؟
يتحقق expression satisfies Type وقت الترجمة من أن التعبير قابل للإسناد إلى Type، فيبلّغ عن الخصائص الناقصة والزائدة وأنواع القيم الخاطئة، ثم يترك النوع المستنتج للتعبير كما هو. تحصل على أمان توصيف النوع ودقة الاستنتاج معًا. ويُحذف من ناتج JavaScript.
ما الفرق بين satisfies وتوصيف النوع؟
كلاهما يفحص القيمة. لكن توصيف النوع (const x: T = ...) يعطي المتغير بعد ذلك النوع T، فينسى ما كان المترجم يعرفه عن القيمة (الأنواع الحرفية، وأي عضو من الـ union تحمله كل خاصية، وأي المفاتيح موجودة). أما satisfies T فيحافظ على النوع المستنتج، فيُعرف أن x.someKey موجودة، وتأخذ خاصية من النوع string | number تحمل نصًا النوع string.
ما الفرق بين satisfies وas في TypeScript؟
as تأكيد: يتجاوز النوع ولا يفحص تقريبًا شيئًا، فتمر الخصائص الناقصة دون ملاحظة. أما satisfies ففحص: يجب أن تطابق القيمة النوع فعلًا، ويُحفظ نوعها المستنتج. حين يُترجم الاثنان بنجاح، فـ satisfies هو الخيار الأكثر أمانًا.
ماذا تعني as const satisfies؟
تطبّق الاثنين: تجعل as const القيمة للقراءة فقط بعمق مع أنواع حرفية، ثم يفحص satisfies تلك النتيجة مقابل نوع. اكتب as const أولًا: const routes = [...] as const satisfies readonly Route[];. يحتفظ المتغير بالأنواع الحرفية الدقيقة لاستخدامها لاحقًا، ويبقى أي عنصر خاطئ خطأ ترجمة.
في أي إصدار من TypeScript أُضيف satisfies؟
في TypeScript 4.9 الصادر في نوفمبر 2022. إنه صيغة قابلة للحذف ببساطة، فيعمل أيضًا مع إزالة الأنواع المدمجة في Node، وتدعمه كل إصدارات TypeScript الحالية (بما فيها 7).