英文:
How to iterate a readonly array while maintaining array entry typing in Typescript?
问题
以下是翻译好的部分:
我尝试严格类型化一个`Map`,以便它只能包含存在于给定的只读数组中的键,我使用以下助手来定义该数组的类型:
```typescript
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) => {
// 这会引发错误
// 参数类型为'string | number'无法分配给类型'ArrayValues<Buttons>'。
// 类型'string'无法分配给类型'ArrayValues<Buttons>'。
// buttonMap.set(buttonPin, true);
// 使用强制转换,一切都按我所需的方式工作
buttonMap.set(buttonPin as ArrayValues<Buttons>, true);
});
return buttonMap;
}
完整的示例代码可以在此处找到:
**问题是:** 是否有更好的方法来迭代给定的数组(`buttonPins`)以自动修复类型,或者是否有更好的方法来确保我们不会向`buttonMap`中注入错误的值?
我尝试了一些`test`函数的通用类型的不同变化,但没有帮助我更接近解决方案。
<details>
<summary>英文:</summary>
I'm trying to strictly type a `Map`, so that it can only contain keys that are present in the given readonly array, which I type using the following helper:
```typescript
type ArrayValues<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<infer ArrayValues>
? ArrayValues
: never;
The Map is getting the correct typings, but when looping over the array, I need to add a manual cast to the ArrayValues
type I gave the map, this seems like a possibility for errors as this could be casting elements that are not actually inside of the array so it should give me an error instead of going trough.
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;
}
The full sample code can be found here:
Question is: Is there a better way to iterate over the given array (buttonPins
) to automatically fix the typing, or is there a better way to make sure we don't inject wrong values into the buttonMap
?
I've tried some different variations of the generic type of the test
function, but none of them helped me get further to a solution.
答案1
得分: 2
您的类型 ArrayValues
可以简单地编写如下:
type ArrayValues<T extends ReadonlyArray<unknown>> = T[number];
由于您在使用具有泛型的条件类型时,TypeScript 懒惰地进行了评估,因此关于 buttonMap.set
的使用是否有效存在一些模糊。
不过,修复方法很简单。使用 Buttons[number]
代替。TypeScript 不会懒惰评估这个,所以它允许您在映射中设置 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
和它们的弱对应也是如此。
英文:
Your type ArrayValues
can be written simply as the following:
type ArrayValues<T extends ReadonlyArray<unknown>> = T[number];
Since you were using a conditional type with a generic, TypeScript was lazily evaluating it and so it gets fuzzy over whether or not the usage of buttonMap.set
was valid.
However, the fix is simple. Use Buttons[number]
instead. TypeScript won't lazily evaluate this so it allows you to set buttonPin
in the map.
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;
}
Another thing, Map
takes two generic parameters. You don't need to cast to Map<..., ...>
; you can simply use new Map<..., ...>()
. This is the same for Set
and their weak counterparts.
答案2
得分: 1
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);
<details>
<summary>英文:</summary>
You can write it like this (see [playground][1])
```ts
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);
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论