typescript - 可分配给未标记默认值的标记类
问题描述
我有一个错误类,它可能有也可能没有一些标签来识别出了什么问题(一个字符串)。如果潜在值在签名中可见,我认为这将很有用,例如:
function f(): 'success' | MyError<'invalid'|'timeout'>
到目前为止,这似乎很容易。但是,问题在于“或可能不”部分,具有将 any 分配MyError<T>
给 default的能力MyError
,以及构造函数的参数的必要性。从逻辑上讲,如果签名说“没有给出额外的信息”,那么可能会给出源自较低级别的任何错误,这可能有额外的信息,而该信息从未被访问过。
我的第一次尝试是简单地string
用作默认值,然后分配没问题:
class MyError<T extends string = string> {
info: T;
constructor(init: T) { this.info = init; }
}
let a: MyError = new MyError<'something'>('something');
这种方法有两个问题:
- 任何消费者都可以访问
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;
然而,这带来的问题多于解决的问题。一切都需要显式输入,所以签名是正确的,当只是返回时new MyError('completely useless')
,它仍然需要一个参数。
有没有更容易被我忽略的东西?
最后一次蛮力尝试(我认为有效,强调“思考”。在这种情况下,“有效”意味着类型安全,尽管有any
-casts,只要它__CompoundError
本身从未被导出),要折叠的片段:
class __CompoundError<T extends string> {
tag: T;
constructor(tag?: T) {
if (tag === undefined) this.tag = '' as any;
else this.tag = tag;
}
}
Object.defineProperty(__CompoundError, 'name', {
value: 'CompoundError',
configurable: true,
});
type CompoundError<T extends string> = string extends T ? Omit<__CompoundError<T>, 'tag'> : __CompoundError<T>;
const CompoundError: {
new <T extends string>(tag: T): CompoundError<T>;
new (): CompoundError<string>
} = __CompoundError as any;
// ################# tests #################
let generic = new CompoundError(); // don't need any argument for generic version
let a = new CompoundError('str'); // generic type inferred
let b: CompoundError<'str' | 'other'>;
// generic.tag // error, generic doesn't have property tag
a.tag; // ok
generic = a; // ok, can assign to generic version
b = a; // ok, can assign to more broad version
console.log(a.constructor.name); // 'CompoundError'
declare function f(): CompoundError<'str'>; // can use as a type
function g() { return new CompoundError('str'); } // can just return value and infer function type
与我可能最终会使用的类似版本:
class CompoundError {}
class CompoundErrorTagged<T extends string> extends CompoundError {
constructor(public tag: T) { super(); }
}
// ################# tests #################
let generic = new CompoundError(); // don't need any argument for generic version
let a = new CompoundErrorTagged('str'); // generic type inferred
let b: CompoundErrorTagged<'str' | 'other'>;
// generic.tag // error, generic doesn't have property tag
a.tag; // ok
generic = a; // ok, can assign to generic version
b = a; // ok, can assign to more broad version
console.log(a.constructor.name); // 'CompoundErrorTagged'
declare function f(): CompoundErrorTagged<'str'>; // can use as a type
function g() { return new CompoundErrorTagged('str'); } // can just return value and infer function type
解决方案
这里的方法是将完全没有标签的错误视为完全不同的类型。
class TaggedError<T extends string> {
constructor(public info: T) { }
}
class UntaggedError {
constructor(/* Other untagged error arguments? */) {}
}
type MyError<T extends string> = TaggedError<T> | UntaggedError;
现在MyError<T>
包括错误实际上可能未被标记的可能性。info
Typescript 将在这里保护您,在您确定错误是标记还是未标记之前,它不会让您访问该属性。
function f(): 'success' | MyError<'invalid' | 'timeout'>;
const result = f();
if (result === 'success') {
// ... success!
}
// ERROR: Property info does not exist on type UntaggedError
console.log(result.info);
if ('info' in result) {
console.log(`TaggedError<${result.info}>`);
} else {
console.log('UntaggedError... I dunno what happened!');
}
如果你不喜欢'info' in result
类型缩小的形式,你总是可以写一个 typeguard。
function isTaggedError<T>(e: MyError<T>): e is TaggedError<T> {
return 'info' in e;
}
if(isTaggedError(result)) { // type inference just works here
console.log(`TaggedError<${result.info}>`);
}
推荐阅读
- r - 试图绘制朴素贝叶斯输出但出现错误 - “x”是一个列表,但没有组件“x”和“y”
- python - 是否可以在 Django 表单的 ModelChoiceField 中具有自动完成功能?
- android - mqt_native_modules react.common.JavascriptException: TypeError: undefined is not an object (评估 'w.default.chapterContent[c].content')
- r - 用每个唯一名称的平均值填充单元格数据
- php - Python Post - PHP 请求
- java - AWS Ubuntu java.lang.ClassNotFoundException: Jama.Matrix
- scheme - 如何在方案中使用延续?
- c++ - 函数调用错误中的参数过多
- go - 使用 go mod 下载时出现“模式匹配无模块依赖项”的原因是什么?
- django - href 没有发送好的链接信息