wandres.dev
INTEGRAR LIBRERÍAS · interoperar con JS

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.

⏱ 15 min

El ecosistema de JavaScript está lleno de fuentes que empujan valores hacia ti: un observable de RxJS que emite en un subscribe, un store de Svelte, un EventTarget que dispara eventos, un WebSocket. Solid, en cambio, vive en el mundo opuesto: sus signals se leen —tú tiras del valor con un getter y el grafo teje la suscripción—. from es la pieza que casa ambos mundos: toma cualquier fuente que empuja y te devuelve un Accessor que se lee como cualquier otro, creando por dentro un signal, suscribiéndose por ti y dando de baja la suscripción cuando el dueño muere.

🎯 Al terminar esta lección sabrás
  • Entender que from convierte una fuente push —observable, store, EventTarget— en un Accessor de Solid.
  • Reconocer sus dos formas de entrada: un objeto con subscribe y una función productora que recibe un set.
  • Comprender por qué el accessor devuelto es Accessor<T | undefined> y usa igualdad equals: false.
  • Confiar la baja de la suscripción al onCleanup que from registra internamente 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 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 signal
createEffect(() => 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.

flowchart LR
P[fuentes push observable eventtarget store] --> F[from crea un signal interno]
F --> A[accessor que se lee en el grafo]
A --> R[efectos y memos reaccionan]
F --> C[onCleanup da de baja la suscripcion]
style F fill:#89b4fa,color:#11111b
style A fill:#a6e3a1,color:#11111b
style C fill:#f38ba8,color:#11111b

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 venga

La 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 -> from
const precios = from(streamDePrecios); // Accessor<Precio | undefined>

// valor asincrono unico con estados de carga y error -> createResource
const [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.

📝
Un WebSocket es el caso de libro de la forma productora

Un WebSocket no expone subscribe, pero encaja perfecto en la forma productora: te suscribes a su evento message, empujas cada mensaje con set, y cierras el socket en el return. from<Mensaje>((set) => { const ws = new WebSocket(url); ws.onmessage = (e) => set(JSON.parse(e.data)); return () => ws.close(); }). En una línea conceptual has convertido un canal que empuja bytes en un accessor que el grafo lee, con el cierre del socket atado a la vida del dueño.

ℹ️
from es un primitivo propio que ya viene hecho

Si abres from por dentro no encuentras nada que no vayas a construir tú mismo un par de lecciones más adelante: crea un signal, se suscribe a la fuente para escribirlo, y registra la baja con onCleanup. Es exactamente el molde de un primitivo reactivo reutilizable, empaquetado por la librería y afinado con dos detalles —equals: false y el tipo T | undefined— que delatan que su autor entendió que una fuente push emite eventos, no valores. Verlo así te ahorra memorizarlo: from no es una API que aprender, es un patrón que reconocer.

🌊

Observable RxJS

Cualquier Observable o Subject de RxJS expone subscribe: from lo consume directo y te da un accessor.

🔗

Store con subscribe

Los stores de Svelte y todo lo que siga el contrato subscribe entran sin adaptador intermedio.

📡

EventTarget imperativo

Con la forma productora envuelves scroll, resize, WebSocket o cualquier fuente de eventos del navegador.

⚠️
from necesita un dueño, y cuidado si el valor es una función

Como from llama a onCleanup internamente, tiene que ejecutarse dentro de un contexto reactivo con dueño —un componente o un createRoot—; al nivel de módulo suelto no tendría a quién atar la baja y la suscripción se filtraría. Segunda trampa, más rara: en la forma productora, set es el setter crudo del signal, que interpreta una función como actualizador. Si tu T es una función, empújala envuelta —set(() => miFuncion)— o Solid la ejecutará creyendo que es un prev => next.

💡
El viaje de vuelta lo hace observable

from trae el mundo push hacia dentro de Solid. La operación inversa —exponer un signal de Solid como un observable que otros puedan consumir— la hace observable, que verás en la próxima lección. Juntas convierten la frontera entre Solid y el ecosistema en una membrana que se cruza en los dos sentidos.

from es el adaptador de impedancia entre empujar y tirar

Toda integración con una librería externa choca, antes que con nada, con un desajuste de impedancia: el ecosistema de JavaScript modela el estado que cambia en el tiempo como algo que empuja —observables, emisores de eventos, callbacks de suscripción—, mientras que Solid lo modela como algo que se tira —accessores que lees y que el grafo rastrea—. No es una diferencia de sintaxis sino de quién gobierna el tiempo: en push, la fuente decide cuándo hay novedad y te interrumpe; en pull, tú lees cuando quieres y el sistema recuerda quién leyó para invalidarlo después. from es el transformador que casa las dos impedancias, y lo hace con la única estructura que puede traducir push a pull sin perder nada: un signal que hace de buffer. Por dentro no hay magia, hay tres gestos que ya conoces sueltos y que aquí van soldados: crear un signal, suscribirse a la fuente para escribir en él cada vez que empuja, y registrar la baja en onCleanup para que la suscripción muera con el dueño. Que use equals: false no es un detalle: delata que from entiende que una fuente push no emite valores sino eventos, y que un evento repetido sigue siendo un evento. Que devuelva T | undefined delata que entiende que el tiempo tiene un antes de la primera emisión. Interiorizar from es dejar de ver el ecosistema de observables como algo ajeno a Solid y empezar a verlo como algo que entra por una puerta de una sola línea, porque toda fuente que empuja —hoy RxJS, mañana un stream de datos, un canal de WebRTC o un emisor que aún no existe— cabe por el mismo agujero con la forma de un accessor.

⚔️ Envuelve tres fuentes distintas con from
  1. Envuelve un interval de RxJS con from y lee el accessor dentro de un createEffect; observa que empieza en undefined hasta el primer tick.
  2. Usa la forma productora para envolver el evento scroll del window, sembrando el valor inicial con set(window.scrollY) y dando de baja el listener en el return.
  3. Emite el mismo valor dos veces desde una fuente y confirma que el accessor notifica las dos veces, demostrando el efecto de equals: false.
  4. Provoca la fuga: llama a from al nivel de módulo, fuera de todo dueño, y razona por qué el onCleanup interno no tiene dónde engancharse.
  5. Cubre el undefined inicial de dos formas —sembrando dentro de la productora y con ?? valorPorDefecto al leer— y decide cuál encaja mejor para un WebSocket.