如何基于React Router v4实现响应式Master/Detail布局?
先逐个回答你的6个疑问:
1) AppLayout组件是什么?
这是一个自定义的全局布局组件,不是React Router自带的。它通常用来包裹整个应用的通用结构,比如顶部导航栏、页脚、全局样式容器等。示例里只是用它做占位,你完全可以像代码里那样注释掉,或者自己实现一个适合你项目的版本。2) 代码中引用的Layout组件是什么,是否重要?
和AppLayout类似,这是Invoices页面专属的自定义布局组件,用来控制导航栏和内容区的排版(比如大屏时左右排列,小屏时上下堆叠)。它不是必须的,但如果需要统一页面内的布局样式,会很有用。你用<div>代替完全没问题,只是要自己调整布局样式。3) Media标签是否指react-media库?
对的!就是react-media这个第三方库的组件,用来在React组件中处理媒体查询。你代码里已经正确导入了,不过要确保已经通过npm install react-media或者yarn add react-media安装了它。4) Media query={PRETTY_SMALL}是否等价于Media query={{ maxWidth: 599 }}?
完全等价!示例里的PRETTY_SMALL就是一个提前定义好的常量,内容应该就是{ maxWidth: 599 },你直接写这个对象是没问题的。5) 示例中没有Router组件,但我认为这是必需的?
你说得太对了!React Router v4+必须用BrowserRouter(或者HashRouter)包裹所有路由相关的组件,这是示例省略的关键部分——它默认你已经在应用的最外层添加了Router。你的代码里漏掉了这一步,这是路由不生效的核心原因之一。6) 示例中没有Link对象?
示例只是简化了结构,实际使用中必须添加Link组件来实现路由跳转。你需要在InvoicesNav里添加Link,才能跳转到仪表盘和具体的发票页面,测试响应式布局的效果。
修正后的完整代码
下面是基于你的代码修改后的可运行版本,我添加了Router包裹、导航链接,还有基础的布局样式来实现Master/Detail的响应式效果:
import Media from "react-media"; import React from 'react'; import { Route, Switch, Redirect, BrowserRouter as Router, Link } from 'react-router-dom'; // 模拟发票数据 const data = [ { id: 1, to: 'Mr. Smith', amount: 10 }, { id: 2, to: 'Mrs. Jones', amount: 100 } ]; // 导航组件:添加跳转链接 const InvoicesNav = () => ( <div style={{ padding: '1rem', backgroundColor: '#f5f5f5' }}> <h3>发票导航</h3> <ul style={{ listStyle: 'none', padding: 0 }}> <li style={{ margin: '0.5rem 0' }}> <Link to="/invoices/dashboard">仪表盘</Link> </li> {data.map(invoice => ( <li key={invoice.id} style={{ margin: '0.5rem 0' }}> <Link to={`/invoices/${invoice.id}`}>发票 #{invoice.id}</Link> </li> ))} </ul> </div> ); const Dashboard = () => ( <div style={{ padding: '1rem' }}> <h2>仪表盘</h2> <p>这里显示发票统计信息</p> </div> ); // 接收路由参数,显示具体发票详情 const Invoice = ({ match }) => { const invoice = data.find(item => item.id === parseInt(match.params.id)); return ( <div style={{ padding: '1rem' }}> <h2>发票详情 #{invoice.id}</h2> <p>收件人: {invoice.to}</p> <p>金额: ${invoice.amount}</p> </div> ); }; // 自定义布局组件(可选,用来控制导航和内容的排列) const Layout = ({ children }) => ( <div style={{ display: 'flex', flexWrap: 'wrap' }}> {children} </div> ); const Invoices = () => ( <Layout> {/* 始终显示导航 */} <InvoicesNav /> <Media query={{ maxWidth: 599 }}> {screenIsSmall => screenIsSmall ? ( // 小屏:导航和内容堆叠,访问/invoices时不重定向(需要用户主动点击链接) <Switch> <Route exact path="/invoices/dashboard" component={Dashboard} /> <Route path="/invoices/:id" component={Invoice} /> </Switch> ) : ( // 大屏:导航和内容并排,访问/invoices时自动重定向到仪表盘 <Switch> <Route exact path="/invoices/dashboard" component={Dashboard} /> <Route path="/invoices/:id" component={Invoice} /> <Redirect from="/invoices" to="/invoices/dashboard" /> </Switch> )} </Media> </Layout> ); const App = () => ( <Router> <div style={{ maxWidth: '1200px', margin: '0 auto' }}> <h1>发票管理系统</h1> <Route path="/invoices" component={Invoices} /> {/* 可以添加根路由重定向 */} <Redirect exact from="/" to="/invoices" /> </div> </Router> ); export default App;
代码说明:
- 添加了Router包裹:用
BrowserRouter包裹了整个App,这是路由生效的前提。 - 完善了InvoicesNav:添加了
Link组件,实现跳转到仪表盘和具体发票页面。 - 添加了布局样式:用
Layout组件实现了大屏时导航和内容并排,小屏时堆叠的效果。 - Invoice组件接收路由参数:通过
match.params.id获取发票ID,显示对应详情。 - 根路由重定向:访问根路径
/时自动跳转到/invoices,方便测试。
现在你运行代码后,调整浏览器窗口大小就能看到响应式效果:
- 小屏(≤599px):导航和内容上下堆叠,访问
/invoices时不会自动跳转,需要点击导航链接。 - 大屏(>599px):导航在左侧,内容在右侧,访问
/invoices时自动跳转到仪表盘。
内容的提问来源于stack exchange,提问作者Phil Giggles

