You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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会给出错误的类型提示。请问如何修改才能既无报错又获得正确的类型提示?

解决方案

问题根源

  1. ExperimentConfig中的variants类型是所有实验变体的联合数组,未与特定实验键绑定,导致Experiment类的variant属性类型推导存在模糊性。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 22:14:58