Remix Web框架
一句话概述:Remix 是React全栈Web框架,通过嵌套路由和渐进增强构建快速、弹性的Web应用,已与React Router v7合并。
核心知识点表
| 概念 | 白话解释 |
|---|
| 嵌套路由(Nested Routes) | 页面可以嵌套,外层保持不变只更新内层(如侧边栏+内容) |
| Loader | 服务端数据加载函数,页面渲染前获取数据 |
| Action | 处理表单提交的服务端函数 |
| 渐进增强 | 不用JS也能工作,JS加载后体验更好 |
| React Router v7 | Remix已合并到React Router v7中 |
| ErrorBoundary | 错误边界,某个路由报错不影响其他路由 |
安装配置
# 方式一:使用React Router v7(推荐,Remix的继任者)
npx create-react-router@latest my-app
cd my-app
npm install
npm run dev # → http://localhost:5173
# 方式二:传统Remix
npx create-remix@latest my-remix-app
cd my-remix-app
npm run dev
基本使用
路由和Loader
// app/routes/users.tsx — /users页面
import type { LoaderFunctionArgs } from 'react-router'
import { useLoaderData } from 'react-router'
// Loader:在服务端加载数据
export async function loader({ request }: LoaderFunctionArgs) {
const users = await db.user.findMany() // 服务端查数据库
return { users } // 传给组件
}
// 组件
export default function UsersPage() {
const { users } = useLoaderData<typeof loader>() // 获取loader数据
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
Action(处理表单)
// app/routes/login.tsx
import type { ActionFunctionArgs } from 'react-router'
import { Form, useActionData } from 'react-router'
// Action:处理POST请求
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData()
const email = formData.get('email') as string
const password = formData.get('password') as string
// 验证
if (!email || !password) {
return { error: '请填写所有字段' }
}
// 登录逻辑...
return redirect('/dashboard')
}
export default function LoginPage() {
const actionData = useActionData<typeof action>()
return (
<Form method="post"> {/* Remix的Form组件,自动渐进增强 */}
{actionData?.error && <p style={{color: 'red'}}>{actionData.error}</p>}
<input name="email" type="email" placeholder="邮箱" />
<input name="password" type="password" placeholder="密码" />
<button type="submit">登录</button>
</Form>
)
}
嵌套路由
// app/routes/dashboard.tsx — 父路由(布局)
import { Outlet, NavLink } from 'react-router'
export default function Dashboard() {
return (
<div style={{ display: 'flex' }}>
<nav>
<NavLink to="/dashboard/overview">概览</NavLink>
<NavLink to="/dashboard/settings">设置</NavLink>
</nav>
<main>
<Outlet /> {/* 子路由在这里渲染 */}
</main>
</div>
)
}
// app/routes/dashboard.overview.tsx — 子路由
export default function Overview() {
return <h2>仪表盘概览</h2>
}
ErrorBoundary
// 每个路由都可以有自己的错误边界
export function ErrorBoundary() {
const error = useRouteError()
return (
<div>
<h1>出错了</h1>
<p>{error instanceof Error ? error.message : '未知错误'}</p>
</div>
)
}
高级用法
乐观更新(Optimistic UI)
import { useFetcher } from 'react-router'
function LikeButton({ postId, liked }: { postId: string; liked: boolean }) {
const fetcher = useFetcher()
// 乐观更新:不等服务器响应就先更新UI
const optimisticLiked = fetcher.formData
? fetcher.formData.get('liked') === 'true'
: liked
return (
<fetcher.Form method="post" action={`/posts/${postId}/like`}>
<input type="hidden" name="liked" value={(!optimisticLiked).toString()} />
<button>{optimisticLiked ? '❤️' : '🤍'}</button>
</fetcher.Form>
)
}
流式渲染(Streaming)
import { defer } from 'react-router'
import { Suspense } from 'react'
import { Await } from 'react-router'
export async function loader() {
const fastData = await getFastData() // 快速数据立即返回
const slowDataPromise = getSlowData() // 慢数据延迟加载
return defer({
fastData,
slowData: slowDataPromise, // Promise,边加载边渲染
})
}
export default function Page() {
const { fastData, slowData } = useLoaderData<typeof loader>()
return (
<div>
<h1>{fastData.title}</h1> {/* 立即显示 */}
<Suspense fallback={<p>加载中...</p>}>
<Await resolve={slowData}> {/* 数据就绪后显示 */}
{(data) => <div>{data.content}</div>}
</Await>
</Suspense>
</div>
)
}
常见报错与解决
| 报错信息 | 原因 | 解决方案 |
|---|
loader not found | 路由文件命名不对 | 检查文件在routes/目录下且命名正确 |
Form action failed | action函数报错 | 检查action中的逻辑和返回值 |
Hydration failed | 服务端和客户端渲染不一致 | 避免在render中使用window等浏览器API |
速查表
npx create-react-router@latest # 创建项目
npm run dev # 开发
npm run build # 构建
# 路由文件约定(点号分隔表示嵌套)
# routes/index.tsx → /
# routes/about.tsx → /about
# routes/blog.$slug.tsx → /blog/:slug
# routes/dashboard.tsx → /dashboard (父)
# routes/dashboard._index.tsx → /dashboard/ (默认子)
同类工具对比
| 特性 | Remix/RR v7 | Next.js | SvelteKit |
|---|
| 核心理念 | Web标准 + 渐进增强 | 全能型 | 编译优化 |
| 表单处理 | 原生Form + Action | Server Action | Form Actions |
| 嵌套路由 | 核心特性 | 支持 | 支持 |
| 无JS工作 | 完美支持 | 有限 | 有限 |
面试建议:Remix的核心理念是"拥抱Web平台标准"——使用原生Form、Request/Response API,不发明新概念。Shopify用Remix让管理后台快了30%。