前端路由
前端根据不同的地址,对应渲染不同的组件或页面。
前端路由一般分为两种:
-
hash:以
#
代表hash值,这个给浏览器识别,根据这个hash值来切换显示不同的内容,这个hash值不会发送给后端,能够兼容低版本的浏览器。http://localhost:8000/#/login
-
history:相比于hash路由,history路由更好看,它会发送给服务端,如果前端的路由和后端服务器的路由一样的话,那么会访问服务器路由,上线后需要后端配合才能使用。
http://localhost:8000/login
react路由的安装
react本身没有提供路由,我们需要安装第三方插件react-router-dom
react路由v6版本地址:https://reactrouter.com/en/6.8.1/start/overview
v5版本地址:https://v5.reactrouter.com/
v6版本更偏向于函数式组件开发
v5版本兼容类组件和函数组件开发。
1)安装路由
yarn add react-router-dom@5.3.4
# or
npm i react-router-dom@5.3.4
2)配置一级路由
App.jsx
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
import Login from "./pages/Login";
import Home from "./pages/Home";
export default function App() {
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">首页</Link>
</li>
<li>
<Link to="/login">登录页面</Link>
</li>
</ul>
</nav>
{/* A <Switch> looks through its children <Route>s and
renders the first one that matches the current URL. */}
<Switch>
<Route path="/login">
<Login />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
Home.jsx
import React, { Component } from 'react'
export default class Home extends Component {
render() {
return (
<div>Home</div>
)
}
}
Login.jsx
import React, { Component } from 'react'
export default class Login extends Component {
render() {
return (
<div>Login</div>
)
}
}
react路由匹配信息
路由器
BrowserRouter:代表history路由模式,
HashRouter:代表hash路由模式
路由器应该放在最外层,其他的路由组件都应该包裹在内。否则会报错如下:
路径匹配器
Switch
类似于js中switch条件判断,它匹配到路径后只渲染匹配到的哪个Route
组件,如果没有匹配到,就不会渲染页面
路由匹配是模糊匹配,如果路径是/
,那么下面的路由路径都匹配不到,导致其他页面渲染不出来。
解决方案:
1)将/
路径放到最下面
<Switch>
<Route path="/login">
<Login />
</Route>
<Route path="/register">
<Register />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
2)使用exact精确匹配
<Switch>
<Route path="/" exact>
<Home />
</Route>
<Route path="/login">
<Login />
</Route>
<Route path="/register">
<Register />
</Route>
</Switch>
Route
将路由路径与组件进行映射,当路径匹配成功后,则渲染对应的页面组件,否则就不会渲染。
-
path
设置路由路径 -
exact:代表精确匹配,只能路由地址恰好与path的地址匹配上才算匹配成功。
<Switch> <Route path="/" exact> <Home /> </Route> </Switch>
-
component:配置要渲染的页面组件(推荐使用)
<Switch> <Route path="/login" component={Login}></Route> </Switch>
-
render:是一个函数,也可以用于配置要渲染的页面组件
<Switch> <Route path="/register" render={() => <Register />}></Route> </Switch>
-
嵌套的方式配置页面
<Switch> <Route path="/" exact> <Home /> </Route> </Switch>
没什么特殊情况,建议使用
component
属性去配置页面组件。
导航
Link组件
用于跳转页面的组件,类似于vue中的router-link
。
-
to:配置跳转页面的路径
可以传递字符串,也可以是一个对象
字符串方式:<Link to="/login">登录页面</Link>
对象方式:
<Link to={{pathname: '/register'}}>注册页面</Link>
-
replace:路由路径替换,当跳转页面后,那么回退页面时不会回退到刚刚的页面,而是回退到了上上个页面。
<Link to={{pathname: '/register'}} replace>注册页面</Link>
Redirect组件
路由重定向。
- from:匹配的路径来源,从哪里来
- to:重定向到哪里去
- exact:精确匹配
<Switch>
<Redirect from="/register" to={'/login'} exact />
<Route path="/" exact>
<Home />
</Route>
<Route path="/login" component={Login}></Route>
<Route path="/register" render={() => <Register />}></Route>
</Switch>
注意:
Redirect
建议写在最前面,如果写在后面可能匹配不到,无法生效。
二级路由
vue中嵌套路由配置:
{
path: '/home',
compoennt: Home,
children: [
{
path: 'user',
component: User
}
]
}
二级路由配置
- 嵌套路由配置不需要嵌套加BrowserRouter和HashRouter。
- 嵌套路由配置需要加入父级路由,后面才是子路由路径
父级路由/子路由
import React, { Component } from 'react'
import { Link, Route, Switch, Redirect } from 'react-router-dom'
import Students from './Students'
import Teacher from './Teacher'
import User from './User'
export default class Home extends Component {
render() {
return (
<div>
<ul>
<li>
<Link to={'/home/teacher'}>教师管理</Link>
</li>
<li>
<Link to={'/home/students'}>学生管理</Link>
</li>
<li>
<Link to={'/home/user'}>用户管理</Link>
</li>
</ul>
{/* 这里渲染对应页面 */}
<Switch>
<Redirect from='/home' to={'/home/teacher'} exact />
<Route path={'/home/teacher'} component={Teacher}></Route>
<Route path={'/home/students'} component={Students}></Route>
<Route path={'/home/user'} component={User}></Route>
</Switch>
</div>
)
}
}
路由懒加载
前端目前的项目基本都是工程化项目单页面应用。
单页面应用一开始加载时就会将所有的文件加载出来,如果项目很大,会导致在首屏加载时时间过长,白屏时间长,用户体验不太好。
vue中解决:
{
path: 'xxx',
compoennt: () => import('./xxx.vue')
}
在react中,有两种方式去完成路由懒加载:
- 使用第三方插件
- 使用react官方的方式
使用了路由懒加载后,会在使用这个页面时才会去加载对应的页面,而不是一开始就全部加载页面。
首屏渲染速度得到了提升。
使用react-loadable插件
1)安装插件
npm i react-loadable
# or
yarn add react-loadable
2)引入插件
import Loadable from "react-loadable";
3)使用插件加载页面
const Login = Loadable({
loader: () => import('./pages/Login'),
loading: () => <div>正在加载中。。。。。。</div>
});
const Home = Loadable({
loader: () => import('./pages/Home'),
loading: () => <div>正在加载中。。。。。。</div>
});
const Register = Loadable({
loader: () => import('./pages/Register'),
loading: () => <div>正在加载中。。。。。。</div>
});
loader
:加载的页面
loading
:加载页面组件完成之前要渲染的加载组件。
使用react官方推荐的方式
-
使用
React.Suspense
包裹路由<React.Suspense fallback={<div>正在加载中。。。。。</div>}> <Switch> {/* <Redirect from="/register" to={'/login'} exact /> */} <Route path="/home" component={React.lazy(() => import('./pages/Home'))}></Route> <Route path="/login" component={React.lazy(() => import('./pages/Login'))}></Route> <Route path="/register" component={React.lazy(() => import('./pages/Register'))}></Route> </Switch> </React.Suspense>
fallback
:加载页面完成之前要渲染的一个loading组件 -
搭配
React.lazy
完成路由懒加载<Route path="/home" component={React.lazy(() => import('./pages/Home'))}></Route>
路由参数
路由传递参数,在页面之间传递参数。
在react路由中,有四种参数传递方式:
prams动态路由
动态路由需要配置路由参数:
-
配置路由参数
<Route path="/register/:id" render={() => <Register />}></Route>
-
跳转路由
Link方式:<Link to={'/register/10'}>到注册页面</Link>
js方式:
this.props.history.push('/register/100');
-
参数获取
动态路由参数是在this.props.match.params
this.props.match.params
query的方式传参(不推荐)
通过对象的方式传递参数
-
跳转传递参数
<Link to={{pathname: '/register', query: {id: '10'}}}>query跳转到注册页面</Link>
js跳转
this.props.history.push({ pathname: '/register', query: {id: '100'} });
-
获取参数
this.props.location.query
注意:有坑
当刷新页面后,参数丢失。
state的方式传递参数(不推荐)
通过对象的方式传递参数
-
跳转传递参数
<Link to={{pathname: '/register', state: {id: '1000'}}}>state跳转到注册页面</Link>
js方式:
this.props.history.push({ pathname: '/register', state: {id: '100'} });
-
获取参数
this.props.location.state
注意:有坑
在history路由模式下刷新页面参数state还在,但是hash路由模式下刷新页面state参数丢失了。
search的方式传参
在地址栏中通过?
传递参数
-
跳转传递参数
字符串传递参数:<Link to={'/register?id=1'}>search跳转到注册页面</Link>
this.props.history.push('/register?id=1');
对象传递参数:
<Link to={{pathname: '/register', search: '?id=10'}}>search跳转到注册页面</Link>
this.props.history.push({ pathname: '/register', search: '?id=100' });
-
获取参数
this.props.location.search
search参数获取到时一个字符串
search参数转换成对象
1)自己封装函数进行转换
export function parseSearch(str) {
let arr = str.split("?")[1].split("&"); //先通过?分解得到?后面的所需字符串,再将其通过&分解开存放在数组里
let obj = {};
for (let i of arr) {
console.log(i.split("="));
obj[i.split("=")[0]] = i.split("=")[1]; //对数组每项用=分解开,=前为对象属性名,=后为属性值
}
return obj;
}
2)通过插件qs
进行转换
-
安装插件
npm i qs
-
引入
import qs from 'qs'
-
使用
将字符串参数转换成对象qs.parse(this.props.location.search, { ignoreQueryPrefix: true });
ignoreQueryPrefix
:去掉前缀?
将对象转换成字符串参数
qs.stringify(obj, { addQueryPrefix: true })
addQueryPrefix
:添加前缀?
路由跳转
通过Link组件跳转
<Link to={'/home/teacher'}>教师管理</Link>
通过js跳转
window.location.href跳转
window.location.href = '/home/students'
这种可以在react项目中跳转路由页面,虽然可以完成页面的跳转,但是存在一些缺陷,会导致资源的重复加载,导致页面重新加载,用户体验不好。
props.push跳转路由
是路由插件提供了能力进行路由页面跳转,这种方式不会重复加载,页面也不会刷新,用户体验更好。
字符串参数:
this.props.history.push('/home/students');
对象参数:
this.props.history.push({
pathname: '/home/students'
});
props.replace
替换路由路径,返回的是返回到了上上个页面
字符串参数:
this.props.history.replace('/home/students');
对象参数:
// 做了很多逻辑处理
this.props.history.replace({
pathname: '/home/students'
});
props.goBack
回退到上一个页面路由
this.props.history.goBack();
props.goForward
前进一个页面
this.props.history.goForward();
withRouter高阶组件
非路由组件的跳转
在react组件中,通过Route
组件配置的页面组件,被称为路由组件,但是直接引入使用的组件被称为非路由组件,非路路由组件内部没有路由相关信息,没法直接跳转路由。
针对非路由组件获取路由信息的解决方案:
1)将父组件的路由信息传入到非路由组件中(不推荐)
<Header {...this.props}></Header>
传入后,非路由组件内部就拥有了路由相关信息,然后就可以跳转路由了。
this.props.history.push('/login');
虽然解决了路由跳转问题,但是不推荐使用,因为这种方式嵌套了其他的非路由组件,会导致将路由信息层层传递下去,开发起来也非常麻烦。
2)withRouter
高阶组件
通过高阶组件withRouter
包裹非路由组件,将路由相关信息注入到组件的props
中,非路由组件就拥有了路由相关信息。
-
引入
import { withRouter } from 'react-router-dom';
-
使用
export default withRouter(Header);
运行
withRouter
函数包裹非路由组件,注入路由信息。 -
跳转
this.props.history.push('/login');
-
全代码
import React, { Component } from 'react' import { withRouter } from 'react-router-dom'; class Header extends Component { gotoLogin = () => { console.log(this.props) this.props.history.push('/login'); } render() { return ( <div> 这是header组件 <button onClick={this.gotoLogin}>去登录</button> </div> ) } } export default withRouter(Header);
高阶组件的案例
函数科里化:https://www.jianshu.com/p/2975c25e4d71
高阶组件本质就是一个函数,接收一个组件作为参数,经过处理返回一个组件。
简单解释withRouter
:
function withRouter(Comp) {
return class WrapperdCompoennt extends React.Component {
history = () => {
}
render() {
return <Comp history={this.history} />
}
}
}
案例:
import React, { Component } from 'react'
function HOC(Comp) {
return class WrapperedComp extends Component {
state = {
title: 'web前端'
}
render() {
return <Comp title={this.state.title} />
}
}
}
class Test extends Component {
render() {
return (
<div>
test页面:
<br />
title: {this.props.title}
</div>
)
}
}
export default HOC(Test);