RxJS 过滤器函数不会缩小类型,除非直接给它一个类型保护,它是唯一的参数
RxJS filter function not narrowing type unless directly given a typeguard its the only parameter
我一直在开发一个 auth 服务,它使用 rxjs 行为主题来存储最后检索到的 auth 对象,并在它已过期(或根本没有被提取)时触发重新提取。
我的问题是关于 TypeScript 类型检查器的。我已经编写了断言的类型保护 isNotUndefined
- 好吧,正是你所期望的。
export function isNotUndefined<T>(input: T | undefined): input is T {
return input !== undefined;
}
我已经不得不编写上面的类型保护而不是能够依赖 auth !== undefined
。我现在无法理解为什么在下面代码的 authGetter$
中的管道中,管道中值的类型在第一个过滤器后没有减少到 Auth
.相反,类型仍然是 Auth | undefined
,它需要第二个过滤器和类型保护来将类型缩小到 Auth
.
总而言之,为什么我需要第二个过滤器来将类型缩小到 Auth
?此外,因为我是在没有人审查的情况下自己编写代码,所以我非常感谢任何人指出 'code smells' 他们认可(并建议如何做)。
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>{});
}
}
我附上了我的代码的照片,它以漂亮的语法突出显示,方便您查看:)
User-defined type guard functions 至少目前是严格由用户定义的。它们不是由编译器自动推断的。如果你想要一个 boolean
-returning 函数作为一个带有类型谓词 return 类型的类型保护,你需要这样显式地注释它:
const doesNotPropagate = <T>(x: T | undefined) => isNotUndefined(x);
// const doesNotPropagate: <T>(x: T | undefined) => boolean
函数 doesNotPropagate()
在运行时的行为与 isNotUndefined()
相同,但编译器不再将其视为类型保护,因此如果将其用作过滤器,则不会消除undefined
在编译器中。
GitHub 中有多个关于此的问题;当前未解决的问题跟踪 propagating/flowing 类型保护签名是 microsoft/TypeScript#16069 (or possibly microsoft/TypeScript#10734)。不过这里好像没什么动静,所以暂时我们只需要按原样处理语言即可。
这里有一个玩具示例,可以用来探索处理这个问题的不同可能方式,因为您问题中的示例代码不构成 minimal reproducible example 适合放入独立 IDE 。您应该能够使这些适应您自己的代码。
假设我们有一个类型为 Observable<string | undefined>
的值 o
。然后这有效:
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
结果视为输入范围缩小到 exclude 守护类型:
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)
returnstrue
,就知道x
就是string
。但是如果 badGuard(x)
returns false
, 你 不知道 知道 x
是 undefined
... 但是这就是编译器的想法。
确实在你的代码中你并没有真正处理过滤器 returns false
的情况(我猜后续的管道参数只是不触发?),所以你真的不必太担心这个。尽管如此,最好将代码重构为一个正确的类型保护,然后是执行额外逻辑的非类型保护过滤器:
const e = o.pipe(filter(isNotUndefined), filter(x => x.length > 3)); // Observable<string>;
这在运行时应该得到相同的结果,但这里第一个过滤器正确地从 Observable<string | undefined>
缩小到 Observable<string>
,第二个过滤器保持 Observable<string>
(并且 x
在回调中是一个 string
) 并执行过滤长度的额外逻辑。
这还有一个额外的好处,即不需要类型注释,因为您不会试图在任何地方传播类型保护签名。所以这可能是我推荐的方法。
好的,希望对您有所帮助;祝你好运!
我一直在开发一个 auth 服务,它使用 rxjs 行为主题来存储最后检索到的 auth 对象,并在它已过期(或根本没有被提取)时触发重新提取。
我的问题是关于 TypeScript 类型检查器的。我已经编写了断言的类型保护 isNotUndefined
- 好吧,正是你所期望的。
export function isNotUndefined<T>(input: T | undefined): input is T {
return input !== undefined;
}
我已经不得不编写上面的类型保护而不是能够依赖 auth !== undefined
。我现在无法理解为什么在下面代码的 authGetter$
中的管道中,管道中值的类型在第一个过滤器后没有减少到 Auth
.相反,类型仍然是 Auth | undefined
,它需要第二个过滤器和类型保护来将类型缩小到 Auth
.
总而言之,为什么我需要第二个过滤器来将类型缩小到 Auth
?此外,因为我是在没有人审查的情况下自己编写代码,所以我非常感谢任何人指出 'code smells' 他们认可(并建议如何做)。
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>{});
}
}
我附上了我的代码的照片,它以漂亮的语法突出显示,方便您查看:)
User-defined type guard functions 至少目前是严格由用户定义的。它们不是由编译器自动推断的。如果你想要一个 boolean
-returning 函数作为一个带有类型谓词 return 类型的类型保护,你需要这样显式地注释它:
const doesNotPropagate = <T>(x: T | undefined) => isNotUndefined(x);
// const doesNotPropagate: <T>(x: T | undefined) => boolean
函数 doesNotPropagate()
在运行时的行为与 isNotUndefined()
相同,但编译器不再将其视为类型保护,因此如果将其用作过滤器,则不会消除undefined
在编译器中。
GitHub 中有多个关于此的问题;当前未解决的问题跟踪 propagating/flowing 类型保护签名是 microsoft/TypeScript#16069 (or possibly microsoft/TypeScript#10734)。不过这里好像没什么动静,所以暂时我们只需要按原样处理语言即可。
这里有一个玩具示例,可以用来探索处理这个问题的不同可能方式,因为您问题中的示例代码不构成 minimal reproducible example 适合放入独立 IDE 。您应该能够使这些适应您自己的代码。
假设我们有一个类型为 Observable<string | undefined>
的值 o
。然后这有效:
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
结果视为输入范围缩小到 exclude 守护类型:
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)
returnstrue
,就知道x
就是string
。但是如果 badGuard(x)
returns false
, 你 不知道 知道 x
是 undefined
... 但是这就是编译器的想法。
确实在你的代码中你并没有真正处理过滤器 returns false
的情况(我猜后续的管道参数只是不触发?),所以你真的不必太担心这个。尽管如此,最好将代码重构为一个正确的类型保护,然后是执行额外逻辑的非类型保护过滤器:
const e = o.pipe(filter(isNotUndefined), filter(x => x.length > 3)); // Observable<string>;
这在运行时应该得到相同的结果,但这里第一个过滤器正确地从 Observable<string | undefined>
缩小到 Observable<string>
,第二个过滤器保持 Observable<string>
(并且 x
在回调中是一个 string
) 并执行过滤长度的额外逻辑。
这还有一个额外的好处,即不需要类型注释,因为您不会试图在任何地方传播类型保护签名。所以这可能是我推荐的方法。
好的,希望对您有所帮助;祝你好运!