英文:
Typescript conflict with Generics
问题
我正在尝试在以下示例中使用通用类型:
export interface CommonFilter<T> {
value?: T,
renderValue: (val?: T) => string
}
export interface StringFilter extends CommonFilter<string> {
type: "string",
customStringAttr?: any
}
export interface NumberFilter extends CommonFilter<number> {
type: "number",
customNumberAttr?: any
}
export type Filter = StringFilter | NumberFilter;
到目前为止,一切正常。
问题出在我想要使用每个过滤器的renderValue
函数时:
const filters: Filter[] = [
{ type: "string", value: "something", renderValue: val => `My string val --> ${val ?? "none"}` },
{ type: "number", value: undefined, renderValue: val => `My number val --> ${val?.toFixed(2) ?? "none"}` }
];
filters.forEach(f => {
f.renderValue(f.value); // 这一行失败
})
我收到的错误信息是:
参数类型 'string | number | undefined' 不能分配给类型 'undefined' 的参数。
类型 'string' 不能分配给类型 'undefined'。
有人有解决方法吗?
英文:
I am trying to use generic types in the following example:
export interface CommonFilter<T> {
value?: T,
renderValue: (val?: T) => string
}
export interface StringFilter extends CommonFilter<string> {
type: "string",
customStringAttr?: any
}
export interface NumberFilter extends CommonFilter<number> {
type: "number",
customNumberAttr?: any
}
export type Filter = StringFilter | NumberFilter;
So far, all good.
The problem comes when I want to use the renderValue of each filter:
const filters: Filter[] = [
{ type: "string", value: "something", renderValue: val => `My string val --> ${val ?? "none"}` },
{ type: "number", value: undefined, renderValue: val => `My number val --> ${val?.toFixed(2) ?? "none"}` }
];
filters.forEach(f => {
f.renderValue(f.value); //this line fails
})
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
因为 renderValue
和 value
是可选属性,意味着类型的这些字段是 T | undefined
。
export interface CommonFilter<T> {
value?: T, // 真实类型:T | undefined
renderValue?: (val?: T) => string // 真实类型 ((val?: T) => string) | undefined
}
所以,在调用可能是未定义的函数时,会出现 TypeScript 错误。您可以在调用 renderValue
之前尝试检查是否为 undefined
。对于 value
字段的真实类型是 string | number | undefined
,您可以将其转换为 any
。
filters.forEach(f => {
if (!f.renderValue) return;
console.log(f.renderValue(f.value as unknown as any)); // val 参数的真实类型是 number | string | undefined。
})
英文:
Because renderValue
and value
is optional properties that mean type's those fields is T | undefined
export interface CommonFilter<T> {
value?: T, // real type: T | undefined
renderValue?: (val?: T) => string // real type ((val?: T) => string) | undefined
}
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
.
filters.forEach(f => {
if (!f.renderValue) return;
console.log(f.renderValue(f.value as unknown as any)); // real type of val param is number | string | undefined.
})
答案2
得分: 1
请检查f.type
以缩小f.value
的类型:
filters.forEach(f => {
if (f.renderValue) {
if (f.type == "number") {
f.renderValue(f.value);
}
else {
f.renderValue(f.value);
}
}
})
或者简单地将renderValue
中的值类型更改为unknown
,然后检查是否存在renderValue
:
renderValue?: (val?: unknown) => string
filters.forEach(f => {
if (f.renderValue) {
f.renderValue(f.value);
}
})
英文:
Either check the f.type
to narrowing the type of the f.value
:
filters.forEach(f => {
if (f.renderValue) {
if (f.type == "number") {
f.renderValue(f.value);
}
else {
f.renderValue(f.value);
}
}
})
or simply change the value type in renderValue
to unkown
and check the renderValue
if exist:
renderValue?: (val?: unknown) => string
filters.forEach(f => {
if(f.renderValue){
f.renderValue(f.value);
}
})
答案3
得分: 1
编译器无法处理在ms/TS#30581中描述的"相关联联合类型",而不是使用断言来破坏类型安全性,或者为每种过滤器类型使用开关案例,让我们使用ms/TS#47109中描述的建议方法。
首先,我们需要一个类型,用于存储所有过滤器所需的类型:
type TypeMap = {
string: {
type: string;
extra: { customStringAttr?: any };
};
number: {
type: number;
extra: { customStringAttr?: any };
};
};
extra
字段用于添加实际需要的一些额外字段。
现在,让我们使用映射类型重新创建您的过滤器类型:
type FilterObject = {
[K in keyof TypeMap]: {
type: K;
value?: TypeMap[K]['type'];
renderValue?: (val?: TypeMap[K]['type']) => string;
} & TypeMap[K]['extra'];
};
测试:
type FilterObject = {
string: {
type: "string";
value?: string | undefined;
renderValue?: ((val?: string | undefined) => string) | undefined;
} & {
customStringAttr?: any;
};
number: {
type: "number";
value?: number | undefined;
renderValue?: ((val?: number | undefined) => string) | undefined;
} & {
...;
};
}
要实际获取过滤器数组,我们将使用此答案中描述的ValueOf
类型:
type ValueOf<T> = T[keyof T];
// (({
// type: "string";
// value?: string | undefined;
// renderValue?: ((val?: string | undefined) => string) | undefined;
// } & {
// customStringAttr?: any;
// }) | ({
// type: "number";
// value?: number | undefined;
// renderValue?: ((val?: number | undefined) => string) | undefined;
// } & {
// ...;
// }))[]
type Result = ValueOf<FilterObject>[]
const filters: ValueOf<FilterObject>[] = [
{
type: 'string',
value: 'something',
renderValue: (val) => `My string val --> ${val ?? 'none'}`,
},
{
type: 'number',
value: undefined,
renderValue: (val) => `My number val --> ${val?.toFixed(2) ?? 'none'}`,
},
];
要完成这个方法,我们需要一个通用函数,它将接受一个受约束的泛型参数,该参数受限于keyof TypeMap
,并且参数将是该键下的整个过滤器:
const render = <T extends keyof TypeMap>(arg: FilterObject[T]) => {
arg.renderValue?.(arg.value);
};
用法:
filters.forEach((f) => {
render(f); // 没有错误
});
英文:
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:
type TypeMap = {
string: {
type: string;
extra: { customStringAttr?: any };
};
number: {
type: number;
extra: { customStringAttr?: any };
};
};
extra
field is used to add some additional fields that you actually need.
Now, let's recreate your filter types using mapped types:
type FilterObject = {
[K in keyof TypeMap]: {
type: K;
value?: TypeMap[K]['type'];
renderValue?: (val?: TypeMap[K]['type']) => string;
} & TypeMap[K]['extra'];
};
Testing:
type FilterObject = {
string: {
type: "string";
value?: string | undefined;
renderValue?: ((val?: string | undefined) => string) | undefined;
} & {
customStringAttr?: any;
};
number: {
type: "number";
value?: number | undefined;
renderValue?: ((val?: number | undefined) => string) | undefined;
} & {
...;
};
}
To actually get the array of filters we will use the ValueOf
described in this answer:
type ValueOf<T> = T[keyof T];
// (({
// type: "string";
// value?: string | undefined;
// renderValue?: ((val?: string | undefined) => string) | undefined;
// } & {
// customStringAttr?: any;
// }) | ({
// type: "number";
// value?: number | undefined;
// renderValue?: ((val?: number | undefined) => string) | undefined;
// } & {
// ...;
// }))[]
type Result = ValueOf<FilterObject>[]
const filters: ValueOf<FilterObject>[] = [
{
type: 'string',
value: 'something',
renderValue: (val) => `My string val --> ${val ?? 'none'}`,
},
{
type: 'number',
value: undefined,
renderValue: (val) => `My number val --> ${val?.toFixed(2) ?? 'none'}`,
},
];
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:
const render = <T extends keyof TypeMap>(arg: FilterObject[T]) => {
arg.renderValue?.(arg.value);
};
Usage:
filters.forEach((f) => {
render(f); // no error
});
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论