Тип строгой условной фильтрации (Typescript) - PullRequest
0 голосов
/ 18 июня 2020

Я пытаюсь создать интерфейс строгой фильтрации:

type Filter<I, O extends I = I> = (value: I) => I extends O ? boolean : false

Написав эту строку, я хочу определить ограничение типа для функции, которая:

  1. получает значение типа I
  2. возвращает false, если значение не ожидаемого типа (I extends O не true)
  3. возвращает true | false если значение является ожидаемого типа и соответствует фильтру

Но Typescript игнорирует условный тип возврата:

type MessageA = { type: 'A' }
type MessageB = { type: 'B' }
type Message = MessageA | MessageB

const filter: Filter<Message, MessageA> = ({ type }) => type === 'A'
const inputMessage: Message = { type: 'B' }

if (filter(inputMessage)) {
  // the following line produces error
  const message: MessageA = inputMessage
  // because according to TS compiler
  // inputMessage is still `MessageA | MessageB`
}

Логически filter(inputMessage) МОЖЕТ дать true если inputMessage равно типа MessageA.

Я хотел бы понять «возможно ли это?» и «как это правильно написать?» если есть.


Я не ограничен версией машинописного текста, на данный момент установлена ​​последняя (на данный момент) версия Typescript 3.9.5. Я использую VSCode 1.46, неважно, имеет ли это значение.

1 Ответ

1 голос
/ 18 июня 2020

Решение 1

Вы можете попробовать следующее:

type Filter<T, U extends T> = (candidate: T) => candidate is U;

const filter: Filter<Message, MessageA> = (message): message is MessageA => message.type === 'A'

Но вам все равно нужно явно указать тип возвращаемого значения (: message is MessageA).

Решение 2

Это более сложное, но оно делает ваши защитные ограждения (уточнения) типобезопасными.

Создайте фабрику для таких охранников. :

namespace Refinement {
  class Hit<T> {
    constructor(readonly value: T) {}
  }

  class Miss {}

  type Result<T> = Hit<T> | Miss;

  export function hit<T> (value: T) {
    return new Hit(value);
  }

  export const miss = new Miss();

  export function create<T, U extends T>(refine: (candidate: T) => Result<U>): (candidate: T) => candidate is U {
    return (candidate): candidate is U => refine(candidate) instanceof Hit;
  }
}

Использование:

declare const inputMessage: Message;

const filter = Refinement.create(
  (message: Message) => message.type === 'A'
    ? Refinement.hit(message)
    : Refinement.miss
)

if (filter(inputMessage)) {
  inputMessage; // MessageA
}

Например, этот подход используется fp-ts.

...