如何在Typescript中迭代一个只读数组,同时保持数组项的类型?

l2osamch  于 2023-03-09  发布在  TypeScript
关注(0)|答案(2)|浏览(123)

我尝试严格地输入Map,以便它只能包含给定只读数组中的键,我使用以下helper输入该数组:

type ArrayValues<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<infer ArrayValues>
  ? ArrayValues
  : never;

Map正在获取正确的类型,但在数组上循环时,我需要向我为Map提供的ArrayValues类型添加手动强制转换,这似乎是一种错误的可能性,因为这可能是强制转换实际上不在数组内部的元素,因此它应该给予我一个错误,而不是陷入困境。

function test<Buttons extends ReadonlyArray<string | number>>(
  buttonPins: Buttons
) {
  const buttonMap = new Map() as Map<ArrayValues<Buttons>, boolean>;

  buttonPins.forEach((buttonPin) => {
    // This gives error
    //    Argument of type 'string | number' is not assignable to parameter of type 'ArrayValues<Buttons>'.
    //      Type 'string' is not assignable to type 'ArrayValues<Buttons>'.
    // buttonMap.set(buttonPin, true);
    // Using the cast everything works as I want it too
    buttonMap.set(buttonPin as ArrayValues<Buttons>, true);
  });

  return buttonMap;
}

完整的示例代码可以在这里找到:

**问题是:**有没有更好的方法来迭代给定的数组(buttonPins)以自动修复类型,或者有没有更好的方法来确保我们不会向buttonMap注入错误的值?

我已经尝试了test函数的泛型类型的一些不同变体,但是它们都不能帮助我进一步找到解决方案。

qojgxg4l

qojgxg4l1#

你可以这样写(见操场)

type ArrayValues<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<infer ArrayValues>
  ? ArrayValues
  : never;

function test<Buttons extends ReadonlyArray<string | number>, T extends ArrayValues<Buttons>>(
  buttonPins: readonly T[]
): Map<T, boolean> {
  const buttonMap = new Map<T, boolean>();

  buttonPins.forEach((buttonPin) => {
    buttonMap.set(buttonPin, true);
  });

  return buttonMap;
}

const testArray = ['a', 1, 0, 1, 'test'] as const;
// Map<'a' | 1 | 0 | 'test', boolean>
const testMap = test(testArray);
1dkrff03

1dkrff032#

您的类型ArrayValues可以简单地写成以下形式:

type ArrayValues<T extends ReadonlyArray<unknown>> = T[number];

由于您使用的是带有泛型的条件类型,TypeScript对它的求值很慢,因此buttonMap.set的使用是否有效变得模糊。
但是,修复方法很简单,使用Buttons[number]代替,TypeScript不会延迟计算,所以它允许你在map中设置buttonPin

function test<Buttons extends ReadonlyArray<string | number>>(
  buttonPins: Buttons
) {
  const buttonMap = new Map<Buttons[number], boolean>();

  buttonPins.forEach((buttonPin) => {
    buttonMap.set(buttonPin, true);
  });

  return buttonMap;
}

Playground
另外,Map有两个泛型参数,不需要强制转换为Map<..., ...>;你可以简单地使用new Map<..., ...>(),这对于Set和它们的弱对应物是一样的。

相关问题