前言
- 先说明一下,我不是专门的前端,我的主业是做java的,react我也只用了几天。有错误请指出(不过我登录的时间比较少,看到了我会及时修改的)
- 这篇文章不会讲过多的内容,我只贴出对应需求的写法
版本
我用的应该是目前最新的版本,项目用的create-react-app
构建的
- “react”: “^16.9.0”,
- “react-router”: “^5.0.1”,
基础用法
这段代码用于演示当我们在地址栏输入 /page1, /page2, /page1/add 时页面上出现对应不同的内容
// APP.jsx 项目根组件
import React from 'react'
import {BrowserRouter} from "react-router-dom";
import {Route, Switch} from "react-router";
export default class App {
constructor(props){super(props)}
render(){
return (
<div className="app">
<BrowserRouter>
<Switch>
<Route exact path="/page1" component={组件1}/>
<Route exact path="/page2" component={组件2}/>
<Route exact path="/page1/add" component={add组件}/>
</Switch>
</BrowserRouter>
</div>
)
}
}
嵌套路由和exact
- 场景: 后台管理系统,登录页在 /login, 其他的页面在 layout里,根据菜单点击不同进入不同的路由
- exact: 如果
<Route>
上加上了exact
,则表示全匹配,比如以下三个路径- /
- /user
- /user/add
对应我们有一个组件<Home>
,
当我们配置<Route path="/" component={Home}/>
,注意一下,这里没有加exact
,这个意思表示/开头的任意路由都可以进入该路由,也就是 /user和/user/add也会进入该路由
什么意思呢,就是说我们可以通过这个特性,将我们的 layout组件的路由上不加exact,在layout组件内部还有一组路由配置,这组路由配置都加上exact。我知道这么说很懵… 还是看代码吧
’以下代码示意的是后台管理系统,假设路由如下
/login 登录页
/user 用户管理页面,左侧是菜单,右侧为用户管理页面
/user/add 添加用户页面,同上,左侧是菜单
/systemSettings 系统设置页面,同上,左侧是菜单
// APP.jsx 项目根组件
import React from 'react'
import {BrowserRouter} from "react-router-dom";
import {Route, Switch} from "react-router";
// 这里后缀加jsx标注是为了说明这两个是组件,后续代码同理,不再赘述
import Login from '@/pages/Login.jsx';
import Layout from '@/components/Layout';
export default class App {
constructor(props){super(props)}
render(){
return (
<div className="app">
<BrowserRouter>
<Switch>
<Route exact path="/login" component={Login}/>
<Route path="/" component={Layout}/>
</Switch>
</BrowserRouter>
</div>
)
}
}
// Layout组件
import React from 'react'
import {BrowserRouter} from "react-router-dom";
import {Route, Switch} from "react-router";
import User from '@/pages/User.jsx';
import UserAdd from '@/pages/UserAdd.jsx';
import SystemSettings from '@pages/SystemSettings.jsx';
export default class Layout {
constructor(props){super(props)}
render(){
return (
<div className="layout">
假设这三个按钮是左侧菜单,点击跳转对应页面
<button>用户管理</button>
<button>添加用户</button>
<button>系统设置</button>
<BrowserRouter>
<Switch>
<Route exact path="/user" component={User}/>
<Route exact path="/user/add" component={UserAdd}/>
<Route exact path="/systemSettings" component={SystemSettings}/>
</Switch>
</BrowserRouter>
</div>
)
}
}
动态路由
这个很好实现,用for循环或者map去render就好了
注意,如果想要用路径引入,就要用
require
函数
import React from 'react'
import {BrowserRouter} from "react-router-dom";
import {Route, Switch} from "react-router";
import User from '@/pages/User.jsx';
import UserAdd from '@/pages/UserAdd.jsx';
import SystemSettings from '@pages/SystemSettings.jsx';
// 假设这个数据来自后端
const routes = [
{
path: '/user',
component: '@/pages/User.jsx'
},
{
path: '/user/add',
component: '@/pages/UserAdd.jsx'
},
{
path: '/systemSettings',
component: '@/pages/systemSettings.jsx'
}
]
export default class Layout {
constructor(props){super(props)}
renderRoutes = (routes = []) => {
return routes.map(route=>{
// 注意这里要加 .default
let component = require(route.component).default;
return (
<Route exact path={route.path} component={component}/>
)
});
}
render(){
return (
<div className="layout">
<BrowserRouter>
<Switch>
{this.renderRoutes()}
</Switch>
</BrowserRouter>
</div>
)
}
}
组件获取路由参数
这点是我的疏忽,我还没有用到react-redux-router,目前我讲下最简单的方法,用withRouter,比如我在<Header>
组件里有个退出按钮,需要退出到登录页
import React from 'react';
class Header {
constructor(props){super(props)}
/**
* 退出登录
*/
logout = () => {
this.props.history.push('/login');
}
render(){
return (
<div className="header">
...
<button onClick={this.logout}>退出登录</button>
</div>
)
}
}
// 这里要用withRouter将组件包裹起来
export default withRouter(Header);
路由守卫
比如在上面的例子里,除了/login之外,其他所有路由都需要判断是否已经登录,登录了才能进入,或者是需要某种权限才能进入,那么这个时候就需要用到路由守卫。
我的写法可能和网上的不太一样,我也不知道可不可以,我先分享一下。
- render属性,这是
<Route>
组件上的属性,使用该属性可以替代component属性,完成对组件的加载
写一个最简单的例子
// 这两行代码是等效的
<Route exact path="/user" component={User}/>
<Route exact path="/user" render={(props) => <User {...props}/>} />
我们可以看到 render 里传入了一个箭头函数,那么既然是函数,我们就可以控制对应的返回了
<Route exact path="/user" render={(props) => {
console.log('进入了route中的render函数');
return (
<User {...props}/>
)
}} />
这个例子中,我们进入了 /user 后就会打印出 进入了route中的render函数
那么箭头函数中的props参数是什么呢?
内容包含如下
- history: 对路由进行操作的对象,对应api百度有很多,在此不再赘述
- location: 当前路由信息,如进入的路径
- match: 当前路由是如何匹配进入的,如 url地址为/login匹配到了 path=/login
- staticContext: 这个我拿到的一直是undefined,有兴趣可以去翻下官方文档,这里不说了
那么既然如此我们就可以去根据props.location里的信息去判断当前是否为登录页,如果是登录页,则直接进入,如果不是则判断本地是否有对应登录token,如果有则进入页面,否则跳转回登录
代码如下
// routerGuard.js
// 规定如果onEnter返回了pass为true,则放行,进入路由,如果返回了 redirectTo,则跳转到对应页面
const onEnter = () => { return {pass: true} }
// 设置onEnter的方法
export function setOnEnter(fn) {
onEnter = fn;
}
// 返回一个render属性可接收的方法,这个方法需要传入一个原本的render方法
export function renderWithGuard(renderFn){
return (props) => {
if(onEnter){
let filterRst = onEnter(props);
// onEnter返回pass为true,进行渲染
if(filterRst.pass) {
return renderFn(props);
}
// onEnter返回了 redirectTo,此处进行跳转
else if(filterRst.redirectTo) {
return props.history.push(filterRst.redirectTo);
}
// onEnter内已经处理好了,不需要我们再处理
else if(filterRst.cancel) {
return null;
}
// 未处理,进入404
return props.history.push('/404');
}
// 不存在onEnter钩子,直接调用render函数
return renderFn(props);
}
}
// permission.js
setOnEnter((props) => {
let { location, history } = props;
// 当前访问路径
let { pathname } = location;
// 登录页,放行
if(pathname === '/login'){
return { pass:true };
}
// 假设token存在localStorage里
let token = localStorage.getItem('token');
// token存在,放行
if(token){
return { pass:true };
}
// token不存在,重定向到登录页
let redirectTo = `/login?redirectUrl=${pathname}`;
return { redirectTo };
})
// 渲染路由的地方,这里我们和前面的require串起来用
<Route path="/user" render={renderWithGuard((props)=> {
let component = require('@/pages/User.jsx').default;
return <component {...props}/>
})}
结束语
无。