Qual é o uso dos tipos de união no TypeScript?
Primeiro, não existem diretrizes oficiais (que eu saiba, a partir de 8 de março de 2015) sobre o uso deste novo recurso do TypeScript chamado tipos união. Bem, eu tentei encontrar uma maneira de usá-los sozinho. Não tem sido fácil. Ainda estou lutando para encontrar um lugar para os tipos união no meu código:
- eles não podem ser usados com interfaces, já que não há uma verificação confiável em tempo de execução que indique se um valor implementa uma interface
- eles não podem ser usados com genéricos devido a problemas com a inferência de tipo: https://github.com/Microsoft/TypeScript/issues/2264
O que resta?
- misturando primitivos (desestruturação via typeof)
- misturando classes (desestruturando via instanceof)
Estou certo de que esses 2 são os únicos modos válidos de consumir tipos de união em TypeScript no momento?
Se for o caso, existem planos para estender sua aplicabilidade?
Se não, o que estou perdendo?
Resposta #1
Não acho que você possa obter uma única resposta para isso; no mínimo, os tipos de união lidam com muitos casos de uso diferentes que permitem aos usuários escrever TypeScript de uma maneira semelhante à forma como poderiam escrever JavaScript canônico. Aqui está uma tentativa de explicar de qualquer forma.
Quando você tem dois tipos que são estruturalmente (e idealmente funcionalmente) semelhantes, um benefício é que você pode usar a interseção de propriedades que eles compartilham como um ponto de abstração.
Nos casos em que você tem dois tipos que não são estruturalmente semelhantes, mas têm paralelos em como suas informações podem ser tratadas, pode ser útil criar uma função para normalizar essas informações em uma forma abstrata comum.
Como exemplo, templates marcados do ES6 são muito semelhantes a expressões de chamada:
f `hello ${ 123 } ${ 456 } world!`
O template marcado acima se desabona em algo semelhante ao seguinte:
var _t = ["hello ", " ", " world!"];
_t.raw = ["hello ", " ", " world!"];
f(_t, 123, 456);
Você poderia imaginar que em um compilador, seria ideal se eles pudessem ser tratados igualmente às expressões de chamada quando conveniente, mas manipulados de forma especial quando os dois forem suficientemente distintos. Vamos tentar modelar isso:
interface TemplateExpression extends Expression {
// ...
}
interface TaggedTemplateExpression extends Expression{
callTarget: Expression;
template: TemplateExpression;
}
interface CallExpression extends Expression {
callTarget: Expression;
arguments: Expression[];
}
type CallLikeExpression = CallExpression | TaggedTemplateExpression;
function getEffectiveCallArguments(expr: CallLikeExpression): Expression[] {
if (expr.kind === SyntaxKind.CallExpression {
// ...
}
else {
// ...
}
// OR, if you don't want to have a tag,
// you could check expr's properties
if ((<CallExpression>expr).arguments) {
// treat as CallExpression
}
else {
// treat as TaggedTemplateExpression
}
}
Tanto CallExpression quanto TaggedTemplateExpression têm um callTarget em comum, então você sempre pode obter a expressão sendo invocada a partir de um CallLikeExpression simplesmente usando a propriedade callTarget.
Se você quiser obter os argumentos que serão passados para call, você pode extraí-los usando getEffectiveCallArguments, que entende como lidar com as diferenças entre os dois.
