Seguidad de Tipos:
Definir los Tipos
Ponerlos en types.js
Cuando crees un nuevo proyecto Composi con create-composi-app
, en la carpeta src/js
habrá un archivo llamado types.js
. Esto importa los tipos predeterminados de @composi/core
que ya se utilizan en el código mismo en el archivo src/js/app.js
de su proyecto. De hecho, aquí es donde definirás los nuevos tipos de JSDoc que necesite para el código que escribas.
Tipos con JSDoc
Hay tres formas de implementar tipos en JSDoc: en parámetros de función o de método, como tipos definidos con la etiqueta @typedef
y con la etiqueta @type
para indicar el tipo de algo. JSDoc admite los siguientes tipos de JavaScript nativos: null
, undefined
, string
, number
, Array code>,
, Object
, Date
, RegExp
, Symbol
, así como todos los tipos DOM, como < code> DocumentoNode
, Element
, HTMLElement
, HTMLDivElement
, etc.
Ejemplo
Para mostrar cómo escribir el código, vamos a utilizar un ejemplo simple de una lista de tareas pendientes. Esto tendrá un objeto de estado y necesitaremos agregar y eliminar elementos del estado. También utilizaremos una unión etiquetada para mensajes, que usaremos para definir las acciones que utiliza el método de actualización del programa.
Estado
Comencemos con el estado. Para nuestra lista de tareas, necesitamos un objeto que contenga una variedad de elementos. Debido a que es una lista, necesitamos hacer un seguimiento de las claves para el DOM virtual, por lo que necesitamos una nueva propiedad clave en el estado. También necesitamos un lugar para almacenar lo que el usuario escriba en un input de forma para permitir agregar nuevos elementos. En base a esto, podríamos tener un objeto de estado como este:
const estado = {
claveNueva: 104,
valorDelInput: '',
ítems: [
{
clave: 101,
valor: 'Hablar con mi mamá'
},
{
clave: 102,
valor: 'Comer antojos'
},
{
clave: 103,
valor: 'Tomar una siesta'
}
]
}
Ahora definimos los tipos. Para hacerlo, creamos nuevos tipos usando la etiqueta @typedef
y definimos sus propiedades con la etiqueta @prop
.
// Definir un tipo Ítem:
/**
* @typedef {Object} Ítem
* @prop {number} clave
* @prop {string} valor
*/
// Definir el tipo Estado:
/**
* @typedef {Object} Estado
* @prop {number} claveNueva
* @prop {string} valorDelInput
* @prop {Ítem[]} ítems
*/
Primero definimos un tipo Ítem
. Luego, cuando definimos el tipo Estado
, definimos elementos como una matriz de tipo Ítem
. Así es como se dividen los tipos de objetos complejos en los más simples. Ahora podemos importar tanto el estado como los tipos de elementos para usar. Necesitaríamos el tipo de elemento si queremos iterar sobre los elementos del estado.
Los Mensajes
El entorno de ejecución Composi usa uniones etiquetadas para facilitar y simplificar el uso de mensajes. El problema es que esa unión resulta que es un poco difícil definir el tipo de los mensajes. Se provee una serie de cadenas de texto para los nombres de los mensajes a la función union
. Esta devuelva un objeto con métodos correspondientes a esos nombres. Estos métodos devuelven objetos de mensage de este tipo:
{
type: 'nombreDeMensaje',
data: undefined
}
El objeto de unión también tiene un método match
que espera dos argumentos, el mensaje enviado y un objeto de métodos para usar si hay una coincidencia. Para que haya una coincidencia, el objeto debe tener un método que corresponda a la propiedad type
del mensaje recibido. Eso significa que los nombres de la unión de mensajes que envía el mensaje deben ser idénticos al método de objeto con el que proporciona el método de coincidencia. Sí suena complicado, pero funciona muy intuitivamente. Aquí están nuestros mensajes y un método de acciones que trata con esos mensajes para nuestra hipotética lista de tareas pendientes:
// Crear una unión etiquetada de mensajes:
const Msj = union('ActualizarValorInput', 'AgregarÍtem', 'EliminarÍtem')
// Destructurar los métodos de la unión:
const {ActualizarValorInput, AgregarÍtem, EliminarÍtem} = Msj
// Ahora definir las acciones para estos mensajes:
function acciones(estado, msj, send) {
// Destructurar el estado
const estadoPrevio = {...{estado}}
// Averiguar cuál mensaje fue recibido:
return Msj.match(msj, {
ActualizarValorInput: value => {
estadoPrevio.valorDelInput = value
return estadoPrevio
},
AgregarÍtem: () => {
if (estadoPrevio.valorDelInput) {
estadoPrevio.ítems.push({
clave: estadoPrevio.claveNueva++,
valor: estadoPrevio.valorDelInput
})
estadoPrevio.valorDelInput = ''
} else {
alert('Por favor prevee un valor antes de someter.')
}
return estadoPrevio
},
EliminarÍtem(key) {
estadoPrevio.ítems = estadoPrevio.ítems.filter(ítem => ítem.key !== key)
return estadoPrevio
}
})
}
Si pasa el cursor sobre estos valores, verá que el editor ahora tiene idea de qué tipos son. Podemos resolver este problema proporcionando tipos para estos. Comenzaremos definiendo tipos para los métodos del objeto de acciones, luego definiremos la unión del mensaje. La razón, el método de coincidencia de la unión de mensajes necesita conocer el tipo del objeto de acciones. Los pondremos en el archivo src/js/types.js
de nuestros proyectos después del tipo que definimos para el estado de nuestro proyecto.
// Definir object de acciones:
/**
* @typedef {Object} MétodosDeAcción
* @prop {(valor: string) => Estado} ActualizarValorInput
* @prop {() => Estado} AgregarÍtem
* @prop {(clave: number) => Estado} EliminarÍtem
*/
// Definir la unión de mensajes.
// Usar MétodosDeAcción como el tipo devuelta por el método match.
/**
* @typedef {Object} UniónDeMensajes
* @prop {(msj: Mensaje, object: MétodosDeAcción) => Estado} match
* @prop {(valor: string) => Mensaje} ActualizarValorInput
* @prop {() => Mensaje} AgregarÍtem
* @prop {(clave: number) => Mensaje} EliminarÍtem
*/
Ahora bien podemos importar el tipo UniónDeMensajes
para usar con nuesta unión etiquetada:
// Importar UniónDeMensajes y aplicarlo al objeto Msj:
/** @type {import('../types').UniónDeMensajes} */
const Msj = union('ActualizarValorInput', 'AgregarÍtem', 'EliminarÍtem')
Al hacer esto nuestas funciones destructuradas tendran los tipos correctos. Ahora vamos a definir los tipos de la función de las acciones.
// Import default types for estado, message and send:
/**
* @typedef {import('../types').State} Estado
* @typedef {import('../types').Message} Mensaje
* @typedef {import('../types').Send} Send
*/
// Aplicar los tipos importados a los parámetros:
/**
* @param {Estado} estado
* @param {Mensaje} msj
* @param {Send} send
*/
function acciones(estado, msj, send) {
// Aplicar tipo Estado a estadoPrevio:
/** @type {Estado} */
const estadoPrevio = {...{estado}}
// Aplicar el tipo para la unión de mensajes aquí:
/** @type {import('../types').UniónDeMensajes} */
return Msj.match(msj, {
ActualizarValorInput: value => {
estadoPrevio.value = value
return estadoPrevio
},
AgregarÍtem: () => {
if (estadoPrevio.valorDelInput) {
estadoPrevio.ítems.push({
clave: estadoPrevio.claveNueva++,
valor: estadoPrevio.valorDelInput
})
estadoPrevio.valorDelInput = ''
} else {
alert('Por favor prevee un valor antes de someter.')
}
return estadoPrevio
},
EliminarÍtem(key) {
estadoPrevio.ítems = estadoPrevio.ítems.filter(ítem => ítem.key !== key)
return estadoPrevio
}
})
}
Con las adiciones de los tipos anteriores, podemos desplazar el cursor sobre cualquier propiedad y ver los tipos correctos que se muestran. Esto te permite saber que TypeScript comprende los tipos que has proporcionado para tu código. También significa que si alguien intenta cambiar el código por usar un tipo incorrecto, inmediatamente recibirá una advertencia de Visual Studo Code de que el tipo es incorrecto.
Aprender Más Sobre Los Tipos
Acabamos de tocar la superficie de los tipos con JSDoc. Para obtener más información, puede consultar la documentación de JSDoc . También escribí dos artículos sobre los tipos para JavaScript: JavaScript Type Linting /a> y Type Safe JavaScript with JSDoc.
Descargar ejemplos
Puede descargar una colección de ejemplos de proyectos utilizando @comosi/core que están completamente tipados como se describió anteriormente. Estos están disponibles en https://github.com/composi/examples . Examine el archivo types.js
en cada carpeta src/js
del proyecto para ver cómo se definen los tipos personalizados. Luego examine todos los demás archivos JavaScript para ver cómo se importan y usan los tipos.