我正在try 编写一个类型安全的映射对象.我只想定义一次键/值对.

我在以下方面取得了成功:

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

addendum


@jcalz建议了另一个解决方案,它的基本优点是简单,所以我在这里重复一下,稍微扩展一下:

const animalProps = {
    0: "cat",
    1: "dog",
    2: "bird"
  } as const;

function getGenericProp2<T extends object>(props: T, val: keyof T): T[keyof T] {
    const found = props[val];
    if(found === undefined) throw new RangeError("unexpected value");
    return found;
}
type TValues = ValueOf<typeof animalProps>;
type TKeys = keyof typeof animalProps;

推荐答案

你可以这样写:

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

It is generic in the type of the elements of propTuple, which we expect to be a union of readonly 2-tuples. The type of val is T[0], one of the first elements of each pair inside propTuple. And the return type is T[1] | undefined. T[1] means one of the second elements of each pair inside propTuple, and the undefined happens because Map.get() can return undefined regardless of whether you've checked has() first (see Why `map.has()` doesn't act as a type guard. This was also true of your non-generic version, so addressing it is out of scope here).

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

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'.

看上go 不错.


注意,该函数不知道针对特定输入返回which个输出,而这样的事情would是可能的……但Map并不是为此而生的.您可以try 重构Map‘S类型以注意这一点,如Typescript: How can I make entries in an ES6 Map based on an object key/value type中所示,但这表明您实际上只需要object,而不是元组:

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

但现在你根本不需要function,只需要一个索引操作.但这也超出了所提出的问题的范围.

Playground link to code

Typescript相关问答推荐

TypeScript Page Routing在单击时不呈现子页面(React Router v6)

如何访问Content UI的DatePicker/中的sx props of year picker?'<>

不推荐使用Marker类-Google map API警告

有条件地删除区分的联合类型中的属性的可选属性

寻址对象中路径的泛型类型

通过泛型函数本身推断受约束的泛型参数的类型脚本问题

声明文件中的类型继承

使用TypeScrip的类型继承

如何在React组件中指定forwardRef是可选的?

无法将从服务器接收的JSON对象转换为所需的类型,因此可以分析数据

将超类型断言为类型脚本中的泛型参数

在TypeScript中,如何通过一系列过滤器缩小类型?

如何在Reaction Native中关注屏幕时正确更新状态变量?

如何使对象同时具有隐式和显式类型

从 npm 切换到 pnpm 后出现Typescript 错误

根据索引将元组拆分为两个块

是否有可能不允许在 TypeScript 中设置类属性,但也会抛出运行时错误?

有没有办法将类型与关键更改相匹配?

如何使用 TypeScript 接口中第一个属性的值定义第二个属性

可选字段和联合之间有什么区别吗?