简体   繁体   English

TypeScript 是否允许从键数组到 map 键入到带有键的 object?

[英]Does TypeScript allow one to map typings from an array of keys to an object with keys?

I'm trying to add types for a library with an interesting API.我正在尝试为具有有趣 API 的库添加类型。 To simplify the problem, it's a function that takes this:为了简化问题,它是一个 function 采取这个:

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

And turns it into an object with this shape:并将其变成具有这种形状的 object:

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

The main problem I'm seeing is that TypeScript infers those arrays as string[] rather than as constants.我看到的主要问题是 TypeScript 将那些 arrays 推断为string[]而不是常量。 I would normally do as const , but I'm writing these typings to add types to a JS library—so users of the library, who are writing JavaScript, can't use as const .我通常会使用as const ,但我正在编写这些类型以将类型添加到 JS 库中——因此编写 JavaScript 的库用户不能使用as const

Edit: To clarify some more, the reason I'm doing this is because adding types to JavaScript packages is still helpful even to JS users, because Intellisense is powered by TypeScript, and a large portion of the company uses VS Code.编辑:为了澄清更多,我这样做的原因是因为向 JavaScript 包添加类型即使对 JS 用户仍然有帮助,因为 Intellisense 由 TypeScript 提供支持,并且公司的很大一部分人使用 VS Code。

The TS type checker uses certain heuristics to infer types for values. TS 类型检查器使用某些启发式方法来推断值的类型。 Generally speaking a variable initialized with array literal of strings like const arr = ["foo", "bar"] will be inferred to be of type Array<string> , because oftentimes developers will go on to modify the contents of arrays.一般来说,使用const arr = ["foo", "bar"]等字符串的数组字面量初始化的变量将被推断为Array<string>类型,因为开发人员通常会在 go 上修改 arrays 的内容。 If it were to infer the type of arr as, say, Array<"foo" | "bar">如果要将arr的类型推断为Array<"foo" | "bar"> Array<"foo" | "bar"> , developers would find themselves unable to put any strings other than "foo" or "bar" into the array... which would be pretty annoying. Array<"foo" | "bar"> ,开发人员会发现自己无法将除"foo""bar"之外的任何字符串放入数组中......这会很烦人。

There are times, however, where the type checker can be persuaded to infer things more narrowly.然而,有时可以说服类型检查器更狭隘地推断事物。 The shortest and least "wacky" way of doing this is probably to use a const assertion via as const .执行此操作的最短且最不“古怪”的方法可能是通过as const使用const断言 The problem here is that it requires the person creating the value to do the assertion.这里的问题是它需要创造价值的人来做断言。 In your case this would be users of the library, and you don't want to burden them with that.在您的情况下,这将是图书馆的用户,您不想给他们带来负担。 It's even worse for you because they are writing JavaScript, and there isn't currently a JSDoc version of as const .这对你来说更糟,因为他们正在编写 JavaScript,并且目前没有as const的 JSDoc 版本。 See microsoft/TypeScript#30445 for an open feature request to support this.有关支持此功能的开放功能请求,请参阅microsoft/TypeScript#30445

You'd prefer your library function call signature to be able to ask for a narrower type.您希望您的库 function调用签名能够要求更窄的类型。 Essentially to behave as if the caller had put as const in there.本质上表现得好像调用者在那里放as const Sadly there is no analogously simple way of writing such a call signature.遗憾的是,没有类似的简单方法可以编写这样的调用签名。 See microsoft/TypeScript#30680 for an open feature request (filed by me fwiw) to allow something like this.请参阅microsoft/TypeScript#30680以获取开放功能请求(由我 fwiw 提交)以允许这样的事情。

For now, we have to use the "wacky" ways of doing this.现在,我们必须使用“古怪”的方式来做到这一点。 If you want the type checker to see a string like "foo" and not widen it to string , one way to do it is to have it match up with a generic type parameter like K extends string instead of just string .如果您希望类型检查器看到像"foo"这样的字符串而不是将其扩展为string ,一种方法是让它与像K extends string这样的泛型类型参数匹配,而不仅仅是string Even though the compiler might not infer K to be anything useful, it will allow things that depend on K to stay narrow.即使编译器可能不会推断出K有什么用处,它也会允许依赖K的东西保持狭窄。 It's wacky, I know.这很古怪,我知道。 I go into this in microsoft/TypeScript#30680.我将 go 放入 microsoft/TypeScript#30680 中。

Here's one possible signature:这是一个可能的签名:

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 } };

It takes a param of type {returns: T} , where T is some object whose properties are K arrays, where K extends string .它需要一个类型为{returns: T}的参数,其中T是一些 object,其属性为K arrays,其中K extends string Again, we're just doing that instead of string[] to cause the type checker to keep track of string literals passed to it:同样,我们只是这样做而不是string[]使类型检查器跟踪传递给它的字符串文字:

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

Hooray!万岁!


Of course this will fail if the caller assigns the payload to a new variable before passing it into transform() .当然,如果调用者在将有效负载传递给transform()之前将其分配给新变量,这将失败。 Any hints or other demands that the type checker infer string literals from the function input will be useless if the function input's type has already been widened to something where it has forgotten the string literals.如果 function 输入的类型已经扩大到它忘记了字符串文字的地方,则类型检查器从 function 输入推断字符串文字的任何提示或其他要求都将毫无用处。 At that point you need to ask users to use as const or something similar (for JS users you can make an identity asConstish() function that looks like the foo() function from microsoft/TypeScript#30680).此时,您需要要求用户使用as const或类似的东西(对于 JS 用户,您可以创建一个身份asConstish() function,它看起来像来自 microsoft/TypeScript#30680 的foo() function)。 Or just tell users not to save it to a variable first.或者只是告诉用户不要先将其保存到变量中。

Playground link to code Playground 代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM