因此,我有一个抽象基类,该基类接收一个ValueType
的值,并具有一个能够将其转换为TransformedType
的函数。这可以通过两个泛型来解决:
abstract class Base<ValueType, TransformedType> {
public abstract transformValue: (value: ValueType) => TransformedType;
}
现在我有两个从Base
适当扩展的类,一个用于ValueType
字符串,另一个用于数字:
interface StringTransformed {
someValue: string;
}
class DerivedString extends Base<string, StringTransformed> {
public transformValue = (value: string): StringTransformed => {
return { someValue: value + "someValue" };
};
}
interface NumberTransformed {
valuePlusPlus: number;
}
class DerivedNumber extends Base<number, NumberTransformed> {
public transformValue = (value: number): NumberTransformed => {
return { valuePlusPlus: value + 1 };
};
}
此后,我创建了一个类型Fields
,该类型需要类型T
中的通用类型object
:
type Fields<T extends object> = {
[A in keyof T]: any extends Base<T[A], infer P> ? Base<T[A], P>: never
};
用法示例:
type MainType = { x: string; y: number };
const fields: Fields<MainType> = {
x: new DerivedString("test"),
y: new DerivedNumber(2)
};
const transformed = fields.x.transformValue("anystring");
这里的问题是关于TransformedType
的类型信息丢失了,因为它是unknown
:
与之相反,关于ValueType
的信息保持正确。
问题:
如何在不增加任何限制的情况下正确采用通用类型?
在这里使用infer
是否正确?
为什么类型信息会丢失?
我可以从TransformedType
的通用参数中删除Base
并只在transformValue
函数中使用它吗?(ValueType
是否需要其他函数等Base
内,但TransformedType
仅用于transformValue
函数)
编辑:
根据评论的需要,我创建了一个codesandbox,其中包含用例的最小示例。基本上有一些我无法解决的any
类型转换,这是我的实际问题。
总的来说,我想在React中编写一个用于处理表单状态的库,因此它确实需要是一种非常通用的方法。请记住,这个示例实际上确实简化了,实际上更加复杂。
答案 0 :(得分:1)
好吧,对于第2个问题,首先:是的,如果这样定义,您基本上可以删除第二个类型参数,并将其默认设置为unknown
:
// default second parameter
abstract class Base<T, U = unknown> {
public abstract transformValue: (value: T) => U;
}
interface StringTransformed {
someValue: string;
}
class DerivedString extends Base<string> {
public transformValue = (value: string): StringTransformed => {
return { someValue: value + "someValue" };
};
}
interface NumberTransformed {
valuePlusPlus: number;
}
class DerivedNumber extends Base<number> {
public transformValue = (value: number): NumberTransformed => {
return { valuePlusPlus: value + 1 };
};
}
实际的类DerivedString
和DerivedNumber
仍然知道其transformValue()
方法返回的类型。
回到问题1 ...首先,您对Fields
的定义可能也是这样:
type Fields<T = any> = { [K in keyof T]: Base<T[K]> };
几乎相同。请注意,我给了默认类型参数any
,因此Fields
本身就是Fields<any>
。
现在,正如我评论的那样,您实际上不需要注释任何特定的常量,因为它们具有某些特定的Fields<...>
类型;类型推断将为您解决这个问题,如果您尝试将不好的事情传递到最终的transformFields()
函数中,则会被警告。不过,如果您想及早收到有关常量错误的警告,则可以执行如下帮助函数:
const asFields = <F extends Fields>(fields: F) => fields;
如果您给它提供一个不匹配的值,它将发出抱怨。这样就可以了:
const fields = asFields({
x: new DerivedString(),
y: new DerivedNumber()
});
这会导致错误:
const badFields = asFields({
x: "whoops", // error! string is not a valid Base
y: new DerivedNumber()
})
编译器会记住fields
的实际作用:
fields.x.transformValue("anystring").someValue; // okay
如果您有特定的类型T
,则需要转换fields
常量,则可以使一个curried辅助函数来手动指定T
并推断出休息:
const asValueFields = <T>() => <F extends Fields<T>>(fields: F) => fields;
然后这有效:
const fieldsAlso = asValueFields<MainType>()({
x: new DerivedString(),
y: new DerivedNumber()
});
但这失败了:
const badFieldsAlso = asValueFields<MainType>()({
x: new DerivedNumber(), // error! string is not number
y: new DerivedString() // error! number is not string
});
它仍然记住特定的输出类型:
fieldsAlso.x.transformValue("anystring").someValue; // okay
最后,让我们实现transformFields()
。描述给定Fields
类型的转换后的输出类型很有用。这是我的定义方式:
type TransformedType<F extends Fields> = {
[K in keyof F]: F[K] extends Base<any, infer T> ? T : never
};
这可能是“如何防止类型丢失”解决方案的症结所在?上面的操作是使用类型F
来确定其处理的输入和输出类型,然后为每个属性提取输出类型。
因此transformFields
接受一个T
和一个F
并返回一个TransformedType<F>
:
const transformFields = <T extends object, F extends Fields<T>>(
fields: F,
values: T
): TransformedType<F> => {
return (Object.keys(fields) as Array<keyof T>).reduce(
<K extends keyof T>(transformedObj: TransformedType<F>, key: K) => ({
...transformedObj,
[key]: (fields[key] as Base<T[K], TransformedType<F>[K]>).transformValue(
values[key]
)
}),
{} as TransformedType<F>
);
};
该实现与您的代码沙箱中的实现相同,但其中的一些经过仔细放置type assertions以使编译器确信您正在执行的操作是可以的。基本上,我们断言Object.keys(fields)
将返回T
的键数组,fields[key]
是一个Base
,它接受一个T[K]
并输出一个{{ 1}},并且TransformedType<F>[K]
累加器是reduce()
。
哇!让我们使用它:
TransformedType<F>
看起来不错。请注意,您不必使用之前的辅助功能来“准备” const transformedMainType = transformFields(fields, { x: "", y: 1 });
transformedMainType.x.someValue; // okay
transformedMainType.y.valuePlusPlus; // okay
。如果您有正确键入的对象文字,它也可以正常工作:
fields
好的,很多。希望能给您一些指导。祝你好运!