React Router 6中用不同布局渲染公共子组件并保留状态
解决React Router 6中公共组件状态保留+DOM位置正确的问题
核心思路
要同时满足「组件状态不丢失」和「DOM位置符合布局要求」,可以通过状态提升 + React Portal结合的方式实现:将公共组件挂载在不会随路由切换卸载的外层组件中(保证状态留存),再通过Portal把组件的DOM节点渲染到当前布局的指定位置。
具体实现步骤
1. 定义公共组件(保留状态)
先写出需要复用且保留状态的公共组件,比如你的.glowing div:
// GlowingComponent.js import { useState } from 'react'; export default function GlowingComponent() { const [count, setCount] = useState(0); // 示例状态,页面切换时会保留 return ( <div className="glowing"> <p>公共组件状态:{count}</p> <button onClick={() => setCount(c => c + 1)}>点击计数</button> </div> ); }
2. 调整外层路由组件,结合Portal渲染
在外层路由组件(比如App.js)中挂载公共组件,同时监听路由变化,动态将Portal指向当前布局的指定容器:
// App.js import { Outlet, useLocation } from 'react-router-dom'; import { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; import GlowingComponent from './GlowingComponent'; export default function App() { const location = useLocation(); const [targetContainer, setTargetContainer] = useState(null); // 路由切换时,找到当前布局里的指定容器 useEffect(() => { const container = document.getElementById('glowing-target-container'); setTargetContainer(container); }, [location]); return ( <> {/* 渲染当前路由对应的布局/页面 */} <Outlet /> {/* 通过Portal将公共组件渲染到目标容器 */} {targetContainer && ReactDOM.createPortal( <GlowingComponent />, targetContainer )} </> ); }
3. 在各布局页面中添加指定容器
每个布局页面都需要在预期的位置添加一个用于挂载公共组件的容器:
// LayoutA.js(布局A) import { Outlet } from 'react-router-dom'; export default function LayoutA() { return ( <div className="layout-a"> <h1>布局A页面</h1> {/* 公共组件要嵌入的位置 */} <div id="glowing-target-container"></div> <Outlet /> {/* 渲染布局A下的子页面 */} </div> ); }
// LayoutB.js(布局B) import { Outlet } from 'react-router-dom'; export default function LayoutB() { return ( <div className="layout-b"> <h1>布局B页面</h1> <div className="layout-b-sidebar">侧边栏</div> {/* 公共组件在布局B中的指定位置 */} <div id="glowing-target-container"></div> <Outlet /> {/* 渲染布局B下的子页面 */} </div> ); }
4. 配置路由结构
确保路由结构是嵌套的,外层路由挂载App组件,内层路由对应不同布局:
// router.js import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import App from './App'; import LayoutA from './LayoutA'; import LayoutB from './LayoutB'; import PageA1 from './PageA1'; import PageB1 from './PageB1'; const router = createBrowserRouter([ { path: '/', element: <App />, children: [ { path: 'layout-a', element: <LayoutA />, children: [{ path: 'page-a1', element: <PageA1 /> }] }, { path: 'layout-b', element: <LayoutB />, children: [{ path: 'page-b1', element: <PageB1 /> }] } ] } ]); export default function Root() { return <RouterProvider router={router} />; }
为什么这个方案可行
- 状态保留:公共组件挂载在App组件中,路由切换时App不会卸载,因此组件实例和内部状态完全留存。
- DOM位置正确:通过Portal将公共组件的DOM节点直接插入到当前布局的指定容器内,完全符合布局的DOM结构要求。
- 路由适配:监听
location变化,每次页面切换后自动重新定位目标容器,确保Portal始终指向当前布局的正确位置。
内容的提问来源于stack exchange,提问作者Seth Lutske
相关产品推荐
相关产品推荐

