Una vez ya conocemos la sintaxis básica de JSDoc que vimos en el artículo anterior, podemos echar un vistazo a algunos detalles un poco más avanzados.
Eventos y callbacks
Al crear eventos y callbacks, algo muy frecuente en Javascript, es posible que los tipos de datos de sus componentes no se identifiquen correctamente en nuestro código. Para evitar esto, podemos utilizar las siguientes etiquetas de JSDoc:
Etiqueta | Descripción | Alias |
---|---|---|
@callback | Define una función que se pasará por parámetro como callback. | - |
@namespace | Indica que estamos en un espacio de nombres para organizar información. | - |
@event | Indica un evento que se puede emitir. | - |
Observa el siguiente ejemplo donde tenemos una función clickEmitter
que espera una función como parámetro. En su interior, usamos un temporizador para ejecutar una nueva función cuando pase 1 segundo, que ejecute la función callback
pasada por parámetro, pasándole el mensaje Click!
.
En esta función se le añaden las etiquetas de JSDoc como hemos visto hasta ahora. El cambio está en como se comenta al utilizar la función clickEmitter
y no al declararla:
/**
* Emite un evento de "click".
* @param {clickCallback} callback Función a ejecutar cuando se produce el click.
*/
function clickEmitter(callback) {
setTimeout(() => callback("Click!"), 1000);
}
/**
* Función de devolución de llamada para el evento de click.
* @callback clickCallback
* @param {string} mensaje Mensaje a mostrar.
*/
clickEmitter((message) => console.log(message));
Observa que lo que hacemos es definir la etiqueta @callback
, ponerle un nombre y definir lo que esperamos que reciba.
Por otro lado, podemos utilizar la etiqueta @event
cuando estemos trabajando con emisores de eventos y tengamos que emitir o recibir alguno.
Por ejemplo, observa este ejemplo donde creamos un objeto DataEmitter
que usaremos como espacio de nombres para organizar métodos que gestionan eventos. Con la etiqueta @namespace
de JSDoc la documentamos.
Luego, dentro del objeto, utilizamos @event
para avisar que ese objeto, clase o estructura tiene la capacidad de emitir ciertos eventos, indicandole su tipo y los datos que emite:
/**
* Espacio de nombres para gestionar eventos relacionados con datos.
* @namespace DataEmitter
*/
const DataEmitter = {
/**
* Emite un evento personalizado `read` cuando el usuario termina de leer un post.
* @event DataEmitter#read
* @type {Object}
* @property {string} post El nombre del post leído
*/
/**
* Función que emite el evento
* @param {string} post El nombre del post
*/
markAsRead(post) {
const event = new CustomEvent("read", { detail: { post } })
document.dispatchEvent(event);
}
}
DataEmitter.markAsRead("introduccion-a-jsdoc");
Finalmente, documentamos el método como una función normal, a las que estamos acostumbrados.
Union types y enumeraciones
Mediante JSDoc podemos definir los tipos de una variable, limitando a un único tipo de dato. Sin embargo, puede que en nuestro caso queramos algo menos rígido. Tenemos varias opciones.
La unión de tipos se puede utilizar en JSDoc para limitar los valores que puede tomar una variable, pero manteniendo cierta flexibilidad.
Por ejemplo, imagina que quieres crear variables que puedan contener uno de los días de la semana. Te interesa que se puedan escribir días como Monday
o Tuesday
, pero que no puedas escribir Paco
. Si definimos el tipo como string
estamos permitiendo cualquier cadena de texto.
Con la sintaxis de las union types podemos ser más específicos. Simplemente se basa en separar las opciones mediante pipes |
. En este ejemplo lo he separado en varias lineas por legibilidad:
/**
* @typedef { 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday'
* | 'Saturday' | 'Sunday' } DayOfWeek
*/
/** @type {DayOfWeek} */
const day = "Sunday"
De hecho, en nuestro editor, al crear la constante day
e intentar asignarle un valor, el autocompletado nos proporcionará los valores que hemos puesto en la parte superior.
Ten en cuenta que las uniones permiten también uniones avanzadas, como definir
{Array<string|number>}
para permitir un array donde sus elementos seanstring
onumber
.
Por otro lado tenemos las enumeraciones. Son muy similares a las union types, pero son más específicas y se utilizan para describir un conjunto de posibilidades, opciones o estados dentro de un cierto contexto.
Etiqueta | Descripción |
---|---|
@enum | Define una enumeración, con varios valores. |
Veamos un ejemplo utilizando @enum
. Hemos creado un objeto Day
que contiene una serie propiedades con un valor que lo identifica (en este caso un texto, aunque podría ser un número u otro valor):
/** @enum {string} */
const Day = {
MONDAY: 'Monday',
TUESDAY: 'Tuesday',
WEDNESDAY: 'Wednesday',
THURSDAY: 'Thursday',
FRIDAY: 'Friday',
SATURDAY: 'Saturday',
SUNDAY: 'Sunday'
};
/** @typedef {keyof typeof Day} WeekDay */
/** @type {WeekDay} */
const today = "MONDAY";
Un poco más abajo, establecemos un tipo con @typedef
llamado WeekDay
que es la key del objeto definido anteriormente (keyof typeof Day). A continuación, creamos una constante today
a la que le decimos que su tipo es una de las keys de Day
(o sea, WeekDay
).
Si todo ha ido bien en cualquiera de las dos formas, el editor nos debería mostrar como opciones los días de la semana en el autocompletado.
Genéricos
Los genéricos son una forma de documentar funciones mediante plantillas que nos permiten que los tipos sean más flexibles y reutilizables. Para ello, utilizaremos la etiqueta @template
de JSDoc y le pondremos un nombre al genérico, normalmente una letra en mayúsculas.
Etiqueta | Descripción |
---|---|
@template | Define un genérico (plantillas). Son estructuras que pueden tener tipos diferentes. |
Observa este ejemplo de una función que devuelve el primer elemento de un array:
/**
* Función genérica que devuelve el primer elemento de un array.
* @template T
* @param {Array<T>} array Array de donde se extraerá el elemento.
* @returns {T | undefined} El primer elemento del array si no está vacío.
*/
function firstElement(array) {
return array[0];
}
- 1️⃣ Con
@template
elegimos un nombre para el genérico. En este caso, la letraT
, pero podría ser otra. - 2️⃣ En el parámetro usamos
Array<T>
. Indica que los arrays tienen elementos (genéricos) de tipoT
. - 3️⃣ La función devuelve elementos de tipo
T
oundefined
si está vacía.
Extender clases
Es muy posible que en nuestros fragmentos de código queramos extender clases con funcionalidad heredada, de modo que podamos reutilizar fragmentos de código facilmente. En JSDoc tenemos a nuestra disposición la etiqueta @extends
, con la que podemos indicar que una clase extiende de otra, heredando sus propiedades y características.
Observa este ejemplo donde definimos la clase EventBase
y luego creamos la clase Button
que extiende de la anterior. En general
/**
* Clase base que proporciona funcionalidades de registro de eventos.
* @class
*/
class EventBase {
// ...
on(event, listener) { /* ... */ }
emit(event, ...args) { /* ... */ }
}
/**
* Clase que representa un botón.
* @class
* @extends EventBase
*/
class Button extends EventBase {
// ...
click() { /* ... */ }
}
// Ejemplo de uso:
const button = new Button('Enviar');
Por lo habitual, no suele hacer falta documentar esto con JSDoc para tener un autocompletado adecuado, pero si queremos añadirlo a nuestra documentación automática, nos puede venir muy bien.
Otro detalle interesante, muy relacionado a esto, es la posibilidad de utilizar la etiqueta @mixin
para documentar mixins de nuestro código, es decir, porciones de código que están construidas para compartirse y «mezclarse» con otros elementos.
Otras etiquetas
Existen muchas otras etiquetas adicionales para JSDoc, entre las que se encuentran algunas como las siguientes:
Etiqueta | Descripción |
---|---|
@module | Indica que el archivo pertenece a un módulo concreto. |
@async | Indica que la función es una función asíncrona. |
@see | Hace referencia a otras partes relacionadas del código. |
@link | Etiqueta en linea para hacer referencia a un elemento: {@link name} . |
@example | Establece un ejemplo de código para añadir contexto. |
@throws | Indica que puede lanzar una excepción. |
@deprecated | Indica que el elemento está marcado como obsoleto y no debe usarse. |