如何将类型分配给元组数组,其条目可能因元组而异?
P粉068486220
P粉068486220 2023-09-06 22:44:17
0
2
539

假设我有function foo(args) {...},其中args是一个二元组数组,这样元组中的条目是相同类型的(即[T,T]),但是元组的条目可能任意变化(即[[T,T], [U,U],[V,V]])。例如:

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

我应该如何输入 fooargs 参数,以便元组中的不匹配类型引发编译时类型错误?例如:

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

如果无法内联显示类型错误,则使整个函数调用错误也是可以接受的。


附录:是否可以使用 [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

全部回复(2)
P粉948258958

我认为您可以通过为 row 创建一个类型来简单地实现此目的,该类型将接受 stringnumber 或 布尔值。

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

现在,我们可以为 foo 函数的 args 参数分配此类型。

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

使用此类型定义,如果您向 foo 提供一个参数,其中行中元素的类型不匹配,Typescript 将引发错误。

这里是游乐场链接代码>.

P粉136356287

为了实现这一点,我们需要使用 泛型数组和映射类型来映射数组的元素。由于我们知道该数组应该是长度为 2 的元组数组,因此我们将推断元组中第一项的泛型参数,并使第二项具有相同类型。要获取泛型参数的类型,我们需要使用 推断关键字。请注意,我们需要确切地知道(或至少是具有相似形状的类型)用于使其工作的泛型类型,在我们的例子中是 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];
  }) => {}

看起来似乎就是全部,但是让我们看看以下数组的类型:

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

如您所见,该类型与我们在 arr 中的类型不完全相同。编译器扩展了类型以确保我们可以改变数组元素。为了让编译器知道该数组是只读的,我们需要使用 const 断言

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

现在看起来不错,这意味着我们需要将传递给 foo 的数组设置为只读`,并且由于只读数组是我们将得到的可变数组的超集如果我们尝试将只读数组传递给数组,则会出现错误:

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

因此,我们将 foo 中的所有数组类型更新为只读。请注意,由于我们的数组是二维的,因此内部数组也将是只读的,并且数组的约束应该是只读数组的只读数组:

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];
}) => {};

测试:

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);

但是,我们仍然存在一些问题。例如,如果元组中的第一个元素是 Variable ,则意味着第二个参数也应该是 7,而不是任何数字,如果这是一个问题我们需要获取 7 的原语,即数字。这可以使用 ToPrimitive 来自我的 type-samurai 开源项目的实用程序类型: 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]>;
    };

更新功能:

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];
}) => {};

另一个问题是,如果在我们当前的 foo 实现中推断的类型是 number[],我们将不会允许只读数组:

foo([
  [ctx5, [4, 5, 6]], // The type 'readonly [4, 5, 6]' is 'readonly' and cannot be assigned to the mutable type 'number[]'
] as const)

修复非常简单,我们将检查推断的类型是否是某个数组,然后获取其元素类型并将 readonly ElemenType[] 作为元组中的第二个参数:

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];
}) => {};

测试:

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);

令人烦恼的部分是我们需要在任何地方使用 const 断言。在 Typescript 5.0 中,const 类型参数,这样我们就可以避免 const 断言

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

不幸的是,我们无法使用它们,因为我们对参数进行了一些操作,而不是直接将 T 作为类型分配给它:

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])

总之,目前,const 断言是确保其按预期工作的唯一方法。

链接到游乐场

热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责声明 Sitemap
PHP中文网:公益在线PHP培训,帮助PHP学习者快速成长!