- web端使用路由安装的是
react-router-dom
"react-router-dom": "^5.2.0"
- 在组件中使用路由,我们先设置2个路由,分别是首页、关于
// src/components/RouteSample.js
import React from 'react';
// 引入路由需要的基础模块
import {BrowserRouter, Link, Route} from 'react-router-dom'
export default function RouteSample () {
return (
<div>
<BrowserRouter>
<div>
{/* 导航链接 */}
<div><Link to="/">首页</Link></div>
<div><Link to="/about">关于</Link></div>
{/* 路由配置:路由就是组件 */}
<Route path="/" component={Home}></Route>
<Route path="/about" component={About}></Route>
</div>
</BrowserRouter>
</div>
)
}
- 然后补充首页、关于2个组件,这时页面就可以切换路由了,但是发现一个问题,当路由切换至
about
时,首页的内容依然展示在页面上,这是react
一个特点叫包容式路由,默认情况是多个组件组成一个页面展示,也就是当我们访问/about
时会包含/
的内容
// src/components/RouteSample.js
function Home() {
return <div>
<h5>Home</h5>
</div>
}
function About () {
return (
<div>
<h5>About</h5>
</div>
)
}
- 接着上面的情况想要改成独占式的路由,也就是一个路由只展示当前组件,我们用到一个属性
exact
{/* 路由配置:路由就是组件 */}
<Route exact path="/" component={Home}></Route>
<Route path="/about" component={About}></Route>
-
react
路由也是有动态路由概念的,我们在首页展示一个课程列表,点击分别进入对应的课程详情页,继续在上面的代码基础上扩展
// src/components/RouteSample.js
function Home() {
return <div>
<h5>Home</h5>
<ul>
<li><Link to="/detail/web">web</Link></li>
<li><Link to="/detail/ios">ios</Link></li>
</ul>
</div>
}
// src/components/RouteSample.js
{/* 新增详情页路由 lesson就是动态传参的部分*/}
<Route path="/detail/:lesson" component={Detail}></Route>
// src/components/RouteSample.js
// 新增详情页组件
// 传递进来的是路由器对象
// 路由器对象主要包含以下3部分,可以结合下面的具体截图理解
// 1. history 导航指令 history.goBack
// 2. match 获取参数信息
// 3. location 当前url信息
function Detail (routeParams) {
console.log('routeParams',routeParams)
return (
<div>
当前课程:{routeParams.match.params.lesson}
<button onClick={routeParams.history.goBack}>后退</button>
</div>
)
}
- 在开发过程中有时候会碰到所有路由都不匹配的情况,也就是路径不对、页面找不到,通常会给一个404页面,在
react
中这样处理,加上404路由之后会发现无论路由在哪个页面404页面会一直显示(如下图),这里又遇到上面同样的问题react
路由匹配时包容性质的,这里用到一个新组件Switch
,不用像之前给每个组件添加exact
属性那么麻烦,路由只会在Switch
包裹范围内选1个,修改完之后会发现只有在路由都不匹配的情况下才会显示404页面
// src/components/RouteSample.js
// 新增404页面组件 展示访问的具体路径不存在
function NoMatch ({location}) {
return (
<div>404, {location.pathname}不存在</div>
)
}
// 新增404路由
{/* 404页面没有path 当有path的路由都不匹配时那必然匹配404*/}
<Route component={NoMatch}></Route>
// src/components/RouteSample.js
import React from 'react';
// 引入Switch
import {BrowserRouter, Link, Route, Switch} from 'react-router-dom'
{/* 路由配置:路由就是组件 */}
{/* 用Switch包裹路由配置,避免展示包容式路由 */}
<Switch>
<Route exact path="/" component={Home}></Route>
<Route path="/about" component={About}></Route>
<Route path="/detail/:lesson" component={Detail}></Route>
{/* 404页面没有path 当有path的路由都不匹配时那必然匹配404*/}
<Route component={NoMatch}></Route>
</Switch>
- 接着再研究下路由嵌套,修改一下
About
组件,用作展示当前用户信息,分为“个人信息”和“订单信息”,按原先的路由结构来看,现在“个人信息”、“订单信息”就是嵌套在About
下的,从页面上可以看出这里有一个问题,就是点击“关于”展示About
时,没有给嵌套路由默认展示项,必须要手动再次点击“个人信息”或者“订单信息”才能正常显示(默认情况如下图),这时候可以给一个路由重定向作为默认展示的路由,结合vue
还是很容易理解的
// src/components/RouteSample.js
// 当前用户信息
function About () {
return (
<div>
<h5>About</h5>
<div>
<Link to="/about/me">个人信息</Link>
<Link to="/about/order">订单信息</Link>
</div>
<Switch>
<Route path="/about/me" component={() => (<div>me</div>)}></Route>
<Route path="/about/order" component={() => (<div>order</div>)}></Route>
</Switch>
</div>
)
}
// src/components/RouteSample.js
import {BrowserRouter, Link, Route, Switch, Redirect} from 'react-router-dom'
<Switch>
<Route path="/about/me" component={() => (<div>me</div>)}></Route>
<Route path="/about/order" component={() => (<div>order</div>)}></Route>
{/* 路由默认项 */}
<Redirect to="/about/me"></Redirect>
</Switch>
- 路由中有一项非常重要的功能是路由守卫,可以用于很多场景,最常见的就是以是否登录判断显示当前访问页面还是跳转登录页,
react
中的路由守卫跟vue-router
的有很大不同,react
是用一个高阶组件扩展<Route>
// src/components/RouteSample.js
// 保留路由组件所需的参数component、以及其他参数例如pth等,同时传一个登录状态isLogin
function PrivateRoute ({component: Comp, isLogin, ...rest}) {
return (
// render函数 根据条件动态渲染组件
// 未登录则重定向至登录页
// 登录路由也配置了一个redirect,这样在设计登录组件的时候当登录成功即重定向跳转至登录前访问的页面
<Route
{...rest}
render = {
props => isLogin ? (<Comp></Comp>) : (<Redirect
to={{pathname: "/login", state: {redirect: props.location.pathname}}}
></Redirect>)
}
></Route>
)
}
- 然后补充登录组件和路由配置,同时修改
About
组件的使用
// src/components/RouteSample.js
// 从路由参数拿到重定向地址,从redux获取登录状态和登录方法
function Login ({location, isLogin, login}) {
const redirect = location.state.redirect || '/'
if(isLogin) {
return <Redirect to={redirect}></Redirect>
}
return (
<div>
<p>用户登录</p>
<hr/>
<button onClick={login}>login</button>
</div>
)
}
{/* 路由配置:路由就是组件 */}
<Switch>
<Route exact path="/" component={Home}></Route>
{/* 使用路由守卫 */}
<PrivateRoute path="/about" component={About}></PrivateRoute>
<Route path="/detail/:lesson" component={Detail}></Route>
<Route path="/login" component={Login}></Route>
{/* 404页面没有path 当有path的路由都不匹配时那必然匹配404*/}
<Route component={NoMatch}></Route>
</Switch>
- 上一步已经实现了路由守卫的逻辑,下一步则补充
redux
中关于登录状态以及登录方法异步请求的相关内容,新建redux
中的用户模块,路径是src/store/user.redux.js
,用户模块添加完成后要在src/store/index.js
中完成注册才能使用
// src/store/user.redux.js
// 初始化状态,没在登录中 并且 未登录
const initial = {
isLogin: false,
loading: false
}
export const user = (state = initial, action) => {
switch(action.type) {
// 登录中
case "requestLogin":
return {
isLogin: false,
loading: true
};
// 已登录
case "login":
return {
isLogin: true,
loading: false
};
default:
return state;
}
}
// 请求登录异步调用时,先修改状态为登录中
export const login = () => dispatch => {
dispatch({type: 'requestLogin'});
setTimeout(() => {
dispatch({type: 'login'})
}, 1500);
}
// src/store/index.js
// applyMiddleware 应用中间件方法
import {createStore, applyMiddleware, combineReducers} from 'redux';
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import {user} from './user.redux'
const store = createStore(
combineReducers({user}),
applyMiddleware(logger, thunk)
);
export default store;
- 完成以上模块添加之后再回到路由守卫的部分做对应修改,路由守卫和登录组件都需要用到登录状态值,这里我们使用
react-redux
// src/components/RouteSample.js
// 添加引用
import {connect} from 'react-redux';
import {login} from '../store/user.redux'
// 路由守卫
const PrivateRoute = connect(
state => ({isLogin: state.user.isLogin})
)(
({component: Comp, isLogin, ...rest}) => {
return (
// render 根据条件动态渲染组件
<Route
{...rest}
render={
props => isLogin ?
(<Comp></Comp>) :
(<Redirect
to={{pathname: "/login", state: {redirect: props.location.pathname}}}
></Redirect>)
}
>
</Route>
)
}
)
// 登录组件
const Login = connect(
// 映射状态与dispatch方法
state => ({
isLogin: state.user.isLogin,
loading: state.user.loading
}),
{login}
)(
({location, isLogin, login, loading}) => {
const redirect = location.state.redirect || '/'
if(isLogin) {
return <Redirect to={redirect}></Redirect>
}
return (
<div>
<p>用户登录</p>
<hr/>
{/* loading优化 */}
<button onClick={login} disabled={loading}>
{loading ? 'login...' : 'login'}
</button>
</div>
)
}
)
- 最后需要注意的是使用路由组件时使用方式、引入
store
的方式
// App.js
import store from './store/index'
import {Provider} from 'react-redux'
import RouteSample from './components/RouteSample'
{/* router */}
<Provider store={store}>
<RouteSample></RouteSample>
</Provider>
-
下图展示了整个登录流程发生的记录