El helper children(): resolver y memoizar
Qué construye el helper children por dentro: envuelve la lectura de los hijos en un memo, resuelve las expresiones reactivas a nodos concretos y devuelve un getter estable con toArray.
La firma y lo que devuelve
children() recibe una función que devuelve JSX.Element —casi siempre el thunk () => props.children— y devuelve un accesor reactivo: una función que llamas para leer los hijos ya resueltos.
import { children, type JSX } from "solid-js";function Marco(props: { children: JSX.Element }) { const resueltos = children(() => props.children); return <section class="marco">{resueltos()}</section>;}Fíjate en dos detalles. Primero, resueltos no es el array de hijos: es una función. La invocas —resueltos()— para leer el contenido. Segundo, le pasas un thunk, no props.children directamente. La razón es central: props.children es un getter; si lo leyeras aquí, lo evaluarías una vez, fuera de todo seguimiento reactivo. Al envolverlo en () => props.children, dejas que children() decida cuándo y dentro de qué memo leerlo.
Qué hace por dentro
La implementación real cabe en unas líneas. Este es su modelo mental fiel:
import { createMemo } from "solid-js";function children(fn: () => JSX.Element) { const leer = createMemo(fn); // 1. lee props.children con tracking const resueltos = createMemo(() => resolve(leer())); // 2. resuelve a nodos y memoiza (resueltos as any).toArray = () => { const c = resueltos(); return Array.isArray(c) ? c : c != null ? [c] : []; }; return resueltos;}function resolve(nodo: unknown): unknown { if (typeof nodo === "function" && !(nodo as Function).length) return resolve((nodo as Function)()); // thunk de 0 args: se ejecuta if (Array.isArray(nodo)) return nodo.flatMap(resolve); return nodo; // nodo DOM, string, number, null: intacto}Son dos memos anidados. El primero aísla la lectura de props.children. El segundo recorre lo leído y lo resuelve: ejecuta las funciones sin argumentos —las expresiones reactivas que el compilador genera para los hijos dinámicos—, aplana los arrays anidados y deja intactos los nodos DOM, los textos y los números.
Que sean memos tiene una consecuencia de ciclo de vida: children() necesita un owner. Como cualquier createMemo, se apoya en el sistema de propiedad de Solid para limpiar sus suscripciones al desmontar el componente. En el nuevo núcleo reactivo de Solid 2.0 cambia parte del motor de propagación, pero este contrato es idéntico: un memo que lee una vez, resuelve y cachea.
El getter y toArray
resueltos() devuelve lo que haya: un único nodo si el hijo es uno, o un array si son varios. Por eso el helper adosa toArray(), que siempre entrega una lista —vacía, de uno o de muchos— y te ahorra ramificar según la forma.
function Contar(props: { children: JSX.Element }) { const resueltos = children(() => props.children); return <p>Hay {resueltos.toArray().length} hijos.</p>;}Como ambos memos son reactivos, leer resueltos() dentro de un JSX o de un createEffect rastrea el valor: si los hijos cambian —porque vienen de un <For> o de un <Show>— el memo se recomputa y quien lee se actualiza. Un getter vivo, no una foto.
En la práctica, este par cubre casi todo: resueltos() para insertar, resueltos.toArray() para inspeccionar. El resto del nivel lo explota: primero entenderemos por qué era imprescindible, y después lo usaremos para filtrar, envolver e inyectar. Interioriza la firma antes de seguir: dos memos, un getter, un toArray, resolución ansiosa.
La resolución es ansiosa
Aquí está el detalle que casi nadie documenta. createMemo ejecuta su cómputo de inmediato, al crearse. Por tanto children(() => props.children) materializa los hijos en el acto, en el cuerpo del componente, aunque nunca los insertes.
function Panel(props: { abierto: boolean; children: JSX.Element }) { const resueltos = children(() => props.children); // crea los nodos YA return <Show when={props.abierto}>{resueltos()}</Show>;}Aunque props.abierto sea false, los hijos ya se construyeron: sus componentes corrieron y sus efectos se montaron. Si buscabas pereza —crearlos solo al abrir— acabas de perderla.