ค้นหาเว็บไซต์

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

สนามเด็กเล่น

อ้อ จริง ๆ เพราะคุณไม่รู้ว่าสิ่งของที่อนุญาตทั้งหมดในทูเพิลมีอะไรบ้าง มันจึงเป็นไปไม่ได้ที่จะทำมันโดยไม่มีฟังก์ชันเสริม