children y props especiales: el borde del modelo
children es una prop reactiva más, y resolverla mal duplica nodos del DOM; el helper children la memoiza y la normaliza. A su lado viven las props especiales del compilador —ref, manejadores de eventos, classList, style, spread y el componente Dynamic— y el patrón de componentes controlados con callbacks como fuente única de verdad.
children es una prop, y peligrosa de leer varias veces
Cuando escribes <Panel>contenido</Panel>, ese contenido llega como props.children. Es una prop dinámica como cualquier otra, con un matiz explosivo: leer props.children crea los nodos. Cada acceso puede instanciar de nuevo los elementos, así que leerla dos veces —una para medir, otra para pintar— produce dos copias del DOM. La solución es el helper children, que envuelve la lectura en un memo: resuelve los hijos una sola vez, los memoiza y te da un accesor estable.
import { children } from "solid-js";function Panel(props) { const resueltos = children(() => props.children); createEffect(() => { const nodos = resueltos(); // array normalizado, creado una sola vez console.log("hijos:", Array.isArray(nodos) ? nodos.length : 1); }); return <section class="panel">{resueltos()}</section>;}children hace dos favores: memoiza (los nodos se crean una vez, aunque los leas mil) y normaliza (desenvuelve funciones, aplana arrays anidados, te da algo uniforme sobre lo que iterar). Siempre que necesites inspeccionar o manipular los hijos —contarlos, envolver cada uno, leer sus props— pasa por children, nunca por props.children crudo.
Y como resueltos es un memo, deriva de él con naturalidad: envolver cada hijo, filtrarlos o inyectarles props es solo transformar el array que devuelve dentro de otro scope reactivo, de modo que si los hijos cambian, tu transformación se rehace sola. Ese es el mecanismo detrás de componentes como Tabs o Menu, que leen sus hijos para saber cuántos son y qué props declaran, sin que el usuario tenga que numerarlos a mano.
El flujo del helper, de un vistazo:
Las props especiales del compilador
Algunas props no se pasan como datos: el compilador las interpreta. Conviene tenerlas mapeadas, porque su comportamiento no se deduce del modelo general de getters:
Estas son el punto donde el grafo reactivo toca la plataforma web. El compilador las cablea a operaciones concretas del DOM —añadir un listener, alternar una clase, fijar una propiedad— manteniendo la reactividad de grano fino: si esActivo() cambia, solo se toca esa clase, no se repinta nada más.
Combinadas, componen la anatomía típica de un componente de entrada: una ref al nodo real, una clase reactiva y un listener directo, todo en el mismo elemento.
function Entrada(props: { invalido: boolean; onTexto: (v: string) => void }) { let campo!: HTMLInputElement; // ref local al nodo return ( <input ref={campo} // asignada antes de onMount classList={{ error: props.invalido }} // conmuta la clase de forma quirúrgica on:input={(e) => props.onTexto(e.currentTarget.value)} // listener nativo directo /> );}Dynamic: elegir el componente en caliente
Cuando el propio elemento o componente a renderizar depende de una prop —un botón que a veces es <a> y a veces <button>, un componente polimórfico con prop as— el compilador no puede saberlo de antemano. Para eso está Dynamic, que recibe qué renderizar como valor y le hace spread del resto:
import { Dynamic } from "solid-js/web";function Texto(props: { as?: string; children: JSX.Element }) { const [local, resto] = splitProps(props, ["as"]); return <Dynamic component={local.as ?? "span"} {...resto} />;}// <Texto as="h1">Título</Texto> -> <h1>Título</h1>El valor de component puede ser tanto una etiqueta en texto —div, h1— como un componente de verdad, lo que convierte a Dynamic en la pieza para renderizar componentes elegidos en tiempo de ejecución: un mapa de tipo a componente en un CMS, un editor por bloques, un sistema de plugins. Dynamic cierra el círculo del nivel: props reactivas que deciden no solo qué datos, sino qué estructura. Y como local.as es una lectura viva, cambiar la prop en caliente re-monta el elemento correcto sin que tú orquestes nada.
Componentes controlados: el callback como fuente de verdad
El último patrón une props de entrada y props de salida. Un componente controlado no guarda su propio estado: recibe el valor por una prop y notifica los cambios por un callback, dejando que el padre sea la única fuente de verdad. La reactividad de grano fino lo hace trivial y sin el ceremonial de React.
function Campo(props: { valor: string; onCambio: (v: string) => void }) { return ( <input value={props.valor} // entra por prop reactiva onInput={(e) => props.onCambio(e.currentTarget.value)} // sale por callback /> );}// El padre posee el estado; el hijo solo lo refleja y lo propone:const [texto, setTexto] = createSignal("");<Campo valor={texto()} onCambio={setTexto} />;props.valor mantiene el <input> sincronizado con el signal del padre, y onCambio sube cada pulsación. No hay estado duplicado, no hay desincronización posible: el dato vive en un único sitio y el componente es una ventana bidireccional sobre él.