تايب سكريبت: النوع العام لزوج خصائص الكائن من نوع قيمة معين
أتساءل عما إذا كان من الممكن تعريف نوع عام يكون عبارة عن زوج مرتب بطول 2 [O, K]، نسميه AccessorPair، بحيث يكون النوع الأول O هو كائن والنوع الثاني K هو مفتاح لذلك الكائن، بحيث يمتد O[K] لنوع معين T.
على سبيل المثال,
const foo: AccessorPair<number> = [new Array(), 'length']
const bar: AccessorPair<boolean> = [[true, false], 0]
محاولتي الأولى هي
type AccessorPair<T> = T extends (infer O)[infer K] ? [O, K] : never
لكنه يؤدي فقط إلى "لا يمكن استخدام النوع 'K' لفهرسة النوع 'O'.ts(2536)"
حاولت إيجاد حل بديل عن طريق
type ValueOf<O, P extends keyof O> = O[P]
type AccessorPair<T> = T extends ValueOf<infer O, infer P> ? [O, P] : never
ويحل دائمًا إلى "أبدًا".
هل هذا التعريف غامض جدًا ليتم دعمه من قبل TypeScript؟ هل هناك أي مفهوم في لغات البرمجة لا أعرفه يتعلق بهذا النوع من التحديد؟ أي نقاش سيكون موضع تقدير!
الإجابة رقم 1
أولاً نحن بحاجة إلى تمييز تعريفين: المصفوفات و الصفوف في تايب سكريبت.
اعتبر array كقائمة ذات طول غير معروف، شيء مثل vector في Rust.
اعتبر tuple كقائمة ذات طول معروف أثناء التجميع.
من أجل التعامل مع المفاتيح المسموح بها لتجميعة [true, false] وهناك اثنان فقط منها 0 | 1 نحتاج إلى كتابة نوع أداة صغير:
type IsTuple<T> =
(T extends Array<any> ?
(T['length'] extends number
? (number extends T['length']
? false
: true)
: true)
: false)
{
type _ = IsTuple<[1, 2]> // true
type __ = IsTuple<number[]> // false
type ___ = IsTuple<{ length: 2 }> // false
}
type AllowedTupleLength<
T extends ReadonlyArray<unknown>,
Length extends number = never
> = T extends readonly [infer _, ...infer Tail]
? AllowedTupleLength<Tail, Length | Tail['length']>
: T extends readonly []
? Length
: never;
type Result = AllowedTupleLength<[0,0]> // 1 | 0
الآن نحن بحاجة إلى أداة ستُرجع بشكل شرطي المفاتيح المسموح بها للزوج والمصفوفة:
type ComputeKeys<Tuple extends any[]> =
IsTuple<Tuple[0]> extends true ? AllowedTupleLength<Tuple[0]> : keyof Tuple[0]
ComputeKeys يُرجع 1|0 لـ [true, false] وجميع مفاتيح المصفوفة للمصفوفة العادية.
بالمناسبة، هل تريد السماح باستخدام forEach, reduce, map كمفاتيح كعنصر ثاني في الزوج؟
أيضًا، نحتاج إلى التعامل مع جميع الكائنات غير المصفوفية. لننشئ نوع Json مع جميع الأنواع القابلة للتسلسل في اتحاد:
type Json = | string | number | boolean | { [prop: string]: Json } | Array<Json>
type IsTuple<T> =
(T extends Array<any> ?
(T['length'] extends number
? (number extends T['length']
? false
: true)
: true)
: false)
type AllowedTupleLength<
T extends ReadonlyArray<unknown>,
Length extends number = never
> = T extends readonly [infer _, ...infer Tail]
? AllowedTupleLength<Tail, Length | Tail['length']>
: T extends readonly []
? Length
: never;
type ComputeKeys<Tuple extends any[]> =
IsTuple<Tuple[0]> extends true ? AllowedTupleLength<Tuple[0]> : keyof Tuple[0]
function handleTuple<
Elem extends Exclude<Json, any[]>, // overload signature for non array serializable values
Tuple extends Elem[]
>(tuple: [...Tuple, keyof Tuple[0]]): [...Tuple]
function handleTuple<
Elem,
NestedTuple extends Elem[],
Tuple extends [...NestedTuple][]
>(tuple: [...Tuple, ComputeKeys<Tuple>]): [...Tuple]
function handleTuple(tuple: unknown[]) {
return tuple
}
handleTuple([[true, false], 1]) // ok
handleTuple([[true, false], 2]) // expected error, index is too big
handleTuple([new Array(), 'length']) // ok
handleTuple([new Array(), 2]) // allowed because we don't know exact length of array
handleTuple([{ name: 'John', age: 32 }, 'name']) // allowed because we don't know exact length of array
ملعب
بالمناسبة، بما أنك لا تعرف جميع الكائنات المسموح بها في الـ tuple، فمن المستحيل إنشاؤه بدون دالة إضافية.
