我有这个TS代码:

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

interface LoggerOptions<CustomLevels extends string, Level extends CustomLevels | DefaultLevel> {
  customLevels?: Record<CustomLevels, number>
  level: Level
}

class Logger<CustomLevel extends string = '', Level extends CustomLevel | DefaultLevel = CustomLevel | DefaultLevel> {
  constructor(options?: LoggerOptions<CustomLevel, Level>) {
    // Initialization based on options
  }

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

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

logger.log('debug', '')

我首先为我的记录器创建了一个全局DefaultLevel union type.然后我有一个LoggerOption接口,它有两个泛型CustomLevelsLevel.CustomLevels允许我为我的记录器创建自定义级别,以确保所有类型的安全,而Level extends CustomLevels | DefaultLevels是为我的默认记录器设置默认级别所必需的.执行level: CustomLevels操作不起作用,因此出现了第二个泛型.

我的问题出现在类的log函数中.

如果我try 调用logger. log('trace'),我会得到一个类型错误:'type'"tracess"'的参数不能赋值给type'"调试"的参数."

因此,我认为LoggerOptions接口正在设置Level类型值.

我的问题是,为什么在LoggerOptions中,级别可以是类型'info' | 'error' | 'debug' | 'trace'(所需的行为),而在后面的代码中,类型Level的变量只能是在LoggerOptions中设置的字符串类型?

我知道在log()方法中,我可以做这样的事情:

log(level: CustomLevel | DefaultLevel, message: string) {
    // Log message
}

但使用Level型肯定会更整洁.

推荐答案

看起来你根本不希望LoggerLevel中的generic.Level类型将由level属性推断,几乎肯定比CustomLevels | DefaultLevel更窄. 如果你想让Level变成CustomLevels | DefaultLevel,那么就显式地使用该类型,而不是试图将其保存到另一个类型参数中.

实际上,您将that作为类型参数包含在一起似乎只是为了防止从构造函数输入的level属性中推断出CustomLevels.你想

new Logger({ customLevels: { a: 1, b: 2 }, level: "c" });

是一个错误,而不是将CustomLevels推断为"a" | "b" | "c".另一个类型参数可以用于此目的,但该工作的"正确"工具是使用the intrinsic NoInfer<T> utility type:

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

现在Logger只需要在一个类型参数中是泛型的:

class Logger<C extends string> {
  constructor(options?: LoggerOptions<C>) { }
  log(level: C | DefaultLevel, message: string) { }
}

你得到了你想要的错误:

new Logger({ customLevels: { a: 1, b: 2 }, level: "c" }) // error

以及正确的行为log():

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

logger.log('trace', ''); // okay

Playground link to code

Typescript相关问答推荐

React router 6.22.3不只是在生产模式下显示,为什么?

TypeScrip:基于参数的条件返回类型

如何使用WebStorm调试NextJS的TypeScrip服务器端代码?

Select 下拉菜单的占位符不起作用

如何将v-for中的迭代器编写为v-if中的字符串?

为什么Typescript不能推断类型?

为什么在leetcode问题的测试用例中,即使我确实得到了比预期更好的解决方案,这段代码也失败了?

如何在ANGLE中注册自定义验证器

从泛型类型引用推断的文本类型

为什么S struct 类型化(即鸭子类型化)需要非严格类型联合?

将接口映射到交叉口类型

如何使函数参数数组不相交?

递归生成常量树形 struct 的键控类型

如何创建一个将嵌套类属性的点表示法生成为字符串文字的类型?

TypeScript:如何使用泛型和子类型创建泛型默认函数?

如何使对象同时具有隐式和显式类型

如何将 MUI 主题对象的自定义属性与情感样式组件中的自定义props 一起使用?

有没有办法从不同长度的元组的联合中提取带有类型的最后一个元素?

如何让 Promise 将它调用的重载函数的类型转发给它自己的调用者?

有没有办法将类型与关键更改相匹配?