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.
Cerramos el nivel subiendo un escalón de abstracción. Has visto qué hace una transición, cómo evita el parpadeo, cómo se anuncia con pending y dónde aplicarla. Falta el marco que lo unifica: qué significa exactamente concurrencia en Solid —y por qué no es lo que significa en React—, cómo se reparte la prioridad entre actualizaciones, y de qué manera Suspense y las transiciones son dos mitades del mismo sistema. Con ese mapa en la mano, mirar hacia Solid 2.0 deja de ser adivinar y pasa a ser leer la dirección natural de un modelo que hace de lo asíncrono un ciudadano de primera.
- Distinguir la concurrencia cooperativa de Solid del time-slicing preventivo de React.
- Situar la prioridad como dos carriles: actualización urgente frente a transición diferida.
- Ver Suspense y transiciones como el dónde esperar y el cómo esperar que componen.
- Anticipar el rumbo de Solid 2.0 con la reactividad asíncrona nativa.
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.
flowchart TD E[actualizacion urgente] -->|sincrona| CO[commit inmediato] S[Suspense declara donde esperar] --> T[actualizacion en transicion] T -->|espera lo async| CO2[commit al resolver] style CO fill:#a6e3a1,color:#11111b style CO2 fill:#89b4fa,color:#11111b
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 instante
setTexto(e.currentTarget.value);
// Carril diferido: la búsqueda pesada que ese texto dispara puede esperar
startTransition(() => 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>
Suspense: el dónde
Declara los límites donde el árbol puede esperar datos y qué fallback poner en frío.
Transición: el cómo
Decide la política durante la espera: fallback en frío, contenido retenido en caliente.
Componen, no compiten
Suspense marca el punto de espera; la transición gobierna la experiencia de esa espera. Juntas, cero parpadeo.
Esta división del trabajo —un primitivo que marca un límite y otro que gobierna la política dentro de él— es un patrón recurrente en Solid. <ErrorBoundary> marca dónde es legítimo capturar un fallo; el recurso decide qué es un fallo. <Suspense> marca dónde esperar; la transición decide cómo esperar. Reconocer ese patrón te ayuda a leer el resto del subsistema async como piezas que encajan por diseño, no como una colección de utilidades independientes que hay que memorizar una a una.
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 mano
const usuario = createAsync(() => obtenerUsuario(id()));
const perfil = createAsync(() => obtenerPerfil(usuario()?.id));
// perfil depende de usuario: el grafo encadena las esperas por sí mismo
La 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.
Aunque createAsync es la cara del futuro, createResource sigue siendo la herramienta correcta cuando necesitas su control fino: el mutate para actualizaciones optimistas, el refetch manual, o el acceso explícito a loading y error como signals. La dirección del framework no invalida lo que ya sabes; añade una capa más ergonómica encima. Elige createAsync para lecturas declarativas que solo dependen de otras fuentes, y createResource cuando quieras gobernar el ciclo de la petición a mano.
Si retrocedes lo suficiente, todo este nivel cuenta una sola historia, y es una historia sobre dónde debe vivir el tiempo asíncrono en un framework. La respuesta tradicional lo pone alrededor del sistema reactivo: los datos llegan por un canal aparte —hooks de fetching, efectos, librerías de estado servidor— y el sistema de renderizado reacciona a ellos como a algo externo que le sucede. Solid hizo la apuesta contraria desde sus recursos y la profundiza con las transiciones: lo asíncrono pertenece dentro del grafo, como una fuente reactiva más, sujeta a las mismas reglas de tracking, ownership y propagación que un signal síncrono. Una transición es la punta visible de esa apuesta. Cuando envuelves una escritura en startTransition, no estás invocando un planificador ni negociando prioridades de CPU; estás diciéndole al grafo que cierta actualización es asíncrona por naturaleza y que su compromiso debe esperar a que sus dependencias async resuelvan, manteniendo mientras tanto la verdad anterior. Esa es toda la idea, y es asombrosamente pequeña una vez que la ves: la concurrencia de Solid no es velocidad de cómputo sino gestión de dos verdades en el tiempo, la de ahora y la que viene. La prioridad no es una escala sino la elección entre comprometer ya o comprometer al resolver. Suspense no es una feature de carga sino la declaración de dónde el grafo puede esperar. Y Solid 2.0 no inventa un paradigma nuevo: acaba de mudar el await al interior del grafo, para que todo lo que aquí hiciste a mano —marcar límites, disparar transiciones, leer pendientes— se vuelva la física por defecto de escribir interfaces asíncronas. Quien entiende que la transición es esa apuesta hecha primitiva no aprende una API más: aprende la tesis central sobre la que Solid está construyendo su próxima década.
- Explica con tus palabras por qué
startTransitionno puede suavizar una animación pesada de JavaScript, apoyándote en la diferencia con el time-slicing. - Implementa el
Typeaheadde dos carriles y confirma que el teclado no se traba y quepending()solo refleja el carril de la lista. - Anida dos
<Suspense>y verifica que la estructura rápida aparece mientras el panel lento se retiene, todo bajo una sola transición. - Reescribe una lectura de datos con
createAsyncy observa cómo suspende de forma natural y encadena dependencias sin orquestar un recurso a mano. - Argumenta en un párrafo qué significa que Solid 2.0 mueva el
awaital interior del grafo y qué de este nivel se vuelve automático como consecuencia.