Enrutamiento y navegación
El enrutamiento en Carats son datos, no un sistema de archivos. Cada ruta se declara en un único objeto de facetas que comparten el cliente y el servidor.
Rutas
El mapa de rutas va de un patrón de ruta a un componente de página. El patrón se compara de forma exacta, salvo en los segmentos que empiezan por dos puntos, que capturan un parámetro.
import { defineFacets } from '@carats/render';
import ErrorPage from './pages/_error';
import NotFound from './pages/_not_found';
import Home from './pages/home';
import Market from './pages/market';
import Profile from './pages/profile';
import Trade from './pages/trade';
export default defineFacets({
routes: {
'/': Home,
'/profile': Profile,
'/market': Market,
'/trade/:symbol': Trade
},
suspense: {
error: ErrorPage,
notFound: NotFound
},
inAppRouting: true
});
El nombre de la carpeta de una página es una convención, no un requisito. La carpeta mantiene juntos los archivos relacionados, pero la ruta que sirve una carpeta es la cadena a la que esté mapeada, así que /market puede vivir en una carpeta llamada browse si se lee mejor.
Parámetros dinámicos
Un segmento :name se convierte en una prop del componente de página, y también está disponible como params.name dentro de un culet de la misma ruta. El nombre debe coincidir en ambos lados.
import { Burnish } from '@carats/render';
import Layout from '../_layout';
import './trade.sass';
export default Burnish<TradeData>(function (data) {
this.head = <title>Trade {data.symbol}</title>;
return (
<Layout>
<h1>{data.symbol}</h1>
</Layout>
);
});
`/trade/btc` renderiza esta página con `data.symbol` resuelto a partir del culet registrado para la misma ruta, y la página renderiza un `
` distinto para cada símbolo. El framework hace la comparación en `getPageComponent`, que devuelve el componente, los `params` extraídos y la `route` coincidente.</p><blockquote><p>Los patrones de ruta los compara @carats/url con matchRoute. No hay comodín, ni segmento opcional, ni patrón que capture todo: una URL sin coincidencia renderiza el componente de suspensión notFound.</p></blockquote><h2>Enrutamiento dentro de la aplicación</h2><p>Con inAppRouting activado, el cliente intercepta los clics en anclas internas y cambia de página sin una carga completa. También requiere clientRender() en el punto de entrada del cliente. Desactívalo cuando una página dependa de una carga completa del documento, por ejemplo una que deba ejecutar un script de terceros en cada navegación.</p><pre><code class="hljs language-typescript"><span class="hljs-comment">// src/client/entrypoint.ts</span><span class="hljs-keyword">import</span> { mount, clientRender } <span class="hljs-keyword">from</span> <span class="hljs-string">'@carats/csr'</span>;<span class="hljs-keyword">import</span> facets <span class="hljs-keyword">from</span> <span class="hljs-string">'./facets.cara'</span>;<span class="hljs-title function_">mount</span>(facets);<span class="hljs-title function_">clientRender</span>();</code></pre><p>Hacen falta las dos líneas cuando el enrutamiento dentro de la aplicación está activo. mount registra las facetas en el navegador, y clientRender empieza a interceptar la navegación.</p><p>Los enlaces no necesitan ningún componente del framework. Un ancla normal se intercepta automáticamente, y un enlace corriente se sigue con normalidad si la interceptación está desactivada, lo que significa que las páginas siguen siendo rastreables y funcionan sin JavaScript.</p><h2>Navegación programática</h2><p>goTo de @carats/csr navega del mismo modo que un clic en un enlace interceptado. Es la forma correcta de enviar un cuadro de búsqueda o cualquier otro formulario que no deba recargar el documento.</p><pre><code class="hljs language-tsx"><span class="hljs-keyword">import</span> { goTo } <span class="hljs-keyword">from</span> <span class="hljs-string">'@carats/csr'</span>;<span class="hljs-keyword">import</span> { afterMount } <span class="hljs-keyword">from</span> <span class="hljs-string">'@carats/hooks'</span>;<span class="hljs-keyword">import</span> <span class="hljs-string">'./search-bar.sass'</span>;<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">SearchInput</span>(<span class="hljs-params"></span>) { <span class="hljs-title function_">afterMount</span>(<span class="hljs-function">() =></span> { <span class="hljs-keyword">const</span> form=<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'search-bar'</span>); <span class="hljs-keyword">const</span> <span class="hljs-title function_">submit</span>=(<span class="hljs-params"><span class="hljs-attr">e</span>: <span class="hljs-title class_">Event</span></span>) => { e.<span class="hljs-title function_">preventDefault</span>(); <span class="hljs-keyword">const</span> input=<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'search-input'</span>) <span class="hljs-keyword">as</span> <span class="hljs-title class_">HTMLInputElement</span>; <span class="hljs-title function_">goTo</span>(<span class="hljs-string">`/search?q=<span class="hljs-subst">${input.value}</span>`</span>); }; form?.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'submit'</span>, submit); <span class="hljs-keyword">return</span> <span class="hljs-function">() =></span> form?.<span class="hljs-title function_">removeEventListener</span>(<span class="hljs-string">'submit'</span>, submit); }); <span class="hljs-keyword">return</span> ( <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">form</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"search-bar"</span>></span> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"search-input"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"q"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> /></span> <span class="hljs-tag"></<span class="hljs-name">form</span>></span></span> );}</code></pre><p>Construye la cadena de consulta con el URLSearchParams estándar, que se encarga de la codificación por ti. Para leer los parámetros de la ruta actual desde un componente, pídeselos a un culet: un culet recibe los parámetros coincidentes en req.params, y la página recibe lo que el culet devolvió.</p><h2>Estados de suspensión</h2><p>El mapa suspense aporta los componentes que se usan cuando una ruta no puede producir una página: notFound para una URL sin coincidencia y error para un componente o culet que lanzó una excepción. Ambos son opcionales, y error recibe el Error lanzado como único argumento.</p><pre><code class="hljs language-tsx"><span class="hljs-comment">// src/client/pages/_not_found/index.tsx</span><span class="hljs-keyword">import</span> <span class="hljs-title class_">Layout</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'../_layout'</span>;<span class="hljs-keyword">import</span> <span class="hljs-string">'./notfound.sass'</span>;<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">NotFound</span>(<span class="hljs-params"></span>) { <span class="hljs-keyword">return</span> ( <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">Layout</span>></span> <span class="hljs-tag"><<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"notfound-page container"</span>></span> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"notfound-code"</span>></span>404<span class="hljs-tag"></<span class="hljs-name">div</span>></span> <span class="hljs-tag"><<span class="hljs-name">h1</span>></span>Page not found<span class="hljs-tag"></<span class="hljs-name">h1</span>></span> <span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>></span>Return to Dashboard<span class="hljs-tag"></<span class="hljs-name">a</span>></span> <span class="hljs-tag"></<span class="hljs-name">section</span>></span> <span class="hljs-tag"></<span class="hljs-name">Layout</span>></span></span> );}</code></pre><pre><code class="hljs language-tsx"><span class="hljs-comment">// src/client/pages/_error/index.tsx</span><span class="hljs-keyword">import</span> <span class="hljs-title class_">Layout</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'../_layout'</span>;<span class="hljs-keyword">import</span> <span class="hljs-string">'./error.sass'</span>;<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">ErrorPage</span>(<span class="hljs-params"><span class="hljs-attr">error</span>: <span class="hljs-title class_">Error</span></span>) { <span class="hljs-keyword">return</span> ( <span class="language-xml"><span class="hljs-tag"><<span class="hljs-name">Layout</span>></span> <span class="hljs-tag"><<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"error-page container"</span>></span> <span class="hljs-tag"><<span class="hljs-name">h1</span>></span>Something went wrong<span class="hljs-tag"></<span class="hljs-name">h1</span>></span> <span class="hljs-tag"><<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"error-message"</span>></span>{error.message}<span class="hljs-tag"></<span class="hljs-name">p</span>></span> <span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>></span>Return to Dashboard<span class="hljs-tag"></<span class="hljs-name">a</span>></span> <span class="hljs-tag"></<span class="hljs-name">section</span>></span> <span class="hljs-tag"></<span class="hljs-name">Layout</span>></span></span> );}</code></pre><p>Envuelve ambos en el diseño compartido. Una página que falla y se renderiza sin el armazón del sitio es mucho más difícil de diagnosticar que otra que conserva la cabecera y la navegación.</p></div> </article> </main> <footer> <span class="footer-mark">◆ carats · documentación</span></footer> </div> <script src="/static/js/ui.js"></script> <script type="module" src="/static/js/search.js" defer></script> </body></html>