课程目标
理解前端路由的作用;
掌握 React-Router 各 API 使用细节;
可根据项目需求,在 React 项目中,组织合理的路由方案。
路由:根据不同的 url 规则,给用户展示不同的视图(页面);
当应用变得复杂的时候,就需要分块的进行处理和展示,传统模式下,我们是把整个应用分成了多个页面,然后通过 url 进行连接。但是这种方式也有一些问题,每次切换页面都需要重新发送所有请求和渲染整个页面,不止性能上会有影响,同时也会导致整个 JavaScript 重新执行,丢失状态。
Single Page Application:单页面应用,整个应用只加载一个页面(入口页面),后续在与用户的交互过程中,通过 DOM 操作在这个单页上动态生成结构与内容。
有更好的用户体验(减少请求、渲染和页面跳转产生的等待与空白),页面切换快;
重前端,数据和页面内容由异步请求(AJAX)+ DOM 操作来完成,前端处理更多的业务逻辑。
首次进入慢;
不利于 SEO。
SPA 的页面切换机制
虽然 SPA 的内容都是在一个页面通过 JavaScript 动态处理的,但是还是需要根据需求在不同的情况下区分内容展示,如果仅仅只是依靠 JavaScript 内部机制去判断,逻辑会变得过于复杂,通过把 JavaScript 与 URL 进行结合的方式:JavaScript 根据 URL 的变化,来处理不同的逻辑,交互过程中只需要改变 URL 即可。这样把不同的 URL 与 JavaScript 对应的逻辑进行关联的方式就是路由,其本质上与后端路由的思想是一样的。
前端路由只是改变了 URL 或 URL 中的某一部分,但一定不会直接发送请求,可以认为仅仅只是改变了浏览器地址栏上的 URL 而已,JavaScript 通过各种手段处理这种 URL 的变化,然后通过 DOM 操作来动态的改变当前页面的结构;
URL 的变化不会直接发送 HTTP 请求;
业务逻辑由前端 JavaScript 来完成。
目前前端路由的主要模式
基于 URL Hash 的路由;
基于 HTML5 History API 的路由。
React Router
理解了路由的基本机制以后,也不需要重复造轮子,我们可以直接使用 React Router 库;
React Router 提供了多种不同环境下的路由库:web、native;
官网:
https://reactrouter.com/
。
基于 Web 的 React Router
基于 web 的 React Router 为:react-router-dom;
安装:npm i -S react-router-dom。
BrowserRouter 组件 -- history
基于 HTML5 History API 的路由组件。
HashRouter 组件 -- hash
基于 URL Hash 的路由组件。
Route 组件:
匹配规则(v6 之前):
默认模糊匹配,当前 URL 以该 path 为开始时,则匹配成功;
exact:精确匹配,URL===path || URL===path/;
strict:严格匹配,URL===path,要注意 strict 必须与精确匹配一起使用才生效;
多路径匹配:通过数组实现;
动态路由,见最下面。
渲染视图:
component;
render。
import { Route } from 'react-router-dom';
import About from './views/About';
import Hire from './views/Hire';
import Home from './views/Home';
* 匹配规则:
* 1、默认情况-——模糊匹配,当前 URL 以该 path 为开始时,就能匹配:/index、/index/、/index/xxx;
* 2、exact——精确匹配,URL===path || URL===path/;
* 3、strict——严格匹配,URL===path,但是需要与 exact 一起使用才生效;
* 4、多路径匹配——通过数组匹配。
function App() {
// 当有值需要传递给对应路由组件时,可以通过 render 属性实现。
const user = {
name: 'yjw',
age: 18
return (
<div>React Router Page</div>
<Route exact path={['/', '/home', '/index']} component={Home} />
<Route exact strict path='/about' component={About} />
<Route
path='/hire'
render={() => {
return <Hire user={user} />
export default App;
// react-router 6 之后的使用方式
import { Routes, Route } from 'react-router-dom';
import About from './views/About';
import Hire from './views/Hire';
import Home from './views/Home';
function App() {
return (
<Routes>
{/* 默认 精确匹配,严格匹配 */}
<Route path='home' element={<Home />} />
<Route path='/about' element={<About />} />
<Route path='/hire' element={<Hire />} />
</Routes>
export default App;
链接组件:
Link;
NavLink:
activeClassName;
activeStyle;
isActive: function。
// 可以使用 a 标签实现,但是页面每次都会刷新
export default () => {
return <>
<a href='/home' >首页</a>
<span> | </span>
<a href='/about' >关于</a>
<span> | </span>
<a href='/hire' >加入</a>
import { Link, NavLink } from "react-router-dom"
* 应用内链接:Link 或者 NavLink
* 应用外链接:a 标签
* NavLink 用于导航的链接制作
* - 当当前的 url 和 NavLink 的 to 属性匹配后,则会给当前的标签加一个选中状态,注:NavLink 默认情况下也是模糊匹配;
* - activeClassName:当前项被选中后的 className,默认为 active;
* - activeStyle:当前项被选中后的 style;
* - isActive:function,返回一个 Boolean 值,表示该标签的 class、style 始终是 active 状态或者 非active 状态。
export default () => {
return <div>
<NavLink to='/' activeClassName='homeActive'>首页</NavLink>
<span> | </span>
<NavLink to='/about' activeStyle={{color: 'red'}}>关于</NavLink>
<span> | </span>
<NavLink to='/hire' isActive={()=>{return true}} activeStyle={{color: 'yellow'}}>加入</NavLink>
<span> | </span>
<a href='https://www.baidu.com' >百度</a>
Switch 组件:只匹配一个路径;
Redirect 组件:当输入的 url 不合法时,可以重定向到 404:
form 属性;
to 属性。
import { Route, Switch, Redirect } from 'react-router-dom';
import Nav from './Nav';
import About from './views/About';
import Hire from './views/Hire';
import Home from './views/Home';
import View404 from './views/404.js';
function App() {
// 当有值需要传递给对应路由组件时,可以通过 render 属性实现。
const user = {
name: 'yjw',
age: 18
return (
<div>React Router Page</div>
<Nav />
<Switch>
<Route exact path={['/', '/home', '/index']} component={Home} />
<Route exact path='/about' component={About} />
<Route
path='/about/join'
render={() => {
return <Hire user={user} />
<Route path='/404' component={View404} />
{/* 当路径找不到时,显示404页面,并且将 url 显示为404 */}
<Redirect to='/404' />
</Switch>
export default App;
通过 props,可以获取 history、location、match 和 staticContext。
history
action:“PUSH” || “POP” || “REPLACE”;
“PUSH”:应用内通过连接跳转到当前视图的,或者通过 push 方法跳转到当前视图;
“POP”:直接输入地址跳转到当前应用,或者从外部链接跳转进来的;
“REPLACE”:通过重定向跳转或者通过 replace 方法跳转;
go:function,go(n)——跳转历史记录n步;
goBack:function,goBack()——返回历史记录上一步;
goForward:function,goForward()——前进历史记录下一步;
length:当前源在历史记录中记录的条目数;
push:function,push(path[,state])——跳转视图,向历史记录中,添加新的一条记录,从而影响视图;state 的值其实就是修改 location 下面的 state 的值,同 replace 方法中的 state;
replace:function,replace(path[,state])——跳转视图,替换掉历史记录中当前这条;
block:当离开当前组件时,会弹窗提示;全局函数,如果只需要在当前组件进行弹窗提示,在当前组件即将卸载时,调用该方法的返回值进行移除;
createHref:function,createHref(location),当 url 比较复杂时,比如含有参数和hash,这时可以使用该方法,返回一个 url 地址,但是需要再调用 push、replace 来进行跳转;
listen:监听 url 跳转,如果跳转了会打印 location和action,同样是全局函数。
location:
hash:hash 值,url 中 # 后面的内容;
pathname:当前的 url,不包含参数和hash;
search:当前的 search 值,? 后面的内容;
state:undefined、push 或 replace 传递的信息。
match:匹配信息
isExact:boolean,和 Route 中配置没有关系,取决于当前 path 和 url 是否能精确匹配;
params:{} 动态路由的参数;
path:和 pathname 不是一个概念,这里的 path 是当前 route 的 path 值;
url:当前 url 中,被当前 path 匹配成功的部分。
路由信息获取
高阶路由 - withRouter
非路由组件获取路由信息
import { NavLink, withRouter } from "react-router-dom";
function SubNav(props) {
console.log('SubNav: ', props);
return (
<div className='sub-nav'>
<NavLink
isActive={(...args) => {
console.log('arg: ', args)
return true;
to='/list/all'
>全部</NavLink>
<NavLink to='/list/good'>精华</NavLink>
<NavLink to='/list/share'>分享</NavLink>
<NavLink to='/list/ask'>问答</NavLink>
const Nav = withRouter(SubNav);
export default Nav;
路由 Hooks,v5 版本之后引入了 Hooks。
useLocation();
useHistory();
useRouteMatch();
useParams():获取动态路由的参数信息。
以下为完整练习代码:
// App.js
import { Route, Switch, Redirect } from 'react-router-dom';
import Nav from './Nav';
import About from './views/About';
import Hire from './views/Hire';
import Home from './views/Home';
import View404 from './views/404.js';
import List from './views/list/List';
import './index.css';
const types = ['good', 'good', 'share', 'ask'];
function App() {
// 当有值需要传递给对应路由组件时,可以通过 render 属性实现。
const user = {
name: 'yjw',
age: 18
return (
<div className="wrap">
<div>React Router Page</div>
<Nav />
<Switch>
<Route path={['/home', '/index']} component={Home} />
<Route path='/about' component={About} />
<Route
path='/join'
render={() => {
return <Hire user={user} />
component 自带路由参数,通过 render 传递路由参数时需要手动将参数传递过去
- 动态路由: :代表动态路由,: 后面代表的是名字
{/* <Route
// path={['/list', '/list/:type', '/list/:type/:page']}
path='/list/:type?/:page?'
render={ (props) => {
return <List {...props}/>
exact
/> */}
<Route
path='/list/:type?/:page?'
exact
render={({match}) => {
console.log('route-> ', match)
const {type='good', page='1'} = match.params;
if(types.includes(type) && String(parseInt(page))===page && parseInt(page)>0) {
return <List />
} else {
return <Redirect to="/404" />
<Route path='/404' component={View404} />
<Redirect to='/404' />
</Switch>
* list 的路径问题:
* - /list
* - /list/分类
* - list/分类/页码
export default App;
// Nav.js
import { Link, NavLink } from "react-router-dom"
* 应用内链接:Link 或者 NavLink
* 应用外链接:a 标签
* NavLink 用于导航的链接制作
* - 当当前的 url 和 NavLink 的 to 属性匹配后,则会给当前的标签加一个选中状态,注:NavLink 默认情况下也是模糊匹配;
* - activeClassName:当前项被选中后的 className,默认为 active;
* - activeStyle:当前项被选中后的 style;
* - isActive:function,返回一个 Boolean 值,表示该标签的 class、style 始终是 active 状态或者 非active 状态。
export default () => {
return <div className='nav'>
<NavLink to='/home' >首页</NavLink>
<span> | </span>
<NavLink to='/about' >关于</NavLink>
<span> | </span>
<NavLink to='/join' >加入</NavLink>
<span> | </span>
<NavLink to='/list' >产品列表</NavLink>
// List.js
import ListList from "./ListList";
import Pagination from "./Pagination";
import SubNav from "./SubNav";
function List(props) {
return (
<h3>List</h3>
<SubNav />
<ListList />
<Pagination />
export default List;
// SubNav.js
import { NavLink, useParams } from "react-router-dom";
function SubNav(props) {
const { type='good' } = useParams();
return (
<div className='sub-nav'>
<NavLink to='/list/good'>精华</NavLink>
<NavLink to='/list/share'>分享</NavLink>
<NavLink to='/list/ask'>问答</NavLink>
export default SubNav;
// Pagination.js
import { useParams } from "react-router";
import { Link } from "react-router-dom";
import data from './data';
const limit = 6;
function Pagination() {
const { type='good', page='1' } = useParams();
const nowData = data[type];
const pageLen = Math.ceil(nowData.length/limit);
const renderPage = () => {
const inner = []
for (let i = 1; i <= pageLen; i++) {
if(pageLen === Number(page)) {
inner.push(<span key={i}>{i}</span>)
} else {
inner.push(<Link to={`/list/${type}/${i}`} key={i}>{i}</Link>)
return inner;
return (
{renderPage()}
export default Pagination;
// ListList.js
import { useParams } from "react-router";
import data from './data';
const limit = 6;
* 求页数
* 每页的第一条:(page-1) * 6
function ListList() {
const { type='good', page='1' } = useParams();
const nowPage = Number(page);
const start = (nowPage - 1) * limit;
const end = nowPage * limit;
const nowData = data[type]?.filter((item, index)=>(index>=start && index<=end));
return (
{nowData?.map(d => {
return <li key={d.id}>{d.title}</li>