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.
Las props no son todas iguales. Casi todas son datos que fluyen, pero unas pocas tienen significado propio para el compilador y el runtime de Solid: children, ref, los manejadores de eventos, classList, style, el spread. Y children es la más especial de todas, porque no transporta un valor sino DOM potencial: nodos que se crearán cuando alguien los lea. Manejarla sin cuidado duplica elementos o los crea antes de tiempo. Esta lección recorre ese borde del modelo de props, donde la reactividad se encuentra con la plataforma.
- Entender
childrencomo prop reactiva y usar el helperchildrenpara resolverla. - Reconocer las props especiales del compilador:
ref, eventos,classList,style, spread. - Componer con
Dynamicpara elegir el elemento o componente en tiempo de ejecución. - Construir componentes controlados con callbacks como única fuente 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 patrón render-prop existe en Solid: <Lista>{(item) => <Fila dato={item} />}</Lista>. Aquí props.children es una función, no nodos. El helper children la resuelve igual, pero cuando esperas una función explícita, invócala tú: props.children(valor). Es la base de componentes que ceden el control del renderizado a quien los usa.
El flujo del helper, de un vistazo:
flowchart TD C[props punto children] --> H[helper children memoiza] H --> R[Resuelve una sola vez] R --> N[Normaliza funciones y arrays] N --> A[Accesor estable] A --> I[Inspeccionar contar o envolver] A --> P[Pintar en el JSX] style C fill:#89b4fa,color:#11111b style H fill:#cba6f7,color:#11111b style A fill:#a6e3a1,color:#11111b
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:
ref
ref={el} te entrega el nodo real del DOM, asignado antes de onMount. Como prop reenviable, props.ref deja que un envoltorio exponga el elemento interno hacia arriba.
Eventos
onClick usa delegación de eventos para rendimiento; on:click adjunta un listener nativo directo, ideal para eventos personalizados; oncapture: engancha la fase de captura.
classList y style
classList={{ activo: esActivo() }} conmuta clases de forma reactiva y quirúrgica; style={{ color: c() }} acepta un objeto y actualiza solo la propiedad que cambia.
Spread y namespaces
{...otras} reenvía props reactivamente; attr:, prop: y bool: fuerzan atributo, propiedad del elemento o atributo booleano cuando la heurística por defecto no basta.
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.
Un componente no controlado guardaría su propio signal interno y solo avisaría al padre de vez en cuando; es cómodo, pero abre la puerta a dos verdades que se desincronizan. El patrón controlado de arriba renuncia a todo estado local: la prop es la única fuente. La regla práctica: si el padre necesita leer o imponer el valor, hazlo controlado; si el valor es puramente interno y efímero, un signal local basta. En Solid la elección es barata porque, controlado, no pagas ningún re-render por teclear: solo se actualiza el nodo afectado.
Todo el nivel 7 ha defendido una tesis: las props son aristas de un grafo reactivo, no datos. Esta última lección muestra dónde ese grafo se enchufa al mundo real, y por qué el diseño de Solid es tan coherente hasta el borde. children es una prop cuya lectura materializa DOM, así que se rodea de un helper que memoiza esa materialización; los eventos, classList, style y el spread son props que el compilador traduce a mutaciones quirúrgicas del DOM en lugar de a re-renders; Dynamic extiende la reactividad de los datos a la estructura misma; y el patrón controlado convierte una prop de entrada más un callback de salida en un enlace bidireccional sin estado redundante. En ningún punto se rompe el principio de grano fino: cada arista, especial o no, actualiza exactamente el trozo de plataforma del que es responsable, y nada más. Cuando ves children, ref, onInput y Dynamic no como casos aparte sino como el mismo modelo de props tocando distintos rincones del DOM, dejas de memorizar API y empiezas a leer Solid como lo que es: un lenguaje coherente para describir cómo un grafo de valores gobierna una página viva.
- Escribe
Envolturaque recibachildren, use el helperchildreny cuente sus hijos en uncreateEffect. - Renderiza los hijos dos veces en el JSX; primero con
props.children(observa la duplicación) y luego con el accesor memoizado (observa que se resuelve una vez). - Crea
Botonpolimórfico conDynamicy propas, y renderízalo comobuttony comoa. - Construye
Campocontrolado convaloryonCambio; conéctalo a un signal del padre y confirma que dosCampoque comparten el mismo signal se mantienen sincronizados. - Cambia un evento de
onClickaon:clicky razona cuándo importa la diferencia entre delegación y listener nativo.