React路由
什么是路由?
路由是根据不同的 url 地址展示不同的内容或页面。 一个针对React而设计的路由解决方案、可以友好的帮你解决React components 到 URl之间的同步映射关系。
- 如果要使用 路由模块,第一步,安装 react-router-dom
- 第二步,导入 路由模块
路由安装
https://reacttraining.com/react-router/web/guides/quick-start
使用React路由之前,我们需要先安装 react-router-dom
这个包。比如:
npm install --save react-router-dom
路由使用
(1) 路由方法导入
import React from 'react'
import {HashRouter,
Route,
Redirect,
Switch,
NavLink
} from 'react-router-dom'
(2) 定义路由以及重定向
-
HashRouter :表示一个路由的跟容器,将来,所有的路由相关的东西,都要包裹在 HashRouter 里面,而且,一个网站中,只需要使用一次 HashRouter 就好了;
- 当 使用 HashRouter 把 组件的元素包裹起来之后,网站就已经启用路由了
- 在一个 HashRouter 中,只能有唯一的一个根元素
- 在一个网站中,只需要使用 唯一的一次 即可
-
Route :表示一个路由规则, 在 Route 上,有两个比较重要的属性,
path
component
- Route 创建的标签,就是路由规则,其中
path
表示要匹配的路由,component
表示要展示的组件 - 在 vue 中有个 router-view 的路由标签,专门用来放置,匹配到的路由组件的,但是,在 react-router 中,并没有类似于这样的标签,而是 ,直接把 Route 标签,当作的 坑(占位符)
- Route 具有两种身份:1. 它是一个路由匹配规则; 2. 它是 一个占位符,表示将来匹配到的组件都放到这个位置
- Route 创建的标签,就是路由规则,其中
-
Redirect:内置两个属性
from
: 匹配到的路径to
:要跳转的路径<HashRouter> {props.children} <Switch> <Route path="/film" component={Film}/> <Route path="/cinema" component={Cinema}/> <Route path="/center" component={Center}/> {/* 动态路由 */} <Route path="/detail/:id" component={Detail}/> <Redirect from="/" to="/film"/> </Switch> </HashRouter>
完整代码如下:
//配置路由-路由组件
import React from 'react'
import {HashRouter,Route,Redirect,Switch} from 'react-router-dom'
import Center from '../views/Center'
import Cinema from '../views/Cinema'
import Detail from '../views/Detail'
import Film from '../views/Film'
export default function MRouter(props) {
return (
<HashRouter>
{props.children}
<Switch>
<Route path="/film" component={Film}/>
<Route path="/cinema" component={Cinema}/>
<Route path="/center" component={Center}/>
{/* 动态路由 */}
<Route path="/detail/:id" component={Detail}/>
<Redirect from="/" to="/film"/>
</Switch>
{/* 模糊匹配 */}
</HashRouter>
)
}
/*
function Route(props){
console.log(props.path)
if(location.hash===props.path){
return <div>
<props.component />
</div>
}
return null
}
*/
注意:
a. <Redirect from="/" to="/film"/>
b. exact 精确匹配 (Redirect 即使使用了exact, 外面还要嵌套Switch 来用)
c. Warning: Hash history cannot PUSH the same path; a new entry will not be added to the history stack,这个警告只有在hash 模式会出现。在NavLink 加上 replace 来解决.
模糊匹配与精准匹配
默认情况下,路由中的匹配规则,是模糊匹配的。上面这种匹配方式,全都匹配到了cinema组件,不会匹配到search组件。
<Route path="/cinema" component={Cinema} ></Route>
<Route path="/cinema/search" component={Search}></Route>
如果想让路由规则,进行精确匹配,可以为Route添加 exact
属性。
<Route path="/cinema" component={Cinema} exact></Route>
<Route path="/cinema/search" component={Search}></Route>
(3)嵌套路由
import React from 'react'
import { Route, Switch,Redirect } from 'react-router-dom'
import Comingsoon from './films/Comingsoon'
import Nowplaying from './films/Nowplaying'
export default function Film() {
return (
<div>
<div style={{height:'200px',background:"yellow"}}>大轮播</div>
<Switch>
<Route path ="/film/nowplaying" component={Nowplaying}/>
<Route path ="/film/comingsoon" component={Comingsoon}/>
<Redirect from="/film" to="/film/nowplaying"/>
</Switch>
</div>
)
}
(4)路由跳转方式
i. 声明式导航
-
Link :表示一个路由的链接(a链接),就好比 vue 中的
<router-link to=""></router-link>
-
NavLink (有高亮)
to
:要跳转的路径(相当于a标签中的"herf"属性)activeClassName
:用来做选中样式的切换(相当于vue的active-class
)
import React from 'react'
import {NavLink} from 'react-router-dom'
import './Tabbar.css'
export default function Tabbar() {
return (
<div>
<ul>
<li>
<NavLink to="/film" activeClassName="active">电影</NavLink>
</li>
<li>
<NavLink to="/cinema" activeClassName="active">影院</NavLink>
</li>
<li>
<NavLink to="/center" activeClassName="active">我的</NavLink>
</li>
</ul>
</div>
)
}
/*
function NavLink(props){
return <a href={"#"+props.to} className={location.href===props.to?props.activeClassName:""}>
{props.childrenm}
</a>
}
*/
ii. 编程式导航
- vue 使用
this.$router.push()
- react 使用
props.history.push()
const handleChange = (id)=>{
props.history.push(`/detail/${id}`)
}
动态路由:
用来路由跳转的过程中传递参数,例如点击列表页某条数据,跳转到详细页。
定义动态路由:
<Route path="/detail/:id" component={Detail}/>
跳转到指定动态路由:
const handleChange = (id)=>{
props.history.push(`/detail/${id}`)
}
<li key={item.filmId} onClick={()=>handleChange(item.filmId)}>{item.name}</li>
如果我想在 Detail组件中显示路由的参数,可以通过 props.match.params
获取路由中的参数。
-
vue:
this.$route.params.id
-
react:
props.match.params.id
useEffect(() => {
console.log("获取详情id,发ajax给后端",props.match.params.id)
}, [props.match.params.id])
<div> {this.props.match.params.id} </div>
完整代码如下:
import {useEffect,useState} from 'react'
import axios from 'axios'
export default function Nowplaying(props) {
const [list, setlist] = useState([])
useEffect(() => {
axios.get("/test.json").then(res=>{
setlist(res.data.data.films)
})
}, [])
const handleChange = (id)=>{
//编程式导航
props.history.push(`/detail/${id}`)
}
return (
<div>
{
list.map(item=>
<li key={item.filmId} onClick={()=>handleChange(item.filmId)}>
<img src={item.poster} alt={item.name}/>
{item.name}</li>
)
}
</div>
)
}
(5) 路由传参
(1)
this.props.history.push({ pathname : '/user' ,query : { day: 'Friday'} })
this.props.location.query.day
(2)
this.props.history.push({ pathname:'/user',state:{day : 'Friday' } })
this.props.location.state.day
(6) 路由拦截
const check = ()=>{
return localStorage.getItem("token")
}
{/* 路由拦截 */}
{/* check 有token跳到center 没有token 重定向login */}
<Route path="/center" render={()=>check()?<Center/>:<Redirect to="/login"/>}/>
<Route path="/login" component={Login}></Route>
(7) withRouter的应用与原理
function CinemaHeader (props) {
return (
<div>
<button onClick={() => {
console.log(props);
props.history.push('/cinema/search')
{/*跳转不到 因为props里面没有history*/}
}}>地点</button>
</div>
)
}
export default function Cinema () {
return (
<div>
<CinemaHeader></CinemaHeader>
</div>
)
}
props 打印{},因为 CinemaHeader的父组件(Cinema) 没有给他传属性,只有Route组件才有props属性。
解决方案有:
- 在父组件自己写一个属性传给子组件CinemaHeader,属性里面写一个回调函数,子组件调用回调函数
export default function Cinema (props) {
return (
<div>
<CinemaHeader onLeftClick={() => {
console.log("leftclick");
props.history.push('/cinema/search')
}}></CinemaHeader>
</div>
)
}
function CinemaHeader (props) {
return (
<div>
<button onClick={() => props.onLeftClick()}>地点</button>
</div>
)
}
- {…props}
// 父组件把Route传过来的props展开传给子组件,子组件就可以用props.history
export default function Cinema (props) {
return (
<div>
<CinemaHeader {...props}></CinemaHeader>
</div>
)
}
function CinemaHeader (props) {
return (
<div>
<button onClick={() => {
props.history.push('/cinema/search')
}}>地点</button>
</div>
)
}
- withRouter
import {withRouter} from 'react-router-dom' // 导入 withRouter
function CinemaHeader(props) {
return (
<div>
<button onClick={()=>{
console.log(props)
props.history.push('/cinema/search')
}}>北京</button>
</div>
)
}
export default withRouter(CinemaHeader) // withRouter 包裹子组件
原理:利用高阶组件封装出来的一个组件
/*
withRouter 高阶函数 高阶组件 (High-order-component) == HOC
(输入低阶组件, 得到高阶组件)
*/
function withRouter(MyComponent){
.....
.....
return function(){
return <div>
<MyComponent history={} ..../>
</div>
}
}
/* function add(a,b){
return a+b
}
function AAA(add){
return function(a,b){
return add(a*a,b*b)
}
}
// var result = add(1,2)
// console.log(result)
var advancedAdd = AAA(add)
console.log(advancedAdd(1,2)) */
自己封装高阶组件(复用思想)
import React,{useEffect} from 'react'
function withCenter(MyComponent){
return function(){
useEffect(() => {
window.onresize = ()=>{
console.log("resize")
}
}, [])
return <div style={{background:"red"}}>
<MyComponent a="1" b="2"/>
</div>
}
}
function Center(props) {
console.log(props)
return (
<div>
center
</div>
)
}
export default withCenter(Center)
history模式
BrowserRouter 没有#,后端找不到会报404
// 配置路由的文件
import React from 'react'
import { BrowserRouter, Route, Switch } from 'react-router-dom'
import Film from '../views/Film'
import Login from '../views/Login'
export default function MyRouter (props) {
return (
<div>
<BrowserRouter>
<Switch>
<Route path="/film" component={Film}></Route>
<Route path="/login" component={Login}></Route>
</Switch>
</BrowserRouter>
</div>
)
}
cssmodule
css样式 会全局影响,react没有scope ,需要把css 文件改为 filename.module.css
//导入
import style from './Film.module.css'
// 用的时候通过style.样式名
<div className={style.active}>film-header</div>