联合类型通用通知的类型保护(Notifier | Success)不起作用

Type guard for union type generic notification (Notifier | Success) does not work

我有以下结构:

type Result<TNotification, TSuccess> = Success<TNotification, TSuccess> | Notifier<TNotification, TSuccess>;

abstract class ResultAbstract<TNotification, TSuccess> {
protected constructor(protected readonly result: TNotification | TSuccess) {}

abstract isSuccess(): this is Success<TNotification, TSuccess>;

abstract isFailure(): this is Notifier<TNotification, TSuccess>;

abstract getData(): TNotification | TSuccess;
}

class Success<F, S> extends ResultAbstract<F, S> {
constructor(readonly data: S) {
  super(data);
}

isSuccess(): this is Success<F, S> {
  return true;
}

isFailure(): this is Notifier<F, S> {
  return false;
}

getData(): S {
  return this.result as S;
}

}

class Notifier<E, _> extends ResultAbstract<E, _> {
constructor(readonly data: E) {
  super(data);
}

isSuccess(): this is Success<E, _> {
  return false;
}

isFailure(): this is Notifier<E, _> {
  return true;
}

getData(): E {
  return this.result as E;
}
}

class Fail {}

class User {
  constructor(private name: string){}
}


我基本上是在尝试构建一个通知模式来处理我的代码流中的异常。我有以下功能:

const findUser = (): Result<Fail, User> => {
    if(Math.random() * 0.5 < 0.2)
        return new Success(new User('Tony stark'))
    
    return new Notifier(new Error('Usuário não existe'))
}


const userExists = () => {
    const result = findUser()

    if(result.isFailure()) return 'Not found'

    const user = result.getData() // Property 'getData' does not exist on type 'never'.(2339)


    return user.name
}

console.log(userExists())

Result 类型是 Notifier (For errors) 或 Success 的联合体,但如果我在联合体上调用其中一个类型守卫,它不会将余数(“else”语句)缩小到另一半工会的。

如果我显式调用第二个类型保护,它会按预期工作:

const userExists = () => {
    const result = findUser()

    if(!result.isSuccess()) return 'Not found'

    const user = result.getData() 

    return user.name
}

console.log(userExists()) // it works!

The sample code is here in this playground

谁能帮我理解一下?

这里问题的核心是SuccessNotifier在结构上都是一样的,所以TypeScript认为如果后面的if语句有机会returning true 对于其中一个 类,它 return 对两者都是正确的:

if(result.isFailure())

因此,此块之后的 result 值类型被自动推断为 never。 我创建了一个示例来说明这一点 here.

通过在两个 类 之一中引入唯一 field/method 或添加私有 field/method.

可以轻松解决此问题

以上说明将解决您遇到的问题,并可用于描述 if(result.isFailure()) 语句的行为。但是,另一方面,if(result.isSuccess()) 似乎仍然适用于您的原始代码。这样做的原因是因为您按照嵌套泛型类型分配给扩展 类:

的顺序引入了动态性

extends ResultAbstract<F, S> 对比 extends ResultAbstract<E, _>

鉴于上述情况,类 可以被认为是不同的,但是 TypeScript 仍然认为 SuccessNotifieris 谓词相同告诉它 Success 可以是 Notifier(通过 isFailure 方法)并且 Notifier 可以是 Success(通过 isSuccess 方法)。因此,您需要做的就是删除 isSuccessisFailure 方法,代码将开始工作,因为它会告诉 TypeScript 它只能是一个。 因为你的代码只使用了两个类,这将是最合适的解决方案。