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

TypeScript:设为never的属性仍被判定为必填的问题

分阶段填充的TypeScript类型优化

问题背景

想实现一个分阶段逐步填充的Data类型,要求如下:

  • Data<DataType.bar>:仅需包含已完成的Foo对象,Bar和Baz无需存在
  • Data<DataType.baz>:需包含Foo和Bar,Baz暂不需要
  • Data<null>:包含Foo、Bar、Baz全部对象

最初的实现代码存在问题:当注释掉非必填属性时,TypeScript会报错提示缺失属性。后来写出了符合需求的实现,但代码重复度太高,希望找到更简洁的方案。

最初有问题的实现

enum DataType {
  'foo',
  'bar',
  'baz',
}

type Foo = {
  a: string;
};
type Bar = {
  b: string;
};
type Baz = {
  c: string;
};

type Data<T extends DataType | null = null> = {
  [DataType.foo]: T extends DataType.bar | DataType.baz | null ? Foo : never;
  [DataType.bar]: T extends DataType.baz | null ? Bar : never;
  [DataType.baz]: T extends null ? Baz : never;
};

const myData: Data = {
  [DataType.foo]: { a: 'I should already exist while bar is being created' },
  /*
  [DataType.bar]: {
     b: `I correctly error when uncommented...
         but when commented, DataType.bar also appears to be required?`
  }
  */
};

问题:注释掉DataType.bar后,TypeScript报错提示缺失[DataType.bar]和[DataType.baz]属性,不符合预期。

符合需求但重复的实现

type Data<T extends DataType | null = null> = T extends null
  ? {
      [DataType.foo]: Foo;
      [DataType.bar]: Bar;
      [DataType.baz]: Baz;
    }
  : T extends DataType.baz
  ? {
      [DataType.foo]: Foo;
      [DataType.bar]: Bar;
    }
  : T extends DataType.bar
  ? {
      [DataType.foo]: Foo;
    }
  : T extends DataType.foo
  ? {}
  : never;

优化方案

咱们可以用映射类型结合条件类型简化代码,避免重复定义每个阶段的结构,同时保证扩展性:

enum DataType {
  foo,
  bar,
  baz,
}

type Foo = { a: string };
type Bar = { b: string };
type Baz = { c: string };

// 定义枚举与对应类型的映射表
type TypeMap = {
  [DataType.foo]: Foo;
  [DataType.bar]: Bar;
  [DataType.baz]: Baz;
};

// 根据传入的泛型T,筛选需要保留的前置属性
type PrecedingTypes<T extends DataType | null> = 
  T extends null 
    ? keyof TypeMap  // 传null时保留所有属性
    : T extends DataType.baz 
      ? DataType.foo | DataType.bar  // 传baz时保留foo和bar
      : T extends DataType.bar 
        ? DataType.foo  // 传bar时只保留foo
        : never;  // 传foo时无属性

// 生成最终的Data类型
type Data<T extends DataType | null = null> = {
  [K in PrecedingTypes<T>]: TypeMap[K];
};

效果验证

  • const d1: Data<DataType.foo> = {}; ✅ 符合预期(空对象)
  • const d2: Data<DataType.bar> = { [DataType.foo]: { a: "test" } }; ✅ 符合预期(仅包含foo)
  • const d3: Data<DataType.baz> = { [DataType.foo]: { a: "test" }, [DataType.bar]: { b: "test" } }; ✅ 符合预期(包含foo和bar)
  • const d4: Data<null> = { [DataType.foo]: { a: "test" }, [DataType.bar]: { b: "test" }, [DataType.baz]: { c: "test" } }; ✅ 符合预期(包含全部属性)

后续如果新增枚举值(比如qux),只需要在TypeMap中添加对应类型,并在PrecedingTypes中补充条件判断即可,无需重复编写整个对象结构,维护成本更低。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 22:20:39