RxJS过滤函数不缩小类型,除非直接给定一个类型保护,否则它是唯一的参数。

我一直在研究一个auth服务,它使用一个rxjs行为主体来存储最后检索到的auth对象,如果它已经过期(或者根本没有被检索到),就会触发重新获取。

我的问题是关于TypeScript类型检查器。我已经写了tyeguard isNotUndefined 的断言–嗯,正是你所期望的。

export function isNotUndefined<T>(input: T | undefined): input is T {
  return input !== undefined;
}

我已经不得不写出了上面的typeguard,而不是能够依靠于 auth !== undefined. 我现在不明白,为什么在管子里… … authGetter$ 在下面的代码中,管子中的值的类型并没有减少到只有 Auth 后的第一个过滤器。相反,类型仍然是 Auth | undefined而它需要第二个过滤器,只用类型保护装置来将类型缩小到只有 Auth.

因此,总之,为什么我需要第二个过滤器将类型缩小到只有 Auth? 另外,由于我是在没有人审查的情况下自己编写的代码,我非常感谢任何人指出他们所认识到的 “代码气味”(以及关于如何代替的建议)。

export default class AuthService {
  private static lastAuth$ = new BehaviorSubject<Auth | undefined>(undefined);

  private static authGetter$ = AuthService.lastAuth$.pipe(
    filter(auth => {
      if (isNotUndefined(auth) && auth.expiry > new Date()) {
        return true ; // identical resulting type with "return isNotUndefined(auth);"
      } else {
        // retry if auth doesn't exist or is expired
        AuthService.authorise().then(newAuth =>
          AuthService.lastAuth$.next(newAuth)
        );
        return false;
      }
    }),
    tap(v => {}), // typechecker says "(parameter) v: Auth | undefined"
    filter(isNotUndefined),
    tap(v => {}) // typechecker says "(parameter) v: Auth"
  );

  static getAuth$(): Observable<Auth> {
    return this.authGetter$.pipe(first());
  }

  private static async authorise(): Promise<Auth> {
    // auth code goes here (irrelevant for this question)...
    // typecast dummy return to make typechecker happy
    return Promise.resolve(<Auth>{});
  }
}

我附上一张我的代码的照片,用漂亮的语法高亮显示,方便大家查看:)

my code in nice syntax highlighting

解决方案:

用户自定义类型保护函数 至少目前是严格由用户定义的。 它们不会被编译器自动推断出来。 如果你想要一个 boolean-返回函数要想作为类型守卫与类型谓词返回类型,你需要显式地将其注释为这样的类型守卫。

const doesNotPropagate = <T>(x: T | undefined) => isNotUndefined(x);
// const doesNotPropagate: <T>(x: T | undefined) => boolean

这个函数 doesNotPropagate() 势同水火 isNotUndefined() 在运行时,但编译器不再将其视为类型守卫,所以如果你将其用作过滤器,你将不会消除 undefined 的编译器中。

在GitHub上有多个关于这个问题的问题;目前开放的跟踪传播flowing类型守卫签名的问题是 微软TypeScript#16069 (或可能 microsoftTypeScript#10734). 但看起来这里并没有什么动作,所以目前我们只需要按照语言的方式来处理。


这里有一个玩具示例,可以用来探索处理这个问题的不同可能的方法,因为你问题中的示例代码并不构成一个 最低限度的重现性例子 适合丢进一个独立的IDE中。 你应该能够将这些适应你自己的代码。

假设我们有一个值 o 类型 Observable<string | undefined>. 那么这就可以了:

const a = o.pipe(filter(isNotUndefined)); // Observable<string>

但这并不是因为上面列出的原因… 类型保护签名不会传播。

const b = o.pipe(filter(x => isNotUndefined(x))); // Observable<string | undefined>

我们可以重新获得类型保护签名和行为 如果我们像这样手动注释箭头函数的话

const c = o.pipe(filter((x): x is string => isNotUndefined(x))); // Observable<string>;

如果你想的话,你可以在这里做额外的过滤逻辑。

const d = o.pipe(filter((x): x is string => isNotUndefined(x) && x.length > 3)); 
// Observable<string>;

这里的过滤器会检查字符串是否被定义 的长度大于3。

请注意,从技术上讲,这并不是一个行为良好的用户定义类型防护,因为他们倾向于将 false 结果意味着输入的范围缩小到了 排除 守护型。

function badGuard(x: string | undefined): x is string {
  return x !== undefined && x.length > 3;
}
const x = Math.random() < 0.5 ? "a" : undefined;
if (!badGuard(x)) {
  x; // narrowed to undefined, but could well be string here, oops
}

这里,如果 badGuard(x) 返回 true你知道的 xstring. 但如果 badGuard(x) 返回 false不要 知道 xundefined…… 但这是编译器的想法。

的确,在你的代码中,你并没有真正处理过滤器返回 false (我猜后续的管道参数就是不开火?),所以你其实不用太担心这个问题。 不过,最好还是将代码重构为一个正确的类型防护,然后是做额外逻辑的非类型防护过滤器。

const e = o.pipe(filter(isNotUndefined), filter(x => x.length > 3)); // Observable<string>;

这在运行时应该是一样的结果, 但这里第一个过滤器正确地缩小了范围… Observable<string | undefined>Observable<string>而第二个过滤器则保持 Observable<string> (以及 x 中的回调是一个 string),并做额外的逻辑,对长度进行过滤。

而且这还有一个额外的好处,就是不需要类型注解,因为你并没有试图在任何地方传播类型保护签名。 所以这可能是我推荐的方法。


好的,希望能帮到你,祝你好运

Stackblitz代码链接

给TA打赏
共{{data.count}}人
人已打赏
未分类

带有外键的数据库。描绘前向与后向关系的好方法?

2022-9-8 4:13:33

未分类

上下文经纪人忽略了价值,fiware orion和iotagent。

2022-9-8 4:24:26

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索