Casi ninguna interfaz real muestra siempre exactamente el mismo contenido: una tarea puede estar pendiente, en progreso o hecha, y probablemente quieras mostrar una insignia distinta según cuál sea su estado. En la lección anterior ya usaste, de pasada, un operador ternario dentro de una interpolación; en esta lección profundizarás en las distintas formas de renderizar contenido condicionalmente en Lit, cuándo conviene cada una, y las aplicarás para dar a <task-card> una insignia de estado que cambia según los datos de la tarea.
Contenido
- Renderizado condicional: no es magia, es JavaScript
- El operador ternario: la opción más habitual
- El operador
&&: mostrar u ocultar sin alternativa - Extraer la lógica a funciones auxiliares
- Cuidado con los "falsy" al usar
&& - La directiva
when: una mención de paso - Aplicándolo a
<task-card>: insignia según el estado
- Renderizado condicional: no es magia, es JavaScript
Una de las ideas más importantes que hay que interiorizar sobre Lit es que no existe una sintaxis especial de plantillas para condicionales, del estilo {% if %} de otros motores de plantillas que quizá conozcas de otros contextos. Lit no necesita inventar una sintaxis propia porque, como viste en la lección "El Motor de Plantillas de Lit", dentro de ${} se puede escribir cualquier expresión JavaScript válida, y JavaScript ya dispone de operadores capaces de expresar una condición en forma de expresión: el operador ternario y el operador &&.
Esta es, de hecho, una de las señas de identidad del diseño de Lit: en lugar de crear un lenguaje de plantillas paralelo con sus propias reglas que aprender, reutiliza JavaScript tal cual. Quien ya sabe JavaScript ya sabe, sin darse cuenta, gran parte de "la sintaxis de condicionales de Lit".
- El operador ternario: la opción más habitual
El operador ternario (condición ? valorSiVerdadero : valorSiFalso) es la forma más común de renderizar condicionalmente en Lit cuando hay dos alternativas posibles, ambas con contenido que mostrar:
El resultado de cada rama del ternario puede ser, igual que en cualquier interpolación, texto simple, un número, o incluso otra plantilla html anidada (como se vio en la lección anterior):
render() {
return html`
<p>
${this.completada
? html`<span class="icono-ok">✓</span> Finalizada`
: html`<span class="icono-pendiente">○</span> Pendiente`}
</p>
`;
}Aquí cada rama del ternario devuelve una plantilla html distinta, con su propio <span> con una clase diferente. Lit gestiona esto sin ningún problema: cada vez que render() se ejecuta, evalúa la condición y coloca en el DOM el resultado de la rama correspondiente, sustituyendo lo que hubiera antes si la rama activa ha cambiado desde el renderizado anterior.
- El operador
&&: mostrar u ocultar sin alternativa
&&: mostrar u ocultar sin alternativaCuando solo interesa mostrar algo si se cumple una condición, y no hay ningún contenido alternativo que mostrar en caso contrario, el patrón habitual es el operador lógico &&:
render() {
return html`
<article>
<h3>${this.titulo}</h3>
${this.urgente && html`<p class="aviso">⚠ Esta tarea es urgente</p>`}
</article>
`;
}Este patrón se apoya en cómo evalúa JavaScript el operador &&: si el operando de la izquierda (this.urgente) es "falso", la expresión completa se evalúa como ese valor falso, sin llegar a evaluar el operando de la derecha; si es "verdadero", la expresión completa toma el valor del operando de la derecha (la plantilla html del aviso). Cuando this.urgente es false, la interpolación completa vale false, y Lit, al encontrar false (o null, o undefined) como resultado de una interpolación de contenido, sencillamente no renderiza nada en ese punto, sin dejar ningún rastro en el DOM.
Este patrón es preferible al ternario cuando no existe una alternativa razonable que mostrar en el caso contrario; escribir this.urgente ? html\...` : ''funciona igual de bien, perothis.urgente && html`...`` comunica con más claridad la intención de "mostrar esto solo si se cumple la condición, y nada en caso contrario".
- Extraer la lógica a funciones auxiliares
Cuando la lógica condicional empieza a tener más de dos o tres ramas, o cuando el cálculo de qué mostrar es algo más elaborado que una simple comparación, resulta mucho más legible extraer esa lógica a un método propio de la clase (una función auxiliar) y llamarlo desde dentro de la interpolación:
import { LitElement, html } from 'lit';
class TaskCard extends LitElement {
constructor() {
super();
this.estado = 'en-progreso'; // 'pendiente' | 'en-progreso' | 'hecha'
}
renderIndicadorEstado() {
if (this.estado === 'hecha') {
return html`<span class="indicador ok">✓ Hecha</span>`;
}
if (this.estado === 'en-progreso') {
return html`<span class="indicador progreso">◐ En progreso</span>`;
}
return html`<span class="indicador pendiente">○ Pendiente</span>`;
}
render() {
return html`
<article>
<h3>${this.titulo}</h3>
${this.renderIndicadorEstado()}
</article>
`;
}
}Este patrón —un método renderAlgo() que la propia clase llama desde render()— es extremadamente habitual en el código Lit real, incluido en la documentación oficial y en proyectos grandes. Permite usar sentencias completas de JavaScript (if, switch, bucles, variables intermedias con const) que no cabrían directamente dentro de un ${}, ya que ahí solo se admiten expresiones, como se explicó en la lección anterior. El único requisito es que el método, igual que el propio render(), termine devolviendo un valor válido para interpolar: una plantilla html, texto, un número, o null/undefined/false si no hay que mostrar nada.
- Cuidado con los "falsy" al usar
&&
&&El patrón condición && html\...`visto en el apartado 3 esconde una trampa clásica de JavaScript que conviene conocer. El operador&&no comprueba estrictamente si la condición estrue; comprueba si es "verdadera" en el sentido amplio de JavaScript (lo que se conoce como "truthy"), y hay varios valores que JavaScript considera "falsy" además de false: 0, ''(cadena vacía),null, undefinedyNaN`.
El problema aparece cuando la condición es un número que puede legítimamente valer 0:
render() {
return html`
<article>
<h3>${this.titulo}</h3>
${this.numeroDeComentarios && html`<p>${this.numeroDeComentarios} comentarios</p>`}
</article>
`;
}Si this.numeroDeComentarios vale 0, la expresión 0 && html\...`se evalúa como0(no como la plantilla), y aquí está la trampa: Lit sí renderiza el número0como contenido de texto, así que en pantalla aparecerá literalmente un0` suelto, en lugar de no mostrar nada. La solución habitual es forzar una comparación explícita que produzca un booleano real:
Esta regla general conviene recordarla siempre que se use && para renderizado condicional: si la condición es un número o una cadena que podría legítimamente ser 0 o '', hay que convertirla explícitamente a un booleano (con una comparación como > 0, o con !!valor) antes de usarla con &&.
- La directiva
when: una mención de paso
when: una mención de pasoLit incluye, entre su catálogo de directivas incorporadas, una llamada when que expresa de forma algo más explícita la misma idea que un operador ternario:
import { when } from 'lit/directives/when.js';
render() {
return html`
${when(
this.completada,
() => html`<span>✓ Finalizada</span>`,
() => html`<span>○ Pendiente</span>`
)}
`;
}when recibe la condición, una función que produce la plantilla para el caso verdadero, y opcionalmente otra función para el caso falso. Su comportamiento, en la práctica, es muy parecido al de un operador ternario con plantillas anidadas; la diferencia principal es que las ramas se pasan como funciones (lo que retrasa su ejecución hasta que realmente se necesitan) y que, para quien lee el código, deja más explícita la intención de "renderizado condicional" como concepto, frente a un ternario que también podría estar seleccionando un simple valor no relacionado con plantillas.
Esta directiva se menciona aquí solo para que la reconozcas si la encuentras en la documentación oficial o en proyectos de terceros; el estudio de las directivas de Lit en general —qué son, cómo funcionan por dentro y cuándo aportan valor frente a JavaScript "a pelo"— es el contenido del módulo 7, "Directivas y Funcionalidades Avanzadas de Plantillas". Durante el resto de este curso, y en particular en este módulo, se seguirá usando JavaScript estándar (ternarios, &&, funciones auxiliares) como técnica principal, precisamente porque no requiere importar nada adicional y demuestra que el renderizado condicional en Lit no depende de ninguna funcionalidad especial.
- Aplicándolo a
<task-card>: insignia según el estado
<task-card>: insignia según el estadoCon las técnicas anteriores ya se puede dar a <task-card> una insignia de estado que cambie su apariencia y su texto según la tarea esté pendiente, en progreso o hecha. Se usará el patrón de función auxiliar del apartado 4, por ser el más legible cuando hay más de dos alternativas:
import { LitElement, html } from 'lit';
class TaskCard extends LitElement {
constructor() {
super();
this.titulo = 'Preparar la demo del sprint';
this.estado = 'en-progreso'; // 'pendiente' | 'en-progreso' | 'hecha'
this.urgente = false;
}
renderInsigniaEstado() {
if (this.estado === 'hecha') {
return html`<span class="insignia insignia--hecha">✓ Hecha</span>`;
}
if (this.estado === 'en-progreso') {
return html`<span class="insignia insignia--progreso">◐ En progreso</span>`;
}
return html`<span class="insignia insignia--pendiente">○ Pendiente</span>`;
}
render() {
return html`
<article>
<h3>${this.titulo}</h3>
${this.renderInsigniaEstado()}
${this.urgente && html`<p class="aviso">⚠ Urgente</p>`}
</article>
`;
}
}
customElements.define('task-card', TaskCard);Este componente combina las tres técnicas vistas en la lección: una función auxiliar (renderInsigniaEstado) que encapsula una lógica de tres ramas mediante if normales (imposible de expresar directamente dentro de un único ${}), y el patrón && para mostrar el aviso de urgencia solo cuando corresponde. Nótese que las clases CSS (insignia--hecha, insignia--progreso...) se interpolan aquí solo como referencia de nombres; su definición visual real —cómo se ven esos colores e iconos— es contenido del módulo 4, "Estilos en Componentes Lit". Por ahora, sin esos estilos, la insignia se verá como texto sin formato especial, y eso es exactamente lo esperado en este punto del curso.
Errores Comunes y Consejos
- Usar
&&con condiciones numéricas sin convertir a booleano: como se ha visto en el apartado 5,cantidad && html\...`puede mostrar un0suelto en pantalla sicantidadvale cero. Conviene forzar siempre una comparación explícita (cantidad > 0`) en estos casos. - Anidar demasiados ternarios seguidos: encadenar varios ternarios (
a ? x : b ? y : c ? z : w) para representar más de dos alternativas es difícil de leer. En cuanto haya tres o más ramas, es preferible extraer la lógica a una función auxiliar conif/switch, como se hizo en el apartado 7. - Olvidar que devolver una cadena vacía
''en una rama no es lo mismo que no renderizar nada: aunque el resultado visual sea el mismo (no aparece texto), devolver''sigue insertando un nodo de texto vacío en el DOM, mientras que devolvernull,undefinedofalsehace que Lit no inserte nada en absoluto. En la práctica esta diferencia rara vez importa, pero conviene conocerla para no sorprenderse al inspeccionar el DOM. - Repetir la misma condición varias veces en la plantilla: si necesitas comprobar
this.estado === 'hecha'en varios sitios de una plantilla larga, es una señal de que conviene extraer esa lógica a una función auxiliar o a una variable local calculada al principio derender(), en lugar de repetir la comparación.
Ejercicios
- Añade a la función
renderInsigniaEstado()un cuarto estado posible,'bloqueada', que muestre una insignia con el texto "⛔ Bloqueada" y la claseinsignia--bloqueada. - Usando el patrón
&&con la corrección del apartado 5, añade a<task-card>un campothis.numeroDeComentarios(inicialízalo a0) y muestra un párrafo "N comentarios" solo cuando ese número sea mayor que cero. Comprueba manualmente, cambiando el valor desde el constructor, que con0no aparece ningún "0" suelto en pantalla. - Reescribe la insignia de estado del apartado 7 usando la directiva
whenmencionada en el apartado 6, consultando su firma exacta en la documentación oficial de Lit (lit/directives/when.js), solo con fines de práctica de lectura de documentación (no hace falta integrarla en el resto del curso).
Soluciones
renderInsigniaEstado() {
if (this.estado === 'hecha') {
return html`<span class="insignia insignia--hecha">✓ Hecha</span>`;
}
if (this.estado === 'en-progreso') {
return html`<span class="insignia insignia--progreso">◐ En progreso</span>`;
}
if (this.estado === 'bloqueada') {
return html`<span class="insignia insignia--bloqueada">⛔ Bloqueada</span>`;
}
return html`<span class="insignia insignia--pendiente">○ Pendiente</span>`;
}constructor() {
super();
// ...
this.numeroDeComentarios = 0;
}
render() {
return html`
<article>
<h3>${this.titulo}</h3>
${this.renderInsigniaEstado()}
${this.numeroDeComentarios > 0
? html`<p>${this.numeroDeComentarios} comentarios</p>`
: ''}
</article>
`;
}Con this.numeroDeComentarios a 0, la condición this.numeroDeComentarios > 0 es false, así que no se muestra ningún párrafo ni ningún "0" suelto.
import { when } from 'lit/directives/when.js';
render() {
return html`
<article>
<h3>${this.titulo}</h3>
${when(
this.estado === 'hecha',
() => html`<span class="insignia insignia--hecha">✓ Hecha</span>`,
() => html`<span class="insignia insignia--pendiente">○ Pendiente / en progreso</span>`
)}
</article>
`;
}Esta solución simplifica a solo dos ramas con fines de práctica; una réplica exacta de las cuatro ramas del apartado 1 requeriría anidar varias llamadas a when, lo que ilustra precisamente por qué, con más de dos alternativas, suele preferirse una función auxiliar con if/switch en lugar de anidar directivas.
Conclusión
En esta lección has visto que el renderizado condicional en Lit no requiere ninguna sintaxis especial: se apoya directamente en operadores estándar de JavaScript, principalmente el operador ternario para elegir entre dos alternativas y el operador && para mostrar u ocultar contenido sin alternativa, con el cuidado de convertir explícitamente a booleano las condiciones que puedan valer 0 o cadena vacía. También has aprendido a extraer lógica condicional más compleja a funciones auxiliares dentro de la propia clase, y has conocido de pasada la directiva when, cuyo estudio completo llegará en el módulo 7. Con todo ello, <task-card> ya muestra una insignia de estado distinta según la tarea esté pendiente, en progreso o hecha, además de un aviso condicional de urgencia.
En la siguiente lección, "Renderizado de Listas", darás un paso más: en lugar de mostrar una única tarea, aprenderás a recorrer un array completo de tareas con Array.map dentro de una plantilla, y usarás esa técnica para construir <task-list>, el primer componente de TaskFlow capaz de renderizar varias tarjetas <task-card> a partir de una colección de datos.
Curso de Lit
Módulo 1: Introducción a Lit y Web Components
- ¿Qué son los Web Components y por qué Lit?
- Configuración del Entorno de Desarrollo
- Tu Primer Componente Lit
- Anatomía de un Componente Lit
Módulo 2: Plantillas Reactivas y Renderizado
- El Motor de Plantillas de Lit
- Expresiones e Interpolación en Plantillas
- Renderizado Condicional
- Renderizado de Listas
- El Ciclo de Renderizado
Módulo 3: Propiedades y Estado Reactivo
- Propiedades Reactivas
- Estado Interno con @state
- Tipos de Propiedades y Conversores Personalizados
- Atributos vs Propiedades y Reflexión
Módulo 4: Estilos en Componentes Lit
- CSS Encapsulado con Shadow DOM
- Estilos Compartidos entre Componentes
- Variables CSS Personalizadas y Theming
- Slots y Estilizado de Contenido Distribuido
Módulo 5: Eventos y Comunicación entre Componentes
- Manejo de Eventos DOM en Plantillas
- Eventos Personalizados: Comunicación Hijo a Padre
- Comunicación de Padre a Hijo con Propiedades
- Patrones de Comunicación entre Componentes Hermanos
Módulo 6: Ciclo de Vida y Comportamiento Avanzado
- Callbacks del Ciclo de Vida
- Hooks Reactivos: willUpdate, updated y firstUpdated
- Controladores Reactivos
- Mixins y Composición de Comportamiento
Módulo 7: Directivas y Funcionalidades Avanzadas de Plantillas
- Directivas Incorporadas: classMap, styleMap e ifDefined
- Directivas Personalizadas
- Renderizado Asíncrono con until
- Contexto Compartido con @lit/context
Módulo 8: Integración, Interoperabilidad y Despliegue
- Usar Componentes Lit en HTML Plano
- Integrar Lit con React, Vue y Angular
- Renderizado en el Servidor con @lit-labs/ssr
- Empaquetado, Publicación y TypeScript
Módulo 9: Pruebas y Buenas Prácticas
- Pruebas Unitarias con Web Test Runner
- Accesibilidad en Web Components
- Rendimiento y Optimización
- Patrones y Anti-patrones Comunes
