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

TypeScript泛型约束异常:可识别错误类型,但允许多余属性

TypeScript泛型约束未拦截对象多余属性的问题

我最近做了个可视化测试(就是在VS Code里检查代码有没有红色波浪线),想验证泛型类型能不能正确约束substitute()方法的入参。测试代码是这样的:

interface IJob { jobId: string; extraneous?: number; }

/**
 * Bear in mind these tests are just to visually identify in
 * the editor that the generics are passing their types around
 * correctly.
 */
describe("Typings →", () => {
  it.skip('setting <T> imposes structure on the substitutions that are allowed in', () => {
    const wrongType = { jobId: "1234", extraneous: "foobar" }
    const nonExistantType = { jobId: "1234", noCanDo: true }
    const ruleAbidingJob = { jobId: "1234" }

    const t = TypedTemplate.create<IJob>();
    t.substitute(wrongType); // this should be failing in TS
    t.substitute(nonExistantType); // this should be failing in TS
    t.substitute(ruleAbidingJob); // this should be fine
  });
});

测试结果有点奇怪:

  • ruleAbidingJob完全符合IJob的结构,能正常传入,没问题;
  • wrongType里的extraneous属性传了字符串(而接口里定义的是可选number),TypeScript正确识别并标红了,这也符合预期;
  • 但nonExistantType带了一个IJob里根本没定义的noCanDo属性,VS Code里居然没报错!而且我确认过substitute方法的预期参数类型确实是IJob。

原因分析

这其实是TypeScript的一个特性——对象字面量超额属性检查。简单说就是:当你直接把对象字面量传给函数参数时,TS会严格检查有没有多余属性;但如果先把对象赋值给一个变量,再把变量传进去,TS会认为这个变量可能是IJob的兼容扩展类型,不会触发超额属性检查。

解决办法

如果想要严格拦截多余属性,有几个办法可以试试:

  1. 直接传入对象字面量
    不要先赋值给变量,直接把对象写在substitute的参数里:

    t.substitute({ jobId: "1234", noCanDo: true }); // 这里会立刻报错
    
  2. 用类型断言或工具函数约束变量类型
    强制把变量约束为严格的IJob类型,这样在变量定义时就会报错:

    // 类型断言方式
    const nonExistantType = { jobId: "1234", noCanDo: true } as IJob; // 此处报错
    
    // 工具函数方式(更直观)
    function strictType<T>(obj: T): T {
      return obj;
    }
    const nonExistantType = strictType<IJob>({ jobId: "1234", noCanDo: true }); // 此处报错
    
  3. 给substitute方法定义严格的参数类型
    可以自己实现一个Exact工具类型,让substitute的参数必须严格匹配T,不允许任何多余属性:

    // 定义Exact工具类型
    type Exact<T, U> = T extends U 
      ? U extends T 
        ? T 
        : never 
      : never;
    
    // 修改TypedTemplate的create方法返回值
    class TypedTemplate {
      static create<T>() {
        return {
          substitute(obj: Exact<T, typeof obj>) {
            // 方法逻辑...
          }
        }
      }
    }
    

    这样不管是传变量还是直接传字面量,只要有多余属性都会被拦截。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 10:07:20