考虑这样一种情况,库返回一个错误,并且所有错误都有一个name: string;字段.

我知道KnownError会有name: 'KnownError'和额外的KnownErrorSpecific: string字段--但我不知道所有可能错误的名称.

这听起来像是歧视联合的完美 case --但由于所有名称都是字符串,试图缩小对鉴别符的输入范围并不排除通用 case .

type UnknownError = {
    name: string;
};

type KnownErrorOne = {
    name: 'KnownError';
    knownErrorSpecific: string;
};

type KnownErrorTwo = {
    name: 'WarpDriveError';
    warpCoreTemperature: number;
};

type GeneralError = KnownErrorOne | KnownErrorTwo | UnknownError;

const handleError = (err: GeneralError) => {
    if(err.name === 'KnownError' as const){
        console.log(err.knownErrorSpecific);
        // err could be KnownErrorOne or UnknownError
    }
};

我认为正确的解决方案是将已知姓名列表作为任意字符串except键入UnknownError.name.假设是Omit<string, "KnownError" | "WarpDriveError">.

这样的事情有可能发生吗?

作为一种解决办法,我可以使用if( 'knownErrorSpecific' in err ),但我想了解上面的解决方案,以防再次遇到这种情况.

TS Playground Link

推荐答案

每一种类型都需要有自己的声音.

因此,有人这样做是完全合理的:

const error: UnknownError = { name: 'KnownError' }

如果您将该对象传递给此函数:

const handleError = (err: GeneralError) => {
    if(err.name === 'KnownError'){
        console.log(err.knownErrorSpecific); // undefined
    }
};

那么就有问题了,因为该对象上不存在该属性.

因此,在这个受到歧视的联盟中,这里的name不是一个足够的判别式.判别式需要是互斥的,但'KnownError'扩展了string,因此它们是重叠的.


Typescript 没有negative种类型.你不能说什么类型是not.这意味着您不能从无限集中排除有限值.因此,您不能将"除此字符串以外的任何字符串"表示为类型."除0以外的任何数字"也是如此.

这意味着,如果您的判别式与其他成员重叠,那么您将需要进行额外的缩小,以便将其缩小到您需要的类型.


潜在的解决方案.

可能不太可能有人会将已知错误代码用于未知错误.(这取决于你做出判断的决定).因此,如果您认为这对您的代码库足够安全,您可以稍微重组您的类型,并添加一个TypeGuard来使其更好一些.

首先,在自己的联盟中将已知错误与未知错误分开:

type UnknownError = {
    name: string;
};

type KnownErrorOne = {
    name: 'KnownError';
    knownErrorSpecific: string;
};

type KnownErrorTwo = {
    name: 'WarpDriveError';
    warpCoreTemperature: number;
};

type KnownError = KnownErrorOne | KnownErrorTwo
type GeneralError = KnownError | UnknownError;

现在可以防止name: string人污染unions .

然后,我们只需要一个类型保护,它可以接受GeneralError,但将该对象验证为KnownError.

function isKnownError<
  T extends KnownError['name']
>(
  error: GeneralError,
  name: T
): error is Extract<KnownError, { name: T }> {
  return error.name === name
}

此函数接受GeneralErrorKnownError的名称.如果名称匹配,则它通过Extract匹配该名称的KnownError并集的所有成员来断言错误属于该已知类型.

现在,这将很好地发挥作用:

const handleError = (error: GeneralError) => {
    if(isKnownError(error, 'KnownError')){
        console.log(error.knownErrorSpecific); // fine
    }
};

请注意,没有什么能阻止你这样做:

const error: UnknownError = { name: 'KnownError' }

这可能会在运行时造成问题,但在这种情况下,是否值得冒这个风险取决于您.

See Playground

Typescript相关问答推荐

TypScript中的算法运算式

为什么ESLint抱怨通用对象类型?

如何在TypeScript对象迭代中根据键推断值类型?

使某些类型的字段变为可选字段'

将props传递给子组件并有条件地呈现它''

webpack错误:模块找不到.当使用我的其他库时,

如何在typescript中设置对象分配时的键类型和值类型

如何在Reaction路由v6.4加载器和操作中获得Auth0访问令牌?

了解类型规则中的关键字

已解决:如何使用值类型限制泛型键?

如何在react组件中使用doctype和html标签?

为什么我的动画状态在组件实例之间共享?

自定义 Select 组件的类型问题:使用带逗号的泛型类型<;T与不带逗号的<;T&>;时出错

有没有办法在Zod中使用跨字段验证来判断其他字段,然后呈现条件

tailwind css未被应用-react

为什么发送空字段?

创建一个TypeScrip对象并基于来自输入对象的约束定义其类型

如何使用TypeScrip在EXPO路由链路组件中使用动态路由?

React-跨组件共享功能的最佳实践

如何判断路由或其嵌套路由是否处于活动状态?