Intersection of mapped types

人走茶凉 提交于 2019-12-09 18:58:05

问题


Consider the following:

type Properties = {
    foo: { n: number };
    bar: { s: string };
    baz: { b: boolean };
};

declare function retrieveValues<K extends keyof Properties>(add?: K[]): Pick<Properties, K>[K];

// what happens
const x: { n: number } | { s: string } = retrieveValues(['foo', 'bar']);

// what I'm really trying to express (type error)
const y: { n: number } & { s: string } = retrieveValues(['foo', 'bar']);

Is there a way to get an intersection of the properties of Pick<Properties, K>? Or just a different way to get the intersection of a set of types based on the presence of relevant strings in an array?


回答1:


There's no straightforward type operator which, say, turns a union into an intersection, or allows you to iterate union types and do stuff programmatically with the pieces. So on the face of it you're stuck.

Backing up, if you allow yourself to build Properties from pieces instead of trying to break the pieces apart, you can do this:

type InnerProperties = {
  n: number;
  s: string;
  b: boolean;
}

type OuterProperties = {
  foo: "n";
  bar: "s";
  baz: "b";
}

You can see how each key in OuterProperties is a mapping to a key in InnerProperties. (Note that in your Properties, each outer property had a single inner property. You aren't restricted to that, though. If you wanted, say, the "foo" outer key to correspond to something with multiple inner properties like {n: number, r: RegExp} then you would add r: RegExp to InnerProperties and put foo: "n"|"r" in OuterProperties.)

Now you can pick out partial properties like this:

type PickProps<P extends keyof OuterProperties = keyof OuterProperties> = {
  [K in OuterProperties[P]]: InnerProperties[K];
}

So PickProps<"foo"> is {n: number}, and PickProps<"bar"> is {s: string}, and PickProps<"baz"> is {b: boolean}. And notice that PickProps<"foo"|"bar"> is {n: number; s: string}, so we have the output type of retrieveValues() ready. We still have to define Properties in terms of InnerProperties and OuterProperties, like this:

type Properties = {
  [K in keyof OuterProperties]: PickProps<K>
}

And finally you can declare that function the way you want it:

declare function retrieveValues<K extends keyof Properties>(add?: K[]): PickProps<K>;
const y: { n: number } & { s: string } = retrieveValues(['foo', 'bar']);

So that works. Hope that's helpful. Good luck!




回答2:


It's currently only available in the nightly typescript@next builds but in Typescript 2.8 conditional types and type inference in conditional types will allow transforming { n: number } | { s: string } directly into { n: number } & { s: string }.

type GetKeys<U> = U extends Record<infer K, any> ? K : never

type UnionToIntersection<U extends object> = {
   [K in GetKeys<U>]: U extends Record<K, infer T> ? T : never
}

type Transformed = UnionToIntersection<{ a: string } | { b: number }>
// Transformed has type {a: string, b: number} in Typescript 2.8

The reason this works is basically because conditional types are distributed over union types. From the conditional types pull request:

Conditional types in which the checked type is a naked type parameter are called distributive conditional types. Distributive conditional types are automatically distributed over union types during instantiation. For example, an instantiation of T extends U ? X : Y with the type argument A | B | C for T is resolved as (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y).

According to the roadmap and the timing of previous releases Typescript 2.8 should be released around the end of March 2018.



来源:https://stackoverflow.com/questions/47464913/intersection-of-mapped-types

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!