首页 > 解决方案 > TypeScript 是否允许将类型从键数组映射到带有键的对象?

问题描述

我正在尝试为具有有趣 API 的库添加类型。为了简化问题,它是一个函数:

const payload = {
  returns: {
    users: ['user1', 'user2'],
    posts: ['post1', 'post2'],
  }
}

并将其变成具有这种形状的对象:

{
  users: {
    user1: any
    user2: any
  }
  posts: {
    post1: any
    post2: any
  }
}

我看到的主要问题是 TypeScript 将这些数组推断string[]为常量而不是常量。我通常会这样做as const,但我正在编写这些类型以将类型添加到 JS 库中——因此编写 JavaScript 的库用户无法使用as const.

编辑:为了澄清更多,我这样做的原因是因为即使对 JS 用户来说,向 JavaScript 包添加类型仍然有帮助,因为 Intellisense 由 TypeScript 提供支持,并且公司的很大一部分人使用 VS Code。

标签: typescripttypescript-generics

解决方案


TS 类型检查器使用某些启发式方法来推断值的类型。一般来说,用字符串数组字面量初始化的变量 likeconst arr = ["foo", "bar"]将被推断为 type Array<string>,因为开发人员经常会继续修改数组的内容。如果要推断 as 的类型arr,例如,Array<"foo" | "bar">开发人员会发现自己无法将任何字符串放入"foo""bar"放入数组中......这将非常烦人。

然而,有时可以说服类型检查器更狭隘地推断事物。执行此操作的最短且最不“古怪”的方法可能是使用const断言via as const。这里的问题是它需要创造价值的人来做断言。在您的情况下,这将是图书馆的用户,您不想给他们带来负担。这对你来说更糟,因为他们正在编写 JavaScript,而且目前还没有 JSDoc 版本的as const. 有关支持此功能的开放功能请求,请参阅microsoft/TypeScript#30445 。[更新这将在 TS4.5 中修复,请参阅microsoft/TypeScript#45464 ]


您希望您的库函数调用签名能够要求更窄的类型。本质上表现得好像调用者已经放在as const那里一样。遗憾的是,没有类似的简单方法可以编写这样的调用签名。请参阅microsoft/TypeScript#30680以获取开放功能请求(由我 fwiw 提交)以允许这样的事情。

现在,我们必须使用“古怪”的方式来做到这一点。如果您希望类型检查器看到类似的字符串"foo"而不是将其扩展为string,一种方法是让它与泛型类型参数匹配,而K extends string不是仅仅string. 即使编译器可能不会推断出 K任何有用的东西,它也会允许依赖的东西K保持狭窄。这很古怪,我知道。我在 microsoft/TypeScript#30680 中对此进行了介绍。

这是一个可能的签名:

declare function transform<
    T extends Record<keyof T, readonly K[]>,
    K extends string
>(obj: { returns: T }): { [K in keyof T]: { [P in T[K][number]]: any } };

它需要一个类型的参数{returns: T},其中T一些对象的属性是K数组,其中K extends string。同样,我们只是这样做,而不是string[]让类型检查器跟踪传递给它的字符串文字:

const transformed = transform({
    returns: {
        users: ['user1', 'user2'],
        posts: ['post1', 'post2'],
    }
})
/* const transformed: {
    users: {
        user1: any;
        user2: any;
    };
    posts: {
        post1: any;
        post2: any;
    };
} */

万岁!


当然,如果调用者在将有效负载传递给transform(). 如果函数输入的类型已经扩大到它忘记了字符串文字的地方,那么类型检查器从函数输入推断字符串文字的任何提示或其他要求都将毫无用处。此时,您需要要求用户使用as const或类似的东西(对于 JS 用户,您可以创建一个asConstish()类似于foo()microsoft/TypeScript#30680 中的函数的标识函数)。或者只是告诉用户不要先将其保存到变量中。

Playground 代码链接


推荐阅读