In Typescript, how to assign a member of a discriminated type union to a generic type which expects that union

huangapple go评论100阅读模式
英文:

In Typescript, how to assign a member of a discriminated type union to a generic type which expects that union

问题

在TypeScript中,您可以使用类型断言来解决这个问题。要将FilterSet<FirstType>分配给FilterSet<Union>,您可以使用类型断言来明确告诉TypeScript编译器这是安全的。在您的InvokerComponent中,您可以这样更改代码:

  1. export const InvokerComponent = () => {
  2. const getFilterSet: FilterSet<FirstType> = () =>
  3. {targetField: "specificToFirst"}
  4. const filterSet: FilterSet<FirstType> = getFilterSet();
  5. return (
  6. <Component filterSet={filterSet as FilterSet<Union>} />
  7. )
  8. }

通过使用as关键字,您将filterSet的类型断言为FilterSet<Union>,这告诉TypeScript编译器这是安全的,因为FirstTypeSecondType都是Union的一部分。

这样应该解决了您遇到的类型错误问题。请注意,虽然这是一种解决方法,但要确保在使用类型断言时要谨慎,确保您了解代码的安全性。

英文:

I'm using React-Typescript. I have a component which takes props like so:

  1. type ComponentPropsType = {
  2. filterSet: FilterSet&lt;Union&gt;
  3. }
  4. type FilterSet&lt;T&gt; = {
  5. targetField: keyof T;
  6. }
  7. type Union = FirstType | SecondType;
  8. type FirstType = {
  9. name: string;
  10. id: number;
  11. specificToFirst: string;
  12. }
  13. type SecondType = {
  14. name: string;
  15. id: number;
  16. specificToSecond: string;
  17. }

Here is the relevant portion of Component:

  1. export const Component = ({ filterSet }: ComponentPropsType) =&gt; {
  2. ...
  3. }

When I attempt to invoke this component like so:

  1. export const InvokerComponent = () =&gt; {
  2. const getFilterSet: FilterSet&lt;FirstType&gt; = () =&gt;
  3. {targetField: &quot;specificToFirst&quot;}
  4. const filterSet: FilterSet&lt;FirstType&gt; = getFilterSet();
  5. return (
  6. &lt;Component filterSet={filterSet} /&gt; // TS ERROR HERE
  7. )
  8. }

Typescript tells me:

  1. Type FilterSet&lt;FirstType&gt; is not assignable to type FilterSet&lt;Union&gt;
  2. Type Union is not assignable to type FirstType
  3. Type SecondType is missing the following properties from type FirstType: specificToFirst

My goal is to allow my component to accept a generic filterSet because I would like to restrict a field of filterSet to keyof the type passed in as the template. That's not displayed in my example above, but I don't think it's important to the problem at hand.

Once we make it into Component, I don't really care whether FirstType or SecondType was passed in to generate the filterSet. I want to restrict filterSet to FilterSet&lt;Union&gt; because I want the Union types well defined. It's important to the definition of the filterSet, but not really the functionality of the component.

I hope I've given enough information to help solve this issue. To restate the question:

In Typescript, how do I assign a member of a discriminated type union to a generic type which expects that union.

答案1

得分: 1

问题是您的联合类型没有在FilterSet中进行分发,这就是为什么FilterSet&lt;Union会被评估为:

  1. {
  2. targetField: &quot;name&quot; | &quot;id&quot;;
  3. }

实际上您需要的是以下格式的内容:

  1. {
  2. targetField: &quot;name&quot;;
  3. } | {
  4. targetField: &quot;id&quot;;
  5. }

针对FirstTypeSecondType特定字段不起作用的原因是keyof操作符的已知问题(可能有一个GitHub问题,但我找不到)。

可以通过参考distributive conditional types docs来实现所需类型:

  1. type FilterSet&lt;T&gt; = T extends T
  2. ? {
  3. targetField: keyof T;
  4. }
  5. : never;

测试:

  1. // type Result = {
  2. // targetField: keyof FirstType;
  3. // } | {
  4. // targetField: keyof SecondType;
  5. // }
  6. type Result = FilterSet&lt;Union&gt;

这正是我们所需要的:

  1. const getFilterSet = () =&gt; ({} as any);
  2. export const InvokerComponent = () =&gt; {
  3. // 以下函数简单返回一个类型为FilterSet&lt;FirstType&gt;的对象
  4. const filterSet: FilterSet&lt;FirstType&gt; = getFilterSet();
  5. return (
  6. &lt;Component filterSet={filterSet} /&gt; // 没有错误
  7. );
  8. };

playground

英文:

The issue is that your union is not being distributed in FilterSet, which is why FilterSet&lt;Union is evaluated as:

  1. {
  2. targetField: &quot;name&quot; | &quot;id&quot;;
  3. }

What you actually need is something in the following format:

  1. {
  2. targetField: &quot;name&quot;;
  3. } | {
  4. targetField: &quot;id&quot;;
  5. }

The reason why the fields that are specific for the FirstType and SecondType is a known issue with the keyof operator (probably there is a GitHub issue, but I couldn't find one).

The desired type can be achieved by referring to distributive conditional types docs:

  1. type FilterSet&lt;T&gt; = T extends T
  2. ? {
  3. targetField: keyof T;
  4. }
  5. : never;

Testing:

  1. // type Result = {
  2. // targetField: keyof FirstType;
  3. // } | {
  4. // targetField: keyof SecondType;
  5. // }
  6. type Result = FilterSet&lt;Union&gt;

This is exactly what we need:

  1. const getFilterSet = () =&gt; ({} as any);
  2. export const InvokerComponent = () =&gt; {
  3. // The following funcntion simply returns a an object of type FilterSet&lt;FirstType&gt;
  4. const filterSet: FilterSet&lt;FirstType&gt; = getFilterSet();
  5. return (
  6. &lt;Component filterSet={filterSet} /&gt; // no error
  7. );
  8. };

playground

huangapple
  • 本文由 发表于 2023年6月16日 04:53:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/76485445.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定