Remix 框架:别再让客户端做服务器的活了
毒舌时刻
这代码写得跟网红滤镜似的——仅供参考。
各位前端同行,咱们今天聊聊 Remix。别告诉我你还在让客户端做所有事情,那感觉就像让蚂蚁搬大象——能搬,但累死人。
为什么你需要 Remix
最近看到一个 Next.js 项目,页面加载时先显示 loading,再获取数据,再显示内容。我就想问:你是在做网站还是在做加载动画展示?
反面教材
// 反面教材:客户端数据获取
// pages/blog/[slug].jsx
import { useState, useEffect } from 'react';
export default function BlogPost() {
const [post, setPost] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchPost() {
setLoading(true);
try {
const res = await fetch('/api/posts/123');
const data = await res.json();
setPost(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchPost();
}, []);
if (loading) return <div>加载中…</div>;
if (error) return <div>错误: {error}</div>;
if (!post) return <div>文章不存在</div>;
return (
<div>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</div>
);
}
毒舌点评:这代码,先显示加载,再获取数据,再显示内容,你是在写网站还是在拍电视剧?
Remix 的正确姿势
1. 服务端数据获取
// 正确姿势:服务端数据获取
// app/routes/blog.$slug.jsx
import { json } from '@remix-run/node';
export async function loader({ params }) {
const post = await fetchPost(params.slug);
if (!post) throw new Response('Not Found', { status: 404 });
return json({ post });
}
export default function BlogPost() {
const { post } = useLoaderData();
return (
<div>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</div>
);
}
2. 表单处理
// 正确姿势:服务端表单处理
// app/routes/contacts.jsx
import { json, redirect } from '@remix-run/node';
export async function action({ request }) {
const formData = await request.formData();
const name = formData.get('name');
const email = formData.get('email');
const message = formData.get('message');
// 服务端验证
if (!name || !email || !message) {
return json({ error: '请填写所有字段' }, { status: 400 });
}
// 保存到数据库
await saveContact({ name, email, message });
return redirect('/thanks');
}
export default function ContactPage() {
const actionData = useActionData();
return (
<form method="post">
{actionData?.error && <div>{actionData.error}</div>}
<input name="name" placeholder="姓名" />
<input name="email" type="email" placeholder="邮箱" />
<textarea name="message" placeholder="留言" />
<button type="submit">提交</button>
</form>
);
}
3. 嵌套路由
// 正确姿势:嵌套路由
// app/routes/admin.jsx
export default function AdminLayout() {
return (
<div className="admin">
<nav>
<Link to="/admin/users">用户管理</Link>
<Link to="/admin/posts">文章管理</Link>
</nav>
<Outlet />
</div>
);
}
// app/routes/admin.users.jsx
export async function loader() {
const users = await getUsers();
return json({ users });
}
export default function UsersPage() {
const { users } = useLoaderData();
return (
<div>
<h2>用户管理</h2>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
毒舌点评:这才叫现代框架,服务端处理数据,客户端只负责渲染。

