Saltar al contenido

Por qué NO se destructuran las props

Destructurar las props en Solid rompe la reactividad de la forma más silenciosa posible: leer los getters una sola vez congela sus valores y corta las aristas del grafo. Aquí ves por qué la destructuración es evaluación ansiosa, cómo lo detecta el linter, y cuáles son las alternativas idiomáticas que preservan el flujo.

Destructurar es evaluar ya, y una sola vez

La asignación por destructuración no es azúcar sintáctico inocente: es una lectura inmediata. Cuando escribes const { valor } = props, JavaScript accede a props.valor en ese preciso momento, ejecuta el getter, obtiene un número y lo copia en una constante llamada valor. A partir de ahí, valor es un primitivo desconectado: no sabe de dónde vino ni tiene forma de enterarse de que la fuente cambió. Es el mismo error conceptual que llamar a un signal y guardar su número —const n = count()— en lugar de guardar el signal: te quedas con la fotografía y tiras la cámara.

TSX
// ROTO: la reactividad muere en la primera líneafunction Boton(props: { etiqueta: string }) {  const { etiqueta } = props;        // getter leído una vez -> valor muerto  return <button>{etiqueta}</button>; // nunca se actualizará}// CORRECTO: cada lectura reevalúa el getter dentro del JSXfunction Boton(props: { etiqueta: string }) {  return <button>{props.etiqueta}</button>; // arista viva}

La regla se resume en una imagen: accede tarde, no pronto. Mantén la palabra props. delante hasta el último momento posible, dentro de un scope de tracking, para que cada lectura sea una llamada fresca al getter y no una copia rancia. Fíjate en el detalle que engaña: los dos Boton de arriba montan idénticos —el primer valor de etiqueta es correcto en ambos—, así que el bug no se ve al abrir la página. Solo aparece cuando el padre cambia la prop y el roto no reacciona, a veces días después, lejos de la línea culpable.

Los tres disfraces del mismo error

El fallo se cuela con muchas caras, y todas comparten la raíz de la lectura ansiosa. Conviene reconocerlas al vuelo:

TSX
// 1. Destructuración explícitaconst { valor } = props;// 2. Parámetro destructurado (el reflejo de React)function Boton({ etiqueta, onClick }) { /* ... */ }// 3. Rest, que además fuerza a leer TODAS las props de golpeconst { clase, ...resto } = props;

El tercero es el más insidioso porque, para construir resto, el operador rest enumera y lee cada propiedad enumerable del objeto en ese instante: dispara todos los getters a la vez y congela el objeto entero. Cualquiera de las tres formas produce lo mismo: valores fotografiados que no volverán a cambiar. Y como el componente monta bien —los valores iniciales son correctos—, el bug es invisible hasta que algo debería moverse y no lo hace.

Las alternativas que sí preservan el flujo

No destructurar no significa repetir props. de forma cansina por todas partes. Solid ofrece herramientas para cada necesidad, todas construidas para mantener las aristas intactas:

TSX
import { mergeProps, splitProps } from "solid-js";function Boton(props: { etiqueta: string; tamano?: string; onClick?: () => void }) {  // Defaults reactivos sin congelar: 7.3  const conDefaults = mergeProps({ tamano: "md" }, props);  // Separar lo propio de lo que reenvías, sin rest destructivo: 7.4  const [propio, resto] = splitProps(conDefaults, ["etiqueta"]);  // Derivar sin perder reactividad: envuelve en función o memo  const clase = () => `btn btn-${conDefaults.tamano}`;  return (    <button class={clase()} {...resto}>      {propio.etiqueta}    </button>  );}

La idea de fondo: cuando necesites un valor por defecto, usa mergeProps; cuando necesites apartar unas props y reenviar el resto, usa splitProps; cuando necesites transformar, envuelve el cálculo en una función o un memo para que se reevalúe. Lo que nunca haces es sacar el valor del getter y guardarlo suelto.

Hay una comodidad legítima que sí se permite: crear un alias en forma de función. Si repetir props.tamano cansa, define const tamano = () => props.tamano y llama a tamano() donde lo necesites. Es una variable, sí, pero contiene el modo de leer, no el valor leído, así que la arista sobrevive. La diferencia es literal y milimétrica: const tamano = props.tamano congela, const tamano = () => props.tamano fluye.

Dónde sí puedes leer directo

La regla no dice “nunca leas una prop en una variable”, dice “no la separes de su getter en un sitio que se ejecuta una sola vez”. Hay un lugar donde acceder directo es no solo seguro sino idiomático: dentro de un manejador de eventos o de cualquier callback diferido. Ahí la lectura ocurre cuando el usuario actúa, no al montar, así que el getter devuelve el valor vigente en ese preciso instante.

TSX
function Guardar(props: { texto: string; onGuardar: (t: string) => void }) {  // props.texto se lee al hacer clic, no antes: el valor siempre es el actual  return <button onClick={() => props.onGuardar(props.texto)}>Guardar</button>;}

Lo mismo vale dentro de un createEffect, un createMemo o el JSX: son scopes que se reevalúan, así que la lectura se repite y la arista vive. El pecado no es leer props.x, es guardar el resultado de esa lectura en un sitio que solo corre una vez. Accede tarde y accede a menudo; nunca fotografíes.