Remix.run博客教程:Optimistic UI实现及PostAdmin路由加载问题求助
Remix乐观UI实现问题解决思路
问题根源
直接在NewPost组件里渲染<PostAdmin />路由组件时,PostAdmin对应的loader函数不会自动执行——Remix的路由loader仅在**路由匹配(通过Outlet/Link跳转)**时才会触发,手动嵌套渲染路由组件不会触发loader,导致useLoaderData()返回undefined,解构posts时就会报错。
正确实现步骤
1. 重构组件,抽离可复用的列表展示部分
把PostAdmin中展示posts的逻辑抽成独立组件(比如PostList),避免直接渲染整个路由组件:
// components/PostList.tsx export function PostList({ posts }) { return ( <aside className="sidebar"> <h3>所有文章</h3> <ul> {posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> </aside> ); }
2. 调整路由结构,用嵌套路由实现父/子分工
将PostAdmin设为父路由,NewPost作为它的子路由:
- 父路由
routes/admin/posts.tsx(原PostAdmin)负责加载posts数据,并通过Outlet渲染子路由(NewPost):
import { useLoaderData, Outlet } from "@remix-run/react"; import { PostList } from "~/components/PostList"; // 父路由的loader,负责获取所有posts export async function loader() { const posts = await fetch("/api/posts").then(res => res.json()); // 替换成你的实际数据获取逻辑 return { posts }; } export default function PostAdmin() { const { posts } = useLoaderData(); return ( <div className="admin-layout"> <Outlet /> {/* 这里渲染NewPost组件 */} <PostList posts={posts} /> </div> ); }
- 子路由
routes/admin/posts/new.tsx(NewPost)负责表单提交和乐观更新:
import { useTransition, Form, useLoaderData } from "@remix-run/react"; import { PostList } from "~/components/PostList"; // 子路由的action,负责创建新post export async function action({ request }) { const formData = await request.formData(); const newPost = await fetch("/api/posts", { method: "POST", body: formData }).then(res => res.json()); return newPost; } export default function NewPost() { const transition = useTransition(); // 获取父路由loader返回的posts数据 const { posts } = useLoaderData(); // 构建乐观更新后的posts列表 let optimisticPosts = posts; if (transition.state === "loading" && transition.submission) { // 从提交的表单数据中提取临时post const formData = transition.submission.formData; const tempPost = { id: `temp-${Date.now()}`, // 临时ID,后续会被后端返回的真实ID替换 title: formData.get("title"), createdAt: new Date().toLocaleString() }; // 把临时post放到列表最前面 optimisticPosts = [tempPost, ...posts]; } return ( <div className="new-post-form"> <Form method="post"> <input name="title" placeholder="文章标题" required /> <textarea name="body" placeholder="文章内容" required /> <button type="submit">发布文章</button> </Form> {/* 渲染乐观更新后的侧边栏列表 */} <PostList posts={optimisticPosts} /> </div> ); }
3. 关键逻辑说明
- 父路由的loader正常运行,
NewPost通过useLoaderData()可以直接拿到父路由返回的数据(Remix会自动合并嵌套路由的loader数据)。 - 利用
useTransition()获取表单提交状态:当transition.state为loading时,从transition.submission中提取表单数据,生成临时post并插入到posts列表,实现乐观渲染。 - 表单提交完成后,Remix会自动重新加载父路由的loader,更新列表为真实数据,替换掉临时post。
内容的提问来源于stack exchange,提问作者webknit
相关产品推荐
相关产品推荐

