الـ tuple في TypeScript مصفوفة بعدد ثابت من العناصر، ولكل موضع فيها نوعه الخاص. [string, number] تعني عنصرين بالضبط: نص أولًا، ثم رقم. تكتب الأنواع بين أقواس مربعة بترتيب ظهور القيم.
وقت التشغيل يكون الـ tuple مصفوفة JavaScript عادية. كل ما يضيفه الـ tuple (الطول الثابت والنوع في كل موضع) يفحصه المترجم ثم يمحوه.
صياغة Tuple
| نوع الـ tuple | يقبل | نوع length |
|---|---|---|
[string, number] | نصًا ثم رقمًا بالضبط | 2 |
[x: number, y: number] | الشيء نفسه، مع تسميات لسهولة القراءة | 2 |
[number, number, number?] | رقمين أو ثلاثة | 2 | 3 |
[string, ...number[]] | نصًا، ثم أي عدد من الأرقام | number |
[...string[], number] | أي عدد من النصوص، ثم رقمًا | number |
readonly [number, number] | زوجًا لا يمكن تعديله | 2 |
[] | مصفوفة فارغة فقط | 0 |
كل صيغة مشروحة أدناه. نوع length جدير بالملاحظة: في الـ tuple الثابت يكون نوعًا حرفيًا، فيعرف المترجم أن pair.length يساوي 2 بالضبط.
ما الذي يفحصه المترجم
يثبّت نوع الـ tuple عدد العناصر وترتيبها والنوع في كل موضع. الخطأ في أي منها خطأ ترجمة:
index.ts(2,7): error TS2322: Type '[string]' is not assignable to type '[string, number]'.
Source has 1 element(s) but target requires 2.
index.ts(3,36): error TS2322: Type 'number' is not assignable to type 'string'.
index.ts(3,40): error TS2322: Type 'string' is not assignable to type 'number'.
index.ts(5,16): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'.
المصفوفة العادية لا تستطيع أبدًا اكتشاف الحالة الأخيرة: مع string[] يكون arr[2] ببساطة string يصادف أنه undefined وقت التشغيل.
عناصر Tuple المسماة
توثّق التسميات معنى كل موضع. لا تغيّر شيئًا في النوع أو في طريقة الفهرسة، لكن المحررات تعرضها عند التمرير وفي تلميحات التوقيع، ما يجعل [number, number] أقل غموضًا بكثير.
منذ TypeScript 5.2 يمكنك تسمية بعض المواضع وترك غيرها دون تسمية، كما في [first: string, number]. التسميات للقراء فقط: [x: number, y: number] و[number, number] النوع نفسه، ويمكن إسناد كل منهما إلى الآخر.
العناصر الاختيارية
علامة ? بعد نوع العنصر تجعل ذلك الموضع اختياريًا. يجب أن تأتي العناصر الاختيارية بعد الإلزامية، وكل واحد منها يوسّع نوع length.
قراءة عنصر اختياري تعطي T | undefined، لذلك تحتاج إلى قيمة افتراضية في نمط التفكيك (a = 1) أو إلى فحص قبل العمليات الحسابية.
عناصر Rest
عنصر rest، ...T[]، يمثل أي عدد من العناصر من النوع T. يمكن أن يكون في النهاية أو البداية أو الوسط، بحد أقصى واحد في كل tuple.
نوع length في tuple فيه عنصر rest هو number، لأن الحجم لم يعد ثابتًا. ما يبقى ثابتًا هو مكان المواضع ذات الأنواع المحددة.
Tuples للقراءة فقط و as const
يحذف readonly [T, U] كلًا من push وpop وsplice والإسناد بالفهرس، وهذا ما يجب أن تكون عليه قيمة ثابتة الطول. وكتابة as const بعد مصفوفة حرفية تستنتج tuple للقراءة فقط من أنواع حرفية.
يحوّل (typeof SIZES)[number] الـ tuple إلى union من أنواع عناصره، وهو نمط تغطيه صفحة indexed access types. لا يمكن تمرير tuple للقراءة فقط إلى معامل نوعه tuple قابل للتعديل، لذلك يجب أن تقبل الدوال التي تكتفي بالقراءة readonly [number, number].
فحص readonly وقت الترجمة فقط. وقت التشغيل لا تكون المصفوفة مجمدة (الإسناد أعلاه نُفذ فعلًا، كما يُظهر الناتج)، لذلك استخدم Object.freeze إذا احتجت إلى ضمان وقت التشغيل.
إعادة tuple من دالة
إعادة عدة قيم كـ tuple هي الطريقة التي يعمل بها useState في React (const [value, setValue] = useState(0)). المشكلة: المصفوفة الحرفية في return تُستنتج كمصفوفة لا كـ tuple.
index.ts(9,13): error TS2365: Operator '+' cannot be applied to types 'number | (() => number)' and 'number'.
index.ts(10,1): error TS2349: This expression is not callable.
Not all constituents of type 'number | (() => number)' are callable.
Type 'number' has no call signatures.
تعيد الدالة (number | (() => number))[]، فيحصل الاسمان المفككان كلاهما على نوع union. هناك حلان: حدد نوع القيمة المُعادة، أو أضف as const.
القيمة المُعادة كـ tuple تتيح للمستدعين تسمية الأجزاء كما يشاؤون. وعندما تكون القيم أكثر من اثنتين أو ثلاث، أو لا يكون الترتيب واضحًا، أعد كائنًا بدلًا من ذلك: { count, increment } يوثّق نفسه.
Tuples كمعاملات للدوال
معامل rest نوعه tuple يصف قائمة وسائط كاملة، ومنها الوسائط الاختيارية. هكذا يمثل نوع الأداة المدمج Parameters<T> معاملات الدالة.
نشر tuple داخل استدعاء يفحص نوع كل وسيط بحسب موضعه، وهو ما لا يستطيعه نشر (string | number)[].
Tuple مقابل المصفوفة
المصفوفة (string | number)[] | الـ tuple [string, number] | |
|---|---|---|
| الطول | أي طول | ثابت (أو محدود بالعناصر الاختيارية وعناصر rest) |
نوع x[0] | string | number | string |
نوع x[5] | string | number | خطأ ترجمة TS2493 |
نوع length | number | 2 |
| ترتيب الأنواع | غير متتبَّع | متتبَّع |
| القيمة وقت التشغيل | مصفوفة JavaScript | مصفوفة JavaScript نفسها |
| الاستخدام المعتاد | قوائم من عناصر متشابهة | مجموعات صغيرة ثابتة: أزواج، وإحداثيات، و[key, value]، وقيم مُعادة متعددة |
تظهر الـ tuples أيضًا في الأنواع المدمجة. يعيد Object.entries(obj) النوع [string, T][]، ويُنشأ Map من tuples بصيغة [key, value]:
فخ واحد: الـ tuple القابل للتعديل ما زال يملك كل دوال المصفوفات، لذلك يُترجم pair.push(3) على [string, number] وينتج بهدوء مصفوفة من ثلاثة عناصر نوعها يقول اثنين. تعريف الـ tuples كـ readonly يسد هذه الثغرة. ولأن الأنواع تُمحى، فإن البيانات القادمة من خارج البرنامج (JSON أو API) لا تُفحص مقابل نوع tuple وقت التشغيل: تحقق من طولها وأنواع عناصرها قبل الوثوق بها.
Variadic Tuple Types
يمكن لأنواع tuple أن تنشر أنواع tuple أخرى، [...T, ...U]. ومع الأنواع العامة، يحدد هذا أنواع الدوال التي تدمج أو تضيف في البداية مع الحفاظ على كل موضع:
وتعتمد أنواع المكتبات على استنتاج tuple أيضًا: Promise.all([fetchUser(), fetchPosts()]) ينتهي إلى tuple فيه نوع لكل promise مُدخل.
الأسئلة الشائعة
ما هو tuple في TypeScript؟
الـ tuple نوع مصفوفة بطول ثابت ولكل موضع فيه نوعه الخاص: [string, number] عنصران بالضبط، نص ثم رقم. وقت التشغيل هو مصفوفة JavaScript عادية؛ ويُفحص الطول والنوع في كل موضع وقت الترجمة فقط.
ما الفرق بين tuple والمصفوفة في TypeScript؟
نوع المصفوفة مثل (string | number)[] له أي طول ولكل عنصر فيه النوع نفسه (union)، لذلك arr[0] نوعه string | number. أما tuple مثل [string, number] فطوله معروف، وt[0] نوعه string وt[1] نوعه number، وt[2] خطأ ترجمة.
كيف أعيد tuple من دالة في TypeScript؟
حدد نوع القيمة المُعادة، function f(): [number, string]، أو أنهِ تعبير الإعادة بـ as const، الذي يعطي tuple للقراءة فقط. دون أي منهما يُستنتج return [count, setCount] كمصفوفة من union، مثل (number | (() => void))[]، ويعطي التفكيك أنواع union.
ما هي عناصر tuple المسماة؟
تسميات على المواضع، [name: string, age: number]. لا تغيّر النوع ولا طريقة الوصول (ما زال t[0])، لكن المحررات تعرضها عند التمرير وفي تلميحات معاملات الدوال التي تُحدد معاملاتها كـ tuple. وتعمل العناصر الاختيارية وعناصر rest مع التسميات: [x: number, y?: number] و[head: string, ...rest: number[]].
هل يمكن استخدام push مع tuple في TypeScript؟
مع tuple قابل للتعديل، نعم: يُترجم push، لأن الـ tuples ترث دوال المصفوفات، مع أنه يكسر الطول الثابت. عرّف الـ tuple بـ readonly (أو أنشئه بـ as const) فيصبح push وpop والإسناد بالفهرس أخطاء ترجمة.