DocsMarcos de trabajo completos
De Remix a Nexy
En Nexy, una ruta de enrutador Remix o React mantiene su componente React, mientras que su loader se convierte en un controlador Python, su action una acción Python y las rutas anidadas se convierten en carpetas con un layout.tsx.
Cada sección a continuación muestra un hábito Remix, luego lo mismo en Nexy. React El enrutador v7 en modo marco usa los mismos nombres.
Elige el modo
Una aplicación Remix navega en el cliente, por lo que se asigna al modo cliente:
from nexy.core.models import NexyConfigModelfrom nexy.frontend import react class NexyConfig(NexyConfigModel): useFF = react() useCSR = TrueUn sitio de contenido que sólo necesita unas pocas partes interactivas se adapta mejor al modo servidor: .nexy páginas con React islas. Consulte Modo servidor y modo cliente.
Archivos de ruta
Remix (app/routes/) |
Nexi (src/routes/) |
|---|---|
_index.tsx |
index.tsx |
about.tsx |
about.tsx |
blog.$slug.tsx |
blog/[slug].tsx |
files.$.tsx (salpicar) |
files/[...path].tsx |
_auth.login.tsx (diseño sin camino) |
(auth)/login.tsx, un grupo de rutas |
api.items.$id.ts (ruta de recursos) |
api/items/[id].py |
Los puntos se convierten en carpetas y $param se convierte en [param].
useParams
import { useParams } from "@remix-run/react"; export default function Post() { const { slug } = useParams(); return <h1>{slug}</h1>;}import { useParams } from "@nexy/client"; export default function Post() { const { slug } = useParams<{ slug: string }>(); return <h1>{slug}</h1>;}loader y useLoaderData
import { json } from "@remix-run/node";import { useLoaderData } from "@remix-run/react"; export async function loader() { return json(await db.user.findMany());} export default function Users() { const users = useLoaderData<typeof loader>(); return <ul>{users.map((user) => <li key={user.id}>{user.name}</li>)}</ul>;}El cargador se mueve a un controlador Python y la página lo lee:
async def GET(): return [{"id": 1, "name": "Ada"}, {"id": 2, "name": "Alan"}]import { useEffect, useState } from "react"; type User = { id: number; name: string }; export default function Users() { const [users, setUsers] = useState<User[]>([]); useEffect(() => { fetch("/api/users").then((response) => response.json()).then(setUsers); }, []); return <ul>{users.map((user) => <li key={user.id}>{user.name}</li>)}</ul>;}En modo servidor, una página .nexy es el cargador y la vista en un archivo, y los datos están en el primer HTML:
users = [{"id": 1, "name": "Ada"}, {"id": 2, "name": "Alan"}]<ul> {% for user in users %} <li>{{ user.name }}</li> {% endfor %}</ul>params y request en un cargador
export async function loader({ params, request }: LoaderFunctionArgs) { const currency = new URL(request.url).searchParams.get("currency") ?? "EUR"; if (Number(params.id) > 100) throw new Response("No such order", { status: 404 }); return json({ id: Number(params.id), currency });}Los valores de ruta y consulta son parámetros escritos y se genera un error:
from nexy.http import HTTPException async def GET(id: int, currency: str = "EUR"): if id > 100: raise HTTPException(status_code=404, detail="No such order") return {"id": id, "currency": currency}action, <Form> y useActionData
import { Form, useActionData } from "@remix-run/react"; export async function action({ request }: ActionFunctionArgs) { const form = await request.formData(); return json({ title: String(form.get("title")), done: false });} export default function Todos() { const todo = useActionData<typeof action>(); return ( <Form method="post"> <input name="title" /> <button>Add</button> {todo && <p>Added {todo.title}</p>} </Form> );}La acción es una función Python con un cliente escrito, y useAction contiene lo que useActionData y useNavigation().state contienen:
from pydantic import BaseModel from nexy.decorators import Action, revalidate class Todo(BaseModel): title: str done: bool = False @Actionasync def add_todo(title: str) -> Todo: revalidate("/todos") return Todo(title=title)import { useState } from "react";import { useAction } from "@nexy/client";import { add_todo } from "@nexy/actions"; export default function Todos() { const [title, setTitle] = useState(""); const { run, pending, error, data } = useAction(add_todo); return ( <form onSubmit={(event) => { event.preventDefault(); run({ title }); }} > <input value={title} onChange={(event) => setTitle(event.target.value)} /> <button disabled={pending}>Add</button> {error && <p>{error.message}</p>} {data && <p>Added {data.title}</p>} </form> );}revalidate("/todos") hace lo que Remix hace después de cada acción: la página carga sus datos nuevamente.
useFetcher
const fetcher = useFetcher();fetcher.submit({ title: "Write the docs" }, { method: "post", action: "/todos" });
useAction es un buscador vinculado a una acción: llámalo desde tantos componentes como necesites, cada uno con sus propios pending y data.
Mejora progresiva
Un Remix <Form> funciona antes de que se cargue JavaScript. En el modo servidor, un formulario simple publica una acción de la misma manera y el visitante regresa a la página con un 303:
from src.actions.todos import add_todo<form method="post" action="{{ __actions_path['add_todo'] }}"> <input name="title"> <button type="submit">Add</button></form>Rutas anidadas y <Outlet />
import { Outlet } from "@remix-run/react"; export default function Dashboard() { return ( <div className="dashboard"> <nav>Dashboard</nav> <Outlet /> </div> );}La ruta principal se convierte en la layout.tsx de la carpeta y <Outlet /> se convierte en children:
import type { ReactNode } from "react"; export default function DashboardLayout({ children }: { children: ReactNode }) { return ( <div className="dashboard"> <nav>Dashboard</nav> <main>{children}</main> </div> );}root.tsx
import { Links, Meta, Outlet, Scripts } from "@remix-run/react"; export default function App() { return ( <html lang="en"> <head> <Meta /> <Links /> </head> <body> <Outlet /> <Scripts /> </body> </html> );}La raíz layout.tsx es propietaria del documento. No hay <Scripts /> o <Links /> para colocar: Nexy agrega el paquete del cliente.
import type { ReactNode } from "react"; export default function RootLayout({ children }: { children: ReactNode }) { return ( <html lang="en"> <head> <title>My app</title> </head> <body>{children}</body> </html> );}meta
export const meta = () => [{ title: "Blog" }, { name: "description", content: "Posts" }];Una página .tsx no tiene meta exportación: escriba las etiquetas en el diseño raíz. En modo servidor, una página .nexy los establece con metadata = {...}, y su diseño los escribe con {{ useMetadata().html() }}.
<Link>, <NavLink> y useNavigate
import { Link, NavLink, useNavigate } from "@remix-run/react";import { Link, useRouter } from "@nexy/client"; export function Nav() { const router = useRouter(); return ( <nav> <Link href="/">Home</Link> <Link href="/about" activeClassName="active">About</Link> <button onClick={() => router.push("/dashboard")}>Dashboard</button> </nav> );}<NavLink> es un Link con activeClassName, y useNavigate()(path) es router.push(path).
redirect y sesiones
export async function loader({ request }: LoaderFunctionArgs) { const session = await getSession(request.headers.get("Cookie")); if (!session.has("user")) throw redirect("/login"); return null;}Un middleware.py en la carpeta se ejecuta antes de cada ruta debajo de ella, incluidas las páginas:
from nexy.http import RedirectResponse, Request async def middleware(request: Request, call_next): if "user" not in request.session: return RedirectResponse("/login", status_code=307) return await call_next(request)async def GET(): return {"visits": 12}createCookieSessionStorage se convierte en un campo de nexyconfig.py, y request.session es un dict:
from nexy.core.models import NexyConfigModel class NexyConfig(NexyConfigModel): useSession = {"secret_key": "change-me"}from pydantic import BaseModel from nexy.http import Request class Login(BaseModel): name: str async def POST(request: Request, body: Login): request.session["user"] = body.name return {"user": body.name}Límites de error
La exportación ErrorBoundary de una ruta no tiene equivalente Nexy. Un cargador fallido es un código de estado: los clientes API obtienen {"detail": ...} y una URL desconocida muestra src/routes/notfound.tsx. Los errores generados mientras se procesa un componente son detectados por un React límite de error propio, alrededor de la parte que puede fallar.
headers y almacenamiento en caché
export const headers = () => ({ "Cache-Control": "public, s-maxage=60, stale-while-revalidate=60" });En modo servidor, una línea en el frontmatter envía ese encabezado y mantiene la página renderizada:
revalidate = 60<h1>News</h1>corriendo
| Remix / React Enrutador | nexy |
|---|---|
npx create-react-router |
nx new, plantilla web-fbr-react |
react-router dev |
nx dev |
react-router build, luego react-router-serve |
nx build, luego nx start |
public/ |
public/, servido en / |
| Nodo en tiempo de ejecución | Python en tiempo de ejecución; Node solo crea los paquetes de clientes |