Реле возвращаемого типа функции Typescript

У меня есть простой класс, реализующий шаблон посетителя:

abstract class MyNode {};
class MyNodeA extends MyNode {};
class MyNodeB extends MyNode {};

abstract class NodeVisitor {
  abstract visitMyNodeA(node: MyNodeA): unknown;
  abstract visitMyNodeB(node: MyNodeB): unknown;

  public visit(node: MyNode) {
    if (node instanceof MyNodeA) {
      return this.visitMyNodeA(node);
    } else if (node instanceof MyNodeB) {
      return this.visitMyNodeB(node);
    } else {
      throw new Error('Unknown node type on visitor');
    }
  } 
}

и позже я хочу иметь настраиваемые типы возврата для каждой функции посещения, когда я реализую NodeVisitor

class MyNodeVisitor extends NodeVisitor {
  visitMyNodeA(node: MyNodeA): number {
    return 1;
  }
  visitMyNodeB(node: MyNodeB): number {
    return this.visit(new MyNodeA()) + 1;
  }
}

но это генерирует ошибку, потому что компилятор TypeScript не понимает, что вызов visit с параметром типа MyNodeA перенаправляет на функцию visitMyNodeA, которая теперь возвращает number.

Как мне реализовать такое решение?

"это вызывает ошибку"... какая конкретно ошибка? Не могли бы вы превратить это в минимальный воспроизводимый пример и, возможно, даже дать ссылку на онлайн-среду разработки, такую ​​как Playground, которая демонстрирует проблему?

jcalz 28.05.2019 21:26

@jcalz Извините, вы правы. Пример, который у меня был раньше, не вызывал ошибки, поскольку я выполнял приведение типов к String. Обновлен пример, чтобы четко показать мою проблему

Komninos 28.05.2019 21:30
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
2
101
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Да, компилятор сам не может понять это. Вы можете помочь ему сделать это за счет большей сложности (и меньшей безопасности типов внутри реализации visit()). Я предлагаю дать visit() подпись общий, тип возвращаемого значения которой — условный тип на основе полиморфный this тип подклассов:

abstract class MyNode {myNode = "myNode"}
class MyNodeA extends MyNode {a = "a"}
class MyNodeB extends MyNode {b = "b"}

abstract class NodeVisitor {
  abstract visitMyNodeA(node: MyNodeA): unknown;
  abstract visitMyNodeB(node: MyNodeB): unknown;

  // call signature    
  public visit<T extends MyNode>(
    node: T
  ): T extends MyNodeA ? ReturnType<this["visitMyNodeA"]> : 
     T extends MyNodeB ? ReturnType<this["visitMyNodeB"]> : 
    never;

  // implementation signature is wider
  public visit(node: MyNode): unknown {
    if (node instanceof MyNodeA) {
      return this.visitMyNodeA(node);
    } else if (node instanceof MyNodeB) {
      return this.visitMyNodeB(node);
    } else {
      throw new Error("Unknown node type on visitor");
    }
  }
}    
class MyNodeVisitor extends NodeVisitor {
  visitMyNodeA(node: MyNodeA): number {
    return 1;
  }
  visitMyNodeB(node: MyNodeB): number {
    return this.visit(new MyNodeA()) + 1;
  }
}

Это работает? Идея состоит в том, что вы проводите компилятор через анализ того, что если вы передадите MyNodeA, то visit() вернет результат this.visitMyNodeA(node), и то же самое для MyNodeB.

Надеюсь, это поможет; удачи!

Ссылка на код

Могу я предложить public visit<T extends MyNode>(node: T): ReturnType<Extract<this[Exclude<keyof this, 'visit'>], (node: T) => any>>; ? ИТ-отдел должен извлечь тип возвращаемого значения на основе переданного параметра, если методы посещения* общедоступны. Может стать немного хрупким, если вы добавите дополнительные методы, соответствующие сигнатуре (node: MyNode) => any, но это может быть вариантом :)

Titian Cernicova-Dragomir 28.05.2019 22:04

@jcalz Похоже, это решение работает, но только когда методы посещения * общедоступны, есть ли обходной путь, если это не так?

Komninos 28.05.2019 22:42

@TitianCernicova-Dragomir Мне это нравится; Я согласен с возможной хрупкостью, но, вероятно, есть обходные пути. @Komninos, если методы не являются общедоступными, вам будет очень трудно ссылаться на них программно (поскольку они больше не являются частью Pick<NodeVisitor, keyof NodeVisitor>). Не уверен, есть ли у кого-то еще предложения по этому поводу, но обратите внимание, что это, похоже, отличается от заданного вопроса.

jcalz 29.05.2019 02:56

Другие вопросы по теме