英文:
Constrain function input type using string variable
问题
I have a few simple types:
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]
I would like to be able to use these to constrain the possible inputs of an object that holds the IDs of each type:
export type StructureIdCache = {
[key in Structure as string]: {
[key in AtomType | MoleculeType]: string
}
}
This works, but allowing AtomType | MoleculeType
on the 2nd level allows me to use "water" as a key when the 1st level key is "atom" which is invalid. How can I modify the types to allow input of only allowable names in the 2nd level of the object?
I have tried creating a conditional type:
export type StructureType<T> = T extends "atom" ? AtomType : MoleculeType
This works for creating specific types:
type AtomTypeNames = StructureType<"atom">
type MoleculeTypeNames = StructureType<"molecule">
But does not allow me to constrain the inputs for a function:
export const getTypeId = async (
c: Context,
type: Structure,
name: StructureType<typeof type> // <-- this should restrict name inputs but does not
): Promise<string> => {
// do something
}
How can I restrict the valid inputs for name
, based on the value of the string passed to type
?
英文:
I have a few simple types:
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]
I would like to be able to use these to constrain the possible inputs of an object that holds the IDs of each type:
export type StructureIdCache = {
[key in Structure as string]: {
[key in AtomType | MoleculeType]: string
}
}
This works, but allowing AtomType | MoleculeType
on the 2nd level allows me to use "water"
as a key when the 1st level key is "atom"
which is invalid. How can I modify the types to allow input of only allowable names in the 2nd level of the object?
I have tried creating a conditional type:
export type StructureType<T> = T extends Atom ? AtomType : MoleculeType
This works for creating specific types:
type AtomTypeNames = StructureType<Atom>
type MoleculeTypeNames = StructureType<Molecule>
But does not allow me to constrain the inputs for a function:
export const getTypeId = async (
c: Context,
type: Structure,
name: StructureType<typeof type> // <-- this should restrict name inputs but does not
): Promise<string> => {
// do something
}
How can I restrict the valid inputs for name
, based on the value of the string passed to type
?
答案1
得分: 1
以下是您要翻译的内容:
One way to proceed is to write out a single const
-asserted object whose type captures the desired relationship between your Structure
and associated AtomType
/MoleculeType
:
const structures = {
atom: ["hydrogen", "oxygen", "argon"],
molecule: ["water", "methane"]
} as const;
And use it to generate a mapped type representing this relationship:
type StructureType = {
[K in keyof typeof structures]: typeof structures[K][number]
};
/* type StructureType = {
readonly atom: "hydrogen" | "oxygen" | "argon";
readonly molecule: "water" | "methane";
} */
And now, instead of trying to make your generic function depend on a conditional type, which is essentially opaque to the compiler and difficult for it to reason about, you make it depend on an indexed access type corresponding to a property lookup:
export const getTypeId = async <K extends keyof StructureType>(
type: K,
name: StructureType[K]
): Promise<string> => {
return null!
}
So type
is some key type K
of StructureType
(that is, either "atom" or "molecule"), and name
is the corresponding property type StructureType[K]
of StructureType
(so, for example if K
is "atom", then StructureType[K]
is "hydrogen" | "oxygen" | "argon"). You're not necessarily actually looking up a property in an object, but the compiler can reason fairly well about generic object lookups, and by phrasing the operation in those terms it compiles without error.
Let's try it out:
getTypeId("atom", "hydrogen"); // okay
getTypeId("atom", "water"); // error
getTypeId("molecule", "water"); // okay
Looks good. In the first and second cases, K
is inferred as "atom", and so "hydrogen" is acceptable but "water" is not. In the third case, K
is inferred as "molecule", and so "water" is acceptable. So the relationship between type
and name
is enforced, at least for these use cases.
英文:
One way to proceed is to write out a single const
-asserted object whose type captures the desired relationship between your Structure
and associated AtomType
/MoleculeType
:
const structures = {
atom: ["hydrogen", "oxygen", "argon"],
molecule: ["water", "methane"]
} as const;
And use it to generate a mapped type representing this relationship:
type StructureType = {
[K in keyof typeof structures]: typeof structures[K][number]
};
/* type StructureType = {
readonly atom: "hydrogen" | "oxygen" | "argon";
readonly molecule: "water" | "methane";
} */
And now, instead of trying to make your generic function depend on a conditional type, which is essentially opaque to the compiler and difficult for it to reason about, you make it depend on an indexed access type corresponding to a property lookup:
export const getTypeId = async <K extends keyof StructureType>(
type: K,
name: StructureType[K]
): Promise<string> => {
return null!
}
So type
is some key type K
of StructureType
(that is, either "atom"
or "molecule"
), and name
is the corresponding property type StructureType[K]
of StructureType
(so, for example if K
is "atom"
, then StructureType[K]
is `"hydrogen" | "oxygen" | "argon"). You're not necessarily actually looking up a property in an object, but the compiler can reason fairly well about generic object lookups, and by phrasing the operation in those terms it compiles without error.
Let's try it out:
getTypeId("atom", "hydrogen"); // okay
getTypeId("atom", "water"); // error
getTypeId("molecule", "water"); // okay
Looks good. In the first and second cases, K
is inferred as "atom
", and so "hydrogen"
is acceptable but "water"
is not. In the third case, K
is inferred as "molecule"
, and so "water"
is acceptable. So the relationship between type
and name
is enforced, at least for these use cases.
(There are some situations where K
is inferred as a union type, and then StructureType[K]
will also be a union, and the relationship might not be enforced the way you want. But this sort of issue is usually not a big deal for most users of generic functions, so I won't digress into a discussion of how to deal with that here.)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论