Универсальная функция TypeScript может работать только при перегрузке функции с несколькими сигнатурами

Я определяю интерфейс с общей функцией, такой как:

export interface IState {
  send: <I, E>(message: I, callback?: (e: E) => void) => IState;
}

Он отлично работает для классов с более чем одной подписью:

class Left implements IState {
  send(m: 'go-on', cb?: (e: never) => void): Left;
  send(m: 'turn-right', cb?: (e: never) => void): Right;
  send(m: 'go-on' | 'turn-right', cb?: any) {
    return m === 'go-on' ? new Left() : new Right();
  }
}

class Right implements IState {
  send(m: 'go-on', cb?: (e: never) => void): Right;
  send(m: 'turn-left', cb?: (e: never) => void): Left;
  send(m: 'go-on' | 'turn-left', cb?: any) {
    return m === 'go-on' ? new Right() : new Left();
  }
}

type Both = Left | Right;

function test(l: Both) {
  if (l instanceof Left) {
    l.send('turn-right')
      .send('turn-left')
      .send('turn-right')
      .send('turn-left');
  }
  const l2 = new Left();
  l2.send('go-on')
    .send('turn-right')
    .send('turn-left');
  l2.send('turn-right').send('turn-left');
}

Однако, когда я хочу определить IState только с одной подписью отправки, я получил ошибки компиляции:

class CountState implements IState {
  constructor(public readonly data: number) {}
  // send(m: 'inc', cb?: (e: number) => void): CountState;
  // send(m: 'inc', cb?: (e: number) => void): CountState;
  send(m: 'inc', cb?: (e: number) => void): CountState {
    const result = this.data + 1;
    if (cb !== undefined) {
      cb(result);
    }
    return new CountState(this.data + 1);
  }
}

Ошибка при отправке:

Свойство send в типе CountState не может быть назначено тому же свойству в базовом типе IState. Тип '(m: "inc", cb?: ((e: number) => void) | undefined) => CountState' нельзя назначить типу '(сообщение: I, обратный вызов?: ((e: E) => void) | undefined) => IState'. Типы параметров 'm' и 'message' несовместимы. Тип "I" нельзя назначить типу "inc". Ts(2416)

Если я добавлю эти две строки комментариев, так что

class CountState implements IState {
  constructor(public readonly data: number) {}
  send(m: 'inc', cb?: (e: number) => void): CountState;
  send(m: 'inc', cb?: (e: number) => void): CountState;
  send(m: 'inc', cb?: (e: number) => void): CountState {
    const result = this.data + 1;
    if (cb !== undefined) {
      cb(result);
    }
    return new CountState(this.data + 1);
  }
}

Он прекрасно компилируется, но выглядит действительно странно. Как я могу это исправить?

1 ответ

Я согласен с Тицианом Черниковой-Драгомиром, что это похоже на ошибку компилятора. Определение IState в основном гласит, что свойство "send" - это функция, которую можно вызывать с любыми типами "message", а параметр обратного вызова: "e" также может иметь любой тип.

export interface IState {
    send: <I, E>(message: I, callback?: (e: E) => void) => IState;
}

Между тем, в вашем примере использования вы явно перечисляете возможные типы, что противоречит определению интерфейсов. Странно, если это проходит компиляцию.

class Left implements IState {
    send(m: 'go-on', cb?: (e: never) => void): Left;
    send(m: 'turn-right', cb?: (e: never) => void): Right;
    send(m: 'go-on' | 'turn-right', cb?: any) {
        return m === 'go-on' ? new Left() : new Right();
    }
}

Глядя на код теста, который вы включили, вы в любом случае проверяете точный тип неизвестного типа "Оба", поэтому может показаться, что функциональность не теряется, даже если вы просто определите отдельные методы в классах Left и Right для каждое действие. э:

class Left {
    turnRight(...) {
        return new Right();
    }
    keepGoin(...) {
        return new Left();
    }
}

в отличие от использования общего метода "send" для каждого действия.

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