Typescript与泛型冲突

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

Typescript conflict with Generics

问题

我正在尝试在以下示例中使用通用类型:

  1. export interface CommonFilter<T> {
  2. value?: T,
  3. renderValue: (val?: T) => string
  4. }
  5. export interface StringFilter extends CommonFilter<string> {
  6. type: "string",
  7. customStringAttr?: any
  8. }
  9. export interface NumberFilter extends CommonFilter<number> {
  10. type: "number",
  11. customNumberAttr?: any
  12. }
  13. export type Filter = StringFilter | NumberFilter;

到目前为止,一切正常。

问题出在我想要使用每个过滤器的renderValue函数时:

  1. const filters: Filter[] = [
  2. { type: "string", value: "something", renderValue: val => `My string val --> ${val ?? "none"}` },
  3. { type: "number", value: undefined, renderValue: val => `My number val --> ${val?.toFixed(2) ?? "none"}` }
  4. ];
  5. filters.forEach(f => {
  6. f.renderValue(f.value); // 这一行失败
  7. })

我收到的错误信息是:

参数类型 'string | number | undefined' 不能分配给类型 'undefined' 的参数。
类型 'string' 不能分配给类型 'undefined'。

有人有解决方法吗?

英文:

I am trying to use generic types in the following example:

  1. export interface CommonFilter&lt;T&gt; {
  2. value?: T,
  3. renderValue: (val?: T) =&gt; string
  4. }
  5. export interface StringFilter extends CommonFilter&lt;string&gt; {
  6. type: &quot;string&quot;,
  7. customStringAttr?: any
  8. }
  9. export interface NumberFilter extends CommonFilter&lt;number&gt; {
  10. type: &quot;number&quot;,
  11. customNumberAttr?: any
  12. }
  13. export type Filter = StringFilter | NumberFilter;

So far, all good.

The problem comes when I want to use the renderValue of each filter:

  1. const filters: Filter[] = [
  2. { type: &quot;string&quot;, value: &quot;something&quot;, renderValue: val =&gt; `My string val --&gt; ${val ?? &quot;none&quot;}` },
  3. { type: &quot;number&quot;, value: undefined, renderValue: val =&gt; `My number val --&gt; ${val?.toFixed(2) ?? &quot;none&quot;}` }
  4. ];
  5. filters.forEach(f =&gt; {
  6. f.renderValue(f.value); //this line fails
  7. })

The error that I get is:
> Argument of type 'string | number | undefined' is not assignable to parameter of type 'undefined'.
Type 'string' is not assignable to type 'undefined'.

Does anyone have an idea how to fix it?

答案1

得分: 1

因为 renderValuevalue 是可选属性,意味着类型的这些字段是 T | undefined

  1. export interface CommonFilter<T> {
  2. value?: T, // 真实类型:T | undefined
  3. renderValue?: (val?: T) => string // 真实类型 ((val?: T) => string) | undefined
  4. }

所以,在调用可能是未定义的函数时,会出现 TypeScript 错误。您可以在调用 renderValue 之前尝试检查是否为 undefined。对于 value 字段的真实类型是 string | number | undefined,您可以将其转换为 any

  1. filters.forEach(f => {
  2. if (!f.renderValue) return;
  3. console.log(f.renderValue(f.value as unknown as any)); // val 参数的真实类型是 number | string | undefined。
  4. })
英文:

Because renderValue and value is optional properties that mean type's those fields is T | undefined

  1. export interface CommonFilter&lt;T&gt; {
  2. value?: T, // real type: T | undefined
  3. renderValue?: (val?: T) =&gt; string // real type ((val?: T) =&gt; string) | undefined
  4. }

So, when call possible undefined function will occur typescript error. You can try check undefined before call renderValue. With value field real type is string | number | undefined, you can cast to any.

  1. filters.forEach(f =&gt; {
  2. if (!f.renderValue) return;
  3. console.log(f.renderValue(f.value as unknown as any)); // real type of val param is number | string | undefined.
  4. })

答案2

得分: 1

请检查f.type以缩小f.value的类型:

  1. filters.forEach(f => {
  2. if (f.renderValue) {
  3. if (f.type == "number") {
  4. f.renderValue(f.value);
  5. }
  6. else {
  7. f.renderValue(f.value);
  8. }
  9. }
  10. })

或者简单地将renderValue中的值类型更改为unknown,然后检查是否存在renderValue

  1. renderValue?: (val?: unknown) => string
  1. filters.forEach(f => {
  2. if (f.renderValue) {
  3. f.renderValue(f.value);
  4. }
  5. })
英文:

Either check the f.type to narrowing the type of the f.value:

  1. filters.forEach(f =&gt; {
  2. if (f.renderValue) {
  3. if (f.type == &quot;number&quot;) {
  4. f.renderValue(f.value);
  5. }
  6. else {
  7. f.renderValue(f.value);
  8. }
  9. }
  10. })

or simply change the value type in renderValue to unkown and check the renderValue if exist:

  1. renderValue?: (val?: unknown) =&gt; string
  1. filters.forEach(f =&gt; {
  2. if(f.renderValue){
  3. f.renderValue(f.value);
  4. }
  5. })

答案3

得分: 1

编译器无法处理在ms/TS#30581中描述的"相关联联合类型",而不是使用断言来破坏类型安全性,或者为每种过滤器类型使用开关案例,让我们使用ms/TS#47109中描述的建议方法。

首先,我们需要一个类型,用于存储所有过滤器所需的类型:

  1. type TypeMap = {
  2. string: {
  3. type: string;
  4. extra: { customStringAttr?: any };
  5. };
  6. number: {
  7. type: number;
  8. extra: { customStringAttr?: any };
  9. };
  10. };

extra字段用于添加实际需要的一些额外字段。

现在,让我们使用映射类型重新创建您的过滤器类型:

  1. type FilterObject = {
  2. [K in keyof TypeMap]: {
  3. type: K;
  4. value?: TypeMap[K]['type'];
  5. renderValue?: (val?: TypeMap[K]['type']) => string;
  6. } & TypeMap[K]['extra'];
  7. };

测试:

  1. type FilterObject = {
  2. string: {
  3. type: "string";
  4. value?: string | undefined;
  5. renderValue?: ((val?: string | undefined) => string) | undefined;
  6. } & {
  7. customStringAttr?: any;
  8. };
  9. number: {
  10. type: "number";
  11. value?: number | undefined;
  12. renderValue?: ((val?: number | undefined) => string) | undefined;
  13. } & {
  14. ...;
  15. };
  16. }

要实际获取过滤器数组,我们将使用此答案中描述的ValueOf类型:

  1. type ValueOf<T> = T[keyof T];
  2. // (({
  3. // type: "string";
  4. // value?: string | undefined;
  5. // renderValue?: ((val?: string | undefined) => string) | undefined;
  6. // } & {
  7. // customStringAttr?: any;
  8. // }) | ({
  9. // type: "number";
  10. // value?: number | undefined;
  11. // renderValue?: ((val?: number | undefined) => string) | undefined;
  12. // } & {
  13. // ...;
  14. // }))[]
  15. type Result = ValueOf<FilterObject>[]
  16. const filters: ValueOf<FilterObject>[] = [
  17. {
  18. type: 'string',
  19. value: 'something',
  20. renderValue: (val) => `My string val --> ${val ?? 'none'}`,
  21. },
  22. {
  23. type: 'number',
  24. value: undefined,
  25. renderValue: (val) => `My number val --> ${val?.toFixed(2) ?? 'none'}`,
  26. },
  27. ];

要完成这个方法,我们需要一个通用函数,它将接受一个受约束的泛型参数,该参数受限于keyof TypeMap,并且参数将是该键下的整个过滤器:

  1. const render = <T extends keyof TypeMap>(arg: FilterObject[T]) => {
  2. arg.renderValue?.(arg.value);
  3. };

用法:

  1. filters.forEach((f) => {
  2. render(f); // 没有错误
  3. });

playground

英文:

The compiler is unable to handle the "correlated union types" described in ms/TS#30581,
Instead of using assertion, which breaks the type-safety, or having a switch case for every type of filter, let's use the suggested approach described in ms/TS#47109.

First, we will need a type that would store all necessary type for filter:

  1. type TypeMap = {
  2. string: {
  3. type: string;
  4. extra: { customStringAttr?: any };
  5. };
  6. number: {
  7. type: number;
  8. extra: { customStringAttr?: any };
  9. };
  10. };

extra field is used to add some additional fields that you actually need.

Now, let's recreate your filter types using mapped types:

  1. type FilterObject = {
  2. [K in keyof TypeMap]: {
  3. type: K;
  4. value?: TypeMap[K][&#39;type&#39;];
  5. renderValue?: (val?: TypeMap[K][&#39;type&#39;]) =&gt; string;
  6. } &amp; TypeMap[K][&#39;extra&#39;];
  7. };

Testing:

  1. type FilterObject = {
  2. string: {
  3. type: &quot;string&quot;;
  4. value?: string | undefined;
  5. renderValue?: ((val?: string | undefined) =&gt; string) | undefined;
  6. } &amp; {
  7. customStringAttr?: any;
  8. };
  9. number: {
  10. type: &quot;number&quot;;
  11. value?: number | undefined;
  12. renderValue?: ((val?: number | undefined) =&gt; string) | undefined;
  13. } &amp; {
  14. ...;
  15. };
  16. }

To actually get the array of filters we will use the ValueOf described in this answer:

  1. type ValueOf&lt;T&gt; = T[keyof T];
  2. // (({
  3. // type: &quot;string&quot;;
  4. // value?: string | undefined;
  5. // renderValue?: ((val?: string | undefined) =&gt; string) | undefined;
  6. // } &amp; {
  7. // customStringAttr?: any;
  8. // }) | ({
  9. // type: &quot;number&quot;;
  10. // value?: number | undefined;
  11. // renderValue?: ((val?: number | undefined) =&gt; string) | undefined;
  12. // } &amp; {
  13. // ...;
  14. // }))[]
  15. type Result = ValueOf&lt;FilterObject&gt;[]
  16. const filters: ValueOf&lt;FilterObject&gt;[] = [
  17. {
  18. type: &#39;string&#39;,
  19. value: &#39;something&#39;,
  20. renderValue: (val) =&gt; `My string val --&gt; ${val ?? &#39;none&#39;}`,
  21. },
  22. {
  23. type: &#39;number&#39;,
  24. value: undefined,
  25. renderValue: (val) =&gt; `My number val --&gt; ${val?.toFixed(2) ?? &#39;none&#39;}`,
  26. },
  27. ];

For finishing the approach we will need a generic function that will accept a generic parameter constrained by keyof TypeMap and the argument will be the whole filter under that key:

  1. const render = &lt;T extends keyof TypeMap&gt;(arg: FilterObject[T]) =&gt; {
  2. arg.renderValue?.(arg.value);
  3. };

Usage:

  1. filters.forEach((f) =&gt; {
  2. render(f); // no error
  3. });

playground

huangapple
  • 本文由 发表于 2023年7月20日 18:48:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/76729107.html
匿名

发表评论

匿名网友

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

确定