TypeScript中如何修正find方法返回带泛型的Experiment类类型错误?
TypeScript 实验类类型匹配问题解决方案
问题场景
我有一个包含特定键和字符串数组值的对象:
const experimentPossibleVariants = { 'experimentOne': ['experimentOne - A', 'experimentOne - B', 'experimentOne - C'], 'experimentTwo': ['experimentTwo - A', 'experimentTwo - B', 'experimentTwo - C'] } as const;
我创建了用于存储和检索实验的类,但return语句存在类型问题。完整代码如下:
const experimentPossibleVariants = { 'experimentOne': ['experimentOne - A', 'experimentOne - B', 'experimentOne - C'], 'experimentTwo': ['experimentTwo - A', 'experimentTwo - B', 'experimentTwo - C'] } as const; type ExperimentKey = keyof typeof experimentPossibleVariants; type ExperimentVariant<T extends ExperimentKey = ExperimentKey> = typeof experimentPossibleVariants[T][number]; type ExperimentConfig = { id: number; variants: ExperimentVariant[] } type ExperimentMap = Partial<Record<ExperimentKey,ExperimentConfig>> const experimentsFromAPI: ExperimentMap = { 'experimentOne': { id: 1, variants: ['experimentOne - A', 'experimentOne - B', 'experimentOne - C'] } } class Experiment<T extends ExperimentKey = ExperimentKey> { public readonly key: T; private config: ExperimentConfig; constructor(key: T, config: ExperimentConfig) { this.key = key; this.config = config; } get variant(): ExperimentVariant<T> { return this.config.variants[Math.floor(Math.random() * this.config.variants.length)]; } } class Experiments { private experiments: Experiment[] = []; constructor(experiments?: ExperimentMap) { if(experiments) { Object.entries(experiments).forEach(([experimentKey, experimentConfig]) => { const typedExperimentKey = experimentKey as ExperimentKey; this.experiments.push(new Experiment(typedExperimentKey, experimentConfig)); }) } } public find<T extends ExperimentKey>(key: T): Experiment | undefined { return this.experiments.find(experiment => experiment.key === key); } } const exp = new Experiments(experimentsFromAPI); exp.find('experimentOne').variant === 'e'
问题描述
当把find方法的返回类型设置为Experiment<T> | undefined时会出现return错误,但实际运行能得到正确结果;仅设置返回类型为Experiment时无错误,但TypeScript会给出错误的类型提示。请问如何修改才能既无报错又获得正确的类型提示?
解决方案
问题根源
ExperimentConfig中的variants类型是所有实验变体的联合数组,未与特定实验键绑定,导致Experiment类的variant属性类型推导存在模糊性。Experiments类的experiments数组类型为Experiment[],TypeScript无法自动推导find方法返回的实例就是与传入键T对应的Experiment<T>类型。
修改步骤
1. 让ExperimentConfig与特定实验键绑定
将ExperimentConfig改为泛型类型,确保variants只包含对应实验键的变体:
type ExperimentConfig<T extends ExperimentKey = ExperimentKey> = { id: number; variants: ExperimentVariant<T>[] }
2. 更新Experiment类的类型定义
调整Experiment类的config属性类型为泛型的ExperimentConfig<T>,确保类型一致性:
class Experiment<T extends ExperimentKey = ExperimentKey> { public readonly key: T; private config: ExperimentConfig<T>; constructor(key: T, config: ExperimentConfig<T>) { this.key = key; this.config = config; } get variant(): ExperimentVariant<T> { return this.config.variants[Math.floor(Math.random() * this.config.variants.length)]; } }
3. 修正find方法的返回类型与断言
修改Experiments类的find方法,指定返回类型为Experiment<T> | undefined,并通过类型断言告诉TypeScript匹配到的实例就是对应类型(逻辑上我们能保证key匹配的实例类型正确):
class Experiments { private experiments: Experiment<ExperimentKey>[] = []; constructor(experiments?: ExperimentMap) { if(experiments) { Object.entries(experiments).forEach(([experimentKey, experimentConfig]) => { const typedExperimentKey = experimentKey as ExperimentKey; this.experiments.push(new Experiment(typedExperimentKey, experimentConfig as ExperimentConfig<typeof typedExperimentKey>)); }) } } public find<T extends ExperimentKey>(key: T): Experiment<T> | undefined { return this.experiments.find(experiment => experiment.key === key) as Experiment<T> | undefined; } }
4. 调整ExperimentMap类型(可选)
为了更严谨,ExperimentMap可以更新为:
type ExperimentMap = Partial<Record<ExperimentKey, ExperimentConfig>>
修改后的完整代码
const experimentPossibleVariants = { 'experimentOne': ['experimentOne - A', 'experimentOne - B', 'experimentOne - C'], 'experimentTwo': ['experimentTwo - A', 'experimentTwo - B', 'experimentTwo - C'] } as const; type ExperimentKey = keyof typeof experimentPossibleVariants; type ExperimentVariant<T extends ExperimentKey = ExperimentKey> = typeof experimentPossibleVariants[T][number]; type ExperimentConfig<T extends ExperimentKey = ExperimentKey> = { id: number; variants: ExperimentVariant<T>[] } type ExperimentMap = Partial<Record<ExperimentKey, ExperimentConfig>> const experimentsFromAPI: ExperimentMap = { 'experimentOne': { id: 1, variants: ['experimentOne - A', 'experimentOne - B', 'experimentOne - C'] } } class Experiment<T extends ExperimentKey = ExperimentKey> { public readonly key: T; private config: ExperimentConfig<T>; constructor(key: T, config: ExperimentConfig<T>) { this.key = key; this.config = config; } get variant(): ExperimentVariant<T> { return this.config.variants[Math.floor(Math.random() * this.config.variants.length)]; } } class Experiments { private experiments: Experiment<ExperimentKey>[] = []; constructor(experiments?: ExperimentMap) { if(experiments) { Object.entries(experiments).forEach(([experimentKey, experimentConfig]) => { const typedExperimentKey = experimentKey as ExperimentKey; this.experiments.push(new Experiment(typedExperimentKey, experimentConfig as ExperimentConfig<typeof typedExperimentKey>)); }) } } public find<T extends ExperimentKey>(key: T): Experiment<T> | undefined { return this.experiments.find(experiment => experiment.key === key) as Experiment<T> | undefined; } } const exp = new Experiments(experimentsFromAPI); // 现在会得到正确的类型提示,比如variant只能是'experimentOne - A'/'experimentOne - B'/'experimentOne - C'中的一个 exp.find('experimentOne')?.variant === 'experimentOne - A';
这样修改后,find方法返回的实例会有正确的类型提示,同时不会出现编译错误。
内容的提问来源于stack exchange,提问作者R. Groot Jebbink
相关产品推荐
相关产品推荐

