Quero usar protetores de tipo para que hasNext possa filtrar os tipos.
type Queue1 = {
operation: 'move';
offset: number;
};
type Queue2 = {
operation: 'eat';
food: string;
};
type Queue3 = {
operation: 'run';
type: 'fast' | 'slow';
};
type ExampleQueue = Queue1 | Queue2 | Queue3;
export class ExampleEvent {
queue: Array<ExampleQueue>;
constructor(queue: Array<ExampleQueue> = []) {
this.queue = queue;
}
get event() {
return this.queue[0];
}
enqueue(value: ExampleQueue) {
this.queue.push(value);
}
dequeue() {
return this.queue.shift();
}
size() {
return this.queue.length;
}
hasNext<T extends ExampleQueue['operation'] = ExampleQueue['operation']>(
operation?: T
): this is ExampleEvent & { event: Extract<ExampleQueue, { operation: T }>; dequeue(): Extract<ExampleQueue, { operation: T }> } {
if (this.size() <= 0) return false;
return operation ? this.event.operation === operation : true;
}
}
Escrevi o código acima e usei-o conforme mostrado abaixo.
const event = new ExampleEvent();
if (event.hasNext('move')) {
event.event.offset // correct!
event.dequeue().offset // error!
}
Por que funciona para event.event.offset, mas não para event.dequeue().offset.type?
hasNext<T extends ExampleQueue['operation'] = ExampleQueue['operation']>(
operation?: T
): this is { event: Extract<ExampleQueue, { operation: T }>; dequeue(): Extract<ExampleQueue, { operation: T }> } & ExampleEvent {
if (this.size() <= 0) return false;
return operation ? this.event.operation === operation : true;
}
O que é incomum é que quando escrevo como acima, ele é validado corretamente. Não sei o que há de errado, é só que a ordem é invertida de cima.
Além disso, se eu escrever como abaixo, obtenho o resultado que desejo.
hasNext<T extends ExampleQueue['operation'] = ExampleQueue['operation']>(
operation?: T
): this is Omit<ExampleEvent, 'event' | 'dequeue'> & { event: Extract<ExampleQueue, { operation: T }>; dequeue(): Extract<ExampleQueue, { operation: T }> } {
if (this.size() <= 0) return false;
return operation ? this.event.operation === operation : true;
}
Interseções de tipos de funções são tratadas como sobrecargas . Quando você chama uma função sobrecarregada, o TypeScript precisa escolher uma assinatura de chamada apropriada... e geralmente faz isso verificando cada uma delas em ordem e selecionando a primeira que corresponda à forma como você a chamou. Isso permite que você escreva coisas como
Observe que o procedimento acima se comportará de maneira completamente diferente se você reordenar as assinaturas de chamada:
Portanto, as sobrecargas são, em geral, dependentes da ordem e as interseções de funções são equivalentes às sobrecargas. Isso explica o comportamento que você está vendo.
Sim, é estranho, já que as interseções são conceitualmente independentes da ordem, mas as funções são uma exceção a isso.
Em particular, isso falha porque
event.dequeue
possui duas assinaturas de chamada com argumento zero, a primeira das quais retornaExampleQueue | undefined
:Se você mudar a ordem da interseção, será bem-sucedido porque agora a primeira assinatura de chamada com argumento zero retorna
Queue1
:Mas mesmo isso não é o que você deseja, já que você não pretende que haja duas assinaturas de chamada. Portanto, se você usar
Omit
a assinatura de chamada original, também terá sucesso:E então é essa terceira abordagem que está de acordo com o que você está procurando e como eu recomendo escrever isso.
Link do Playground para o código