#typescript #typescript-typings
#typescript #typescript-типизации
Вопрос:
В настоящее время я использую https://github.com/stephenh/ts-proto для генерации типов TypeScript из сообщений protobuf с использованием объединений типов для oneOf
полей.
Тип образца может быть показан следующим образом:
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
при попытке прочитать это поле.
Есть ли способ сообщить компилятору, что выходные данные функции имеют определенный набор типов?
Ответ №1:
Используйте определяемый пользователем тип защиты:
interface CustomMilestoneWrapper {
milestoneLabel: { $case: 'customMilestone', customMilestone: CustomMilestone };
targetDate: Date | undefined;
}
function isCustomMilestoneWrapper(milestone: Milestone): milestone is CustomMilestoneWrapper {
return milestone.milestoneLabel?.$case === 'customMilestone'
}
function getCustomMilestones(milestones: Milestone[]): CustomMilestoneWrapper[] {
return milestones.filter(isCustomMilestoneWrapper);
}
Ответ №2:
Хотя ответ Лесиака правильный, я хотел бы расширить: вы также можете использовать as
для «приведения» к определенному типу. Пока вы уверены, что ваше приведение является надежным, это так же хорошо, как обычная защита типа.
Пример (игровая площадка):
function filterMilestoneCustom(arg: Milestone[]): Array<Milestone amp; {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 amp; {milestoneLabel: { $case: 'customMilestone' }}
правильно распознается компилятором.
Недостатком является то, что вам приходится писать такую функцию для каждого oneof
случая. Проблема могла бы быть решена общим способом, если бы ts-proto не сделал oneof
свойство необязательным.
oneof
Тип объединения в ts-proto основан на моем предложении. С тех пор я написал плагин 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 amp; {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);
}
Которое можно использовать следующим образом (игровая площадка):
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 . Размер кода для веб-приложений значительно меньше, чем в ts-proto, и вы можете делать некоторые интересные вещи с отражением и пользовательскими параметрами.
Комментарии:
1. Очень приятно!! Мне нравится то, что вы сделали с вашей реализацией oneOf! Ваш фрагмент кода очень соответствует тому, о чем я думал. Спасибо за ответ!