打字稿断言字段值

时间:2020-09-09 19:38:48

标签: typescript typescript-typings

我目前正在使用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

有没有一种方法可以告诉编译器函数的输出具有特定类型集?

2 个答案:

答案 0 :(得分:2)

使用user-defined type guard

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,您可以使用反射和自定义选项来做一些有趣的事情。