本章将介绍以下配方:
- 实现 React 路由 v4
- 创建嵌套路由并向路径添加参数
在本章中,我们将学习如何使用 React Router v4 在项目中添加动态路由。
与 Angular 不同,React 是一个库而不是一个框架,这意味着特定的功能,例如路由或propTypes不是 React 核心的一部分。相反,路由由称为 React Router 的第三方库处理。
我们将使用第 3 章中实现 Airbnb React/JSX 风格 G**uide配方中的代码,处理事件、绑定和有用的 React 包(Repository:``Chapter03/Recipe4/airbnb来启用 linter 验证。
我们需要做的第一件事是安装 React Router v4,我们可以使用以下命令:
npm install react-router-dom您可能对我们为什么要安装react-router-dom而不是react-router感到困惑。React 路由包含react-router-dom和react-router-native的所有公共组件。这意味着,如果您使用 React for the web,则应使用react-router-dom,如果您使用 React Native,则需要使用react-router-native。react-router-dom包最初创建时包含版本 4,react-router使用的是版本 3。react-router-dom比react-router有一些改进。它们列在这里:
- 改进的
<Link>组件(呈现<a>)。 - 包括与浏览器的
window.history交互的<BrowserRouter>。 - 包括
<NavLink>,它是一个<Link>包装器,知道它是否处于活动状态。 - 包括
<HashRouter>,它使用 URL 中的哈希来呈现组件。如果你有一个静态页面,你应该使用这个组件而不是<BrowserRouter>。
在此配方中,我们将根据路线显示一些组件:
- 我们需要创建四个功能组件(
About、Contact、Home和Error 404,并在它们的目录中将它们命名为index.jsx。 - 创建
Home组件:
import React from 'react';
const Home = () => (
<div className="Home">
<h1>Home</h1>
</div>
);
export default Home;File: src/components/Home/index.jsx
- 创建
About组件:
import React from 'react';
const About = () => (
<div className="About">
<h1>About</h1>
</div>
);
export default About;File: src/components/About/index.jsx
- 创建
Contact组件:
import React from 'react';
const Contact = () => (
<div className="Contact">
<h1>Contact</h1>
</div>
);
export default Contact;File: src/components/Contact/index.jsx
- 创建
Error 404组件:
import React from 'react';
const Error404 = () => (
<div className="Error404">
<h1>Error404</h1>
</div>
);
export default Error404;File: src/components/Error/404.jsx
- 在我们的
src/index.js文件中,我们需要包括我们的路线,我们将在下一步中创建这些路线。我们需要从react-router-dom导入BrowserRouter对象,我们可以将其重命名为路由:
import React from 'react';
import { render } from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import './index.css';
// Routes
import AppRoutes from './routes';
render(
<Router>
<AppRoutes />
</Router>,
document.getElementById('root')
);File: src/index.js
- 现在我们需要创建我们的
src/routes.jsx文件,我们将导入我们的App和Home组件,并且使用Route组件,我们将添加一个路由,在用户访问根(/)时执行我们的Home组件:
// Dependencies
import React from 'react';
import { Route } from 'react-router-dom';
// Components
import App from './components/App';
import Home from './components/Home';
const AppRoutes = () => (
<App>
<Route path="/" component={Home} />
</App>
);
export default AppRoutes;File: src/routes.jsx
- 之后,我们需要修改我们的
App.jsx文件,将路由组件呈现为子级:
import React from 'react';
import { element } from 'prop-types';
import Header from '../shared/components/layout/Header';
import Content from '../shared/components/layout/Content';
import Footer from '../shared/components/layout/Footer';
import './App.css';
const App = props => (
<div className="App">
<Header title="Routing" />
<Content>
{props.children}
</Content>
<Footer />
</div>
);
App.propTypes = {
children: element
};
export default App;File: src/components/App.jsx
- 如果运行应用,您将在根目录(
/中看到Home组件:
- 现在,当用户尝试访问任何其他路由时,让我们添加我们的
Error 404:
// Dependencies
import React from 'react';
import { Route } from 'react-router-dom';
// Components
import App from './components/App';
import Home from './components/Home';
import Error404 from './components/Error/404';
const AppRoutes = () => (
<App>
<Route path="/" component={Home} />
<Route component={Error404} />
</App>
);
export default AppRoutes;File: src/routes.jsx
- 如果您运行应用,您将看到它正在呈现两个组件(
Home和Error 404。你可能想知道为什么:
- 这是因为我们需要使用
<Switch>组件来执行一个与路径匹配的组件。为此,我们需要导入Switch组件,并将其作为包装添加到路由中:
// Dependencies
import React from 'react';
import { Route, Switch } from 'react-router-dom';
// Components
import App from './components/App';
import Home from './components/Home';
import Error404 from './components/Error/404';
const AppRoutes = () => (
<App>
<Switch>
<Route path="/" component={Home} />
<Route component={Error404} />
</Switch>
</App>
);
export default AppRoutes;File: src/routes.jsx
- 现在,如果我们转到根(
/,我们将看到我们的Home组件,Error404不会同时执行(它将只执行Home组件),但是如果我们转到/somefakeurl,我们将看到Home组件也被执行,这是一个问题:
- 要解决此问题,我们需要在路线中添加要精确匹配的确切道具。问题是
/somefakeurl将匹配我们的根(/),但如果我们想非常具体地描述路径,我们需要将确切的道具添加到Home路线:
// Dependencies
import React from 'react';
import { Route, Switch } from 'react-router-dom';
// Components
import App from './components/App';
import Home from './components/Home';
import Error404 from './components/Error/404';
const AppRoutes = () => (
<App>
<Switch>
<Route path="/" component={Home} exact />
<Route component={Error404} />
</Switch>
</App>
);
export default AppRoutes;- 现在,如果您转到
/somefakeurl,您将能够看到Error404组件:
如您所见,React 路由库的实现非常简单。现在我们可以为我们的About(/about)和Contact(/contact)组件添加更多路线:
// Dependencies
import React from 'react';
import { Route, Switch } from 'react-router-dom';
// Components
import App from './components/App';
import About from './components/About';
import Contact from './components/Contact';
import Home from './components/Home';
import Error404 from './components/Error/404';
const AppRoutes = () => (
<App>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/about" component={About} exact />
<Route path="/contact" component={Contact} exact />
<Route component={Error404} />
</Switch>
</App>
);
export default AppRoutes;如果您转到/about,您将看到以下视图:
如果您转到/contact,您将看到以下视图:
到目前为止,我们已经学会了如何在我们的项目中创建简单的路由,但在下一个配方中,我们将学习如何在路由中包含参数,如何添加嵌套路由,以及如何使用<Link>组件在我们的站点中导航。
对于这个配方,我们将使用与上一个配方相同的代码,我们将添加一些参数,并展示如何将它们引入到我们的组件中。
在这个配方中,我们将创建一个简单的Notes组件,在我们访问/notes路线时显示我们的所有注释,但当用户访问/notes/:noteId时,我们将显示一个注释(我们将使用noteId过滤注释):
*1. 我们需要创建一个名为 Notes(src/components/Notes/index.jsx的新组件,这是我们的Notes组件的框架:
import React, { Component } from 'react';
import './Notes.css';
class Notes extends Component {
constructor() {
super();
// For now we are going to add our notes to our
// local state, but normally this should come
// from some service.
this.state = {
notes: [
{
id: 1,
title: 'My note 1'
},
{
id: 2,
title: 'My note 2'
},
{
id: 3,
title: 'My note 3'
},
]
};
}
render() {
return (
<div className="Notes">
<h1>Notes</h1>
</div>
);
}
}
export default Notes;File: src/components/Notes/index.jsx
- CSS 文件如下所示:
.Notes ul {
list-style: none;
margin: 0;
margin-bottom: 20px;
padding: 0;
}
.Notes ul li {
padding: 10px;
}
.Notes a {
color: #555;
text-decoration: none;
}
.Notes a:hover {
color: #ccc;
text-decoration: none;
}File: src/components/Notes/Notes.css
- 创建
Notes组件后,我们需要将其导入src/routes.jsx文件:
// Dependencies
import React from 'react';
import { Route, Switch } from 'react-router-dom';
// Components
import App from './components/App';
import About from './components/About';
import Contact from './components/Contact';
import Home from './components/Home';
import Notes from './components/Notes';
import Error404 from './components/Error/404';
const AppRoutes = () => (
<App>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/about" component={About} exact />
<Route path="/contact" component={Contact} exact />
<Route path="/notes" component={Notes} exact />
<Route component={Error404} />
</Switch>
</App>
);
export default AppRoutes;File: src/routes.jsx
- 现在,如果我们转到
/notesURL,我们可以看到 Notes 组件:
- 现在我们的
Notes组件已连接到 React Router,让我们将注释呈现为一个列表:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import './Notes.css';
class Notes extends Component {
constructor() {
super();
this.state = {
notes: [
{
id: 1,
title: 'My note 1'
},
{
id: 2,
title: 'My note 2'
},
{
id: 3,
title: 'My note 3'
},
]
};
}
renderNotes = notes => (
<ul>
{notes.map((note, key) => (
<li key={key}>
<Link to={`/notes/${note.id}`}>{note.title}</Link>
</li>
))}
</ul>
);
render() {
const { notes } = this.state;
return (
<div className="Notes">
<h1>Notes</h1>
{this.renderNotes(notes)}
</div>
);
}
}
export default Notes;File: src/components/Notes/index.jsx
- 您可能已经注意到,我们正在使用指向
/notes/notes.id的<Link>(这将生成一个<a>标记)组件,这是因为我们将在src/routes.jsx文件中添加一个新的嵌套路由,以匹配注释的id:
// Dependencies
import React from 'react';
import { Route, Switch } from 'react-router-dom';
// Components
import App from './components/App';
import About from './components/About';
import Contact from './components/Contact';
import Home from './components/Home';
import Notes from './components/Notes';
import Error404 from './components/Error/404';
const AppRoutes = () => (
<App>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/about" component={About} exact />
<Route path="/contact" component={Contact} exact />
<Route path="/notes" component={Notes} exact />
<Route path="/notes/:noteId" component={Notes} exact />
<Route component={Error404} />
</Switch>
</App>
);
export default AppRoutes;File: src/routes.jsx
- React Router 有一个名为
match的特殊道具,它是一个包含所有关于我们执行的路由的信息的对象,如果我们有参数,我们将能够在match对象中看到它们,如下所示:
render() {
// Let's see what contains our props object.
console.log(this.props);
// We got the noteId param from match object.
const { match: { params: { noteId } } } = this.props;
const { notes } = this.state;
// By default our selectedNote is false
let selectedNote = false;
if (noteId > 0) {
// If the note id is higher than 0 then we filter it from our
// notes array.
selectedNote = notes.filter(
note => note.id === Number(noteId)
);
}
return (
<div className="Notes">
<h1>Notes</h1>
{/* We render our selectedNote or all notes */}
{this.renderNotes(selectedNote || notes)}
</div>
);
}File: src/components/Notes/index.jsx
match道具看起来像这样。
match对象包含很多有用的信息。React Router 还包括对象的历史记录和位置。如您所见,我们可以在match对象中获得我们在路由中传递的所有参数。
如果您运行应用并转到/notesURL,您将看到以下视图:
如果您单击任何链接(我单击了我的注释 2),您将看到以下视图:
在此之后,我们可以在Header组件中添加一个菜单来访问我们的所有路线:
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import logo from '../img/logo.svg';
// We created a component with a simple arrow function.
const Header = props => {
const {
title = 'Welcome to React',
url = 'http://localhost:3000'
} = props;
return (
<header className="App-header">
<a href={url}>
<img src={logo} className="App-logo" alt="logo" />
</a>
<h1 className="App-title">{title}</h1>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/notes">Notes</Link></li>
<li><Link to="/contact">Contact</Link></li>
</ul>
</header>
);
};
// Even with Functional Components we are able to validate our PropTypes.
Header.propTypes = {
title: PropTypes.string.isRequired,
url: PropTypes.string
};
export default Header;File: src/shared/components/layout/Header.jsx
在那之后,我们需要修改我们的src/components/App.css文件来设置菜单样式。只需在 CSS 文件末尾添加以下代码:
.App-header ul {
margin: 0;
padding: 0;
list-style: none;
}
.App-header ul li {
display: inline-block;
padding: 0 10px;
}
.App-header ul li a {
color: #fff;
text-decoration: none;
}
.App-header ul li a:hover {
color: #ccc;
}File: src/components/App.css Now you can see the menu like this:










