Remix TypeScript集成 #
一、TypeScript配置 #
1.1 tsconfig.json #
json
{
"include": ["env.d.ts", "**/*.ts", "**/*.tsx"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["@remix-run/node", "vite/client"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"target": "ES2022",
"strict": true,
"noEmit": true,
"paths": {
"~/*": ["./app/*"]
}
}
}
1.2 环境类型定义 #
创建 env.d.ts:
tsx
/// <reference types="@remix-run/dev/vite" />
/// <reference types="@remix-run/node" />
declare global {
namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
SESSION_SECRET: string;
NODE_ENV: "development" | "production" | "test";
}
}
interface Window {
ENV: {
PUBLIC_API_URL: string;
};
}
}
export {};
二、Loader类型 #
2.1 基本类型 #
tsx
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
export async function loader({ params, request }: LoaderFunctionArgs) {
const post = await getPost(params.id);
return json({ post });
}
2.2 类型推断 #
tsx
import { useLoaderData } from "@remix-run/react";
export default function Post() {
// 自动推断类型
const { post } = useLoaderData<typeof loader>();
return <div>{post.title}</div>;
}
2.3 定义返回类型 #
tsx
interface LoaderData {
post: Post;
comments: Comment[];
}
export async function loader({ params }: LoaderFunctionArgs) {
const [post, comments] = await Promise.all([
getPost(params.id),
getComments(params.id),
]);
return json<LoaderData>({ post, comments });
}
三、Action类型 #
3.1 基本类型 #
tsx
import type { ActionFunctionArgs } from "@remix-run/node";
export async function action({ request, params }: ActionFunctionArgs) {
const formData = await request.formData();
// ...
}
3.2 表单数据类型 #
tsx
import { z } from "zod";
const postSchema = z.object({
title: z.string().min(3),
content: z.string().min(10),
});
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const result = postSchema.safeParse(Object.fromEntries(formData));
if (!result.success) {
return json({ errors: result.error.flatten() }, { status: 400 });
}
const post = await createPost(result.data);
return redirect(`/posts/${post.id}`);
}
四、组件类型 #
4.1 路由组件 #
tsx
import type { MetaFunction, LinksFunction } from "@remix-run/node";
import { useLoaderData, useActionData } from "@remix-run/react";
export const meta: MetaFunction<typeof loader> = ({ data }) => {
return [
{ title: data?.post.title },
{ name: "description", content: data?.post.excerpt },
];
};
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesUrl },
];
export default function Post() {
const { post } = useLoaderData<typeof loader>();
const actionData = useActionData<typeof action>();
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
4.2 普通组件 #
tsx
interface ButtonProps {
variant?: "primary" | "secondary";
size?: "sm" | "md" | "lg";
disabled?: boolean;
onClick?: () => void;
children: React.ReactNode;
}
export function Button({
variant = "primary",
size = "md",
disabled,
onClick,
children,
}: ButtonProps) {
return (
<button
className={`btn btn-${variant} btn-${size}`}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
}
五、API类型 #
5.1 API响应类型 #
tsx
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
}
export async function loader({ params }: LoaderFunctionArgs) {
const post = await getPost(params.id);
return json<ApiResponse<Post>>({
success: true,
data: post,
});
}
5.2 fetcher类型 #
tsx
import { useFetcher } from "@remix-run/react";
export function useApiFetcher<T>(action: string) {
const fetcher = useFetcher<ApiResponse<T>>();
return {
submit: (data: Record<string, any>) => {
fetcher.submit(data, { method: "post", action });
},
data: fetcher.data?.data,
error: fetcher.data?.error,
isLoading: fetcher.state !== "idle",
};
}
六、类型工具 #
6.1 路由类型 #
tsx
import type { RouteMatch } from "@remix-run/react";
export function useRouteData<T>(routeId: string): T | undefined {
const matches = useMatches();
const match = matches.find((m) => m.id === routeId);
return match?.data as T | undefined;
}
6.2 参数类型 #
tsx
type RouteParams<T extends string> = T extends `${string}$${infer Param}`
? { [K in Param]: string }
: {};
export async function loader<Params extends string>({
params,
}: LoaderFunctionArgs & { params: RouteParams<Params> }) {
// params 有类型推断
}
七、最佳实践 #
7.1 严格模式 #
json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}
7.2 类型导出 #
tsx
export type { User, CreateUserInput };
export { getUser, createUser };
7.3 类型复用 #
tsx
export type { LoaderData as PostLoaderData } from "./routes/posts.$id";
export type { LoaderData as UserLoaderData } from "./routes/users.$id";
八、总结 #
本章我们学习了:
- TypeScript配置:tsconfig.json设置
- Loader类型:类型推断和定义
- Action类型:表单数据类型
- 组件类型:路由和普通组件
- 最佳实践:严格模式和类型复用
核心要点:
- 使用 typeof loader 推断类型
- 定义清晰的接口类型
- 启用严格模式
最后更新:2026-03-28