【问题标题】:Type 'Session' is not assignable to type 'Record<string, unknown>'类型 'Session' 不可分配给类型 'Record<string, unknown>'
【发布时间】:2021-04-21 09:06:15
【问题描述】:

我正在尝试实现https://next-auth.js.org/configuration/callbacks#session-callback 的回调。 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;
}

我做错了什么? next-auth 的 repo 托管在 https://github.com/nextauthjs/next-auth 和类型 https://github.com/DefinitelyTyped/DefinitelyTyped/blob/7c785c26527720bf726f8b8bcbab2f96c600d1a4/types/next-auth/index.d.ts

【问题讨论】:

  • 这里的问题是Session 是一个没有索引签名的接口,并且根据microsoft/TypeScript#15300,接口不会获得“隐式索引签名”。因此,您不能只使用Session 编译器期望Session &amp; Record&lt;string, unknown&gt;,因为前者没有索引签名而后者有一个。您可以使用type assertion 使其工作,例如this。这对你有用吗?
  • 如果这能解决您的问题,我会写一个答案;如果没有,你能详细说明我缺少什么吗?
  • 哇......就像一个魅力。还有一个问题。什么是隐式索引签名?
  • 当我有机会写下答案时,我会讨论这个问题,但基本上编译器有时会允许您通过“隐式”添加将没有索引签名的类型分配给有签名的类型前一种类型的索引签名。

标签: typescript


【解决方案1】:

当你写作时

Promise.resolve<WithAdditionalParams<Session>(session); // error!

您正在尝试在需要WithAdditionalParams&lt;Session&gt; 类型值的位置使用Session 类型的值。并且因为编译器没有看到Session 可以分配给WithAdditionalParams&lt;Session&gt;,所以出现了错误。如果你试着写

Promise.resolve<number>("someString"); // error! string is not a number

你会遇到类似的错误。大概你不会想到在需要number 的地方使用string。所以问题是:为什么Session 不能分配给WithAdditionalParams&lt;Session&gt;


好吧,WithAdditionalParams&lt;Session&gt; 类型是Session子类型,其中包含一个string index signature,其属性的类型为unknown。 (这就是Record&lt;string, unknown&gt; 的意思。)由于Session 没有索引签名,编译器不认为WithAdditionalParams&lt;Session&gt; 可以分配给Session

但是,您可能会认为,session 的任何随机额外属性都可以分配给unknown 类型...所有内容都可以分配给unknown。那么为什么编译器不把Session 当作它有一个字符串索引签名呢?事实上,这种事情确实有时会发生,通过implicit index signatures"。观察:

type SessionType = {
  user: WithAdditionalParams<User>;
  accessToken?: string;
  expires: string;
}
declare const session: SessionType;
Promise.resolve<WithAdditionalParams<Session>>(session); // no error

这里的值session 属于SessionType 类型,type alias 的对象类型与Session 具有相同的结构。并且编译器非常高兴使用SessionType,其中WithAdditionalParams&lt;Session&gt; 是预期的......它给SessionType 一个隐式索引签名,一切都会成功。

(因此,解决此问题的一种可能方法是使用SessionType 而不是Session。)


所以现在问题是,为什么SessioninterfaceSessionType, an identically-structured typealias, *does*? Surely, you might again think, the compiler does not simply deny implicit index signatures tointerface`类型时获得隐式索引签名?令人惊讶的是,这正是发生的事情

microsoft/TypeScript#15300,特别是this comment

只是为了填补人们的空白,此行为目前是设计使然。因为接口可以通过额外的声明来扩充,但类型别名不能,所以推断类型别名的隐式索引签名比接口的隐式索引签名“更安全”(在那个上加上重引号)。但如果这似乎有意义的话,我们也会考虑为接口做这件事

然后就可以了。您不能使用Session 代替WithAdditionalParams&lt;Session&gt;,因为可能有人可能会在以后的某个日期与索引签名冲突的merge 属性。这是否是一个令人信服的理由还有待于相当激烈的争论,您可以通过阅读 microsoft/TypeScript#15300 看到这一点。


那么,我们该如何进行呢?如果您不想将 Sessioninterface 更改为 type 别名,则始终可以使用告诉编译器“我不在乎您是否认为值 x 不是 @ 类型的工具987654370@,我告诉你是!”也就是说,使用type assertion

Promise.resolve(session as WithAdditionalParams<Session>); // no error

这是因为编译器将session 的类型视为与WithAdditionalParams&lt;Session&gt;“相关”,因此当您断言它实际上该类型时,编译器会接受您的话并且继续前进。请注意,一旦您给session 提供了WithAdditionalParams&lt;Session&gt; 类型,您就不必在调用Promise.resolve() 时手动指定类型参数;编译器会自动推断。

请记住,当您使用类型断言时,您有责任自行验证其准确性...因为编译器无法为您验证它,所以它无法验证您是否已完成断言正确。如果事实证明您的断言不正确,那么您对编译器撒了谎,因此在运行时发生的任何不愉快的事情都是您的错,而不是编译器的错。因此,请注意仅在您相对确定这样做是安全的情况下才使用断言。

Playground link to code

【讨论】:

  • 很棒的解释。非常感谢。
猜你喜欢
  • 2023-01-18
  • 2022-06-11
  • 2022-10-15
  • 2021-09-26
  • 2019-11-15
  • 1970-01-01
  • 2020-07-28
  • 2021-10-28
  • 2020-07-12
相关资源
最近更新 更多