我正在尝试实现https://next-auth.js.org/configuration/callbacks#session-回调。sesssion
回调定义如下:
export interface CallbacksOptions {
session?:
| ((session: Session) => WithAdditionalParams<Session>)
| ((session: Session, userOrToken: User | JWT) => Promise<WithAdditionalParams<Session>>);
}
session
回调的实现:
async session(session: Session, token: JWT) {
if (token?.accessToken) {
session.user.accessToken = token.accessToken;
session.accessToken = token.accessToken as string;
}
if (token?.provider) {
session.user.provider = token.provider;
}
if (token?.accountId) {
session.user.accountId = token.accountId;
}
return Promise.resolve<WithAdditionalParams<Session>>(session);
}
编译器抱怨:
TS2345: Argument of type 'Session' is not assignable to parameter of type 'WithAdditionalParams<Session> | PromiseLike<WithAdditionalParams<Session>>'.
Type 'Session' is not assignable to type 'WithAdditionalParams<Session>'.
Type 'Session' is not assignable to type 'Record<string, unknown>'.
Index signature is missing in type 'Session'.
session
定义如下:
export interface Session {
user: WithAdditionalParams<User>;
accessToken?: string;
expires: string;
}
export type WithAdditionalParams<T extends Record<string, any>> = T & Record<string, unknown>;
export interface User {
name?: string | null;
email?: string | null;
image?: string | null;
}
我做错了什么?下一个身份验证的repo托管在https://github.com/nextauthjs/next-auth和类型https://github.com/DefinitelyTyped/DefinitelyTyped/blob/7c785c26527720bf726f8b8bcbab2f96c600d1a4/types/next-auth/index.d.ts
当您编写时
Promise.resolve<WithAdditionalParams<Session>(session); // error!
您正试图在需要WithAdditionalParams<Session>
类型的值的位置使用Session
类型的值。由于编译器不认为Session
可分配给WithAdditionalParams<Session>
,因此存在错误。如果你试图写
Promise.resolve<number>("someString"); // error! string is not a number
你也会有类似的错误。假设您不会考虑在需要number
的地方使用string
。所以问题是:为什么Session
不能分配给WithAdditionalParams<Session>
?
类型WithAdditionalParams<Session>
是Session
的子类型,包括属性为类型unknown
的string
索引签名。(这就是Record<string, unknown>
的意思。(由于Session
没有索引签名,编译器不认为WithAdditionalParams<Session>
可分配给Session
。
但是,你可能会认为,session
的任何随机额外属性都可以分配给unknown
类型所有都可分配给unknown
。那么,为什么编译器不把Session
当作它有一个字符串索引签名来处理呢?事实上,这种事情确实有时会发生,通过隐式索引签名";。观察:
type SessionType = {
user: WithAdditionalParams<User>;
accessToken?: string;
expires: string;
}
declare const session: SessionType;
Promise.resolve<WithAdditionalParams<Session>>(session); // no error
这里,值session
是类型SessionType
,即具有与Session
相同结构的对象类型的类型别名。编译器非常满意地使用了SessionType
,而WithAdditionalParams<Session>
是预期的。。。它给CCD_ 29一个隐式索引签名,一切都成功了。
(因此,处理此问题的一种可能方法是使用SessionType
而不是Session
。(
所以现在的问题是,为什么Session
,一个interface
,没有得到隐式索引签名,而SessionType
,一个结构相同的type
别名,却得到了?当然,您可能会再次认为,编译器并不是简单地拒绝interface
类型的隐式索引签名吗?令人惊讶的是,这正是发生的事情。
请参阅microsoft/TypeScript#15300,特别是以下注释:
只是为了填充人员,这种行为目前是故意的。因为接口可以通过额外的声明来扩充,但类型别名不能,所以它是";"更安全";(上面用大引号(来推断类型别名的隐式索引签名,而不是接口。但如果有意义的话,我们也会考虑为接口做这件事
就这样。不能使用Session
代替WithAdditionalParams<Session>
,因为可能有人会在以后合并与索引签名冲突的属性。这是否是一个令人信服的原因,还有相当激烈的争论,正如你在阅读microsoft/TypeScript#15300时所看到的那样。
那么,我们该如何继续?如果您不想将Session
从interface
更改为type
别名,您可以始终使用告诉编译器";我不在乎你是否认为值x
不是Y
类型,我告诉你它是"也就是说,使用类型断言:
Promise.resolve(session as WithAdditionalParams<Session>); // no error
这是因为编译器将CCD_ 44的类型视为"0";相关的";到WithAdditionalParams<Session>
,所以当你断言它实际上是那个类型时,编译器会接受你的说法并继续前进。注意,一旦你给session
类型WithAdditionalParams<Session>
,你就不必在调用Promise.resolve()
时手动指定类型参数;编译器会自动推断。
请记住,当您使用类型断言时,您有责任自行验证其准确性。。。由于编译器无法为您验证它,因此它将无法验证您是否正确地完成了断言。如果你的断言不正确,那么你对编译器撒谎了,因此在运行时发生的任何不愉快的事情都是你的错,而不是编译器的错。因此,只有当你相对确信这样做是安全的时,才要小心使用断言
游乐场链接到代码