路由
一、介绍
前端应用大多数是SPA(单页应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好、对服务器压力更小,所以更受欢迎。为了有效的使用单个页面来管理多页面的功能,前端路由应运而生。
前端路由模式
①、hash模式
②、history模式
③、memeory模式
浏览器中前端路由模式
①、hash模式
onhashchange
②、history模式
pushState/replaceState/go/onpopstate
二、路由使用
2.1安装路由模块
https://serializedowen.github.io/docs/react-router-dom
yarn add react-router-dom@5 支持类组件和函数组件 react支持18及以下版本
react-router-dom@6 react16.8之后 推荐使用函数组件
2.2 相关组件
Ø 路由模式组件:包裹整个应用,一个React应用只需要使用一次
HashRouter: 使用URL的哈希值实现 (localhost:3000/#/first) 浏览器
BrowserRouter:使用H5的history API实现(localhost3000/first) 浏览器
MemoryRouter:此内存,浏览器中不建议使用
Ø 导航组件:用于指定导航链接
Link/NavLink:最终Link会编译成a标签,而to属性会被编译成 a标签的href属性
Ø 路由定义组件:指定路由规则和对应匹配成功后要渲染的组件
Route:
path属性:路由规则,这里需要跟Link组件里面to属性的值一致
component属性:展示的组件
import React, { Component } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
// 引入路由渲染的组件
import Film from './views/Film'
import Detail from './views/Film/Detail'
class App extends Component {
render() {
return (
<Router>
<Switch>
<Route path='/detail/:id(\d+)' component={Detail} />
<Route path='/' component={Film} />
</Switch>
</Router>
)
}
}
export default App
2.3声明式导航
使用Link或NavLink组件完成声明式导航的定义
Link/NavLink区别
² Link组件不会根据路由的变化而添加或修改编译后html标签中的属性
² NavLink会根据路由的变化而自动修改编译后html标签中的属性
如果当前的路由规则和Navlink中的To所写的规则一致则添加class样式,
默认名称为 active,可以通过activeClassName来修改匹配成功后样式名称。
import React, { Component } from 'react'
import './style/app.css'
// 引入路由模块
// BrowserRouter
// + basename: 设置根路径,默认为'/',自定义一访问url前经 basename='/app'
// Switch 只要子Route匹配成功,则停止匹配
// + 使用Switch组件要把范围大的放在后面,把范围小的放在前面
// Redirect 重定向,一般写在规则的最后
// + exact 严格匹配,一般多和404匹配在一起使用时才用
// Link, NavLink,NavLink它有激活样式状态,默认className='active'
import { BrowserRouter as Router, Route, Switch, Redirect, Link, NavLink } from 'react-router-dom'
// 引入路由渲染的组件
import Home from './views/Home'
import About from './views/About'
import Detail from './views/Detail'
import Notfound from './views/Notfound'
class App extends Component {
render() {
return (
<Router>
{/*
声明式导航
Link / NavLink
+ to 跳转的路径 string|object 必填的
+ replace 是否替换当前的历史记录,默认false
NavLink
+ activeClassName 激活的样式,默认'active' activeClassName='abc'
+ exact 严格匹配,默认false
*/}
{/* <Link to='/home'>首页</Link> |
<Link to='/about'>关于</Link> */}
<NavLink to='/home'>首页</NavLink> |
<NavLink to='/about'>关于</NavLink>
<hr />
{/*
定义路由规则
路由匹配是从上向下依赖去匹配的,直接没有Route组件后停止匹配
path 匹配的路径,它匹配是按包含去匹配的
component 匹配成功后要渲染的组件
exact 严格匹配,一般不用去设置
<Route exact path='/' component={Home} />
*/}
<Switch>
{/* Route匹配渲染的组件才会注入到props对象中,把路由相关对象 */}
<Route path='/about' component={About} />
<Route path='/home' component={Home} />
{/*
动态路由参数,它是要先定义后使用
+ :id 路由参数 this.props.match.params.id 获取路由参数 /detail/100
+ :id(\d+) 路由参数限制 正则表达式 \d+ 1到多个数字
+ :id? 可选路由参数 可以不传 /detail/100 /detail
*/}
<Route path='/detail/:id' component={Detail} />
{/* 重定向 */}
<Redirect exact from='/' to='/home' />
{/* 404页面 */}
<Route path='*' component={Notfound} />
</Switch>
</Router>
)
}
}
export default App
2.4编程式导航
react-router-dom中通过history对象中的push/replace/goBack等方法实现编程式导航功能。
this.props.history.push("/home")
this.props.history.push/replace ({
pathname: "/home",
search: "from=404", // 表示传递查询字符串
state: {
// 隐式传参,地址栏不体现
username: "admin",
},
});
注:在react路由中this.props要想得到路由中的对象,则默认必须要通过路由规则匹配渲染的组件才能有此对象 - 必须是直接渲染的组件
2.5页面路由数据传递
路由参数:在Route定义渲染组件时给定动态绑定的参数。
React路由传参方式有三种:
Ø 动态路由参数(param)
以“/detail/:id”形式传递的数据
在落地组件中通过this.props.match.params得到
Ø 查询字符串(search)
通过地址栏中的 ?key=value&key=value传递
在落地组件中通过this.props.location.search得到
Ø 隐式传参(state),通过地址栏是观察不到的
通过路由对象中的state属性进行数据传递
在落地组件中通过this.props.location.state得到
2.6嵌套路由
在有一些功能中,往往请求地址的前缀是相同的,不同的只是后面一部份,此时就可以使用多级路由(路由嵌套)来实现此路由的定义实现。
import React, { Component } from 'react'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import Layout from './views/layout'
class App extends Component {
render() {
return (
<BrowserRouter>
<Switch>
{/*
父路由
注:父路由一定不要去设置严格匹配,否则子路由永远不会被匹配到 exact
*/}
<Route path='/admin' component={Layout} />
</Switch>
</BrowserRouter>
)
}
}
export default App
三、路由渲染
路由渲染分为component和render和children三种渲染模式
import React, { Component } from 'react'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import PageRender from './views/pageRender'
class App extends Component {
state = {
num: 100
}
add = () => {
this.setState(state => ({
num: state.num + 1
}))
}
render() {
return (
<BrowserRouter>
<h1>Num值:{this.state.num}</h1>
<button onClick={this.add}>++++++++++++</button>
<hr />
<Switch>
{/* component */}
{/*
类方式
1.它不能进行路由匹配成功后,二次验证
2.它会自动把路由信息通过props传递给子组件
3.宿主组件更新,它不会销毁PageRender组件,只是重新渲染
*/}
{/* <Route path='/' component={PageRender} /> */}
{/*
函数方式 -- 相对类方式性能差一些
1.它路由规则匹配成功后,还可以进行二次验证
2.它不会自动把路由信息通过props传递给子组件,需要手动传递
3.宿主组件更新,它会销毁PageRender组件,然后重新创建
*/}
{/* <Route
path='/'
component={route => {
if (route.location.search === '?name=admin') {
return <PageRender {...route} />
} else {
return <div>你没有权限</div>
}
}}
/> */}
{/* render */}
<Route
path='/'
render={route => {
if (route.location.search === '?name=admin') {
return <PageRender {...route} />
} else {
return <div>你没有权限</div>
}
}}
/>
</Switch>
</BrowserRouter>
)
}
}
export default App
import React, { Component } from 'react'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import PageRender from './views/pageRender'
class App extends Component {
render() {
// console.log('App', this.props)
return (
<BrowserRouter>
{/*
children
+ 它一般不是用来定义路由规则的,它一般用于定义一些组件是否在特定的路由下显示
+ 它一般不会和Switch路由组件一起使用
*/}
{/*
直接在children属性中使用自定义组件,它会进行路由规则匹配
但是它不会把路由信息通过props传给它所调用的子组件中
此方案,知道就可以,不推荐使用
*/}
{/* <Route path='/aaa' children={<PageRender />} /> */}
{/*
回调函数方式,它常用一些,主要用于给元素来进行判断是否显示
回调函数方式,这不会去匹配路由规则,不管path和当前的路由是否匹配,都会执行回调函数
如果当前地址和path匹配,那么route参数中的match属性就会有值,否则就为null
*/}
<Route
path='/user'
children={route => {
// console.log(route.match)
// return route.match ? <h3 style={
{ color: 'red' }}>你好child按钮</h3> : null
return <h3 style={
{ color: route.match ? 'red' : 'black' }}>你好child按钮</h3>
}}
/>
</BrowserRouter>
)
}
}
export default App
四、获取路由信息
让任意组件获取到路由信息
import React, { Component } from 'react'
import { Route, Switch, withRouter, Link } from 'react-router-dom'
// withRouter 高阶组件,它可以让任意的组件获取到路由信息
// 此高阶组件也不建议随意使用,尽可能少用,因为它会增加原有组件的层级
import PageRender from './views/pageRender'
import User from './views/user'
@withRouter
class App extends Component {
checkLogin = location => {
// 根据location.pathname来判断当前的路由是否需要登录,才能访问到
console.log('route', location)
// this.props.history.replace('/user')
}
componentDidMount() {
// console.log(this.props.location)
// 监听路由变化,初始化不会触发,只有路由变化才会触发
// 初始化时我主动调用一次,来获取当前的路由信息
this.checkLogin(this.props.location)
// 可以用它来完成导航守卫功能
this.props.history.listen(this.checkLogin)
}
render() {
return (
<div>
<Link to='/user'>user</Link> |
<Link to='/'>home</Link>
<hr />
<Switch>
<Route path='/user' component={User} />
<Route path='/' component={PageRender} />
</Switch>
</div>
)
}
}
// export default withRouter(App)
export default App
五、过渡
网址:https://reactcommunity.org/react-transition-group/css-transition
安装
npm install react-transition-group
import React, { Component } from 'react'
// CSSTransition 对元素进行过渡切换
// + in 元素是否显示 true/false
// + timeout 过渡样式切换时长 ms
// + classNames 过渡样式类名 string|object
// + string 过渡样式添加前缀 className='fade' fade_enter
// + object 过渡样式不添加前缀 className={ { enter: 'fade-enter' } } 自定义过渡样式名称
// + unmountOnExit 元素隐藏时是否移除DOM true/false
// + appear 元素第一次显示时是否添加过渡动画 true/false 默认false
// + fade-appear, fade-appear-active, fade-appear-done
// + 事件
// + onEnter 元素显示前触发
// + onEntering 元素显示时触发
// + onEntered 元素显示后触发
// + onExit 元素隐藏前触发
// + onExiting 元素隐藏时触发
// + onExited 元素隐藏后触发
// 注:CSSTransition它只能有一个子元素
import { CSSTransition } from 'react-transition-group'
import './style/transition.css'
class App extends Component {
state = {
isShow: true
}
setIsShow = () => {
this.setState(state => ({
isShow: !state.isShow
}))
}
render() {
return (
<div>
<CSSTransition
// in属性不能少,true进场动画,false出场动画
in={this.state.isShow}
// 样式切换时长
timeout={1000}
// 过渡样式前缀名称
classNames='fade'
// 出场完毕后,删除dom
unmountOnExit
onEnter={el => {
console.log('onEnter', el)
}}
onExited={el => {
console.log('onExited', el)
}}
>
<div className='box'>
<h1>App过渡切换</h1>
<h1>App过渡切换</h1>
</div>
</CSSTransition>
<hr />
<button onClick={this.setIsShow}>切换元素</button>
</div>
)
}
}
export default App
六、补充
因为学习周期过长,我后面设置一个React学习的专栏,方便学习和管理笔记