typescript 使用字符串变量约束函数输入类型

2o7dmzc5  于 2023-02-10  发布在  TypeScript
关注(0)|答案(1)|浏览(197)

我有几个简单的类型:

export const structureTypes = ["atom", "molecule"] as const
export type Structure = typeof structureTypes[number]
export const atomTypes = [
  "hydrogen",
  "oxygen",
  "argon",
] as const
export const moleculeTypes = [
  "water",
  "methane",
] as const
export type AtomType = typeof atomTypes[number]
export type MoleculeType = typeof moleculeTypes[number]

我希望能够使用这些来约束一个对象的可能输入,该对象保存每种类型的ID:

export type StructureIdCache = {
  [key in Structure as string]: {
    [key in AtomType | MoleculeType]: string
  }
}

这是可行的,但是在第二层允许AtomType | MoleculeType允许我使用"water"作为一个键,当第一层键是"atom"时,这是无效的。我如何修改类型以允许在对象的第二层只输入允许的名称?
我尝试过创建条件类型:

export type StructureType<T> = T extends Atom ? AtomType : MoleculeType

这适用于创建特定类型:

type AtomTypeNames = StructureType<Atom>
type MoleculeTypeNames = StructureType<Molecule>

但不允许我约束函数的输入:

export const getTypeId = async (
  c: Context,
  type: Structure,
  name: StructureType<typeof type> // <-- this should restrict name inputs but does not
): Promise<string> => {
  // do something
}

如何根据传递给type的字符串值限制name的有效输入?

bxgwgixi

bxgwgixi1#

一种方法是写出一个constAssert的对象,其类型捕获Structure和关联的AtomType/MoleculeType之间所需的关系:

const structures = {
  atom: ["hydrogen", "oxygen", "argon"],
  molecule: ["water", "methane"]
} as const;

并使用它生成表示以下关系的mapped type

type StructureType = { 
  [K in keyof typeof structures]: typeof structures[K][number] 
};
    
/* type StructureType = {
    readonly atom: "hydrogen" | "oxygen" | "argon";
    readonly molecule: "water" | "methane";
} */

现在,我们不再试图让generic函数依赖于conditional type(这对编译器来说本质上是不透明的,也很难推理),而是让它依赖于indexed access type(对应于一个属性查找):

export const getTypeId = async <K extends keyof StructureType>(
  type: K,
  name: StructureType[K]
): Promise<string> => {
  return null!
}

因此typeStructureType的某个键类型K(即,"atom""molecule"),并且nameStructureType的对应属性类型StructureType[K](因此,例如,如果K"atom",则StructureType[K]是"氢")。|"氧气"|你不一定要在对象中查找属性,但是编译器可以很好地推理泛型对象查找,并且通过使用这些术语来描述操作,编译器不会出错。
我们来试试看:

getTypeId("atom", "hydrogen"); // okay
getTypeId("atom", "water"); // error
getTypeId("molecule", "water"); // okay

看起来不错。在第一种和第二种情况下,K被推断为"atom ",因此"hydrogen"可接受,但"water"不可接受。在第三种情况下,K被推断为"molecule",因此"water"可接受。因此,typename之间的关系是强制的,至少对于这些使用情况。
(在某些情况下,K被推断为一个联合类型,然后StructureType[K]也将是一个联合类型,并且这种关系可能不会以您想要的方式强制执行。但是对于泛型函数的大多数用户来说,这类问题通常不是什么大问题,所以我不会在这里讨论如何处理它。)
Playground代码链接

相关问题