Перейти к содержимому

17. TypeScript в Remix

Remix создан с первоклассной поддержкой TypeScript. Типизация loader и action данных происходит автоматически через typeof loader.

// Типы автоматически выводятся из loader
export async function loader({ params }: LoaderFunctionArgs) {
const post = await db.post.findUnique({
where: { id: params.id }
});
if (!post) throw new Response("Not Found", { status: 404 });
return { post }; // Нет json() — в Remix v2 возвращаем напрямую
}
export default function Post() {
// post: { id: string, title: string, content: string, ... }
const { post } = useLoaderData<typeof loader>();
return <h1>{post.title}</h1>;
}
import type {
LoaderFunctionArgs,
ActionFunctionArgs,
MetaFunction,
LinksFunction,
} from "@remix-run/node";
export async function loader({ request, params, context }: LoaderFunctionArgs) {
// request: Request
// params: Record<string, string | undefined>
// context: AppLoadContext
}
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const email = formData.get("email"); // → string | File | null
// Кастуем после валидации:
const validEmail = email as string;
}
type ActionData = {
errors?: {
email?: string;
password?: string;
};
user?: {
id: string;
email: string;
};
};
export async function action({ request }: ActionFunctionArgs) {
// ...
return json<ActionData>({ user: { id: "1", email: "[email protected]" } });
}
export default function LoginForm() {
const actionData = useActionData<ActionData>();
// actionData: ActionData | undefined
actionData?.errors?.email; // string | undefined
}

useRouteLoaderData — данные родительских маршрутов

Заголовок раздела «useRouteLoaderData — данные родительских маршрутов»
import type { loader as rootLoader } from "~/root";
import { useRouteLoaderData } from "@remix-run/react";
export default function Child() {
const rootData = useRouteLoaderData<typeof rootLoader>("root");
// rootData полностью типизирован!
return <p>{rootData?.user.name}</p>;
}
import { z } from "zod";
const RegisterSchema = z.object({
email: z.string().email("Неверный email"),
password: z.string().min(8, "Минимум 8 символов"),
name: z.string().min(2, "Минимум 2 символа"),
});
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const raw = Object.fromEntries(formData);
const result = RegisterSchema.safeParse(raw);
if (!result.success) {
return json({
errors: result.error.flatten().fieldErrors,
}, { status: 400 });
}
// result.data полностью типизирован и валиден
const { email, password, name } = result.data;
await createUser({ email, password, name });
return redirect("/dashboard");
}
// Кастомный хук для типизированных параметров
function useTypedParams<T extends Record<string, string>>() {
const params = useParams();
return params as T;
}
// Использование
const { id, slug } = useTypedParams<{ id: string; slug: string }>();