我目前正在使用https://github.com/stephenh/ts-proto通过oneOf
字段的类型联合从protobuf消息生成TypeScript类型。
示例类型可以显示如下:
export interface Milestone {
milestoneLabel?:
{ $case: 'milestoneType', milestoneType: MilestoneType } |
{ $case: 'customMilestone', customMilestone: CustomMilestone };
targetDate: Date | undefined;
}
现在,在代码的后面,我想要一个这样的函数:
export function getCustomMilestones(milestones: Milestone[]): Milestone[] {
return milestones.filter(milestone => milestone.milestoneLabel?.$case === 'customMilestone');
}
问题是,TypeScript语言服务器/编译器无法理解此函数的返回类型在类型联合上具有过滤器,因此在尝试读取该字段时,我需要再次断言$case
。
有没有一种方法可以告诉编译器函数的输出具有特定类型集?
答案 0 :(得分:2)
x_vec0
答案 1 :(得分:1)
尽管Lesiak的答案是正确的,但我想扩展一下:您还可以使用as
“投射”为特定类型。只要您确定自己的类型转换是可靠的,就和常规类型的后卫一样好。
示例(playground):
function filterMilestoneCustom(arg: Milestone[]): Array<Milestone & {milestoneLabel: { $case: 'customMilestone' }}> {
return arg.filter(milestone => milestone.milestoneLabel?.$case === 'customMilestone') as any;
}
let milestones: Milestone[] = [];
for (let x of filterMilestoneCustom(milestones)) {
let y: CustomMilestone = x.milestoneLabel.customMilestone;
}
请注意,不必声明帮助程序接口。编译器可以正确识别类型Milestone & {milestoneLabel: { $case: 'customMilestone' }}
。
缺点是您必须为每种oneof
情况编写这样的函数。如果ts-proto没有将oneof
属性设为可选,则可以通过一般方式解决该问题。
ts-proto中的oneof
联合类型基于my proposal。从那时起,我编写了一个protobuf插件,该插件还对oneof
使用联合类型。但是对于什么都没有选择的情况,它使用undefined
。生成的界面(基本上)如下所示:
export interface Milestone {
milestoneLabel:
{ $case: 'milestoneType', milestoneType: MilestoneType } |
{ $case: 'customMilestone', customMilestone: CustomMilestone } |
{ $case: undefined };
targetDate: Date | undefined;
}
这使得可以编写以下类型保护和过滤器功能:
type MilestoneCase<C extends Milestone["milestoneLabel"]["$case"]> = Milestone & {milestoneLabel: {$case: C}};
function isMilestoneCase<C extends Milestone["milestoneLabel"]["$case"]>($case: C, milestone: Milestone): milestone is MilestoneCase<C> {
return milestone.milestoneLabel.$case === $case;
}
function filterMilestoneCase<C extends Milestone["milestoneLabel"]["$case"]>(milestones: Milestone[], $case: C): MilestoneCase<C>[] {
const is = (ms: Milestone): ms is MilestoneCase<C> => isMilestoneCase($case, ms);
return milestones.filter(is);
}
可以这样使用(playground):
let milestones: Milestone[] = [];
let customMilestones = filterMilestoneCase(milestones, "customMilestone");
for (let x of customMilestones) {
let y: CustomMilestone = x.milestoneLabel.customMilestone;
}
let milestoneTypes = filterMilestoneCase(milestones, "milestoneType");
for (let x of milestoneTypes) {
let y: MilestoneType = x.milestoneLabel.milestoneType;
}
let labelUndefined = filterMilestoneCase(milestones, undefined);
for (let x of labelUndefined) {
let y: undefined = x.milestoneLabel.$case;
}
如果将案例添加到oneof
,则无需编写新函数。
有关插件,请参见protobuf-ts。相对于ts-proto的主要好处可能是它是完全从头开始编写的,不需要protobuf.js或Long.js。 Web应用程序的代码大小为considerable smaller than ts-proto,您可以使用反射和自定义选项来做一些有趣的事情。