Typescript 可分配给未标记默认值的标记类

Typescript 可分配给未标记默认值的标记类,typescript,Typescript,我有一个用于错误的类,它可能有也可能没有一些标记来识别出错的地方(字符串)。我认为如果在签名中可以看到潜在值,这将非常有用,例如: function f(): 'success' | MyError<'invalid'|'timeout'> 这种方法有两个问题: 任何消费者都可以访问a.info,即使它不是有用的 构造函数需要一个参数,即使它是一个没有更多信息的错误 第一个问题可以通过在顶部补上一个额外的类型来解决: type InfoError<T extends My

我有一个用于错误的类,它可能有也可能没有一些标记来识别出错的地方(字符串)。我认为如果在签名中可以看到潜在值,这将非常有用,例如:

function f(): 'success' | MyError<'invalid'|'timeout'>
这种方法有两个问题:

  • 任何消费者都可以访问
    a.info
    ,即使它不是有用的
  • 构造函数需要一个参数,即使它是一个没有更多信息的错误
第一个问题可以通过在顶部补上一个额外的类型来解决:

type InfoError<T extends MyError> = string extends T['info'] ? Omit<T, 'info'> : T;

let b = new MyError<'something'>('something');
let c: InfoError<MyError> = b;

这里的方法是将一个完全没有标记的错误视为完全不同的类型

类标记错误{
构造函数(公共信息:T){}
}
类未分类错误{
构造函数(/*其他未标记的错误参数?*/){}
}
类型MyError=TaggedError | UntaggedError;
现在
MyError
包含了错误实际上可能未标记的可能性。Typescript将在此处保护您,在您确定错误是标记的还是未标记的之前,它不会让您访问
info
属性

函数f():“成功”| MyError; const result=f(); 如果(结果==“成功”){ //…成功! } //错误:类型UntagedError上不存在属性信息 console.log(result.info); 如果(结果中的“信息”){ log(`TaggedError`); }否则{ log('untagederror…我不知道发生了什么!'); } 如果您不喜欢结果中的
“info”
类型缩小形式,您可以编写一个typeguard

函数IStageDerror(e:MyError):e是TaggedError{
在e中返回“info”;
}
如果(isTaggedError(result)){//类型推断在这里有效
log(`TaggedError`);
}

谢谢,但有一点是,消费者从签名中知道他得到了什么。必须测试,
info
存在,而签名已经表明它存在,这有点奇怪。可能是类型MyError=string扩展了T?untagederror:TaggedError会更好,但我也可以使用
省略
,但这里有一个奇怪的可分配性问题我无法解决。此外,所有内容都需要显式键入,我不能只
返回new MyError()并让推断者处理其余的问题。我理解您的担忧,我认为每个问题都是可以解决的。即使您将
info
保留为可选值或为其提供一个虚拟值,您的消费者仍然必须确定他们是否应该关注
信息。但是,如果您拆分了这些类型,现在它们有了一种类型安全的、确定的方法来确定是否有任何额外的信息可以从错误中获得。此外,您不会失去任何明确性,因为在本例中,
MyError
不再具有构造函数。您必须显式返回一个
TaggedError
UntaggedError
。我接受这一点,因为它一直在正确地推动我,使我不必强制尝试统一类。这就是说,重要的部分是,
TaggedError
应该继承自
UntaggedError
,因此赋值
声明让a:TaggedError;让b:UntagedError=a
保证工作。我得出的结论是,某种统一类型(例如,
MyType
)只会让事情变得更加乏味。我忘了基本的东西。我还想在问题中加入我最后一次用暴力强迫的尝试,我认为这是可行的,但是哇,这有点乱。
type InfoError<T extends MyError> = string extends T['info'] ? Omit<T, 'info'> : T;

let b = new MyError<'something'>('something');
let c: InfoError<MyError> = b;