如何在 TypeScript 中使用带有泛型的元组?

问题描述 投票:0回答:1

我正在尝试编写一个类型安全的地图对象。我想定义我的键/值对一次且仅一次。

我已成功完成以下操作:

const myPropTuple = [
    [0, "cat"],
    [1, "dog"],
    [2, "bird"]
] as const;

type TMyPropKey = TInnerTupple<typeof myPropTuple>[0];
type TMyPropValue = TInnerTupple<typeof myPropTuple>[1];

function getMyProp(val: number) {
    type TKey = TInnerTupple<typeof myPropTuple>[0];

    const mapA2 = new Map(myPropTuple);
    if(!mapA2.has(val as TKey)) throw new RangeError("unexpected value");
    return mapA2.get(val as TKey);
}

// helper library (not to be inlined)
type TTupleType<T extends Iterable<any>> = T extends ReadonlyArray<infer R> ? R :never;
type TInnerTupple<I extends Iterable<any>> = TTupleType<I>;

// Tests
console.assert(getMyProp(1) === "dog");
//console.assert(getMyProp(1) === "turle");    // throws compiler error

const a: TMyPropValue = "cat";
//const b: TMyPropValue = "eagle";    // throws compiler error

但我想让函数变得通用并仍然保持类型安全:

目标是能够写作

const myPropTuple = [
    [0, "cat"],
    [1, "dog"],
    [2, "bird"]
] as const;

console.assert(getGenericProp(myPropTuple, 1) === "dog");

const yourPropTuple = [
    [0, "fish"],
    [1, "towel"],
    [2, "whale"]
] as const;

console.assert(getGenericProp(yourPropTuple, 0) === "fish");

并且以下内容无法编译

console.assert(getGenericProp(myPropTuple, 1) === "turle");    // throws compiler error

type TYourPropValue = TInnerTupple<typeof yourPropTuple>[1];
const y: TYourPropValue = "dog";    // throws compiler error
typescript tuples typescript-generics
1个回答
1
投票

你可以这样写:

function getGenericProp<T extends readonly [any, any]>(
  propTuple: readonly T[], val: T[0]
) {
  const mapA2: Map<T[0], T[1]> = new Map(propTuple);
  if (!mapA2.has(val)) throw new RangeError("unexpected value");
  return mapA2.get(val);
}

它是propTupleelements类型中的

generic
,我们期望它是readonly2元组
union
val
的类型是
T[0]
,它是
propTuple
内每对的第一个元素。返回类型为
T[1] | undefined
T[1]
表示
propTuple
内每对的第二个元素之一,发生
undefined
是因为
Map.get()
可以返回
undefined
,无论您是否先检查了
has()
(请参阅为什么` map.has()` 不充当类型保护。您的非通用版本也是如此,因此解决它超出了此处的范围)。

无论如何,您可以验证它是否按预期工作:

const myPropTuple = [
  [0, "cat"],
  [1, "dog"],
  [2, "bird"]
] as const;
console.assert(getGenericProp(myPropTuple, 1) === "dog"); // okay

const yourPropTuple = [
  [0, "fish"],
  [1, "towel"],
  [2, "whale"]
] as const;
console.assert(getGenericProp(yourPropTuple, 0) === "fish"); // okay

console.assert(getGenericProp(myPropTuple, 1) === "turtle"); // error!
// This comparison appears to be unintentional because the types 
// '"cat" | "dog" | "bird" | undefined' and '"turtle"' have no overlap.

getGenericProp(myPropTuple, 3) // error!
// Argument of type '3' is not assignable to parameter of type '0 | 2 | 1'.

看起来不错。


请注意,该函数不知道针对特定输入返回哪个输出,并且这样的事情是可能的......但

Map
并不是真正的目的。您可以尝试重构
Map
的类型来注意这一点,如 Typescript: How can I make Entry in an ES6 Map based on an object key/value type 所示,但这建议不要使用元组的元组你真的只是想要一个对象:

const obj = {
  0: "cat",
  1: "dog",
  2: "bird"
} as const;
const val = obj[1];
// const val: "dog"

但现在您根本不需要 function,只需进行索引操作即可。但这也超出了所提出问题的范围。

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.