Show como guard: el accesor estrechado
La forma callback de Show entrega un Accessor de NonNullable: el valor ya no-nulo, con el tipo estrechado, sin dobles lecturas ni afirmaciones con exclamacion. Por que es un accesor vivo y no el valor directo, y como encadenar guards.
El antipatrón: leer dos veces y afirmar
Sin la forma callback, guardar contra un nulo obliga a leer la señal dos veces —una para la condición, otra para el uso— y a afirmarle a TypeScript, con !, que el valor no es nulo:
// dos lecturas de usuario(), y una asercion no-nula fragil<Show when={usuario()}> <Perfil nombre={usuario()!.nombre} /></Show>Hay dos problemas. El de tipos: el ! es una promesa que le haces al compilador y que él no verifica; si un día cambia la condición, el ! queda mintiendo. Y el conceptual: son dos lecturas de la misma fuente que, en principio, podrían ver valores distintos. Show resuelve ambos de un golpe con el hijo-función.
La forma callback: un accesor ya estrechado
Pasa una función como hijo. Show la invoca con un accesor que devuelve el valor de when con el tipo estrechado a NonNullable<T>:
// una lectura, tipo estrechado, sin exclamacion<Show when={usuario()} fallback={<Login />}> {(usuario) => <Perfil nombre={usuario().nombre} />}</Show>Dentro del callback, usuario es Accessor<NonNullable<Usuario>>: lo invocas con usuario() y obtienes el usuario garantizado no-nulo. TypeScript lo sabe sin ayuda, así que .nombre es legal y ! desaparece. La firma oficial lo confirma: en la forma sin keyed, los hijos son JSX.Element | ((item: Accessor<NonNullable<T>>) => JSX.Element).
Por qué es un accesor y no el valor
Aquí está la profundidad. En la forma sin keyed, Show conserva el subárbol mientras when siga siendo verdadero (lección 15.1). Si el callback recibiera el valor directo, ese valor quedaría congelado en la primera invocación y Show tendría que recrear el subárbol para entregarte uno nuevo cada vez que cambiara. Eso es, precisamente, lo que hace keyed. Al entregarte un accesor, Show puede conservar el subárbol y, a la vez, mantener el valor vivo: cada lectura de usuario() devuelve el estado más reciente, y los bindings finos de dentro se actualizan sin recrear nada.
Dos garantías refuerzan el diseño. Primera: el accesor “solo puede leerse mientras la condición siga siendo verdadera”; como Show desmonta el subárbol en cuanto when pasa a falsy, nunca leerás un nulo a través de él —el estrechamiento es honesto, no una ilusión de tipos—. Segunda: el hijo-función se envuelve en untrack, así que la función que construye la vista no se resuscribe a when; el seguimiento reactivo vive en el accesor y en los bindings internos. Por eso el subárbol se crea una vez y se actualiza fino, en vez de reconstruirse.
Compruébalo con un valor que cambia sin volverse nulo:
// el accesor entrega SIEMPRE el valor mas reciente sin recrear el subarbol<Show when={sesion()} fallback={<Login />}> {(sesion) => ( <header> Hola, {sesion().nombre} <Racha dias={sesion().rachaDias} /> {/* se refresca solo este binding */} </header> )}</Show>Si sesion() actualiza su rachaDias sin volverse nula, el header no se reconstruye: solo cambia el binding de la racha. Ese es el pago exacto del accesor —persistencia del subárbol y frescura del dato a la vez—, algo que un valor congelado, entregado una única vez, no podría ofrecer sin recrear.
Encadenar guards
Como cada Show-guard estrecha un nivel, encadenarlos estrecha una cadena de valores opcionales sin una sola afirmación no-nula, y cada nivel aporta su propio fallback:
<Show when={usuario()} fallback={<Login />}> {(usuario) => ( <Show when={usuario().perfil} fallback={<CompletarPerfil />}> {(perfil) => <Cabecera avatar={perfil().avatar} />} </Show> )}</Show>Cada capa responde una pregunta —¿hay usuario?, ¿tiene perfil?— con su propia respuesta para el caso negativo. El resultado se lee como una secuencia de guardas de precondición, igual que los guard let encadenados de otros lenguajes, pero reactivo: si usuario() se vuelve nulo, toda la cadena interior se desmonta en cascada y aparece <Login />.