Bagaimana untuk menetapkan jenis kepada susunan tupel yang entrinya mungkin berbeza dari tupel ke tupel?
P粉068486220
P粉068486220 2023-09-06 22:44:17
0
2
570

Andaikan saya ada function foo(args) {...},其中args是一个二元组数组,这样元组中的条目是相同类型的(即[T,T]),但是元组的条目可能任意变化(即[[T,T], [U,U],[V,V]]). Contohnya:

foo([
  [1, 3],
  ["hello", "world"],
  [true, true],
  [2, 7]
]) // no error

Bagaimanakah saya harus memasukkan parameter fooargs supaya jenis yang tidak sepadan dalam tuple menimbulkan ralat jenis masa kompilasi? Contohnya:

foo([
  [1, 3],
  ["hello", 5], // type error here
  [true, true],
  [2, 7n] // type error here
])

Jika ralat jenis tidak dapat ditunjukkan sebaris, ia juga boleh diterima untuk menjadikan keseluruhan panggilan fungsi salah.


Lampiran: Adakah ia tersedia [SomeType, T] 类型的 2 元组(即第二个条目的类型应与第一个),但 T 仍然可以在元组之间变化 [[SomeType, T],[SomeType, U],[SomeType, V]]?

foo([
  [{value: 1}, 3],
  [{value: "hello"}, 5], // type error here
  [{value: true}, true],
  [{value: 2}, 7n] // type error here
])

P粉068486220
P粉068486220

membalas semua(2)
P粉948258958

Saya rasa anda boleh lulus sebagai row 创建一个类型来简单地实现此目的,该类型将接受 stringnumber 或 布尔值.

类型 Row = string[] |布尔值[] |数字[]

Kini kita boleh menetapkan jenis ini kepada parameter foo 函数的 args.

function foo(args: Row[]): void {
 ...
 ...
 ...
}

Menggunakan takrif jenis ini, TypeScript akan membuang ralat jika anda memberikan hujah kepada foo di mana jenis elemen dalam baris tidak sepadan.

Ini adalah taman permainanPautan代码>.

P粉136356287

Untuk mencapai ini, kita perlu menggunakan generiktatasusunan dan jenis yang dipetakan untuk memetakan elemen tatasusunan. Memandangkan kita tahu bahawa tatasusunan mestilah tatasusunan tupel dengan panjang 2, kita akan membuat kesimpulan parameter generik untuk item pertama dalam tupel dan menjadikan item kedua mempunyai jenis yang sama. Untuk mendapatkan jenis parameter generik, kita perlu menggunakan kata kunci Infer. Ambil perhatian bahawa kita perlu mengetahui dengan tepat (atau sekurang-kurangnya jenis bentuk yang serupa) jenis generik yang digunakan untuk menjadikannya berfungsi, yang dalam kes kita ialah Variable:

const foo = <T extends unknown[][]>(arr: {
  [K in keyof T]: T[K] extends unknown[]
    ? T[K][0] extends Variable<infer Type>
      ? [Variable<Type>, Type]
      : T[K]
    : T[K];
  }) => {}

Ia mungkin kelihatan seperti itu sahaja, tetapi mari lihat jenis tatasusunan berikut:

const arr = [1, '2', false];
// (string | number | boolean)[]
type Arr = typeof arr;

Seperti yang anda lihat, jenisnya tidak sama persis dengan apa yang kita ada dalam arr. Pengkompil memanjangkan jenis untuk memastikan kita boleh menukar elemen tatasusunan. Untuk memberitahu pengkompil bahawa tatasusunan adalah baca sahaja, kita perlu menggunakan konst penegasan:

const arr = [1, '2', false] as const;
// readonly [1, "2", false]
type Arr = typeof arr;

Nampak baik sekarang, ini bermakna kita perlu menetapkan tatasusunan yang dihantar kepada foo sebagai baca sahaja`, dan memandangkan tatasusunan baca sahaja ialah superset tatasusunan boleh ubah yang akan kita perolehi jika kita cuba menghantar tatasusunan baca sahaja kepada tatasusunan, Ralat akan muncul:

// false
type Case1 = readonly number[] extends number[] ? true : false;
// true
type Case2 = number[] extends readonly number[] ? true : false;

Jadi kami mengemas kini semua jenis tatasusunan dalam foo untuk dibaca sahaja. Ambil perhatian bahawa memandangkan tatasusunan kami ialah 2D, tatasusunan dalam juga akan dibaca sahaja dan kekangan tatasusunan hendaklah tatasusunan baca sahaja tatasusunan baca sahaja:

const foo = <T extends readonly (readonly unknown[])[]>(arr: {
  [K in keyof T]: T[K] extends readonly unknown[]
    ? T[K][0] extends Variable<infer Type>
      ? readonly [Variable<Type>, Type]
      : T[K]
    : T[K];
}) => {};

Ujian:

declare const ctx1: Variable<number>;
declare const ctx2: Variable<string>;
declare const ctx3: Variable<boolean>;
declare const ctx4: Variable<number>;
declare const ctx5: Variable<number[]>;
declare const ctx6: Variable<{ name: string; age: number }>;

foo([
  [ctx1, 3],
  [ctx2, 'world'],
  [ctx3, true],
  [ctx4, 7],
] as const);

foo([
  [ctx1, 3],
  [ctx2, 'world'],
  [ctx3, true],
  [ctx4, 'invalid'], // error
] as const);

Namun, kami masih menghadapi beberapa masalah. Contohnya, jika elemen pertama dalam tupel ialah primitif Variable ,则意味着第二个参数也应该是 7,而不是任何数字,如果这是一个问题我们需要获取 7, iaitu nombor. Ini boleh dilakukan menggunakan jenis utiliti ToPrimitive daripada projek sumber terbuka type-samurai saya: p>

type ToPrimitive<T> = T extends string
  ? string
  : T extends number
  ? number
  : T extends null
  ? null
  : T extends undefined
  ? undefined
  : T extends boolean
  ? boolean
  : T extends bigint
  ? bigint
  : T extends symbol
  ? symbol
  : {
      [K in keyof T]: ToPrimitive<T[K]>;
    };

Ciri yang dikemas kini:

const foo = <T extends readonly (readonly unknown[])[]>(arr: {
  [K in keyof T]: T[K] extends readonly unknown[]
    ? T[K][0] extends Variable<infer Type>
      ? ToPrimitive<Type> extends infer PrimitiveType
        ? readonly [Variable<PrimitiveType>, PrimitiveType]
        : T[K]
      : T[K]
    : T[K];
}) => {};

Masalah lain ialah jika jenis yang disimpulkan dalam pelaksanaan foo 实现中推断的类型是 number[] semasa kami ialah nombor[]

, kami tidak akan membenarkan tatasusunan baca sahaja:

foo([
  [ctx5, [4, 5, 6]], // The type 'readonly [4, 5, 6]' is 'readonly' and cannot be assigned to the mutable type 'number[]'
] as const)
Pembetulannya sangat mudah, kami akan menyemak sama ada jenis yang disimpulkan ialah beberapa tatasusunan, kemudian dapatkan jenis elemennya dan baca sahaja ElementType[] sebagai hujah kedua dalam tupel:

const foo = <T extends readonly (readonly unknown[])[]>(arr: {
  [K in keyof T]: T[K] extends readonly unknown[]
    ? T[K][0] extends Variable<infer Type>
      ? ToPrimitive<Type> extends infer PrimitiveType
        ? readonly [
            Variable<PrimitiveType>,
            PrimitiveType extends Array<infer ArrayItem>
              ? readonly ArrayItem[]
              : PrimitiveType,
          ]
        : T[K]
      : T[K]
    : T[K];
}) => {};
Ujian:🎜
foo([
  [ctx1, 3],
  [ctx2, 'world'],
  [ctx3, true],
  [ctx4, 7],
  [ctx5, [4, 5, 6]],
  [ctx6, {name: "Hi", age: 23}],
] as const);

foo([
  [ctx1, 3],
  [ctx2, 'world'],
  [ctx3, true],
  [ctx4, true], // error here
  [ctx5, [4, 5, 6]],
  [ctx6, 50], // error here
] as const);

Bahagian yang menjengkelkan ialah kita perlu menggunakan const 断言。在 Typescript 5.0 中,const 类型参数,这样我们就可以避免 const 断言 di mana-mana:

const foo = <const T extends readonly unknown[]>(item: T) => item
// readonly [1, 2, 3] 
const result = foo([1,2,3])

Malangnya kami tidak boleh menggunakannya kerana kami melakukan sesuatu dengan parameter dan bukannya memberikan terus T kepadanya sebagai jenis:

const foo = <const T extends readonly unknown[]>(item: {[K in keyof T]: T[K]}) => item

// const result: (2 | 1 | 3)[]
const result = foo([1, 2, 3])

Apa pun, pada masa ini, const 断言 adalah satu-satunya cara untuk memastikan ia berfungsi seperti yang diharapkan.

Pautan ke Taman Permainan

Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan