Contenido
Conceptos básicos
Comprender los conceptos básicos de Carats te ayudará a construir aplicaciones con más eficacia.
JJSX
JJSX permite una experiencia de HTML y JavaScript puros, similar a PHP. Te deja escribir código JavaScript dentro de archivos TSX de forma parecida a React, pero sin los hooks de React ni React DOM. Como los componentes son funciones simples que devuelven HTML, el mismo componente se renderiza de forma idéntica en el servidor y en el navegador.
Características clave
- Sintaxis pura de HTML y JavaScript
- Sin dependencias de React
- Renderizado automático en el servidor y en el cliente
JJSX se compila a una llamada a función, así que un componente es una función corriente que devuelve una cadena HTML. No hay DOM virtual que comparar ni instancias de componente que mantener vivas, y por eso es seguro ejecutar la misma función en el servidor y de nuevo en el navegador.
export default function Greeting(props: { name: string }) {
return <p>Hello {props.name}</p>
}
Facetas
Las facetas son el sistema de rutas de Carats. Son un único objeto que relaciona patrones de ruta con componentes de página, y el mismo objeto lo usan tanto el cliente como el servidor, de modo que una ruta nunca puede divergir entre ambos. Las facetas se definen en src/client/facets.cara.ts y pueden declarar parámetros de ruta, estados de suspensión y si el enrutamiento dentro de la aplicación está activado.
Definir facetas
import { defineFacets } from '@carats/render';
import ErrorPage from './pages/_error';
import NotFound from './pages/_not_found';
import Home from './pages/home';
import Profile from './pages/profile';
export default defineFacets({
routes: {
'/': Home,
'/profile': Profile
},
suspense: {
error: ErrorPage,
notFound: NotFound
},
inAppRouting: true
});
defineFacets acepta una definición parcial, así que tanto routes como suspense son opcionales. La plantilla de referencia solo declara error y notFound; omite loading salvo que una página se suspenda de verdad.
Una faceta es también la unidad de obtención de datos. Una ruta dentro de `routes` es la ruta en la que debe registrarse un culet, y una página envuelta con `Burnish` recibe el valor que ese culet devolvió.
Culets
Los culets son funciones del servidor que aportan datos a los componentes. Se definen en src/server/culets y se sientan en el punto de entrada del servidor. Los componentes que necesitan datos del servidor están bruñidos, lo que hace que el framework obtenga los datos de su culet automáticamente. Un culet se relaciona con una página por su cadena de ruta, así que la ruta del culet y la de la faceta deben ser idénticas.
Definir un culet
// src/server/culets/profile.ts
import { culet } from '@carats/ssr';
export default culet<User>('/profile', (req) => {
return {
id: '1',
name: 'John Doe',
username: 'johndoe',
email: 'john@example.com'
};
});
Con una ruta dinámica, el parámetro se lee de `req.params`:
// src/server/culets/trade.ts
import { culet } from '@carats/ssr';
export default culet<TradeData>('/trade/:symbol', async (req) => {
const symbol = req.params.symbol.toUpperCase();
const quote = await fetchQuote(symbol);
return { symbol, price: quote.price };
});
Sentar culets
Un culet definido en su propio archivo se crea con culet(route, handler) y luego se registra con seat(). seat devuelve el culet sin cambios, así que sentarlo es un puro paso de registro. No registres el mismo culet una segunda vez con otra llamada a culet().
// src/server/entrypoint.ts
import { defineServerEntry, seat } from '@carats/ssr';
import facets from '../client/facets.cara';
import getTradeData from './culets/trade';
seat(getTradeData);
export default defineServerEntry(facets);
Props del servidor
Las props del servidor son los valores que devuelve un culet. El framework las resuelve durante el renderizado en el servidor, las serializa dentro de la página y las pasa al componente bruñido como primer argumento. El componente nunca obtiene datos por su cuenta.
Las props del servidor llegan como primer argumento de un componente bruñido:
import { Burnish } from '@carats/render';
import Layout from '../_layout';
export default Burnish<TradeData>(function (data) {
this.head = <title>Trade {data.symbol}</title>;
return (
<Layout>
<h1>{data.symbol}</h1>
</Layout>
);
});
Una página que no está bruñida simplemente no recibe props, que es el caso normal de una página estática.
Modelo de renderizado
Cada página se renderiza primero en el servidor. Se rellena la plantilla HTML, se ejecuta el componente de página y el marcado resultante se envía al navegador junto con las props del servidor resueltas. En el navegador se montan las mismas facetas y la aplicación toma el relevo. Una función de componente se ejecuta, por tanto, en ambos sitios, y esa es la razón por la que los efectos secundarios deben aplazarse a afterMount o beforeMount.
La consecuencia práctica es que las sentencias del cuerpo de un componente se ejecutan dos veces en una carga de página normal: una en el servidor y otra en el navegador. Todo lo que toque `document`, lea `window` o produzca un efecto secundario debe aplazarse a un hook; de lo contrario, el renderizado en el servidor falla.
Entorno de ejecución del cliente
@carats/csr monta las facetas, intercepta los clics en enlaces internos cuando el enrutamiento dentro de la aplicación está activo y expone las props del servidor serializadas. window.carats.ssp.for guarda la ruta a la que pertenecen las props actuales y window.carats.ssp.data guarda las props en sí. Es preferible leerlas a través del framework en lugar de analizar el HTML, y usar goTo para la navegación programática.
import { goTo } from '@carats/csr';
import { afterMount } from '@carats/hooks';
import './search-bar.sass';
export default function SearchInput() {
afterMount(() => {
const form = document.getElementById('search-bar');
const submit = (e: Event) => {
e.preventDefault();
const q = (document.getElementById('search-input') as HTMLInputElement).value;
goTo(`/search?q=${q}`);
};
form?.addEventListener('submit', submit);
return () => form?.removeEventListener('submit', submit);
});
return (
<form id="search-bar">
<input id="search-input" name="q" type="text" />
</form>
);
}
`goTo` navega del mismo modo que un clic en un enlace interceptado, así que no se recarga el documento cuando el enrutamiento dentro de la aplicación está activo.