Saltar al contenido

Integrar librerías imperativas

El patrón para envolver charts, mapas y editores imperativos en un componente Solid: onMount crea la instancia cuando el DOM ya existe, onCleanup la destruye al desmontar, y un createEffect por cada prop sincroniza los cambios mutando la instancia en vez de recrearla. La instancia se guarda en una variable normal, no en un signal, y cada efecto empuja al mundo imperativo solo lo que cambió.

El patrón de tres tiempos

La librería imperativa tiene su propio ciclo: nace, cambia, muere. Tu trabajo es mapear ese ciclo a las primitivas de Solid sin dejar cabos sueltos. El montaje crea, los efectos sincronizan, la limpieza destruye.

Lo importante es que la instancia se crea una sola vez y luego se muta, nunca se recrea en cada cambio de prop. Recrear un gráfico o un mapa en cada actualización sería caro y borraría su estado interno —zoom, selección, scroll—. El grano fino de Solid encaja perfecto aquí: cada prop tiene su efecto, y cada efecto empuja al mundo imperativo exactamente el cambio que le corresponde.

onMount instancia, onCleanup destruye

Toma un gráfico como caso concreto. La librería expone un constructor que recibe el nodo canvas y una configuración, métodos para actualizar, y un destroy. La instancia se guarda en una variable local del componente:

TSX
import { onMount, onCleanup } from "solid-js";import { Chart } from "chart.js/auto";function Grafico(props: { datos: number[]; leyenda: boolean }) {  let lienzo!: HTMLCanvasElement;  let grafico: Chart | undefined;  onMount(() => {    grafico = new Chart(lienzo, {      type: "line",      data: { labels: [], datasets: [{ data: props.datos }] },      options: { plugins: { legend: { display: props.leyenda } } },    });    onCleanup(() => grafico?.destroy());   // libera canvas, listeners y animaciones  });  return <canvas ref={lienzo} />;}

onMount es obligatorio, no opcional: la librería necesita un nodo insertado para medirlo y dibujar, y en el cuerpo del componente ese nodo aún no existe. onCleanup es igual de obligatorio: sin destroy, cada montaje deja atrás un canvas vivo, animaciones corriendo y listeners globales colgados. La pareja mantiene la correspondencia “mientras el componente vive, existe el gráfico”.

Un efecto por cada prop

La instancia ya existe; falta que reaccione a las props. Aquí está la parte que distingue a Solid: en vez de un único bloque que reconstruye todo cuando “algo” cambia, escribes un efecto por cada prop independiente, y cada uno muta solo su parte. Los declaras dentro de onMount, después de crear la instancia, para que solo arranquen cuando ya hay algo que mutar.

TSX
import { onMount, onCleanup, createEffect } from "solid-js";import { Chart } from "chart.js/auto";function Grafico(props: { datos: number[]; leyenda: boolean }) {  let lienzo!: HTMLCanvasElement;  let grafico: Chart | undefined;  onMount(() => {    grafico = new Chart(lienzo, {      type: "line",      data: { labels: [], datasets: [{ data: [] }] },      options: { plugins: { legend: { display: props.leyenda } } },    });    createEffect(() => {      grafico!.data.datasets[0].data = props.datos;   // se re-ejecuta al cambiar datos      grafico!.update();    });    createEffect(() => {      grafico!.options.plugins!.legend!.display = props.leyenda; // solo la leyenda      grafico!.update();    });    onCleanup(() => grafico?.destroy());  });  return <canvas ref={lienzo} />;}

Cada efecto rastrea únicamente la prop que lee: el primero se re-ejecuta cuando cambia props.datos y no le importa la leyenda; el segundo, al revés. Cambiar los datos no reinicia la leyenda ni viceversa, porque son suscripciones distintas. Esa separación es gratis en Solid y es justo lo que una librería imperativa necesita: mutaciones mínimas y dirigidas.

Un efecto creado dentro de onMount rastrea con normalidad —el untrack de onMount solo afecta a las lecturas síncronas de su cuerpo, no a los cómputos anidados— y pertenece al dueño del componente, así que su limpieza también corre al desmontar.

Cuando la sincronización de una prop es cara —redibujar miles de puntos— acota el efecto con on para controlar con exactitud qué lo dispara y no leer dentro más signals de los necesarios:

TSX
createEffect(on(() => props.datos, (datos) => {  grafico!.data.datasets[0].data = datos;  grafico!.update("none");    // sin animacion para grandes volumenes}));

El patrón completo, de un vistazo: