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

如何在TypeScript键重映射映射对象中兼容forEach并保留类型安全?

用映射对象替代switch语句的类型优化方案

我常使用映射对象替代switch语句,以此提升代码的可读性与可扩展性,基础示例如下:

interface Foo {
    type: 'foo';
    foo: number;
}

interface Bar {
    type: 'bar';
    bar: string;
}

type FooBar = Foo | Bar;

const mapper: Record<FooBar['type'], () => string | number> = {
    foo: () => 1,
    bar: () => 'bar',
}

const foo = { type: 'foo', foo: 1 } satisfies Foo;
const bar = { type: 'bar', bar: '123' } satisfies Bar;

mapper[foo.type]() // -> 1
mapper[bar.type]() // -> 'bar'

Record方案的弊端

这种基于Record的方案在多数场景下可行,但当需要向映射函数传递对象时存在明显问题:映射函数的参数会被统一类型化为联合类型FooBar,导致内部必须手动添加类型断言才能访问具体类型的属性。

// 沿用之前的Foo、Bar和FooBar类型

const mapper: Record<FooBar['type'], (fooBar: FooBar) => string | number> = {
    // 必须手动断言参数类型才能访问foo/bar属性
    foo: (fooBar) => (fooBar as Foo).foo,
    bar: (fooBar) => (fooBar as Bar).bar,
}

const foo = { type: 'foo', foo: 1 } satisfies Foo;
const bar = { type: 'bar', bar: '123' } satisfies Bar;

mapper[foo.type](foo) // -> 1
mapper[bar.type](bar) // -> 'bar'

// 此代码可运行,但类型检查不严谨(比如传入错误类型也不会报错)
[foo, bar].forEach(fooOrBar => {
    mapper[fooOrBar.type](fooOrBar)
})

键重映射的类型优化与遍历问题

使用TypeScript的键重映射可以优化类型定义,让每个映射函数的参数自动匹配对应类型,无需手动断言,但此时联合类型数组的forEach遍历会失效——因为TypeScript无法推断出遍历项的具体类型与mapper函数参数的对应关系,会出现类型缩减为never的错误。

// 沿用之前的Foo、Bar和FooBar类型

type Mapper = {
    [E in FooBar as E['type']]: (obj: E) => number | string;
};

const mapper: Mapper = {
    foo: foo => foo.foo,
    bar: bar => bar.bar,
};

const foo = { type: 'foo', foo: 1 } satisfies Foo;
const bar = { type: 'bar', bar: 'bar' } satisfies Bar;

mapper[foo.type](foo); // -> 1
mapper[bar.type](bar); // -> 'bar'

// 无法运行,类型检查报错:Foo & Bar 缩减为 never
[foo, bar].forEach(fooOrBar => {
    mapper[fooOrBar.type](fooOrBar)
})

解决方案:保留键重映射类型同时支持遍历

可以通过泛型函数来关联遍历项的具体类型与映射函数的参数类型,既保留键重映射带来的精确类型约束,又让forEach遍历正常运行:

// 沿用之前的Foo、Bar、FooBar和Mapper定义

// 定义泛型处理函数,捕获每个遍历项的具体类型
const handleFooBar = <T extends FooBar>(obj: T) => {
    return mapper[obj.type](obj);
};

// 现在forEach可以正常运行,且类型完全安全
[foo, bar].forEach(handleFooBar);

原理说明

泛型T extends FooBar会在每次调用时自动捕获当前遍历项的具体类型(Foo或Bar),TypeScript可以据此推断出mapper[obj.type]对应的函数参数类型正好是T,从而完成类型匹配,无需任何类型断言,同时保证了类型安全性。

内容的提问来源于stack exchange,提问作者Lehks

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 10:15:34