本文记录 React 的学习过程,内容为路由。
本节内容是如何将页面和 URL 一一对应起来,并实现前端渲染。
1. Web分类
Web 页面可以分为两大类:
- 静态页面:页面里的数据是写死的,即整个文件存放在服务器上,当用户访问 URL 时,服务器原封不动地将页面信息传给前端。
- 动态页面:页面里的数据是动态填充的,即服务器上存的是页面的模板,数据是存到数据库里的,当用户打开页面时,会动态将这个页面拼接起来。现在一般都是动态页面。
- 后端渲染:数据在后端填充,即模板与数据的拼接操作是在服务器端进行的。客户端向服务器端发送 URL,服务器端返回拼接好的页面。
- 前端渲染:数据在前端填充,即模板与数据的拼接操作是在用户的浏览器进行的。第一次打开页面时,客户端向服务器端发送 URL,服务器端返回所有页面的模板,渲染的时候根据当前需要哪些数据再向服务器端请求数据;第二次打开页面时,直接用 JS 刷新当前页面,不一定会向后端发送请求。
2. Route组件
Route 组件可以让我们的前端页面也可以和 URL 唯一对应起来,使得前端渲染的模式看起来假装和后端渲染是一样的。
我们创建一个新的项目 route-app
,然后用 VS Code 打开项目:
1
| create-react-app route-app
|
配置一下环境:
- VS Code 安装插件:
Auto Import - ES6, TS, JSX, TSX
- 安装 Route 组件(在项目根目录下安装,安装好后重启一下 VS Code):
npm i react-router-dom
- 安装 Bootstrap:
npm i bootstrap
Route 组件介绍:
BrowserRouter
:所有需要路由的组件,都要包裹在 BrowserRouter
组件内;
Link
:跳转到某个链接(但是没有向后端发请求),to
属性表示跳转到的链接;
Routes
:类似于 C++ 中的 switch
,但是只匹配第一个路径,即从前往后看每个 Route
,判断当前链接是否等于 Route
中的链接,如果是则渲染 Route
中的组件,之后的就不继续往下判断了;
Route
:路由,path
属性表示路径,element
属性表示路由到的内容(组件)。
我们先创建好我们项目的根组件 App
、导航栏 NavBar
,以及多个子页面的组件:Home
、Linux
、Django
、Web
、NotFound
。
NavBar
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import React, { Component } from 'react';
class NavBar extends Component { state = { } render() { return ( <nav className="navbar navbar-expand-lg bg-body-tertiary"> <div className="container-fluid"> <a className="navbar-brand" href="/">讲义</a> <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation"> <span className="navbar-toggler-icon"></span> </button> <div className="collapse navbar-collapse" id="navbarNavAltMarkup"> <div className="navbar-nav"> <a className="nav-link active" aria-current="page" href="/">Home</a> <a className="nav-link" href="/linux">Linux</a> <a className="nav-link" href="/django">Django</a> <a className="nav-link" href="/web">Web</a> </div> </div> </div> </nav> ); } }
export default NavBar;
|
App
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import React, { Component } from 'react'; import NavBar from './navbar'; import Home from './home'; import Linux from './linux'; import Django from './django'; import Web from './web'; import NotFound from './notfound';
class App extends Component { state = { } render() { return ( <React.Fragment> <NavBar /> </React.Fragment> ); } }
export default App;
|
Home
、Linux
、Django
、Web
、NotFound
代码类似,只展示一个:
1 2 3 4 5 6 7 8 9 10 11 12
| import React, { Component } from 'react';
class Home extends Component { state = { } render() { return ( <h1>Home</h1> ); } }
export default Home;
|
现在我们根据 URL 来渲染页面,注意此时还是属于后端渲染,每次都会重新加载页面,我们修改 App
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import React, { Component } from 'react'; import NavBar from './navbar'; import Home from './home'; import Linux from './linux'; import Django from './django'; import Web from './web'; import NotFound from './notfound'; import { Routes, Route } from 'react-router-dom'
class App extends Component { state = { } render() { return ( <React.Fragment> <NavBar /> <Routes> // 一定要将路由包含在Routes里面,里面会有很多个Route <Route path='/' element={<Home />} /> // 如果链接为'/'就跳到Home组件 <Route path='/linux' element={<Linux />} /> <Route path='/django' element={<Django />} /> <Route path='/web' element={<Web />} /> </Routes> </React.Fragment> ); } }
export default App;
|
现在我们用 Link
替换 NavBar
中的链接标签 a
,这样就变为了前端渲染:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import React, { Component } from 'react'; import { Link } from 'react-router-dom'
class NavBar extends Component { state = { } render() { return ( <nav className="navbar navbar-expand-lg bg-body-tertiary"> <div className="container-fluid"> <Link className="navbar-brand" to="/">讲义</Link> <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation"> <span className="navbar-toggler-icon"></span> </button> <div className="collapse navbar-collapse" id="navbarNavAltMarkup"> <div className="navbar-nav"> <Link className="nav-link" aria-current="page" to="/">Home</Link> <Link className="nav-link" to="/linux">Linux</Link> <Link className="nav-link" to="/django">Django</Link> <Link className="nav-link" to="/web">Web</Link> </div> </div> </div> </nav> ); } }
export default NavBar;
|
3. URL中传递参数
当网站的页面数量很多的时候,我们肯定不可能去写那么多个 Route
。
假设我们现在有几篇 Web 讲义,第 $i$ 篇的路由链接为:/web/content/i
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import React, { Component } from 'react'; import { Link } from 'react-router-dom'
class Web extends Component { state = { webs: [ {id: 1, title: 'HTML基础标签'}, {id: 2, title: 'CSS'}, {id: 3, title: 'JavaScript'}, {id: 4, title: '中期项目-拳皇'}, {id: 5, title: 'React'}, ] } render() { return ( <React.Fragment> <h1>Web</h1> <hr /> <div> {this.state.webs.map(web => ( <div key={web.id}> <Link to={`/web/content/${web.id}`}>{web.id + '.' + web.title}</Link> </div> ))} </div> </React.Fragment> ); } }
export default Web;
|
我们先实现一下讲义内容的组件 WebContent
:
1 2 3 4 5 6 7 8 9 10 11 12
| import React, { Component } from 'react';
class WebContent extends Component { state = { } render() { return ( <h1>Web Content</h1> ); } }
export default WebContent;
|
然后在 App
中写一下路由(我们不能写多个 <Route path='/web/content/i' element={<WebContent />} />
,而是用 :xxx
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import React, { Component } from 'react'; import NavBar from './navbar'; import Home from './home'; import Linux from './linux'; import Django from './django'; import Web from './web'; import WebContent from './webcontent'; import NotFound from './notfound'; import { Routes, Route } from 'react-router-dom'
class App extends Component { state = { } render() { return ( <React.Fragment> <NavBar /> <div className='container'> <Routes> // 一定要将路由包含在Routes里面,里面会有很多个Route <Route path='/' element={<Home />} /> // 如果链接为'/'就跳到Home组件 <Route path='/linux' element={<Linux />} /> <Route path='/django' element={<Django />} /> <Route path='/web' element={<Web />} /> <Route path='/web/content/:chapter' element={<WebContent />} /> </Routes> </div> </React.Fragment> ); } }
export default App;
|
现在我们如何在 WebContent
中获取 :chapter
参数呢?先看一下函数组件获取参数的方式,可以直接用 useParams
函数获取参数:
1 2 3 4 5 6 7 8 9 10 11
| import React from 'react'; import { useParams } from 'react-router-dom';
const WebContent = () => { console.log(useParams()) return ( <h1>Web Content - {useParams().chapter}</h1> ); }
export default WebContent;
|
如果是类组件的话就需要先套一层函数组件,然后把 useParams
函数作为参数传给自己:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React, { Component } from 'react'; import { useParams } from 'react-router-dom';
class WebContent extends Component { state = { } render() { console.log(this.props.params) return ( <h1>Web Content - {this.props.params.chapter}</h1> ); } }
export default (props) => ( <WebContent {...props} // 先把函数组件里面的属性展开 params={useParams()} /> );
|
4. Search Params传递参数
如果网站链接形式为:/web/content?chapter=3
,这样的链接也可以获取参数。
我们先改一下 Web
中的链接形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import React, { Component } from 'react'; import { Link } from 'react-router-dom'
class Web extends Component { state = { webs: [ ... ] } render() { return ( <React.Fragment> <h1>Web</h1> <hr /> <div> {this.state.webs.map(web => ( <div key={web.id}> <Link to={`/web/content?chapter=${web.id}`}>{web.id + '.' + web.title}</Link> </div> ))} </div> </React.Fragment> ); } }
export default Web;
|
然后在 WebContent
中获取链接的参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import React, { Component } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Link } from 'react-router-dom'
class WebContent extends Component { state = { searchParams: this.props.params[0], setSearchParams: this.props.params[1], }; render() { console.log(this.state.searchParams.get('chapter')) return ( <React.Fragment> <h1>Web Content - {this.state.searchParams.get('chapter')}</h1> <hr /> <div>讲义内容</div> <hr /> <Link to='/web'>返回上一级</Link> </React.Fragment> ); } }
export default (props) => ( <WebContent {...props} // 先把函数组件里面的属性展开 params={useSearchParams()} /> );
|
函数组件的写法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React from 'react'; import { useSearchParams } from 'react-router-dom'; import { Link } from 'react-router-dom'
const WebContent = () => { let [searchParams, setSearchParams] = useSearchParams(); console.log(searchParams.get('chapter')); return ( <React.Fragment> <h1>Web Content - {searchParams.get('chapter')}</h1> <hr /> <div>讲义内容</div> <hr /> <Link to='/web'>返回上一级</Link> </React.Fragment> ); }
export default WebContent;
|
5. 重定向
当打开一个不存在的链接时应该重定向到 404 Not Found
,我们先将这个路由定义出来:<Route path='/404' element={<NotFound />} />
。
使用 Navigate
组件可以重定向,我们可以使用通配符 *
匹配其余的所有路径,然后将其重定向到 /404
页面即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import React, { Component } from 'react'; import NavBar from './navbar'; import Home from './home'; import Linux from './linux'; import Django from './django'; import Web from './web'; import WebContent from './webcontent'; import NotFound from './notfound'; import { Routes, Route, Navigate } from 'react-router-dom'
class App extends Component { state = { } render() { return ( <React.Fragment> <NavBar /> <div className='container'> <Routes> // 一定要将路由包含在Routes里面,里面会有很多个Route <Route path='/' element={<Home />} /> // 如果链接为'/'就跳到Home组件 <Route path='/linux' element={<Linux />} /> <Route path='/django' element={<Django />} /> <Route path='/web' element={<Web />} /> <Route path='/web/content' element={<WebContent />} /> <Route path='/404' element={<NotFound />} /> <Route path='*' element={<Navigate replace to='/404' />} /> </Routes> </div> </React.Fragment> ); } }
export default App;
|
6. 嵌套路由
假设 Linux
组件中有两个子模块 Homework
和 Terminal
,我们可以在 App
中创建嵌套路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import React, { Component } from 'react'; import NavBar from './navbar'; import Home from './home'; import Linux from './linux'; import Django from './django'; import Web from './web'; import WebContent from './webcontent'; import NotFound from './notfound'; import { Routes, Route, Navigate } from 'react-router-dom'
class App extends Component { state = { } render() { return ( <React.Fragment> <NavBar /> <div className='container'> <Routes> // 一定要将路由包含在Routes里面,里面会有很多个Route <Route path='/' element={<Home />} /> // 如果链接为'/'就跳到Home组件 <Route path='/linux' element={<Linux />}> <Route path='homework' element={<h4>Homework</h4>} /> <Route path='terminal' element={<h4>Terminal</h4>} /> </Route> <Route path='/django' element={<Django />} /> <Route path='/web' element={<Web />} /> <Route path='/web/content' element={<WebContent />} /> <Route path='/404' element={<NotFound />} /> <Route path='*' element={<Navigate replace to='/404' />} /> </Routes> </div> </React.Fragment> ); } }
export default App;
|
但是现在执行网页 /linux/homework
时不会渲染出子路由的内容,我们需要在父组件中添加 <Outlet />
组件,用来填充子组件的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import React, { Component } from 'react'; import { Link, Outlet } from 'react-router-dom'
class Linux extends Component { state = { } render() { return ( <React.Fragment> <h1>Linux</h1> <hr />
<ul className="nav justify-content-center"> <li className="nav-item"> <Link className="nav-link" to="/linux/homework">Homework</Link> </li> <li className="nav-item"> <Link className="nav-link" to="/linux/terminal">Terminal</Link> </li> </ul> <hr />
<Outlet /> </React.Fragment> ); } }
export default Linux;
|
上一章:Web学习笔记-React(组合Components)。
下一章:Web学习笔记-React(Redux)。