React路由
React路由
安装及引入
安装router-dom:
npm install --save react-router-dom
在路由控制组件引入:
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
一般的项目结构为:
root-directory
├─ 📁src
│ ├─ 📁router
│ │ └─ 📄Layout.jsx
│ ├─ 📁views
│ │ ├─ 📄About.jsx
│ │ └─ 📄Home.jsx
│ └─ 📄index.js
路由管理放在router下,被管理的可切换视图放在View下。
基本使用
实现上述Home和About的相互跳转。在路由控制组件中:
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
import Home from '../views/Home'
import About from '../views/About'
export default class Layout extends Component {
render() {
return (
<Router>
<Routes>
<Route path='/about' Component={ About }></Route>
<Route path='/' Component={ Home }></Route>
</Routes>
</Router>
)
}
}
path控制路由,Component控制显示哪个组件。注意,在路由容器和具体路由条目之间还嵌了一层<Routes>
标签。Routes
组件确保仅渲染匹配到的第一个Route
组件,避免多个路由同时渲染。比如,如果不加这一层,当输入/about路径时,会同时匹配到它的所有前缀包括根目录一起显示在页面上,加了<Routes>
就能避免这个问题。
HashRouter和BrowserRouter
在刚才的例子中用的是BrowserRouter,其实还有另一种路由实现方式HashRouter。如果用HashRouter,路径url会多出一个/#/
,这是表象差异。在生产环境中如果使用BrowserRouter,想要直接访问子地址(而不是从根目录一层层进入),会报404,需要后端做重定向。
在实现上,HashRouter使用锚点方式:
<a href='#/about'></a>
利用 URL 中的 #
符号(哈希部分)来模拟路由变化。哈希部分的变化不会触发页面刷新,但会触发 hashchange
事件。根据锚点不同在视口内展示内容。这样不美观,且SEO不友好,搜索引擎通常忽略哈希部分的内容。
而BrowserRouter使用h5新特性:pushState()
来操作浏览器的会话历史栈,实现无刷新路由切换。通过 window.addEventListener('popstate', callback)
监听前进/后退按钮触发的路由变化。
为什么BrowserRouter需要重定向?
假设你的 React 应用有一个路由 /about
,部署在 https://example.com
。用户点击页面内的 <Link to="/about">
,React Router 通过 history.pushState()
修改 URL,不会触发页面刷新,前端正常渲染 About 组件。当直接访问About时,浏览器会向服务器发送请求:GET https://example.com/about
。如果服务器未配置 Fallback,会尝试查找 /about
目录或文件 → 返回 404(因为实际资源是 index.html
,其他路径是前端虚拟路由)。
而HashRouter,浏览器只会请求 https://example.com/
(忽略 #/about
),服务器始终返回 index.html
。哈希部分 #
之后的内容不会发送到服务器。
Link跳转
实现点击跳转到对应的路由:
<Router>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/about'>About</Link></li>
</ul>
<Routes>
<Route path='/about' Component={ About }></Route>
<Route path='/' Component={ Home }></Route>
</Routes>
</Router>
当然可以把视图抽离到路由配置之外。Link最终会渲染成a标签,但是不能在源码中用a代替Link,因为会损失一些Link的特性比如参数配置等。不过简单功能上是能做到的。
exact和strict严格模式匹配
exact用来严格匹配路径,刚才说到的前缀相同问题也可以用它解决:
<Route exact path='/' Component={ Home }></Route>
strict用来匹配具体路径,比如
https://fuufhjn.link/
https://fuufhjn.link
是否有区别?如果加了strict就有区别,strict要求一模一样。一般不会用。
Switch和404页面匹配
Switch可以让当前页面只匹配第一个,这是个老用法,现在用Routes即可。而404是用来匹配所有不存在于项目中的页面,可以看起来更美观而不是直接报错。假设页面写在NotFound组件里,不给path就可以匹配到所有404:
<Route component={ NotFound }></Route>
注意,404一定要放在最下方,防止优先被匹配到。
NavLink高亮
帮助用户识别出自己在哪个路由下。把Link改成NavLink。当处于NavLink的路由时,它会被添加一个active,这样就可以在css中对其添加样式。同时,也可以在标签中添加activeClassName:
<NavLink activeClassName='selected' to='/'>Hmoe</NavLink>
这样当处于这个路由下时会应用selected类选择器来渲染。
路由携带参数跳转
注册路由时,参数用:key
表示:
<Route path='/details/:id/:title'>
跳转时增加参数:
<Link to=`details/${element.id}/${element.title}`/></Link>
对应页面读取参数的方法:
this.props.match.params.id
const params = queryString.parse(this.props.match.params.id)...
遵循restful原则:没有key-value,而是纯用资源导向:/.../question/276317/answer/1283781...
路由重定向
直接在Routers下加一对<Redirect>
标签:
<Redirect from='/home' to='/'></Redirect>
携带参数的重定向:
<Redirect from='/query' to={{
pathname: '/query',
search: '?name=fuufhjn'
}}>
这里的双括号,外层表示进入js语法,内层是对象外边的括号。
编程式导航
有时需要用事件触发自动跳转,而不是用户点击,不能用Link
。示例为点击按钮从Home跳转到About:
handleClick = () => {
this.props.history.push('/about');
}
// Home.jsx
<button onClick={ this.handleClick }>Jump</button>
两种方式:
this.props.history.push('/about');
this.props.history.replace('/about');
区别是,用push跳转后点击回退按钮,会回到跳转前的页面。而replace则回不去,它直接替换掉了页面栈,会回到上上个页面。
跳转时也可以携带一个state过去,但是很少用:
this.props.history.push({
pathname: '/about',
state: {
name: fuuhjn
}
})
withRouter
当你试图在一个没有被路由管理的页面中使用history,会报错。如果想要在未处于路由树中的组件中使用跳转,一种方式是父级组件把自己的history通过props传给它:
<NotInRoute history={ this.props.history} />
但如果离得远就没法用props来做,需要用withRouter
。对于没有被路由管理的组件,导出时用withRouter包围:
export default withRouter(NotInRoute);
这样一来它成为一个高阶组件,在外面如果有人引用了它,NotInRoute
内部就可以获得history对象。
路由嵌套
如果info
与order
是user
的子页面:
<User path='/user'>
<Route path='user/info' component={ UserInfo }></Route>
<Route path='user/order' component={ UserOrder }></Route>
</User>
User
中需要显示出其内部的子页面:
<div>
{ this.props.children }
</div>
生命周期的问题
当通过路由转到别处时,将不再显示的组件会调用即将销毁钩子,此时该取消的东西要取消。