简体   繁体   English

已翻译的条件类型和存在类型 Typescript Object

[英]Conditional and Existential Types on Translated Typescript Object

I hope this makes sense.我希望这是有道理的。

I'm trying to write a function that takes an object where each key equals a function and returns a function who's rest argument(s) are a translation of that object to [a key of the object, and the params of that key's function] I'm trying to write a function that takes an object where each key equals a function and returns a function who's rest argument(s) are a translation of that object to [a key of the object, and the params of that key's function]

My Code:我的代码:

export const useConfig =
  <T extends Record<string, (...a: any) => null>>(features: T) =>
  <O extends T, K extends keyof O>(...args: [K, ...Parameters<O[K]>][]): void =>
    void 0;

const config = useConfig({
  attr: (prop: 'id', val: string) => null,
  style: (prop: 'font', val: 'arial') => null,
});

The Problem:问题:

This const t2 = config(['attr', 'font','']);这个const t2 = config(['attr', 'font','']); shouldn't work, it should:不应该工作,它应该:

  1. complain that "font" is wrong.抱怨“字体”不对。
  2. After having defined "attr" as the first param, the second param should be typed against "prop:'id'" and the third against "val:string".将“attr”定义为第一个参数后,第二个参数应针对“prop:'id'”输入,第三个参数应针对“val:string”输入。

R&D研发

  1. I've been trying my best to figure out if this is an Existential Type issue (which I believe it is) but I can't make the connection我一直在尽力弄清楚这是否是存在类型问题(我相信是),但我无法建立联系
  2. I know this is an issue of Conditional typing because if I create multiple generics for the returned function with a bunch of optional param, each assigned to each generic, it works.我知道这是条件类型的问题,因为如果我为返回的 function 创建多个 generics 并带有一堆可选参数,每个参数都分配给每个泛型,它就可以工作。 However, every time I try to figure out how to do this "dynamically" I seem to land on "Existential Types" and I go round and round.然而,每次我试图弄清楚如何“动态地”做到这一点时,我似乎都落在了“存在类型”上,我 go 绕了又绕。

Any help would be much appreciated.任何帮助将非常感激。 I feel like I'm just not getting something fundamental about the type system.我觉得我只是没有得到关于类型系统的一些基本知识。

In what follows I'll call the operation in question, turning a key K (which extends keyof T for some suitable T whose values are all function types) into the tuple [K, ...Parameters<T[K]>] , "parameterizing" K , or a "parameterization" of K .在下文中,我将调用有问题的操作,将键K (它extends keyof T为一些合适的T ,其值都是 function 类型)进入元组[K, ...Parameters<T[K]>] , “参数化” K ,或K的“参数化”。

Conceptually you want the return type of useConfig to be a function which accepts a variadic number of arguments, where each argument is a parameterization of some key in keyof T .从概念上讲,您希望 useConfig 的返回类型为useConfig ,它接受可变数字 arguments,其中每个参数都是keyof T某个键的参数化。 You don't really want to know which argument is a parameterization of which key, just that each one corresponds to some key.您真的不想知道哪个参数是哪个键的参数化,只是每个参数对应于某个键。 This use of "some" is indeed a hint that the kind of generic quantification you'd need here is existential instead of the "normal" universal quantification.这种“一些”的使用确实暗示你在这里需要的那种通用量化是存在的而不是“正常”的通用量化。 You can think of normal, universal generics as intersections over every acceptable type, while existential generics are unions over every acceptable type.您可以将普通的通用 generics 视为每个可接受类型的交集,而存在的generics是每个可接受类型的并集。

And here, since "every acceptable type" is just the single members of keyof T , then you can represent this union directly.在这里,由于“每个可接受的类型”只是keyof T的单个成员,因此您可以直接表示这个联合。 All you want to do is distribute the parameterization operation over the union in K to make a new union.您要做的就是将参数化操作分布K中的并集上以创建一个新的并集。

If you want to distribute an operation over keylike types, you can use a distributive object type (as coined in microsoft/TypeScript#47109 ) where you make a mapped type and then immediately index into it .如果你想在 keylike 类型上分发一个操作,你可以使用一个分发的 object 类型(在microsoft/TypeScript#47109中创造),你可以在其中创建一个映射类型,然后立即对其进行索引 If you have a key set KS and you want to distribute the operation F<K> over it, you can write that like {[K in KS]: F<K>}[KS] .如果你有一个键集KS并且你想在它上面分配操作F<K> ,你可以这样写{[K in KS]: F<K>}[KS] In your case KS is keyof T and F<K> is [K, ...Parameters<T[K]>] .在您的情况下, KSkeyof TF<K>[K, ...Parameters<T[K]>] So you get this:所以你得到这个:

const useConfig =
<T extends Record<string, (...a: any) => null>>(features: T) =>
  (...args: { [K in keyof T]-?: [K, ...Parameters<T[K]>] }[keyof T][]): void =>
    void 0;

Let's see what happens when we call it:让我们看看当我们调用它时会发生什么:

const config = useConfig({
  attr: (prop: 'id', val: string) => null,
  style: (prop: 'font', val: 'arial') => null,
});

/* const config: (...args: (
  ["attr", "id", string] | ["style", "font", "arial"]
)[]) => void */

So this is exactly what you want.所以这正是你想要的。 Each argument passed into config() should be a tuple of type ["attr", "id", string] or one of type ["style", "font", "arial"] .传递给config()的每个参数应该是["attr", "id", string]类型或["style", "font", "arial"]类型之一的元组。 And you'll get the type checking you care about:你会得到你关心的类型检查:

const t = config(
  ["attr", "id", "abc"], // okay
  ["style", 'font', "arial"], // okay
  ["attr", "font", ""] // error!
  //~~~~~~~~~~~~~~~~~~
);

Unfortunately that doesn't give you a great experience with IntelliSense and autocompletion.遗憾的是,这并不能为您提供出色的 IntelliSense 和自动完成体验。 This isn't really a problem with the above solution, but a limitation or missing feature of TypeScript, which is requested at microsoft/TypeScript#38603 .这并不是上述解决方案的真正问题,而是 TypeScript 的限制或缺失功能,这是在microsoft/TypeScript#38603中请求的。

We can work around this by having the compiler actually try to figure out which argument is a parameterization of which key.我们可以通过让编译器实际尝试找出哪个参数是哪个键的参数化来解决这个问题。 That means if we call这意味着如果我们打电话

const t = config(
  ["attr", "id", "abc"], 
  ["style", 'font', "arial"], 
  ["attr", "font", "abc"]
);

the compiler needs to know "the first key is "attr" , the second one is "style" , and third one is "attr" . If you think of this as a tuple of keys KS , then here KS is ["attr", "style", "attr"] . And we need to represent the operation of turning the KS tuple into a tuple of parameterizations of each key in the tuple. That is, we want to map the parameterization operation over the input tuple to get an output tuple.编译器需要知道“第一个键是"attr" ,第二个是"style" ,第三个是"attr" 。如果你认为这是键KS的元组,那么这里KS["attr", "style", "attr"] 。而我们需要表示将KS元组转化为元组中每个键的参数化元组的操作。即我们要map对输入元组进行参数化操作得到一个 output 元组。

That version looks like this:版本看起来像这样:

const useConfig =
<T extends Record<string, (...a: any) => null>>(features: T) =>
  <KS extends Array<keyof T>>(...args: {
    [I in keyof KS]: [KS[I], ...Parameters<T[Extract<KS[I], keyof T>]>]
  }): void =>
    void 0;

It's a bit more involved;这有点复杂; the compiler does map tuples to tuples with {[I in keyof KS]: F<I>} acting only on the numeric-like indices I , but it doesn't quite know that it's doing this inside the body of the mapped type, so it will balk if you treat KS[I] as if it's keyof T , (because "wHaT iF I iS somE arrAy meTHod nAme LIke "push" ? see ms/TS#27995 ). We need to use the Extract<T, U> utility type to convince the compiler that KS[I] can be treated as if it's assignable to keyof T .编译器将 map 元组转换为元组,其中{[I in keyof KS]: F<I>}仅作用于类似数字的索引I ,但它不太清楚它是在映射类型的主体内执行此操作,因此,如果您将KS[I]视为keyof T ,它会犹豫不决(因为“如果I是某种类似于"push"的方法名称?请参阅ms/TS#27995 )。我们需要使用Extract<T, U>实用程序类型以说服编译器KS[I]可以被视为可分配给keyof T

Now when we call config() :现在当我们调用config()时:

const t = config(
  ["attr", "id", "abc"], // okay
  ["style", 'font', "arial"], // okay
  ["attr", "font", "abc"], error!
  // -----> ~~~~ <---- error here
);

/* const config: <["attr", "style", "attr"]>(
    args_0: ["attr", "id", string], 
    args_1: ["style", "font", "arial"], 
    args_2: ["attr", "id", string]) => void 
*/

You can see that the compiler infers ["attr", "style", "attr"] for KS , and then is unhappy specifically about the invalid "font" value in the args_2 argument.您可以看到编译器为KS推断["attr", "style", "attr"] ,然后特别不满args_2参数中的无效"font"值。

Playground link to code 游乐场代码链接

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

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