我一直在研究一个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>{});
}
}
我附上一张我的代码的照片,用漂亮的语法高亮显示,方便大家查看:)
用户自定义类型保护函数 至少目前是严格由用户定义的。 它们不会被编译器自动推断出来。 如果你想要一个 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
你知道的 x
是 string
. 但如果 badGuard(x)
返回 false
你 不要 知道 x
是 undefined
...... 但这是编译器的想法。
的确,在你的代码中,你并没有真正处理过滤器返回 false
(我猜后续的管道参数就是不开火?),所以你其实不用太担心这个问题。 不过,最好还是将代码重构为一个正确的类型防护,然后是做额外逻辑的非类型防护过滤器。
const e = o.pipe(filter(isNotUndefined), filter(x => x.length > 3)); // Observable<string>;
这在运行时应该是一样的结果, 但这里第一个过滤器正确地缩小了范围... Observable<string | undefined>
到 Observable<string>
而第二个过滤器则保持 Observable<string>
(以及 x
中的回调是一个 string
),并做额外的逻辑,对长度进行过滤。
而且这还有一个额外的好处,就是不需要类型注解,因为你并没有试图在任何地方传播类型保护签名。 所以这可能是我推荐的方法。
好的,希望能帮到你,祝你好运