如何在Remix路由中嵌入带Loader的可复用路由组件?
针对Remix嵌入路由与复用组件的解决方案
一、将Apicheck路由内容嵌入Dashboard并正常加载
Remix中直接引入组件不会触发对应路由的Loader,你可以通过两种方式解决:
方案1:复用Loader逻辑,手动传递数据
把Apicheck的核心验证与数据获取逻辑抽成独立函数,在Dashboard的Loader中调用并将数据传给组件,同时保留Apicheck独立路由的功能:
- 抽取共享逻辑
// 可放在utils/apicheck.ts或直接在apicheck.tsx中导出 export async function getApicheckData({ request }: LoaderFunctionArgs) { // 身份验证逻辑 const user = await authenticator.isAuthenticated(request, { failureRedirect: "/login", }); // 数据获取逻辑 const data = await fetchApicheckRelatedData(user); return { data, user }; }
- Apicheck路由使用共享逻辑
import { LoaderFunctionArgs, useLoaderData } from "@remix-run/react"; import { getApicheckData } from "./utils/apicheck"; export const loader = async ({ request }: LoaderFunctionArgs) => { return getApicheckData({ request }); }; // 支持props传入数据,兼容独立访问和嵌入场景 type ApicheckProps = { data?: typeof loader.returnType["data"]; }; export default function Apicheck({ data }: ApicheckProps) { const loaderData = useLoaderData<typeof loader>(); const finalData = data ?? loaderData.data; return <div>{finalData.content}</div>; }
- Dashboard路由调用共享逻辑并传递数据
import { LoaderFunctionArgs, useLoaderData } from "@remix-run/react"; import { getApicheckData } from "./utils/apicheck"; import Apicheck from "./apicheck"; export const loader = async ({ request }: LoaderFunctionArgs) => { // Dashboard自身的身份验证 const user = await authenticator.isAuthenticated(request, { failureRedirect: "/login", }); // 调用Apicheck逻辑获取数据 const apicheckData = await getApicheckData({ request }); return { user, apicheckData }; }; export default function Dashboard() { const { apicheckData } = useLoaderData<typeof loader>(); return ( <div> <h1>Dashboard</h1> <Apicheck data={apicheckData.data} /> </div> ); }
方案2:使用嵌套路由(适合独立访问+嵌入展示场景)
将Apicheck设为Dashboard的子路由,既保留独立访问路径,又能在Dashboard中通过Outlet展示内容:
- 调整路由结构
routes/ ├── dashboard.tsx └── dashboard/ └── apicheck.tsx
- Dashboard父路由添加Outlet
import { Outlet, useLoaderData } from "@remix-run/react"; import { LoaderFunctionArgs } from "@remix-run/node"; export const loader = async ({ request }: LoaderFunctionArgs) => { const user = await authenticator.isAuthenticated(request, { failureRedirect: "/login", }); return { user }; }; export default function Dashboard() { return ( <div> <h1>Dashboard</h1> {/* 子路由内容会渲染在这里 */} <Outlet /> </div> ); }
- Dashboard子路由保留原逻辑
import { LoaderFunctionArgs, useLoaderData } from "@remix-run/react"; export const loader = async ({ request }: LoaderFunctionArgs) => { const user = await authenticator.isAuthenticated(request, { failureRedirect: "/login", }); const data = await fetchApicheckRelatedData(user); return { data }; }; export default function Apicheck() { const { data } = useLoaderData<typeof loader>(); return <div>{data.content}</div>; }
此时访问/dashboard/apicheck会同时加载Dashboard和Apicheck内容,若想在/dashboard默认展示Apicheck,可添加dashboard/index.tsx索引路由,在其中渲染Apicheck组件或重定向到/dashboard/apicheck。
二、实现布局中可复用的语言选择器
利用Remix布局的Loader加载全局数据,让导航栏中的语言选择器可以访问身份信息和后端数据:
- 根布局添加Loader和Action
import { LoaderFunctionArgs, ActionFunctionArgs, Outlet, useLoaderData } from "@remix-run/react"; import LanguageSelector from "./components/LanguageSelector"; export const loader = async ({ request }: LoaderFunctionArgs) => { // 全局身份验证(也可在子路由单独处理) const user = await authenticator.isAuthenticated(request, { failureRedirect: "/login", }); // 获取语言列表数据 const languages = await fetchSupportedLanguages(); return { user, languages }; }; // 处理语言切换的Action export const action = async ({ request }: ActionFunctionArgs) => { const formData = await request.formData(); const language = formData.get("language") as string; const userId = formData.get("userId") as string; await updateUserPreferredLanguage(userId, language); return { success: true }; }; export default function Root() { const { user, languages } = useLoaderData<typeof loader>(); return ( <html> <head>...</head> <body> <nav> <LanguageSelector languages={languages} user={user} /> </nav> <Outlet /> </body> </html> ); }
- 语言选择器组件实现
import { Form } from "@remix-run/react"; type LanguageSelectorProps = { languages: Array<{ code: string; name: string }>; user: { id: string; preferredLanguage: string }; }; export default function LanguageSelector({ languages, user }: LanguageSelectorProps) { return ( <Form method="POST"> <input type="hidden" name="userId" value={user.id} /> <select name="language" value={user.preferredLanguage} onChange={(e) => e.target.form?.submit()} > {languages.map(lang => ( <option key={lang.code} value={lang.code}> {lang.name} </option> ))} </select> </Form> ); }
内容的提问来源于stack exchange,提问作者czioutas
相关产品推荐
相关产品推荐

