TypeScript: ชนิดทั่วไปของทูเพิลของคุณสมบัติวัตถุที่มีชนิดค่าที่แน่นอน
ฉันสงสัยว่ามันเป็นไปได้หรือไม่ที่จะกำหนดชนิดทั่วไปที่เป็นทูเพิลความยาว 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
แต่ก็แค่ทำให้เกิด "Type '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
ก่อนอื่นเราจำเป็นต้องแยกความแตกต่างระหว่างสองคำนิยาม: arrays และ tuples ใน typescript
พิจารณา 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
สนามเด็กเล่น
อ้อ จริง ๆ เพราะคุณไม่รู้ว่าสิ่งของที่อนุญาตทั้งหมดในทูเพิลมีอะไรบ้าง มันจึงเป็นไปไม่ได้ที่จะทำมันโดยไม่มีฟังก์ชันเสริม
