typescript - 类型作为参数传递的递归泛型函数
问题描述
我想要一个递归泛型函数,但我不能在泛型函数调用中使用作为参数传递的类型,原因是
'memberType' refers to a value, but is being used as a type here.
有没有办法传递memberType
给泛型方法调用?
例子:
class Packet {
header: Header
body: Body
static MEMBERS = [
['header', Header, 0, 6],
['body', Body, 6, 10],
]
}
class Header {
size: number
ttl: number
static MEMBERS = [
['size', 'number', 0, 2],
['ttl', 'number', 2, 3],
]
}
class Body {
raw: string
static MEMBERS = [
['raw', 'string', 0, 10]
]
}
class Deserializer {
static Deserialize<T>(serialized: string, type: { new(): T ;}): T {
const obj = new type();
const members = obj.constructor['MEMBERS'];
for(const member of members) {
var [memberName, memberType, startOffset, len] = member;
const serializedMember = serialized.substr(startOffset, len) // cut string holding serialized member (like 'qwertyuiop' from example)
if(memberType == 'number') { // handle primitive type differently
obj[memberName] = parseInt(serializedMember);
} else if(memberType == 'string') { // handle primitive type differently
obj[memberName] = serializedMember
} else { // handle non primitives, like Header and Body
// *** issue is on the following line ***
obj[memberName] = Deserialize<memberType>() // 'memberType' refers to a value, but is being used as a type here.
}
}
return obj as T;
}
}
// HEADRBOOOOOOODY - description of `serialized` string below
// SSTTL (where SS = header.size, TTL = header.ttl)
const serialized = '11222qwertyuiop'
const packet: Packet = Deserializer.Deserialize<Packet>(serialized, Packet)
// expected `packet` object structure after running `Deserialize`
// Packet
// * header -> Header
// * size -> 11
// * ttl -> 222
// * body -> Body
// * raw -> BOOOOOOODY
解决方案
对,memberType
是一个值,而不是一个类型。您可以从编译器中获取其类型为typeof memberType
,但在您的情况下,递归调用Deserialize
需要参数,可以从中推断出泛型类型参数,typeof memberType
就像您将其传入一样:
obj[memberName] = Deserializer.Deserialize(serializedMember, memberType);
这应该可以解决您询问的问题,但是示例代码有很多类型错误和隐含的any
问题,您可能应该解决这些问题。
解决它们的一种方法是通过轻微的重构和明智地使用类型断言来处理编译器无法验证为安全的地方(例如obj[memberName] = parseInt(...)
,没有断言就无法工作,因为编译器不会意识到属性位于memberName
应该是 a number
,因为它是无法执行的高阶推理)。也许是这样的:
type DeserializableClass<T> = {
new(): T,
MEMBERS: readonly {
[K in keyof T]: readonly [K, string | DeserializableClass<T[K]>, number, number]
}[keyof T][]
}
class Deserializer {
static Deserialize<T>(serialized: string, type: DeserializableClass<T>): T {
const obj = new type();
const members = type.MEMBERS;
for (const member of members) {
var [memberName, memberType, startOffset, len] = member;
const serializedMember = serialized.substr(startOffset, len) // cut string holding serialized member (like 'qwertyuiop' from example)
if (typeof memberType !== "string") {
obj[memberName] = Deserializer.Deserialize(serializedMember, memberType);
} else if (memberType == 'number') { // handle primitive type differently
obj[memberName] = parseInt(serializedMember) as any as T[keyof T];
} else if (memberType == 'string') { // handle primitive type differently
obj[memberName] = serializedMember as any as T[keyof T];
}
}
return obj;
}
}
在这里,我们更准确地描述了可以反序列化哪种类:它必须具有具有MEMBERS
正确类型的元组数组的属性。请注意,这不是完全安全的类型,因为T[keyof T]
它太宽了,您可以继续更改内容以使其更安全,但在这一点上,我倾向于让您的MEMBERS
属性也包含原语的反序列化器并放弃字符串。请注意编译器如何将递归Deserialize
调用视为可接受的,但仍需要告诉它string
andnumber
案例有效。
此外,为了接受这一点,Packet
您需要确保您的MEMBERS
静态属性足够文字(元组往往会扩大到数组);一种简单的方法是使用const
assertion,例如:
static MEMBERS = [
['size', 'number', 0, 2],
['ttl', 'number', 2, 3],
] as const
无论如何,我现在不会比这更深入地进行重构。希望这可以帮助。祝你好运!
推荐阅读
- ios - RXswift,重复 observable 直到完成
- html - 输入类型文本最多 2 位数字
- xamarin - 我应该如何使用 Prism 和 Unity 构建视图的多个实例,所有实例都具有非常相似的 ViewModel
- android - Google Play 服务愿景检查可用性
- reactjs - React Router 避免卸载
- javascript - 向下滚动到特定部分时运行一些代码
- c# - 团结与跳跃运动
- pandas - 从另一个数据帧更新一个数据帧
- php - 如何在 Windows 中使用 xampp 服务器在我的本地运行现有的蛋糕 php 项目
- c - openCL 中的字符串扩展