wandres.dev
PROPS · mergeProps, splitProps

splitProps: separar lo propio de lo que reenvías

splitProps parte un objeto de props en grupos que siguen siendo proxies reactivos, para consumir unas props y reenviar el resto con spread al DOM o a un hijo sin romper la reactividad. Además infiere los tipos de cada grupo, algo que la destructuración con rest jamás podría dar sin congelar el objeto entero.

⏱ 15 min

Un componente envoltorio tiene un problema recurrente: quiere quedarse con algunas props para su lógica y reenviar todas las demás a un elemento del DOM o a un hijo. En JavaScript normal eso es un rest: const { clase, ...resto } = props. Pero ya sabes de 7.2 que el rest enumera y lee todas las props de golpe, congelando el objeto entero. splitProps es la herramienta que hace exactamente ese reparto sin cortar ninguna arista: te devuelve varios proxies reactivos en lugar de un objeto muerto.

🎯 Al terminar esta lección sabrás
  • Entender por qué el rest destructivo no sirve para reenviar props.
  • Dominar la firma de splitProps y su devolución de proxies reactivos.
  • Reenviar el resto con spread al DOM o a un hijo conservando la reactividad.
  • Aprovechar la inferencia de tipos que da un resultado tipado por grupo.

El problema del envoltorio que reenvía

El patrón es universal: un Boton que envuelve al <button> nativo, añade una clase y una variante, y quiere que cualquier otra prop —onClick, disabled, aria-label, type— llegue intacta al elemento real. Necesitas apartar variante y class, y hacer spread del resto:

// ROTO: el rest lee y congela todas las props
function Boton(props) {
  const { variante, class: clase, ...resto } = props; // dispara todos los getters
  return <button class={`btn-${variante} ${clase}`} {...resto} />; // resto es una foto
}

Ese {...resto} reenvía valores muertos: si el padre cambia disabled más tarde, el <button> no se entera. El spread de un objeto plano rompe justo lo que un envoltorio debe preservar: la transparencia reactiva hacia el elemento que envuelve.

splitProps: un rest que no congela

splitProps(props, claves) recibe el objeto de props y uno o más arrays de claves. Devuelve un array de proxies: uno por cada grupo de claves que pediste, y un último proxy con todo lo que sobró. Cada proxy conserva los getters, así que leer de cualquiera de ellos sigue siendo una lectura viva.

import { splitProps } from "solid-js";

function Boton(props: JSX.ButtonHTMLAttributes<HTMLButtonElement> & { variante: string }) {
  const [propio, resto] = splitProps(props, ["variante", "class"]);
  //     ┃ solo variante y class       ┃ todo lo demás, reactivo
  return (
    <button class={`btn-${propio.variante} ${propio.class ?? ""}`} {...resto} />
  );
}

Ahora {...resto} es un proxy: el compilador de Solid hace el spread de forma reactiva, de modo que si el padre cambia disabled u onClick, el <button> lo recibe al instante. Has consumido dos props y reenviado el universo restante sin que ninguna pierda su cable con la fuente.

Fíjate en un matiz clave: propio y resto no son objetos nuevos con datos copiados, sino vistas —proxies— sobre las mismas props del padre. No hay clonación ni coste de memoria en el reparto.

Eso distingue a splitProps de cualquier utilidad casera de “omitir claves”: las artesanales copian y congelan; esta reencamina y mantiene vivas las aristas a ambos lados.

flowchart TD
P[props del padre proxy completo] --> S[splitProps con lista de claves]
S --> A[Proxy propio variante y class]
S --> B[Proxy resto reactivo]
A --> U[Uso interno del componente]
B --> D[Spread al button del DOM]
style P fill:#89b4fa,color:#11111b
style A fill:#cba6f7,color:#11111b
style B fill:#a6e3a1,color:#11111b
style D fill:#f9e2af,color:#11111b
🎯

Los grupos que pediste

Un proxy por cada array de claves, en el mismo orden. Contienen solo esas props, siguen siendo getters vivos, y son lo que consume la lógica interna del componente.

📤

El resto, para reenviar

El último proxy reúne todo lo que no reclamaste. Es el que va al {...spread} hacia el DOM o el hijo, y por ser proxy transporta cada cambio del padre sin fotografiarlo.

📝
El resto siempre va al final, y nunca es undefined

Sea cual sea el número de grupos, splitProps coloca el proxy del resto en la última posición del array. Si no sobra ninguna prop, ese proxy es un objeto vacío, no undefined: puedes hacerle spread sin comprobar nada. Y si pides una clave que no existe en props, no ocurre nada raro: no aparece en el grupo y no se inventa. El reparto es total y sin sorpresas.

Varios grupos a la vez

Puedes pedir más de un grupo pasando varios arrays de claves; splitProps devuelve un proxy por cada uno, en orden, y el rest al final. Es útil cuando repartes props entre varios destinos: unas para estilos, otras para eventos, el resto al elemento.

const [estilo, eventos, resto] = splitProps(
  props,
  ["class", "style"],        // grupo 1: presentación
  ["onClick", "onFocus"],    // grupo 2: comportamiento
);
// estilo.class, eventos.onClick, {...resto} — tres proxies vivos

Una clave solo cae en el primer grupo que la reclama; el rest recoge lo que nadie pidió. Así puedes dirigir cada familia de props a su sitio sin duplicar ni perder reactividad en el camino.

El caso estrella es el componente polimórfico: un Box que acepta una prop as para elegir su etiqueta y reenvía todo lo demás al elemento resultante. splitProps aparta as y children, y el resto viaja íntegro y reactivo:

function Box(props) {
  const [propio, resto] = splitProps(props, ["as", "children"]);
  const Tag = () => propio.as ?? "div";
  return <Dynamic component={Tag()} {...resto}>{propio.children}</Dynamic>;
}

El componente Dynamic llega en 7.5; aquí basta ver que resto reenvía atributos reactivos a la etiqueta que as decida, y que cambiar as en caliente cambia el elemento sin tocar el resto de las props.

Tipar el resultado

En TypeScript, splitProps no te pide anotar nada: sus genéricos derivan el tipo de cada grupo a partir de la lista de claves. El primer proxy recibe un Pick de las claves que extrajiste; el rest recibe el Omit complementario. Los conjuntos son disjuntos también en el plano de tipos, así que el compilador te protege de leer en el sitio equivocado.

type BotonProps = JSX.ButtonHTMLAttributes<HTMLButtonElement> & {
  variante: "solido" | "fantasma";
  cargando?: boolean;
};

function Boton(props: BotonProps) {
  const [propio, resto] = splitProps(props, ["variante", "cargando"]);
  // propio: Pick<BotonProps, "variante" | "cargando">
  // resto:  Omit<BotonProps, "variante" | "cargando">  -> atributos nativos del button
  return (
    <button disabled={propio.cargando} {...resto}>
      {propio.variante === "solido" ? "OK" : "..."}
    </button>
  );
}

resto queda tipado exactamente como los atributos nativos de <button>, que es justo lo que el spread necesita para ser type-safe: no puedes reenviar al DOM una prop que el elemento no admite sin que el compilador te frene. Esta inferencia por diferencia de conjuntos es la que la destructuración con rest daría en tipos, pero sin la reactividad; splitProps te entrega las dos cosas a la vez, y por eso es el cimiento de los sistemas de componentes tipados en Solid.

💡
splitProps y mergeProps son socios

El patrón canónico de un componente robusto encadena ambos: primero mergeProps para inyectar defaults reactivos (7.3), y sobre ese resultado splitProps para separar lo propio de lo reenviable. const merged = mergeProps(defaults, props); const [local, otras] = splitProps(merged, [...]). Defaults abajo, reparto arriba, reactividad intacta de punta a punta.

splitProps preserva la identidad de las aristas al repartirlas

La proeza de splitProps no es cosmética, es estructural. Un rest de JavaScript construye un objeto nuevo copiando valores: en el proceso, cada prop pierde su identidad como arista del grafo y se convierte en un dato desarraigado. splitProps hace lo contrario: no copia valores, redistribuye referencias. Cada proxy que devuelve no contiene los datos, sino getters que siguen apuntando a los signals originales del padre; el reparto es un cambio de enrutado, no de contenido. Por eso puedes trocear un objeto de props en tres y hacer spread de un trozo a un <button>, y que ese <button> reaccione a cambios del padre a través de dos capas de proxy sin que se pierda una sola actualización. Esta es la propiedad que hace posibles los componentes verdaderamente componibles y polimórficos de Solid: envoltorios que son transparentes a la reactividad porque nunca la materializan, solo la reencaminan. Cuando entiendes que separar props es redirigir aristas y no partir datos, dejas de temer los componentes envoltorio y empiezas a construir sistemas de diseño enteros sobre mergeProps y splitProps, sabiendo que la reactividad atraviesa cada capa sin fugas.

⚔️ Un envoltorio transparente
  1. Escribe Input que aparte label y reenvíe el resto a un <input> con {...resto}.
  2. Desde el padre, pásale disabled={estaDeshabilitado()} con un signal y cámbialo; confirma que el <input> reacciona.
  3. Reemplaza splitProps por un rest destructurado y repite el paso 2: verifica que se congela.
  4. Añade un segundo grupo para separar los manejadores onInput/onChange del resto y comprueba que los tres proxies funcionan.
  5. En TypeScript, intenta leer resto.label tras haberla movido al grupo propio y observa el error de tipos: los conjuntos son disjuntos.