Desmitificación de uniones diferenciables de TypeScript trucos CSS
TypeScript es una gran herramienta para escribir JavaScript extensible. Este es más o menos el estándar de facto para la web cuando se trata de grandes proyectos de JavaScript. Tan grande como es, hay algunas partes difíciles para los que no están acostumbrados. Una de esas áreas son las uniones discriminatorias de TypeScript.
En particular, teniendo en cuenta el siguiente código:
interface Cat {
weight: number;
whiskers: number;
}
interface Dog {
weight: number;
friendly: boolean;
}
let animal: Dog | Cat;
... muchos desarrolladores se sorprenden (y tal vez incluso se indignan) cuando se dan cuenta de esto cuando lo hacen animal.
solo weight
la propiedad es válida en su lugar whiskers
o friendly
. Al final de esta publicación, esto tendrá mucho sentido.
Antes de sumergirnos en él, repasemos rápidamente (y es necesario) los tipos estructurales y cómo se diferencian de los tipos nominales. Esto construirá nuestra discusión sobre las mejores alianzas de TypeScript.
tipo de estructura
La mejor forma de presentar el tipo de estructura es combinándola con NadaLa mayoría de los idiomas ingresados que puede haber utilizado se ingresan nominalmente. Piense en este código C # (Java o C ++ se ven similares):
class Foo {
public int x;
}
class Blah {
public int x;
}
A pesar de que Foo
y Blah
Tienen exactamente la misma estructura, no se pueden atribuir entre sí. El siguiente código:
Blah b = new Foo();
... produce este error:
Cannot implicitly convert type 'Foo' to 'Blah'
Esta estructura Estas clases son irrelevantes Tipo de variable Foo
solo se puede asignar a Foo
clase (o sus subclases).
TypeScript funciona al revés. TypeScript considera que los tipos son compatibles si son iguales estructura- de ahí el nombre, Tipificación estructural. ¿Tómalo?
Por lo tanto, lo siguiente funciona sin errores:
class Foo {
x: number = 0;
}
class Blah {
x: number = 0;
}
let f: Foo = new Blah();
let b: Blah = new Foo();
como el tipo del conjunto coincidente de valores
Destruyamos esta casa. Dado este código:
class Foo {
x: number = 0;
}
let f: Foo;
f
es una variable que contiene cada objeto coincidente estructura instancia creada por Foo
clase en este caso significa un x
Propiedades que representan números. Esto significa que se aceptarán incluso los objetos JavaScript ordinarios.
let f: Foo;
f = {
x: 0
}
Unión
Gracias por estar siempre conmigo. Volvamos al código desde cero:
interface Cat {
weight: number;
whiskers: number;
}
interface Dog {
weight: number;
friendly: boolean;
}
sabemos:
let animal: Dog;
... hacer animal
cada objeto con la misma estructura Dog
interfaz. Entonces, ¿qué significa lo siguiente?
let animal: Dog | Cat;
este tipo animal
como cualquier objeto coincidente Dog
interfaz, o cualquier objeto coincidente Cat
interfaz.
Entonces por qué animal
- porque ahora existe - solo nos permite acceder weight
¿Propiedad? En pocas palabras, es porque TypeScript no sabe de qué tipo es. animal
tiene que ser Dog
o Cat
pero yo puedo es uno de ellos (o ambos, pero seamos simples) .si se nos permite el acceso friendly
propiedad, pero la instancia es en última instancia Cat
reemplazar Dog
También por whiskers
propiedad si el objeto resulta Dog
.
Una unión de tipos es una unión de valores válidos, no una unión de propiedades. Los desarrolladores a menudo escriben esto:
let animal: Dog | Cat;
... y espera animal
tener articulaciones Dog
y Cat
Característica. Sin embargo, este es otro error, esto aclara animal
como tener un valor coincide con una unión válida Dog
valor y válido Cat
valores Pero TypeScript solo le permite acceder a sus propiedades conocimiento allí. Actualmente, esto significa todo tipo de propiedad en la unión.
estrecho
Ahora tenemos esto:
let animal: Dog | Cat;
¿Cómo lidiamos con animal
ya tengo Dog
cuando es Dog
y acceso a la propiedad Dog
interfaz de la misma cuando es Cat
Por el momento podemos usar in
Este es un operador de JavaScript anticuado que quizás no vea muy a menudo, pero esencialmente nos permite Probar si la propiedad está en el objeto.. como esto:
let o = { a: 12 };
"a" in o; // true
"x" in o; // false
Resulta que TypeScript funciona con in
operador. Veamos cómo:
let animal: Dog | Cat = {} as any;
if ("friendly" in animal) {
console.log(animal.friendly);
} else {
console.log(animal.whiskers);
}
Este código no crea errores. Cuando está dentro if
bloque, TypeScript sabe que hay un friendly
propiedad tan tirada animal
ya tengo Dog
.cuando en else
bloques que TypeScript trata de manera similar animal
ya tengo Cat
Incluso puedes ver esto manteniendo el cursor del mouse sobre los objetos animales dentro de estos bloques en el editor de código:
sindicato discriminado
Es posible que desee que esta publicación de blog termine aquí, pero lamentablemente reducir la unión de tipos al verificar la existencia de una propiedad es muy limitado. Funciona muy bien para nuestras tareas domésticas. Dog
y Cat
tipos, pero cuando tenemos más tipos y hay más superposición entre estos tipos, las cosas pueden volverse más complejas y frágiles fácilmente.
Los sindicatos discriminados son útiles aquí. Mantendremos todo igual que antes, excepto agregar una propiedad para cada tipo cuya única tarea es distinguir (o "diferenciar") los tipos:
interface Cat {
weight: number;
whiskers: number;
ANIMAL_TYPE: "CAT";
}
interface Dog {
weight: number;
friendly: boolean;
ANIMAL_TYPE: "DOG";
}
Aviso ANIMAL_TYPE
Dos tipos de propiedad. No confunda esto con una cadena con dos valores diferentes; este es un tipo literal. ANIMAL_TYPE: "CAT";
Es un tipo que contiene todas las cadenas. "CAT"
eso es todo.
Ahora nuestra verificación es más confiable:
let animal: Dog | Cat = {} as any;
if (animal.ANIMAL_TYPE === "DOG") {
console.log(animal.friendly);
} else {
console.log(animal.whiskers);
}
Suponga que cualquier tipo involucrado en la unión es para ANIMAL_TYPE
Propiedad, esta verificación se vuelve confiable.
El único inconveniente es que ya tiene una nueva propiedad con la que lidiar. Cada vez que crea una instancia Dog
o un Cat
debes proveer único el valor correcto de ANIMAL_TYPE
Pero no te preocupes por olvidar, porque TypeScript te lo recordará. 🙂
Más información
Si quieres aprender más, te recomiendo TypeScript Acerca de los documentos minimizadosEsto proporcionará una reflexión más profunda de lo que estamos discutiendo aquí. tipo de predicadoLe permiten definir sus propios controles personalizados para reducir los tipos sin usar un tipo de discriminador y sin depender de in
Palabras clave.
en conclusión
Al comienzo de esta publicación, dije que tiene sentido por qué weight
es la única propiedad disponible en el siguiente ejemplo:
interface Cat {
weight: number;
whiskers: number;
}
interface Dog {
weight: number;
friendly: boolean;
}
let animal: Dog | Cat;
Lo que aprendimos es que TypeScript solo sabe animal
quizás Dog
o un Cat
pero no ambos Por lo tanto, todo lo que obtenemos es weight
que es la única propiedad común entre los dos.
el concepto de sindicato discriminado Así es como TypeScript distingue estos objetos y lo hace realmente agradable, incluso para conjuntos de objetos más grandes. Por lo tanto, necesitamos crear un nuevo ANIMAL_TYPE
Ambos tipos de propiedades contienen un valor literal que podemos usar para verificar. Por supuesto, el seguimiento es otra cuestión, pero también brinda resultados más confiables, algo que queremos proporcionar TypeScript en primer lugar.
Deja una respuesta