BA Barış Akın~/blog

·4 min de lectura·47 lecturas

Componentes de zap: un pequeño kit de interfaz basado en signals

Un núcleo de 3.7 KB, cero dependencias y 57 componentes: un breve repaso de cómo funciona zap con signals y de cómo se usan sus componentes. Todos están en vivo, con ejemplos, en deck.kartex.net.

Componentes de zap: un pequeño kit de interfaz basado en signals

Las pequeñas piezas interactivas de este blog (el selector de idioma, el visor de código, la paleta de búsqueda) están escritas con zap. zap es una diminuta biblioteca de interfaz en JavaScript, sin dependencias y basada en signals, junto con un kit de componentes que la acompaña. No voy a repasar aquí los componentes uno por uno; todos están disponibles en vivo, con ejemplos y código, en deck.kartex.net. Este artículo es solo un breve resumen de cómo funciona la tecnología y de cómo se usa.

deck.kartex.net: una tarjeta de valoración y el código que la construye

zap en resumen

  • Núcleo de 3.7 KB (gzip). Sin DOM virtual, sin compilador, sin paso de build obligatorio.
  • Cero dependencias. Se ejecuta en el navegador como módulos ES normales.
  • 57 componentes: formularios y campos de entrada, superposiciones y retroalimentación, navegación, visualización, tablas de datos, gráficos, multimedia.
  • SSR e hidratación: los mismos componentes se renderizan a HTML en el servidor y cobran vida en el navegador. Las 65 páginas del sitio deck se prerenderizan de esta forma.
  • Temas: variables CSS (--zap-*) con los temas Default, Emerald, Violet, Rosé y macOS Aqua, cada uno con su modo oscuro.
  • Accesibilidad: roles, atributos aria, gestión del foco y uso con teclado en todos los componentes; se respeta prefers-reduced-motion.

Signals

El corazón de zap es el signal. Un signal es una función que guarda un valor; cada lugar que lo lee se suscribe y, cuando el valor cambia, solo se actualizan esos lugares. El componente en su conjunto nunca se vuelve a dibujar.

import { signal, computed, effect } from "@barisakin/zap";

const count = signal(0);
const double = computed(() => count() * 2);

effect(() => console.log("count", count(), "double", double()));

count.set(1);              // the effect runs again
count.update(n => n + 1);  // 2

untrack deja una lectura fuera de la suscripción, onCleanup hace limpieza antes de que un effect se vuelva a ejecutar o de que un componente desaparezca, y store ofrece reactividad a nivel de campo para objetos anidados.

Plantillas html

Las vistas se escriben con los propios tagged template literals de JavaScript. Todo lo que se pasa a la plantilla como función (un signal o () => ...) es reactivo; solo se actualiza ese nodo de texto o ese atributo.

import { signal, html, mount } from "@barisakin/zap";

function Counter() {
  const n = signal(0);
  return html`
    <button onclick=${() => n.update(x => x + 1)}>
      clicked ${n} times
    </button>
  `;
}

mount(Counter, document.getElementById("app"));

when y each cubren el contenido condicional y el repetido, y bind=${signal} enlaza un campo de entrada con un signal en ambos sentidos. Un componente no es más que una función de la forma (props) => Node: sin clases, sin métodos de ciclo de vida, sin un formato de archivo especial.

Uso de los componentes

Cada componente vive en su propio módulo, así que solo se carga lo que se usa. Las props pueden ser valores simples o signals; si se pasa un signal, el componente lo lee y también lo escribe.

import { signal, html } from "@barisakin/zap";
import { Card } from "@barisakin/zap/components/card.js";
import { Rating } from "@barisakin/zap/components/rating.js";
import { Button } from "@barisakin/zap/components/button.js";
import { toast } from "@barisakin/zap/components/toast.js";

const stars = signal(0);

Card({
  title: "How was your order?",
  children: html`${Rating({ value: stars })}`,
  footer: Button({
    label: "Send",
    disabled: () => stars() === 0,
    onClick: () => toast.success("Thank you!"),
  }),
});

Un diálogo funciona de la misma manera: open es un signal; al ponerlo en true el diálogo se abre y, al cerrarlo, el signal vuelve a false.

import { Modal } from "@barisakin/zap/components/modal.js";

const open = signal(false);
Modal({ open, title: "Delete it?", children: "This cannot be undone." });
open.set(true);

Los menús desplegables, los tooltips, los popovers y los modales comparten dos capas comunes (floating.js y overlay.js), de modo que el posicionamiento, la captura del foco, el cierre con Escape y el bloqueo del scroll se resuelven en un solo lugar.

Kanban: arrastrar y soltar, y movimientos con el teclado

Datos y gráficos

En el lado más pesado hay una tabla de datos (scroll virtual, ordenación, filtrado, agrupación, edición), un planificador, un tablero kanban, un árbol y gráficos. Los datos que se le dan a un gráfico pueden ser un signal; al cambiarlo, el gráfico se anima hacia los nuevos valores.

import { ColumnChart } from "@barisakin/zap/components/chart.js";

const data = signal([12, 19, 7, 24]);
ColumnChart({ labels: ["Q1", "Q2", "Q3", "Q4"], series: [{ name: "Sales", data }] });

Gráficos: de líneas, de áreas y de barras

Router, i18n y SSR

  • Router: un pequeño router para navegar sin recargar la página (Router, Link, navigate).
  • i18n: t() para las traducciones y setLocale() para cambiar de idioma; los textos propios de los componentes vienen listos en turco, inglés, alemán, francés, español y montenegrino.
  • SSR: renderToString de @barisakin/zap/ssr convierte el mismo componente en HTML en el servidor; en el navegador, hydrate reutiliza el DOM existente y conecta los eventos.
  • TypeScript: las definiciones de tipos (index.d.ts) vienen con el paquete.

Instalación

Con npm:

npm install @barisakin/zap

o directamente desde la CDN, sin ninguna herramienta:

<link rel="stylesheet" href="https://zapjs.baltavista.com/theme.css">
<script type="module">
  import { signal, html, mount } from "https://zapjs.baltavista.com/zap.min.js";
</script>

Verlos todos

Todos los componentes, cada uno con un ejemplo en vivo, código para copiar e interruptores de tema y de modo oscuro, están en deck.kartex.net. Si se quiere saber cómo usar un componente, lo más rápido es mirar allí: elegirlo en el menú de la izquierda, probar el ejemplo y copiar el código.

BA
Barış Akın

Arquitecto de soluciones de software con más de 25 años de experiencia. FinTech y sistemas distribuidos; creador de OxiDB, Rill y Cobra.