Switch y Match: ramas mutuamente exclusivas
Switch renderiza el primer Match cuyo when es verdadero y un fallback si ninguno acierta. Es el if-else-if reactivo de Solid: exclusividad mutua garantizada, evaluacion en orden y una unica rama montada a la vez.
Cuando las ramas dejan de ser dos y hay tres, cuatro o cinco caminos mutuamente excluyentes, anidar Show dentro de Show produce una pirámide ilegible que además no declara la exclusividad. Para eso existe Switch con Match: renderiza el primer Match cuyo when sea verdadero, y solo ese. Es el if / else if / else de un lenguaje reactivo, con la garantía estructural de que exactamente una rama —o el fallback— está viva en cualquier instante.
- Escribir un
Switchcon variosMatchy unfallback. - Entender por qué gana el primer
Matchque acierta y qué implica el orden. - Ver que solo una rama está montada a la vez y que cambiar de rama destruye la anterior.
- Reconocer que
Matchhereda deShowla memoización,keyedy el accesor.
La firma: Switch envuelve, Match ramifica
Switch no lleva condición propia: es un contenedor que recibe Match como hijos directos y un fallback opcional. Cada Match lleva su when. Switch los evalúa en orden y monta el primero cuyo when sea truthy:
import { Switch, Match } from "solid-js";
<Switch fallback={<p>estado desconocido</p>}>
<Match when={estado() === "cargando"}><Spinner /></Match>
<Match when={estado() === "error"}><ErrorVista error={err()} /></Match>
<Match when={estado() === "listo"}><Datos datos={datos()} /></Match>
</Switch>
Esto sustituye a una cadena de else if o a un ternario anidado de tres niveles. La diferencia no es solo estética: Switch declara que las ramas son mutuamente excluyentes. El lector no tiene que demostrar, leyendo condiciones cruzadas, que no hay dos ramas que puedan encenderse a la vez; la semántica de “el primero que acierta” lo garantiza por construcción.
El primer Match que acierta: el orden es lógica
Switch recorre sus Match de arriba abajo y se detiene en el primero verdadero. Por eso el orden es parte de la lógica, no un detalle cosmético. Si dos condiciones pueden ser verdaderas a la vez, gana la de arriba:
// el orden importa: una condicion general arriba TAPA a las especificas
<Switch>
<Match when={n() >= 0}><p>no negativo</p></Match>
<Match when={n() === 0}><p>cero</p></Match> {/* inalcanzable */}
</Switch>
Aquí n() === 0 jamás se muestra, porque n() >= 0 ya lo captura. La regla operativa es la misma que en un switch clásico bien escrito: de lo más específico a lo más general, y el caso general —si lo hay— va al fallback, no a un Match final. Cuando ningún Match acierta, Switch monta el fallback; si no hay fallback, no monta nada.
flowchart TD S[Switch reevalua sus Match en orden] --> M1[Match 1 when] M1 -->|verdadero| R1[monta rama 1 y se detiene] M1 -->|falso| M2[Match 2 when] M2 -->|verdadero| R2[monta rama 2 y se detiene] M2 -->|falso| M3[Match 3 when] M3 -->|verdadero| R3[monta rama 3 y se detiene] M3 -->|falso| F[monta el fallback] style R1 fill:#a6e3a1,color:#11111b style R2 fill:#a6e3a1,color:#11111b style R3 fill:#a6e3a1,color:#11111b style F fill:#fab387,color:#11111b
Una sola rama viva: montar destruye la anterior
Aquí está el comportamiento reactivo que hay que interiorizar. Switch mantiene un memo sobre qué índice gana. Mientras el ganador no cambie, no toca el DOM. Cuando el ganador cambia, Switch desmonta la rama anterior —ejecutando sus onCleanup, cancelando sus efectos, liberando su owner— y monta la nueva. No coexisten: la transición es un relevo, no una superposición.
<Switch>
<Match when={vista() === "mapa"}>
<Mapa /> {/* si vista() pasa a "lista", Mapa se DESTRUYE por completo */}
</Match>
<Match when={vista() === "lista"}>
<Lista />
</Match>
</Switch>
Esta destrucción es una garantía, no un efecto secundario: el estado, las suscripciones y los nodos DOM de la rama saliente desaparecen limpiamente. Si Mapa abrió un listener de geolocalización en un efecto, su onCleanup corre al cambiar de vista. Por eso Switch es también una herramienta de gestión de recursos: cada rama es un ámbito de vida acotado a que su Match sea el ganador.
Switch lee sus Match como descriptores estructurales, no ejecuta un render que los descubra. Por eso Match tiene que ser hijo directo de Switch. Si envuelves un Match en otro componente o en un fragmento con lógica, Switch no lo verá como rama. Si necesitas generar ramas dinámicamente, computa el arreglo de condiciones y usa Show/Dynamic, o construye los Match inline; no los escondas tras una capa de componente.
Switch con Match
Ramas mutuamente excluyentes evaluadas en orden, con fallback y keyed por rama. La forma que declara “una de estas”. Idónea para máquinas de estado y tipos unión.
Show anidados
Válido para dos caminos, pero con tres o más te obliga a sostener a mano la exclusividad y a anidar fallback dentro de fallback. La invariante deja de ser visible.
Cadena de ternarios
Compacta y reactiva, pero pierde fallback nombrado, keyed y el accesor estrechado, y degenera en una pirámide ilegible en cuanto crece. La estudiaremos como antipatrón en 15.3.
Match hereda de Show: keyed y accesor
Match no es un primo lejano de Show: comparte su firma de hijos. Un Match acepta JSX directo o un hijo-función, y obedece las mismas reglas de keyed. Sin keyed, el hijo-función recibe un Accessor<NonNullable<T>> que estrecha el tipo y mantiene vivo el subárbol mientras esa rama gane; con keyed, recibe el valor directo y recrea al cambiar la identidad:
<Switch fallback={<Anonimo />}>
<Match when={sesion()}>
{(s) => <Perfil usuario={s().nombre} />} {/* s es accesor, ya no-nulo */}
</Match>
<Match when={invitado()}><Bienvenida /></Match>
</Switch>
Y como en Show, los hijos-función de Match se envuelven en untrack: la función que produce la rama no crea por sí misma un ámbito de seguimiento sobre el valor; el valor fluye por el accesor, y el seguimiento fino vive en los bindings de dentro. Todo lo que aprendiste de Show sobre memoización por verdad y recreación por identidad se aplica, sin cambios, a cada rama de un Switch. Así, una rama puede pedir keyed para resetear su estado cuando cambia la entidad que representa, mientras las hermanas conservan el suyo:
<Switch fallback={<Vacio />}>
<Match when={documento()} keyed>
{(doc) => <Editor documento={doc} />} {/* nuevo doc, editor limpio */}
</Match>
<Match when={cargando()}><Spinner /></Match>
</Switch>
Cuando lo que cambia es qué componente montar y las opciones son un mapa cerrado, Dynamic despacha por valor sin escribir el condicional: <Dynamic component={vistas[tipo()]} />. Switch gana cuando cada rama tiene una condición propia y arbitraria —no una simple clave— o cuando quieres fallback y keyed por rama. No compiten: a menudo un Show cubre el hueco de carga y un Dynamic elige la vista dentro, y un Switch orquesta los estados de nivel superior.
La razón profunda para preferir Switch sobre una torre de Show anidados no es la indentación: es que Switch modela una verdad del dominio que los Show anidados solo implementan por accidente. Cuando dices “el estado es cargando, o error, o listo, o desconocido”, estás describiendo una suma disjunta —un tipo unión— y Switch es su forma exacta en la vista: exactamente un habitante activo, evaluado en orden, con un caso por defecto. Anidar Show te obliga a mantener tú la invariante de exclusividad en la cabeza y a rezar para que dos condiciones no se solapen; Switch la vuelve estructural e inspeccionable de un vistazo. Esta es la misma lección que enseña el match de Rust o el switch exhaustivo de un lenguaje tipado: cuando la forma del código coincide con la forma de los datos, los estados imposibles se vuelven inexpresables. En Solid, además, esa correspondencia es reactiva: el memo del índice ganador convierte “qué rama del tipo unión está activa” en otro valor que cambia con el tiempo, gestionado por el grafo, con montaje y limpieza automáticos en cada transición. Escribe Switch cuando el dominio dice “una de estas”; reserva Show para el “esto o nada” de dos caras.
- Toma un ternario anidado de tres niveles y reescríbelo como
Switchcon tresMatchy unfallback. - Provoca un solapamiento: pon una condición general antes de una específica y demuestra que la específica queda inalcanzable. Corrige el orden.
- Mete un
createEffectcononCleanupdentro de una rama y observa en consola que el cleanup corre al cambiar de rama. - Convierte una rama a hijo-función con
when={objeto()}y usa el accesor estrechado sin!. - Explica por qué
Switchgarantiza “exactamente una rama viva” y qué invariante tendrías que sostener a mano si usarasShowanidados.