Tipos en TypeScript
Experimentando con los tipos de TypeScript.
TypeScript es mi parada actual en este viaje de aprendizaje y, como siempre, uso Exercism para practicar el nuevo lenguaje.
El ejercicio Resistor Color Duo fue una buena introducción a sus tipos.
Descripción del problema
Las dos primeras bandas de una resistencia tienen una codificación sencilla: cada color corresponde a un número. Por ejemplo, una banda marrón (valor 1) seguida de una verde (valor 5) representa el número 15.
Los colores se codifican así:
- Negro: 0
- Marrón: 1
- Rojo: 2
- Naranja: 3
- Amarillo: 4
- Verde: 5
- Azul: 6
- Violeta: 7
- Gris: 8
- Blanco: 9
¿Cómo representamos esta información en TypeScript?
Arreglo
Documentación: tipos cotidianos.
Como los números están limitados al intervalo 0..9, podemos crear un arreglo así:
const bandColors = ["black", "brown", "red", "orange", "yellow", "green", "blue", "violet", "grey", "white"]
El índice de cada color es su valor.
Esto resuelve el problema, pero no responde a preguntas como:
- ¿Cómo limitar el valor de la resistencia al intervalo 0..9?
- ¿Cómo almacenar información cuyas claves no son consecutivas?
Tupla
Documentación: tipos de objetos.
Permite responder a la pregunta: ¿Cómo limitar el valor de la resistencia al intervalo 0..9?
type colorType = [string, string, string, string, string, string, string, string, string, string]
// OK
const colors: colorType = ["black", "brown", "red", "orange", "yellow", "green", "blue", "violet", "grey", "white"]
// Wrong: The tuple colorType can't have more than 10 elements
const colors: colorType = ["black", "purple", "brown", "red", "orange", "yellow", "green", "blue", "violet", "grey", "white"]
Firma de índice
Documentación: tipos de objetos.
Este enfoque responde a la pregunta: ¿Cómo almacenar información cuyas claves no son consecutivas?
Podemos definir un tipo Resistors cuyas keys sean los colores y cuyos values sean los valores de las bandas. Así podemos almacenar cualquier información sin limitarnos a los índices numéricos consecutivos de un arreglo o tupla.
type BandColor = 'black' | 'brown' | 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'violet' | 'grey' | 'white';
type ResistanceValue = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
**type Resistors = {
[color in BandColor]: ResistanceValue;
}**
const resistors: Resistors = {
'black': 0,
'brown': 1,
'red': 2,
'orange': 3,
'yellow': 4,
'green': 5,
'blue': 6,
'violet': 7,
'grey': 8,
'white': 9,
};
Técnicamente podríamos tener más de diez claves, pero los valores tendrían que repetirse: un valor no incluido en el tipo BandColor produciría un error.
Esta guía es muy instructiva: Firmas de índice.
Map
La solución anterior es buena, pero básicamente estamos recreando un Map. Es mejor usarlo directamente que reinventar la rueda.
const resistors: Map<BandColor, ResistanceValue> = new Map([
['black', 0],
['brown', 1],
['red', 2],
['orange', 3],
['yellow', 4],
['green', 5],
['blue', 6],
['violet', 7],
['grey', 8],
['white', 9],
])
Solución
Puedes ver mi solución final aquí: https://exercism.io/tracks/typescript/exercises/resistor-color-duo/solutions/045c8237cf2f4c9db56d8f11ad39bd5c.
type BandColor = 'black' | 'brown' | 'red' | 'orange' | 'yellow' | 'green' | 'blue' | 'violet' | 'grey' | 'white';
type ResitanceValue = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
const resistors: Map<BandColor, ResitanceValue> = new Map([
['black', 0],
['brown', 1],
['red', 2],
['red', 2],
['orange', 3],
['yellow', 4],
['green', 5],
['blue', 6],
['violet', 7],
['grey', 8],
['white', 9],
])
export class ResistorColor {
private colors: string[]
constructor(colors: string[]) {
if (colors.length < 2)
throw new Error('At least two colors need to be present')
this.colors = colors
}
value = (): number => {
return parseInt(
this.colors
.slice(0, 2)
.reduce((accumulator: string, currentColor: string): string => {
return accumulator + (resistors.get(currentColor as BandColor)?.toString() ?? '')
}, '')
)
}
}
La propiedad colors conserva una copia de los valores recibidos en el constructor.
El método value es interesante. Primero copiamos los dos primeros elementos del arreglo con this.colors.slice(0, 2). Después reducimos ese arreglo a la suma de sus valores con la función:
reduce((accumulator: string, currentColor: string): string => {
return accumulator + (resistors.get(currentColor as BandColor)?.toString() ?? '')
}, '')