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

Partial وRequired في TypeScript: أمثلة وDeepPartial

Partial<T> يجعل كل خصائص T اختيارية، وهذا بالضبط نوع كائن التحديث أو الـ patch. تعرّف على Partial في دوال التحديث، ولماذا هو سطحي، وكيف تكتب DeepPartial، وفخ undefined الصريحة، وعكسه Required<T>.

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

Partial<T> نوع أداة مدمج يجعل كل خصائص T اختيارية. إنه النوع الطبيعي للتحديث أو الـ patch: يرسل المستدعي الحقول التي تغيرت فقط.

Partial<User> هو { id?: number; name?: string; email?: string }. وما زال المترجم يفحص الحقول التي تمررها: { nmae: "x" } أو { name: 42 } خطأ، وهذا ما يجعل Partial أفضل من معامل فضفاض من النوع object أو any.

كيف يُعرَّف Partial

Partial نوع mapped type من سطر واحد في المكتبة القياسية لـ TypeScript:

type Partial<T> = {
  [P in keyof T]?: T[P];
};

لكل مفتاح P في T يعرّف خاصية اختيارية بالنوع نفسه. ولأنه يمر على keyof T فإنه يحافظ على readonly في الخصائص التي كانت تملكه. وبما أنه نوع عادي فلا أثر له وقت التشغيل: الكائن المُمرَّر كـ changes هو الكائن نفسه في الحالتين.

القراءة من Partial تعطي T | undefined

كل خاصية في Partial<T> قد تكون مفقودة، لذا فقراءة إحداها تعطي نوع الخاصية مضافًا إليه undefined. ويجبرك المترجم على معالجة حالة الغياب:

{ ...defaults, ...opts } هو الطريقة المعتادة لتحويل Partial<Options> إلى Options كامل من جديد: انشر القيم الافتراضية أولًا ودع القيم المُعطاة تستبدلها.

فخ undefined الصريحة

الخاصية الاختيارية قد تكون مفقودة، لكنها قد تكون أيضًا موجودة بالقيمة undefined. والنشر ينسخ تلك undefined فوق القيمة الحقيقية، ونوع النتيجة لا يُظهر ذلك:

تظهر هذه المشكلة عندما يُبنى الـ patch من نموذج أو من query string تتحول فيه الحقول الفارغة إلى undefined. خيار المترجم exactOptionalPropertyTypes (ليس جزءًا من strict) يجعل { name: undefined } خطأ ترجمة مع name?: string ما لم تكتب name?: string | undefined، فيمنع المشكلة من مصدرها.

Partial سطحي

يجعل Partial خصائص المستوى الأعلى فقط اختيارية. أما الكائن المتداخل، إن مررته، فيجب أن يكون كاملًا:

index.ts(8,3): error TS2741: Property 'tabSize' is missing in type '{ fontSize: number; }' but required in type '{ fontSize: number; tabSize: number; }'.

الخاصية editor اختيارية، لكنها متى وُجدت يكون نوعها { fontSize: number; tabSize: number } دون تغيير. وفي كائنات الإعدادات وتحديثات API وبيانات الاختبار كثيرًا ما تريد خصائص اختيارية في كل مستوى. وهذا يحتاج إلى نوع عودي.

DeepPartial: نسخة عودية من Partial

لا توجد في TypeScript نسخة عميقة مدمجة، لكنها بضعة أسطر. تُترك الدوال والمصفوفات كما هي، لأن جعل عناصر المصفوفة اختيارية سيسمح بـ [undefined]:

النوع عودي، أما الدمج فليس كذلك. تدمج applySettings الخاصية editor يدويًا لأن النشر سطحي هو الآخر. توجد دالة دمج عميق عامة في مكتبات مثل lodash (merge)، وكتابة أنواعها أصعب من النوع أعلاه.

Required: عكس Partial

يزيل Required<T> علامة ? من كل خاصية. وهو معرّف بالمُعدِّل -?، الذي يزيل أيضًا undefined من نوع كل خاصية:

النمط هو نفسه مع Partial لكن بالعكس: يمرر مستخدمو الواجهة إعدادات فضفاضة، ويعمل الكود في الداخل على نسخة Required يُعرف فيها أن كل قيمة موجودة. وفي النشر الثغرة نفسها التي في دالة التحديث السابقة: المستدعي الذي يمرر port: undefined صراحةً يستبدل القيمة الافتراضية بـ undefined، ويقبل المترجم ذلك ما لم يكن exactOptionalPropertyTypes مفعّلًا. وRequired سطحي بالطريقة نفسها التي يكون بها Partial سطحيًا.

جعل بعض الخصائص فقط اختيارية أو إلزامية

يُطبَّق Partial وRequired على كل خاصية. لتغيير بعضها فقط، قسّم النوع بـ Pick وOmit ثم أعد تركيبه:

type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;

interface Post {
  id: number;
  title: string;
  body?: string;
}

type NewPost = PartialBy<Post, "id">;      // id optional, title required, body optional
type Published = RequiredBy<Post, "body">; // body now required
النوعالأثرعميق؟
Partial<T>كل خاصية اختياريةلا
Required<T>كل خاصية إلزامية، مع إزالة undefinedلا
DeepPartial<T> (من كتابتك)اختياري في كل مستوىنعم
PartialBy<T, K> (من كتابتك)المفاتيح K فقط اختياريةلا
Readonly<T>كل خاصية readonlyلا

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

ماذا يفعل Partial في TypeScript؟

ينشئ Partial<T> نوعًا فيه كل خصائص T معلّمة كاختيارية. مع interface User { name: string; email: string } يكون Partial<User> هو { name?: string; email?: string }، فتكون {} و{ name: "Ada" } والمستخدم الكامل كلها قيمًا صالحة.

هل Partial عميق في TypeScript؟

لا، يؤثر Partial في خصائص المستوى الأعلى فقط. الكائن المتداخل داخل Partial<T> يجب أن يبقى كاملًا. وللحصول على نسخة عودية، اكتب نوع DeepPartial<T> يطبّق نفسه على الخصائص التي نوعها كائن.

ما عكس Partial في TypeScript؟

Required<T>. يزيل ? من كل خاصية ويزيل أيضًا undefined من أنواعها، فيكون Required<{ port?: number }> هو { port: number }. وهو معرّف كـ mapped type مع المُعدِّل -?.

كيف أجعل بعض الخصائص فقط اختيارية؟

اجمع بين Omit وPick وPartial: type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>. يُبقي PartialBy<User, "email"> كل خاصية كما كانت، عدا email التي تصبح اختيارية.

لماذا تصبح خاصية undefined بعد دمج تحديث Partial؟

يسمح Partial<T> بأن تكون الخاصية موجودة بالقيمة undefined، والنشر (spread) ينسخها: { ...user, ...{ name: undefined } } فيه name: undefined مع أن TypeScript تعطي النتيجة النوع User. استبعد قيم undefined قبل الدمج، أو فعّل exactOptionalPropertyTypes لكي تُرفض undefined الصريحة.

Coddy programming languages illustration

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

ابدأ الآن