from(): envolver una fuente externa en un signal
from es el adaptador universal que convierte una fuente push del exterior (un observable de RxJS, un store con subscribe, un EventTarget) en un Accessor de Solid que puedes leer dentro del grafo reactivo. Sus dos formas, el objeto con subscribe y la funcion productora que recibe un set, por que devuelve un valor que empieza en undefined, por que usa igualdad equals false y como delega la baja de la suscripcion en el onCleanup que registra por ti.
De un mundo que empuja a uno que se lee
La diferencia entre push y pull no es cosmética: define quién tiene el control del tiempo. Una fuente push decide cuándo hay un valor nuevo y te lo entrega llamando a tu callback; un signal pull expone un valor y deja que tú decidas cuándo leerlo, registrando de paso quién lo leyó para poder avisarle. from construye el puente colocando un signal interno en medio: se suscribe a la fuente, y cada vez que esta empuja un valor, lo escribe en el signal. El resto del grafo —efectos, memos, el JSX— solo ve un accessor normal y reacciona como siempre.
import { from } from "solid-js";import { interval } from "rxjs";const tick = from(interval(1000)); // Accessor<number | undefined>// en el grafo se lee como cualquier signalcreateEffect(() => console.log("tick:", tick()));Lo que has ganado es enorme: todo el catálogo de librerías basadas en observables o en el contrato subscribe entra en Solid sin fricción, hablando el único idioma que el grafo entiende, que es el de los accessores.
Las dos formas de from
from acepta dos tipos de entrada, y conviene saber cuál toca en cada caso. La primera es un objeto con un método subscribe: el contrato que comparten RxJS, los stores de Svelte y el protocolo de observables de TC39. from llama a subscribe, recibe la función de baja —o un objeto con unsubscribe— y la enchufa a onCleanup sin que tú toques nada.
import { from } from "solid-js";import { writable } from "svelte/store";const contador = writable(0);const valor = from(contador); // el contrato subscribe basta, venga de donde vengaLa segunda forma es una función productora (set) => cleanup, pensada para fuentes imperativas que no exponen subscribe, como un EventTarget. Recibes el set del signal interno, empujas valores cuando quieras y devuelves la función de limpieza, que from volverá a atar a onCleanup.
const scrollY = from<number>((set) => { const onScroll = () => set(window.scrollY); window.addEventListener("scroll", onScroll, { passive: true }); set(window.scrollY); // siembra el valor inicial return () => window.removeEventListener("scroll", onScroll);});Las dos formas hacen lo mismo por dentro —alimentar un signal y programar su baja—; solo cambia si la fuente ya habla el idioma subscribe o si tú tienes que traducirla a mano dentro de la productora.
Un detalle que from te ahorra: el contrato subscribe admite dos formas de devolver la baja, una función suelta —el estilo de RxJS clásico y de los stores de Svelte— o un objeto con un método unsubscribe —el estilo del protocolo TC39—. from normaliza ambas por ti, detectando cuál te dio la fuente y llamándola como corresponda desde su onCleanup. No tienes que saber cuál de las dos convenciones sigue la librería que envuelves; from acepta cualquiera y hace lo correcto.
El undefined inicial y la igualdad equals: false
Dos decisiones de diseño de from sorprenden hasta que se entienden. La primera: el tipo devuelto es Accessor<T | undefined>, no Accessor<T>. La razón es que muchas fuentes push no emiten de forma síncrona en el momento de suscribirse —un WebSocket tarda, un interval espera su primer tick—, así que entre la suscripción y la primera emisión el signal no tiene valor. from es honesto y lo tipa como undefined. Lo resuelves de dos maneras: sembrando un valor inicial dentro de la productora, como hicimos con scrollY, o dando un valor por defecto al leer.
const mensaje = from<string>(fuenteWebSocket);// en el JSX, cubres el hueco inicial<p>{mensaje() ?? "conectando…"}</p>La segunda decisión: el signal interno se crea con equals: false. Esto significa que cada emisión notifica, aunque el valor sea idéntico al anterior. Es lo correcto para una fuente de eventos: si un stream emite 5 dos veces, son dos eventos distintos que ocurrieron en dos instantes, y silenciar el segundo porque 5 === 5 perdería información temporal real. Un signal normal, con su igualdad por ===, se tragaría esa segunda emisión; from la deja pasar porque un flujo no es un valor, es una secuencia.
from para flujos, createResource para promesas
Hay una confusión habitual que conviene desactivar cuanto antes: from no es la herramienta para traer un valor asíncrono único, como el resultado de un fetch. Esa tarea es de createResource. La diferencia es de forma temporal, no de gusto. Una promesa se resuelve una sola vez y tiene estados propios —cargando, resuelta, con error—; un flujo emite muchas veces a lo largo del tiempo y no tiene un “cargando” natural, porque nunca termina de la misma manera. from modela lo segundo; createResource, lo primero.
// flujo continuo: muchas emisiones a lo largo del tiempo -> fromconst precios = from(streamDePrecios); // Accessor<Precio | undefined>// valor asincrono unico con estados de carga y error -> createResourceconst [usuario] = createResource(() => fetch(url).then((r) => r.json()));Usar from para una promesa te deja sin los estados de carga y error que createResource te da gratis; usar createResource para un stream te ata a un ciclo de una sola resolución que no encaja con una fuente que emite sin fin. Elegir bien empieza por preguntarte si la fuente entrega un valor o una secuencia de ellos.