Concurrencia, prioridad, Suspense y el rumbo de Solid 2.0
El marco conceptual que unifica el nivel: la concurrencia de Solid es cooperativa y vive en el plano asíncrono, no es el time-slicing preventivo de React que trocea el trabajo síncrono sobre la CPU. La prioridad se reduce a dos carriles, la actualización urgente que compromete de inmediato y la transición diferida que espera a lo asíncrono. Suspense y transiciones son dos mitades complementarias, el dónde esperar y el cómo esperar, que componen en lugar de competir. Y hacia dónde va Solid 2.0 con la reactividad asíncrona nativa, createAsync, projections y un store reescrito que difuminan la frontera entre Suspense y transición.
Concurrencia cooperativa, no time-slicing
En React, concurrencia significa un planificador que puede interrumpir un render largo, ceder el hilo al navegador y reanudar después: trocea el trabajo síncrono en fragmentos para que la CPU no se bloquee al pintar. Solid no hace nada de eso, y no le hace falta. Su reactividad de grano fino ya actualiza solo los nodos exactos que cambian, sin reconciliar árboles ni volver a ejecutar componentes, así que el trabajo síncrono es tan barato que no hay nada que trocear. La concurrencia de Solid vive en otro plano: el asíncrono. No interrumpe cálculos; mantiene dos versiones de una porción del grafo —la comprometida y la que se prepara— y decide cuándo canjearlas.
La palabra cooperativa es precisa: nadie interrumpe a nadie. La actualización en transición no compite por el hilo con la urgente; simplemente se aparta a una rama clonada y espera su turno de compromiso hasta que lo asíncrono resuelve. Por eso no hay startTransition que pueda hacer más fluida una animación pesada de JavaScript: eso sería trabajo síncrono, y las transiciones de Solid no tocan ese dominio. Confundir los dos modelos es el malentendido más común de quien llega desde React, y tiene una consecuencia práctica: si tu página se traba, la culpa no es de la falta de transiciones sino de un cálculo síncrono caro, y la solución es otra —memoizar, dividir el trabajo, sacarlo a un worker—, no envolverlo en startTransition.
El corolario es liberador. Como Solid no paga el impuesto del virtual DOM ni de reconciliar, casi nunca necesitas concurrencia de cómputo; la que sí necesitas, una y otra vez, es concurrencia de datos: coordinar esperas por la red sin castigar al usuario con parpadeos. Ese es exactamente el nicho que las transiciones ocupan, y explica por qué en Solid son una herramienta de uso cotidiano y no un mecanismo de rescate para rendimiento extremo.
Prioridad: dos carriles, urgente y diferido
Con ese marco, la prioridad en Solid se reduce a una distinción binaria y nítida. Una actualización urgente es la escritura normal de un signal: se propaga y se compromete de inmediato, en el mismo tick. Una actualización en transición es diferida: se propaga en una rama aparte y su compromiso espera a que lo asíncrono que dispara esté listo. No hay una escala de cinco prioridades ni carriles numerados como en el planificador de React; hay dos regímenes, y tú eliges en cuál cae cada cambio con el simple gesto de envolverlo, o no, en startTransition.
// Carril urgente: el input debe responder al instantesetTexto(e.currentTarget.value);// Carril diferido: la búsqueda pesada que ese texto dispara puede esperarstartTransition(() => setConsulta(e.currentTarget.value));Este patrón —una escritura urgente para el feedback inmediato y otra en transición para el trabajo caro— es la forma canónica del typeahead: mantener el input fluido mientras una lista costosa se recalcula detrás. El componente completo lo deja ver:
import { createResource, createSignal, Suspense, useTransition } from "solid-js";function Typeahead() { const [texto, setTexto] = createSignal(""); const [consulta, setConsulta] = createSignal(""); const [pending, start] = useTransition(); const [resultados] = createResource(consulta, buscar); const escribir = (v: string) => { setTexto(v); // urgente: el input no se traba start(() => setConsulta(v)); // diferido: la lista espera }; return ( <> <input value={texto()} onInput={(e) => escribir(e.currentTarget.value)} /> <ul classList={{ atenuado: pending() }}> <Suspense><Lista items={resultados()} /></Suspense> </ul> </> );}El teclado nunca se traba porque su signal —texto— está en el carril urgente; la lista no parpadea porque el suyo —consulta— está en el diferido. Fíjate en que pending() refleja solo el carril diferido: es el estado de la transición, no del input. Dos carriles, una decisión por escritura, y un único signal que anuncia la espera del que puede permitírsela.
Suspense y transiciones: el dónde y el cómo
Es tentador ver Suspense y transiciones como dos features sueltas de async. Son, en realidad, dos mitades de un solo sistema. Suspense declara el dónde: los límites del árbol en los que es legítimo esperar por datos. Una transición declara el cómo: la política de qué mostrar durante esa espera —el fallback, o el contenido anterior retenido—. Suspense sin transición muestra el fallback en cada pendiente; transición sobre Suspense convierte ese fallback en retención silenciosa. Ninguna sustituye a la otra; se necesitan.
Esa separación tiene una virtud de composición: puedes anidar límites <Suspense> para trocear la espera y una única transición los coordina a todos. Un límite exterior para la estructura de la página y uno interior para un panel que carga más lento dejan que lo rápido aparezca pronto mientras lo lento se retiene, todo bajo el mismo compromiso atómico de la transición.
<Suspense fallback={<EsqueletoPagina />}> <Cabecera datos={usuario()} /> <Suspense fallback={<EsqueletoPanel />}> <PanelLento datos={detalle()} /> {/* su propio límite, misma transición */} </Suspense></Suspense>Hacia Solid 2.0: async de primera clase
El rumbo de Solid 2.0, en desarrollo activo, empuja este modelo hasta su conclusión lógica: hacer que lo asíncrono viva dentro del grafo reactivo en lugar de coordinarse desde fuera. createAsync —ya presente en el router— apunta a esa dirección, tratando una promesa como una fuente reactiva que suspende de forma natural al leerse, sin la ceremonia de la tupla y los estados de createResource.
import { createAsync } from "@solidjs/router";// La lectura suspende sola; no orquestas recurso ni Suspense a manoconst usuario = createAsync(() => obtenerUsuario(id()));const perfil = createAsync(() => obtenerPerfil(usuario()?.id));// perfil depende de usuario: el grafo encadena las esperas por sí mismoLa reactividad asíncrona nativa persigue que una derivación pueda esperar por datos sin que tú orquestes recursos y Suspense a mano, y las projections y el store reescrito buscan que el estado derivado de fuentes async sea tan de grano fino como el síncrono lo es hoy. En ese mundo, la frontera entre Suspense y transición se difumina porque el await deja de ser un caso especial y pasa a ser una propiedad del grafo: si una lectura está pendiente, el sistema ya sabe dónde esperar y cómo coordinar el compromiso, con las transiciones como el mecanismo por defecto de esa coordinación.
No es una ruptura con lo que has aprendido en este nivel: es su generalización. Los conceptos —retener lo viejo, comprometer atómico, anunciar el pendiente— siguen intactos; lo que cambia es que dejan de requerir cableado explícito y se vuelven el comportamiento base del modelo. Aprender bien las transiciones hoy no es aprender una API que caducará: es aprender la semántica que Solid 2.0 va a volver ubicua.