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

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

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中,您可以这样更改代码:

export const InvokerComponent = () => {
    const getFilterSet: FilterSet<FirstType> = () => 
        {targetField: "specificToFirst"}

    const filterSet: FilterSet<FirstType> = getFilterSet();

    return (
        <Component filterSet={filterSet as FilterSet<Union>} />
    )
}

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

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

英文:

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

type ComponentPropsType = {
    filterSet: FilterSet&lt;Union&gt;
}

type FilterSet&lt;T&gt; = {
    targetField: keyof T;
}

type Union = FirstType | SecondType;

type FirstType = {
    name: string;
    id: number;
    specificToFirst: string;
}

type SecondType = {
    name: string;
    id: number;
    specificToSecond: string;
}

Here is the relevant portion of Component:

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

When I attempt to invoke this component like so:

export const InvokerComponent = () =&gt; {
    const getFilterSet: FilterSet&lt;FirstType&gt; = () =&gt; 
        {targetField: &quot;specificToFirst&quot;}

    const filterSet: FilterSet&lt;FirstType&gt; = getFilterSet();

    return (
        &lt;Component filterSet={filterSet} /&gt;  // TS ERROR HERE
    )
}

Typescript tells me:

Type FilterSet&lt;FirstType&gt; is not assignable to type FilterSet&lt;Union&gt; 
Type Union is not assignable to type FirstType
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会被评估为:

{
    targetField: &quot;name&quot; | &quot;id&quot;;
}

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

{
    targetField: &quot;name&quot;;
} | {
    targetField: &quot;id&quot;;
}

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

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

type FilterSet&lt;T&gt; = T extends T
  ? {
      targetField: keyof T;
    }
  : never;

测试:

// type Result = {
//   targetField: keyof FirstType;
// } | {
//   targetField: keyof SecondType;
// }
type Result = FilterSet&lt;Union&gt;

这正是我们所需要的:

const getFilterSet = () =&gt; ({} as any);

export const InvokerComponent = () =&gt; {
  // 以下函数简单返回一个类型为FilterSet&lt;FirstType&gt;的对象
  const filterSet: FilterSet&lt;FirstType&gt; = getFilterSet();

  return (
    &lt;Component filterSet={filterSet} /&gt; // 没有错误
  );
};

playground

英文:

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

{
    targetField: &quot;name&quot; | &quot;id&quot;;
}

What you actually need is something in the following format:

{
    targetField: &quot;name&quot;;
} | {
    targetField: &quot;id&quot;;
}

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:

type FilterSet&lt;T&gt; = T extends T
  ? {
      targetField: keyof T;
    }
  : never;

Testing:

// type Result = {
//   targetField: keyof FirstType;
// } | {
//   targetField: keyof SecondType;
// }
type Result = FilterSet&lt;Union&gt;

This is exactly what we need:

const getFilterSet = () =&gt; ({} as any);

export const InvokerComponent = () =&gt; {
  // The following funcntion simply returns a an object of type FilterSet&lt;FirstType&gt;
  const filterSet: FilterSet&lt;FirstType&gt; = getFilterSet();

  return (
    &lt;Component filterSet={filterSet} /&gt; // no error
  );
};

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:

确定