近几年单页应用(Single Page Web Application,SPA)发展迅猛,前端控制路由变为主流,单页应用也是前端路由的主要使用场景。在传统多页应用开发中,路由的概念往往存在于后端(后端路由),用户每次访问一个新的页面会向后台发起请求,然后服务器去响应。这个过程需要用户等待,从性能和用户体验上来讲,无疑前端路由会比后端路由提升很多。
react-router是React中用来实现路由的第三方JavaScript库,也是基于React开发的。它拥有简单API和强大的路由处理机制,如代码缓冲加载、动态路由匹配,以及建立正确的位置过渡处理。它可以快速地在应用中添加视图和数据流,保持页面展示内容和URL的同步。
Hello React Router |
直接上代码演示,现在不懂没关系,带着疑惑继续看下文章节就能明白。
基础路由(单层路由) |
1.借助 Create React App 快速构建 React 环境应用
npx create-react-app demo-app
2.安装react router(目前react-router 4)
npm install react-router-dom
3.简化项目文件
将src
文件夹下除了index.js
和App.js
的文件全部删除,并修改index.js代码如下:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
4.修改App.js文件代码
import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
export default function App() {
return (
<BrowserRouter>
<Route path="/" exact ><h1>home</h1></Route>
<Route path="/about" ><h1>about</h1></Route>
</BrowserRouter>
);
}
5.启动项目
npm run start
为什么/about1
是空白页,而/about/1
或者/about
都是指向about页面呢?
因为没有匹配的/about1
路由,如果了给<Route path="/about" >
加了exact
属性(路径绝对匹配),那么/about/1
也是空白。
嵌套路由(多层路由) |
结合上述案例,修改App.js文件代码
import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
export default function App() {
return (
<BrowserRouter>
<Route path="/" exact ><h1>home</h1></Route>
<Route path="/about" component={About} />
</BrowserRouter>
);
}
function About() {
return <>
<Route path='/about' exact><h1>about</h1></Route>
<Route path='/about/me' ><h1>about me</h1></Route>
<Route path='/about/she'><h1>about she</h1></Route>
</>;
}
三大基础组件 |
使用React Router组件之前必须将需要使用的组件从react-router-dom
库中引入,如:
import { BrowserRouter, Route, Link } from "react-router-dom";
There are three primary categories of components in React Router:
- routers, like
<BrowserRouter>
and<HashRouter>
- route matchers, like
<Route
> and<Switch>
- navigation, like
<Link>
,<NavLink>
, and<Redirect>
React Router中有三大组件,分别为:routers
(路由组件的根组件) route matchers
(路径匹配组件) navigation
(导航组件),下面逐一讲解。
Routers 路由组件的根组件 |
Router是所有路由组件共用的底层接口组件(路由组件的根组件),它是 route matchers
(路径匹配组件) 的最外层的容器。对于前端web项目而言,最常用到的是<BrowserRouter>
和<HashRouter>
。这两者分别对应前端路由的两种主要实现方式:HTML 5的history API 和 Hash。
关于路由组件,如果我们的应用有服务器响应web的请求,建议使用<BrowserRouter>
组件; 如果使用静态文件服务器,建议使用<HashRouter>
组件。
Router作用 |
Router 会创建一个 history 对象,history 用来跟踪 URL, 当URL 发生变化时,Router的后代组件会重新渲染。
React Router 中提供的其它组件可以通过 context
获取 history
对象,这也隐含说明了 React Router 中其他组件必须作为 Router 组件后代使用。
ReactDOM.render(
(
<BrowserRouter>
<App />
</BrowserRouter>),
document.getElementById('root')
)
To use a router, just make sure it is rendered at the root of your element hierarchy. Typically you’ll wrap your top-level
<App>
element in a router.
Router应作为项目应用的根元素,最典型的就是将应用顶级组件<App>
包含在Router内。也可以在App组件return
时将Router作为最外层组件即可。
BrowserRouter |
BrowserRouter调用了HTML5 history API (pushState, replaceState and the popstate event)路由实现方式保证UI与URL保持同步。
BrowserRouter 创建的 URL 形式如下:
http://example.com/some/path
BrowserRouter 组件签名:
<BrowserRouter
basename={optionalString}
forceRefresh={optionalBool}
getUserConfirmation={optionalFunc}
keyLength={optionalNumber}
>
<App />
</BrowserRouter>
basename: string |
路由器的根路径。如果你的页面部署在服务器的二级(子)目录,你需要将 basename
设置到此子目录。正确的 URL 格式是前面有一个前导斜杠,但不能有尾部斜杠。
<BrowserRouter basename="/calendar">
<Link to="/today">today</Link> // renders <a href="/calendar/today">
<Link to="/tomorrow">tomorrow</Link> // renders <a href="/calendar/tomorrow">
...
</BrowserRouter>
点击了today链接,网址栏为:http://ip:port/calendar/today
getUserConfirmation: func |
当导航需要确认时执行的函数。默认使用 window.confirm。
<BrowserRouter
getUserConfirmation={(message, callback) => {
// this is the default behavior
const allowTransition = window.confirm(message);
callback(allowTransition);
}}
/>
forceRefresh: bool |
当设置为 true
时,在导航的过程中整个页面将会刷新。 只有当浏览器不支持 HTML5 的 history API 时,才设置为 true
。
<BrowserRouter forceRefresh={true} />
keyLength: number |
location.key
的长度。默认是 6。
<BrowserRouter keyLength={12} />
HashRouter |
HashRouter调用了hash路由实现方式保证UI与URL保持同步。
注意:使用 hash 的方式记录导航历史不支持 location.key
和location.state
。
HashRouter 创建的 URL 形式如下:
http://example.com/#/some/path
HashRouter 组件签名:
<HashRouter
basename={optionalString}
getUserConfirmation={optionalFunc}
hashType={optionalString}
>
<App />
</HashRouter>
其中basename、getUserConfirmation属性与BrowserRouter 是一样的。
basename: string |
<HashRouter basename="/calendar">
<Link to="/today">today</Link> // renders <a href="#/calendar/today">
</HashRouter>
点击了today链接,网址栏为:http://ip:port/#/calendar/today
hashType: string |
定义window.location.hash 使用的 hash 类型。有如下几种:
- “slash” - 后面跟一个斜杠,例如
#/
和#/sunshine/lollipops
- “noslash” - 后面没有斜杠,例如
#
和#sunshine/lollipops
- “hashbang” - 后面跟一个感叹号斜杠,Google(已淘汰) 风格的 “ajax crawlable”,例如
#!/
和#!/sunshine/lollipops
默认为 “slash”。
Route Matchers 路径匹配组件 |
Route |
将location
与路径(path
)相匹配的组件渲染,每次路由切换都触发路由对应的组件重新渲染,无论采用的是何种渲染组件的方式及。如果有多个Route匹配(包含匹配法),那么这些Route的Component都会被渲染。比如:
<Route path="/topics"> <Topics /> </Route>
<Route path="/"><Home /></Route>
http://ip:port/
只会渲染Home
组件 ,但是http://ip:port/topics
时将会把Topics
和Home
组件一并渲染。这样很可能不符合我们的使用意图,为此可以让被包含的路径通过加上exact
属性,保证只有在绝对匹配时才会该渲染组件。<Route exact path="/">
路由渲染(组件的)方式 Route render methods |
官方文档给出三种经典Route render methods
,分别是:
<Route component>
<Route render>
<Route children>
以下写法是<Route children>
的另外一种形式:(了解一下props.children)
<Route path="/work">
<MyComp />
</Route>
官方建议使用的是:<Route children>
。性能好,接收的参数类型多,具体看下文分析
权重:children>component>render
🌙 component <Route component>
🌙
When you use
component
the router usesReact.createElement
to create a new React element from the given component. That means if you provide aninline function
to the component prop, you would create a new component every render.
使用component
渲染方式,Router是直接通过React.createElement
方法创建新的React组件。换言之,如果使用的是内联函数构建组件,每一次路由渲染都会创建一个新的React组件(路由匹配上才会)。
This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop (below).
这将导致的不仅仅是组件的更新而是组件的重新挂载,如果想要使用内联函数,建议使用render
或children
路由渲染方式。
1.内联函数
组件无法获取 routeProps,可以传参,父组件的每次 re-render(无论有没有props传递给子组件)都会造成子组件重新挂载。(文末附有组件生命周期测试代码,可自行测试)
<Route path="/about" component={()=><About/>} />
2.react组件名称
接收的是组件的名称,组件可获取routeProps,不可传参,父组件的每次 re-render(重新渲染) 都不会造成子组件重新挂载(执行constructor…)但是会更新和渲染(componentDidUpdate render)
<Route path="/about" component={About} />
✿解疑✿
不是说了使用 <Route component>
,Router是直接通过React.createElement
方法创建新的React组件吗?那为什么component={About}
时,父组件重新渲染,子组件却不会重新构建(挂载)?
这也没有矛盾呀!只不过每次渲染的时候,()=><About/>
每次执行都会返回(创建)新的About组件,但是component={About}
是根据组件名称,直接引用该组件。
🌰routeProps演示:
import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
export default function App() {
return (
<BrowserRouter>
<Route path="/work" component={Work} />
<Route path="/about" component={()=><About/>} />
</BrowserRouter>
);
}
function About(obj){
console.dir(obj);
return <h1>about</h1>;
}
function Work(obj){
console.dir(obj);
return <h1>Work</h1>;
}
🌙 render <Route render>
🌙
render
接收的类型是function
,Route会渲染这个function的返回值。父组件的每次 re-render(重新渲染) 都不会造成子组件重新挂载(执行constructor…)但是会更新和渲染(componentDidUpdate render)
1.内联函数
组件无法获取 routeProps,可以传参
<Route path="/about" render={()=><About/>} />
2.react组件名称
组件可以获取 routeProps,无法传参
<Route path="/about" render={About} />
✿拓展✿
render 方法也可用来进行权限认证:
import { Route, Redirect } from 'react-router';
<Route exact path="/" render={() => (
loggedIn ? (
<Redirect to="/homePage"/>
) : (
<LoginPage/>
)
)}/>
🌙 children <Route children>
🌙
父组件的每次 re-render(重新渲染) 都不会造成子组件重新挂载(执行constructor…),但是会更新和渲染(componentDidUpdate render)
1.内联函数
组件无法获取 routeProps,可以传参,path
没有匹配上组件也会被渲染出来
<Route path="/about" children={()=><About/>} />
2.react组件名称
组件可以获取 routeProps,无法传参,path
没有匹配上组件也会被渲染出来
routeProps中有个match
属性来说明这个Route的path
和location匹配上没有,如果没匹配上match
将为null
<Route path="/about" children={About} />
3.react组件或元素
形式一:
组件无法获取 routeProps,可以传参,path
没有匹配上组件就不会被渲染出来
<Route path="/about" children={<h1>contact</h1>} />
<Route path="/about" children={<About {..args}/>} />
举个🌰:
import React from "react";
import { BrowserRouter, Route } from "react-router-dom";
export default function App() {
return (
<BrowserRouter>
<Route path="/work" children={<About/>} />
<Route path="/about" children={()=><About/>} />
<Route path="/about/me" children={AboutMe} />
</BrowserRouter>
);
}
function About(obj){
console.dir(obj);
return <h1>about</h1>;
}
function AboutMe(obj){
console.log(obj);
return <h1>about me</h1>;
}
形式二:
组件无法获取 routeProps,可以传参
<Route path="/about">
<About {...args}/>
</Route>
路由属性 Route props |
只要是component render children
三种路由渲染方式中直接以组件名称作为参数的,组件都会接收到以下的几个路由属性:
更多路由属性:
🌙 path: string | string[]
路径可以数组,如果Route没有设置path
,将会全匹配(无论输入什么URL都会匹配上)
<Route path={["/about","/about1"]} render={About} />
🌙 exact: bool
路径绝对匹配才会渲染对应组件,路由采用包含匹配法。
<BrowserRouter>
<Route path="/" ><h1>home</h1></Route>
<Route path="/about" ><h1>about</h1></Route>
</BrowserRouter>
加入excat就不会被包含渲染了,只有绝对匹配才会渲染组件
<Route path="/" exact ><h1>home</h1></Route>
🌙 sensitive: bool
设置路径名大小写敏感,默认false
🌙 strict: bool
如果设置路由后面加了/
,是否让 /
生效,默认false
.
Switch |
渲染与该地址匹配的第一个子节点 <Route>
或者 <Redirect>
。
<Switch>
<Route path="/about" ><h1>about</h1></Route>
<Route path="/about/:id" ><h1>about some</h1></Route>
</Switch>
当路由为/about/1
时,页面呈现的是about, 所以需要在/about
路由上加上exact
,才会渲染/about/:id
路由
Navigation 导航组件 |
Link
NavLink
Redirect
。NavLink
其实就是可以设置样式的Link
。
Link |
Link 是 React Router提供的链接组件,一个 Link 组件定义了当点击该 Link 时,页面应该如何路由。只介绍to
属性的两种常用方式。
to: string |
<Link to="/courses?sort=name" />
to: object |
<Link
to={{
pathname: "/courses",
search: "?sort=name",
hash: "#the-hash",
state: { fromDashboard: true }
}}
/>
- pathname: 表示要链接到的路径的字符串。
- search: 表示查询参数的字符串形式。
- hash: 放入网址的 hash,例如 #a-hash。静态锚点页面,点击链接直接到页面的某部分可视域。
- state: 状态保存到 location。通常用于隐式传参(埋点),比如可以用来统计页面来源
点击链接 进入 courses页面后,就可以在this.props.location.state
中看到 fromDashboard: true
NavLink |
可以看做 一个特殊版本的 Link,当它与当前 URL 匹配时,为其渲染元素添加样式属性。
- activeClassName: string
<NavLink to="/faq" activeClassName="selected">
FAQs
</NavLink>
- activeStyle: object
<NavLink
to="/faq"
activeStyle={{
fontWeight: "bold",
color: "red"
}}
>
FAQs
</NavLink>
- exact: 如果为 true,则仅在位置完全匹配时才应用 active 的类/样式。
- strict: 当为 true,要考虑位置是否匹配当前的URL时,pathname 尾部的斜线要考虑在内。
- location 接收一个location对象,当url满足这个对象的条件才会跳转
- isActive: 接收一个回调函数,只有当 active 状态变化时才能触发,如果返回false则跳转失败
<NavLink
to="/events/123"
isActive={(match, location) => {
if (!match) {
return false;
}
// only consider an event active if its event id is an odd number
const eventID = parseInt(match.params.eventID);
return !isNaN(eventID) && eventID % 2 === 1;
}}
>
Event 123
</NavLink>
Redirect |
将路由重定向到新的位置
- to: string
- from: string
<Switch>
<Redirect from="/old-path" to="/new-path" />
<Route path="/new-path">
<Place />
</Route>
</Switch>
等价
<Switch>
<Route path="/old-path">
<Redirect to="/new-path" />
</Route>
<Route path="/new-path">
<Place />
</Route>
</Switch>
- to: object
<Redirect
to={{
pathname: "/login",
search: "?utm=your+face",
state: { referrer: currentLocation }
}}
/>
补充了解 |
Hooks |
React Router附带了一些钩子,可以让你访问路由器的状态,并在组件内部执行导航。
- useHistory
- useLocation
- useParams
- useRouteMatch
Please note: You need to be using React >= 16.8
in order to use any of these hooks!
useHistory |
useHistory钩子允许你访问history
实例,你可以用它来导航。
import { useHistory } from "react-router-dom";
function HomeButton() {
let history = useHistory();
function handleClick() {
history.push("/home");
}
return (
<button type="button" onClick={handleClick}>
Go home
</button>
);
}
useLocation |
返回 react router 封装的 location 实例
import { useLocation} from "react-router-dom";
function BlogPost() {
let location = useLocation();
...
}
location 可以用于:
<Link to={location}/>
<Redirect to={location}/>
history.push(location)
history.replace(location)
useParams |
useParams返回一个URL参数的键/值对对象。使用它访问match.params
读取当前路由的参数。
import { useParams} from "react-router-dom";
function BlogPost() {
let { slug } = useParams();
return <div>Now showing post {slug}</div>;
}
<Route path="/blog/:slug">
<BlogPost />
</Route>
useRouteMatch |
useRouteMatch 返回一个match
对象(params ,isExact ,path,url )。
import { useRouteMatch } from "react-router-dom";
function Topics() {
let match = useRouteMatch();
return (
<div>
<Link to={`${match.url}/components`}>Components</Link>
<Switch>
<Route path={`${match.path}/:topicId`}>
<Topic />
</Route>
</Switch>
</div>
);
}
withRouter |
不是通过路由切换过来的组件,是没有routeProps的。使用withRouter包装了这些非路由切换而来的组件,可以将routeProps(history、location、match)传入组件props对象上。
默认情况下必须是经过路由匹配渲染的组件才存在this.props,才拥有路由参数,才能使用编程式导航的写法,执行this.props.history.push(‘/detail‘)
跳转到对应路由的页面。然而不是所有组件都直接与路由相连(通过路由跳转到此组件)的,当这些组件需要路由参数时,使用withRouter就可以给此组件传入路由参数,此时就可以使用this.props
🌰
比如app.js这个组件,一般是首页,不是通过路由跳转过来的,而是直接从浏览器中输入地址打开的,如果不使用withRouter此组件的this.props为空,没法执行props中的history、location、match等方法。
我就通过在App.js组件中使用withRouter来简单介绍一下:
设置withRouter很简单只需要两步:(1)引入 (2)将App组件 withRouter() 一下
import React,{Component} from ‘react‘
import {Switch,Route,NavLink,Redirect,withRouter} from ‘react-router-dom‘ //引入withRouter
import One from ‘./One‘
import NotFound from ‘./NotFound‘
class App extends Component{
//此时才能获取this.props,包含(history, match, location)三个对象
console.log(this.props); //输出{match: {…}, location: {…}, history: {…}, 等}
render(){return (<div className=‘app‘>
<NavLink to=‘/one/users‘>用户列表</NavLink>
<NavLink to=‘/one/companies‘>公司列表</NavLink>
<Switch>
<Route path=‘/one/:type?‘ component={One} />
<Redirect from=‘/‘ to=‘/one‘ exact />
<Route component={NotFound} />
</Switch>
</div>)
}
}
export default withRouter(App); //这里要执行一下WithRouter
=========================================================================================
行文至此,若有不足,还望指点;尚觉还行,多多支持。
参考文档:
react-router官网
React Router V4 精讲
React Router教程
React-router5.x 路由的使用及配置
React router的Route中component和render属性理解
withRouter的作用和一个简单应用