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

TypeScript实例化新类对象时联合类型错误的解决求助

问题描述

我在TypeScript中定义了一个Logger类,构造函数接受可选的options参数,类泛型C代表自定义日志级别,用于类中的log(level: Level<C>)等方法。我希望实现updateConfig方法,返回一个新的Logger实例,其C参数为原实例的自定义级别与新options对象中定义的级别联合类型。

当前代码存在问题:调用updateConfig后生成的newLogger无法识别新添加的自定义级别'test',且类型显示为Logger<"debug" | "trace"> | Logger<"debug" | "trace" | "test">,不符合预期。

当前代码:

type DefaultLevel = 'info' | 'error'; // Default log levels

type Level<C extends string> = DefaultLevel | C
type LevelWithSilence<C extends string> = Level<C> | 'silence'

interface LoggerOptions<C extends string> {
  customLevels?: Record<C, number>
  level: LevelWithSilence<NoInfer<C>>
}

interface UpdatedLoggerOptions<C extends string, L extends string> {
  customLevels?: Record<L, number>
  level: LevelWithSilence<NoInfer<C | L >>
}

class Logger<C extends string = ''> {

  options?: LoggerOptions<C>

  constructor(options?: LoggerOptions<C>) {
    this.options = options
  }

  log(level: Level<C>, message: string) {
    // Log message
  }

  updateConfig<L extends string = never>(options?: UpdatedLoggerOptions<C, L>) {
    
    if(!this.options) return new Logger (options as LoggerOptions<C>)

    if(!options) return this

    return new Logger<C | L>({
      customLevels: {
        ...this.options.customLevels as Record<C, number>,
        ...options.customLevels as Record<L, number>
      },
      level: options.level
    } as LoggerOptions<C | L>)
  }
}

// Usage
const logger = new Logger({
  customLevels: {
    debug: 1,
    trace: 2,
  },
  level: 'error'
});

logger.log('debug', '')

const newLogger = logger.updateConfig({
  customLevels: {
    test: 3,
  },
  level: 'test'
})

newLogger.log('debug', '') // Works
newLogger.log('test', '') // Should work
解决方案

问题核心在于updateConfig的返回值分支类型合并,以及泛型L的自动推导失效。以下是修正后的代码:

type DefaultLevel = 'info' | 'error'; // Default log levels

type Level<C extends string> = DefaultLevel | C;
type LevelWithSilence<C extends string> = Level<C> | 'silence';

// 简化配置接口,让泛型自动从customLevels键推导
interface LoggerOptions<C extends string = never> {
  customLevels?: Record<C, number>;
  level: LevelWithSilence<C>;
}

class Logger<C extends string = never> {
  options?: LoggerOptions<C>;

  constructor(options?: LoggerOptions<C>) {
    this.options = options;
  }

  log(level: Level<C>, message: string) {
    // Log message
  }

  // 明确返回类型,让L从传入的customLevels自动推导
  updateConfig<L extends string = never>(
    options?: LoggerOptions<L>
  ): Logger<C | L> {
    // 合并原配置与新配置
    const mergedOptions = this.options
      ? {
          customLevels: {
            ...this.options.customLevels,
            ...options?.customLevels,
          } as Record<C | L, number>,
          level: options?.level ?? this.options.level,
        }
      : options;

    return new Logger<C | L>(mergedOptions as LoggerOptions<C | L>);
  }
}

// Usage
const logger = new Logger({
  customLevels: {
    debug: 1,
    trace: 2,
  },
  level: 'error',
});

logger.log('debug', ''); // 正常运行

const newLogger = logger.updateConfig({
  customLevels: {
    test: 3,
  },
  level: 'test',
});

newLogger.log('debug', ''); // Works
newLogger.log('test', ''); // 现在正常运行
关键修改点
  • 简化配置接口:移除NoInfer和独立的UpdatedLoggerOptions,让TypeScript能直接从customLevels的键名自动推导泛型类型。
  • 固定返回类型:强制updateConfig返回Logger<C | L>,避免分支逻辑导致的联合类型问题。
  • 优化配置合并:处理level的默认值(新配置未传时沿用原级别),同时确保合并后的customLevels类型正确。

修改后,newLogger的类型会正确推导为Logger<"debug" | "trace" | "test">,调用log('test')不再报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 22:43:18