第一节 react-router安装&介绍
1、安装
npm install react-router-dom --save
2、学习
3、特点
3.1、秉承react一切皆组件,路由也是组件。
路由器 - Router 、
链接 - Link 、
路由 - Route 、
独占 - Switch 、
重定向 - Redirect
3.2、分布式的配置,分布在你页面的每个角落。
3.3、包含式配置,可匹配多个路由。
3.4、渲染优先级:
children>component>render 这三种⽅式互斥,你只能⽤用一种。
3.4.1、children:func
有时候,不管location是否匹配,你都需要渲染一些内容,这时候你可以⽤用children。
除了不管location是否匹配都会被渲染之外,其它⼯作⽅法与render完全⼀样。
****
3.4.2、render:func
但是当你用render的时候,你调用的只是个函数。 只在当location匹配的时候渲染。
3.4.3、component: component****
只在当location匹配的时渲染。
3.5、404页面
设定⼀个没有path 的路由在路由列表最后面,表示⼀定匹配
{/* 添加Switch表示仅匹配⼀一个*/}
<Switch>
{/* 根路路由要添加exact,实现精确匹配 */}
<Route
exact
path="/"
component={HomePage}
/>
<Route path="/user" component={UserPage} />
<Route component={EmptyPage} />
</Switch>
第二节 react-router基本使用
1、使用步骤
step1:引入顶层路由组件包裹根组件。
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom';
import RtouerSamples from './react-router/RouterSamples';
ReactDOM.render(
<div>
<h4>使用react-router</h4>
<BrowserRouter>
<RtouerSamples></RtouerSamples>
</BrowserRouter>
</div>, document.getElementById('root')
)
step2:引入Link组件编写路由导航。
<Link to="/">首页</Link>
to: 指定要到的路径
step3:引入Route组件编写导航配置。
<Route exact path="/" component={Home}></Route>
// path: 配置路径
// component: 配置对应显示的组件,也可以使用render函数,优先级低于component
// exact 精准完全匹配,避免包含匹配,到多个组件
step4:引入Switch组件,默认只匹配上一个Route即可返回。
2、完整代码
RouterSamples.js
import React from 'react'
import { Switch, Link, Route } from 'react-router-dom';
function Home() {
return <div>首页</div>
}
function Classes() {
return <div>课程列表</div>
}
function Mine() {
return <div>个人中心</div>
}
function NoFound() {
return <div>404</div>
}
export default function routerSamples() {
return (
<div>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="classes">课程</Link></li>
<li><Link to="mine">我的</Link></li>
<li><Link to="sssssssssss">其他找不到的页面</Link></li>
</ul>
<div>
<Switch>
<Route exact path="/" component={Home}></Route>
<Route path="/classes" component={Classes}></Route>
<Route path="/mine" component={Mine}></Route>
<Route component={NoFound}></Route>
</Switch>
</div>
</div>
)
}
第三节 react-router路由传参取参
1、方式一:声明式导航路由配置时-添加路由参数
配置:
<Route path="/detail/:goodsId" component={GoodsDetail}></Route>
传递:
<Link to="/detail/g100986">查看商品详情</Link>
获取:
解构路由器对象里的match出来(match获取参数信息)。
{match.params.goodId}。
2、方式二:编程式导航路由传参和获取
解构路由器对象获取到导航对象 history (用作命令行导航)
通过事件执行history.push({pathname:'/', state:{backId:'g100986'}}) 传递的参数装载在state里面
从路由信息解构出location(当前的url信息)对象location.state进行获取
3、完整代码
RouterSamples.js
...
function Home({ location }) {
return <div>
<p>首页</p>
<p>{location.state && `返回的课程是:${location.state.backId}`}</p>
</div>
}
function Classes() {
return <div>
<ul className="course">
<li>
<Link to="/detail/vue">vue.js</Link>
</li>
<li>
<Link to="/detail/react">react.js</Link>
</li>
</ul>
</div>
}
function Detail({ match, history }) {
return <div>
<p>{match.params.goodsId}详细课程</p>
<p onClick={() => { history.push({ pathname: '/', state: { backId: match.params.goodsId } }) }}>返回</p>
</div>
}
export default function routerSamples() {
return (
<Switch>
<Route exact path="/" component={Home}></Route>
<Route path="/classes" component={Classes}></Route>
<Route path="/detail/:goodsId" component={Detail}></Route>
</Switch>
)
}
第四节 react-router路由嵌套&重定向
1、组件内再次使用做路由结构。
2、使用做路由默认选中。
// 一级路由
<Route path="/mine" component={Mine}></Route>
// 一级路由对应的组件
function Mine() {
return <div>
<ul className="course">
<li>
<Link to="/mine/order">个人订单</Link>
</li>
<li>
<Link to="/mine/userInfo">个人中心</Link>
</li>
</ul>
<Route path="/mine/order" component={()=><div>个人订单的页面</div>}></Route>
<Route path="/mine/userInfo" component={()=><div>个人中心的页面</div>}></Route>
<Redirect to="/mine/userInfo"></Redirect>
</div>
}
第五节 react-router路由守卫
1、路由守卫:路由拦截(保证串行),当我们有些页面需要登录后才能有权限访问的时候,就要启用路由守卫。
区分:Svip 和 vip 和 普通用户 要用路由守卫。
2、路由守卫:类似于Axios 的 前面req请求拦截和res的返回拦截。但是有区别,关系到组件的生命周期触发。
3、路由守卫:React中其实也是一个组件, 最终返回的还是Route组件
一、包装组件
实现效果:点击个人中心判断是否登录,没有登录、去登录;登录后,从哪里来回哪里去。
...
{/* 路由配置 */}
<Switch>
<Route exact path="/login" component={Login}></Route>
{/* <Route path="/mine" component={Mine}></Route> */}
<RouteGuard path="/mine" component={Mine}></RouteGuard>
</Switch>
// 接口状态
const auth = {
isLogin: false,
login(callback) {
this.isLogin = true;
setTimeout(callback, 1000);
}
}
// 编写路由守卫进行权限控制
class RouteGuard extends Component {
render() {
const { component: Component, ...otherProps } = this.props;
return (
<Route {...otherProps} render={(props) => (
auth.isLogin ? <Component {...props}></Component> : (<Redirect to={
{ pathname: '/login', state: { from: props.location.pathname } }
}></Redirect>)
)}></Route>
)
}
}
// 登录组件
class Login extends Component {
login = () => {
// 方法1、可以用setDate()触发render函数,在render函数中<Redirect>根据this.state.isLogin做跳转。
// 方法2、简便写法使用history.push。
// 回调地址
const pathname = (this.props.location.state && this.props.location.state.from) || '/'
auth.login(() => {
this.props.history.push({pathname})
})
}
render() {
return <div>
<p>请先登录</p>
<button onClick={this.login}>登录</button>
</div>
}
}
整体代码
import React, { Component } from 'react'
import { Switch, Link, Route, Redirect } from 'react-router-dom';
function Home({ location }) {
return <div>
<p>首页</p>
<p>{location.state && `返回的课程是:${location.state.backId}`}</p>
</div>
}
function Classes() {
return <div>
<h5>课程列表</h5>
<ul className="course">
<li>
<Link to="/detail/vue">vue.js</Link>
</li>
<li>
<Link to="/detail/react">react.js</Link>
</li>
<li>
<Link to="/detail/angular">angular.js</Link>
</li>
<li>
<Link to="/detail/node">node.js</Link>
</li>
</ul>
</div>
}
function Mine() {
return <div>
<h5>个人中心</h5>
<ul className="course">
<li>
<Link to="/mine/order">个人订单</Link>
</li>
<li>
<Link to="/mine/userInfo">个人中心</Link>
</li>
</ul>
{/* 子路由配置 */}
<Route path="/mine/order" component={() => <div>个人订单的页面</div>}></Route>
<Route path="/mine/userInfo" component={() => <div>个人中心的页面</div>}></Route>
<Redirect to="/mine/userInfo"></Redirect>
</div>
}
function Detail({ match, history }) {
// 没有解构
// console.log(props);
return <div>
<p>{match.params.goodsId}详细课程</p>
<p onClick={() => { history.push({ pathname: '/', state: { backId: match.params.goodsId } }) }}>返回</p>
</div>
}
function NoFound() {
return <div>404</div>
}
// 接口状态
const auth = {
isLogin: false,
login(callback) {
this.isLogin = true;
setTimeout(callback, 1000);
}
}
// 编写路由守卫进行权限控制
class RouteGuard extends Component {
render() {
const { component: Component, ...otherProps } = this.props;
return (
<Route {...otherProps} render={(props) => (
auth.isLogin ? <Component {...props}></Component> : (<Redirect to={
{ pathname: '/login', state: { from: props.location.pathname } }
}></Redirect>)
)}></Route>
)
}
}
// 登录组件
class Login extends Component {
login = () => {
// 方法1、可以用setDate()触发render函数,在render函数中<Redirect>根据this.state.isLogin做跳转。
// 方法2、简便写法使用history.push。
// 回调地址
const pathname = (this.props.location.state && this.props.location.state.from) || '/'
auth.login(() => {
this.props.history.push({pathname})
})
}
render() {
return <div>
<p>请先登录</p>
<button onClick={this.login}>登录</button>
</div>
}
}
export default function routerSamples() {
return (
<div>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="classes">课程</Link></li>
<li><Link to="mine">我的</Link></li>
<li><Link to="sssssssssss">其他找不到的页面</Link></li>
</ul>
<div>
{/* 路由配置 */}
<Switch>
<Route exact path="/" component={Home}></Route>
<Route exact path="/login" component={Login}></Route>
<Route path="/classes" component={Classes}></Route>
{/* <Route path="/mine" component={Mine}></Route> */}
<RouteGuard path="/mine" component={Mine}></RouteGuard>
<Route path="/detail/:goodsId" component={Detail}></Route>
<Route component={NoFound}></Route>
</Switch>
</div>
</div>
)
}
二、嵌套路由
step1、 入口文件
index.js
ReactDOM.render(
<Provider {...stores}>
<Page />
</Provider>,
document.getElementById('root')
);
step2、通过Page.tsx,做路由区分:/app:业务,/login:登录页面,/login-guide:过期, /404
...
export default function Page() {
const urlParms = getUrlAllParams();
useEffect(() => {
let key = '/app/shop/index';
async function loginApp(code: any, plat: any) {
history.push(key);
}
// 根路径去登陆
if (history.location.pathname === '/') {
// 优先使用code
if (code && state) {
loginApp(code, state);
return;
}
// 其他有无token;
let userInfo: any = {};
if (localStorage.userInfo) {
userInfo = JSON.parse(localStorage.userInfo);
}
if (userInfo.dsfToken) {
store.userStore.selectMenu(key);
history.push(key);
} else {
history.push('/login-guide');
}
}
// 非根路径,自动走路由,ajax调用,统一处理接口失败,后自动跳转到登录页。
},[]);
return (
<Router>
<ConfigProvider locale={zhCN}>
<Switch>
<Route exact path="/" />
<Route path="/app" component={App} />
<Route path="/login-guide" component={LoginGuide} />
<Route component={NotFound} />
</Switch>
</ConfigProvider>
</Router>
)
};
step3、业务组件全部是/app子路由
App.tsx
....
render() {
const { collapsed } = this.state;
const { isTopMenu, completeProductGuide } = this.props.userStore;
return <div className="app">
<Layout className="app-section">
{isTopMenu ? '' : <SiderCustom collapsed={collapsed} {...this.props}></SiderCustom>}
<Layout className="site-layout">
<Header className={isTopMenu ? 'top-layout-background' : 'site-layout-background'}>
{isTopMenu ? '' : React.createElement(collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
className: 'trigger',
onClick: this.toggle,
})}
<UserInfo></UserInfo>
</Header>
<PersonGuide></PersonGuide>
<Content style={{ position: 'relative' }}>
<Routes />
</Content>
</Layout>
{completeProductGuide ? '' : <ProductGuide></ProductGuide>}
</Layout>
</div >
}
routes/index.tsx
/*
* @Author: lxq
* @Date: 2020-06-11 21:32:55
*/
import React, { Component } from 'react';
import { Route, HashRouter as Router, Redirect, Switch } from 'react-router-dom';
import queryString from 'query-string';
import DocumentTitle from 'react-document-title';
import AllComponents from '../components';
import routesConfig, { IFMenuBase, IFMenu } from './config';
type BaseRouterProps = {
auth?: any
};
type BaseRouterState = {};
class BaseRouter extends Component<BaseRouterProps, BaseRouterState> {
// requireLogin = (component: React.ReactElement, permit = false) => {
// if (permit) {
// return <Redirect to={'/login-guide'} />;
// }
// return component;
// };
mergeQueryToProps = (props: any) => {
const queryReg = /?\S*/g;
const matchQuery = (reg: RegExp) => {
const queryParams = window.location.hash.match(reg);
return queryParams ? queryParams[0] : '{}';
};
const removeQueryInRouter = (props: any, reg: RegExp) => {
const { params } = props.match;
Object.keys(params).forEach((key) => {
params[key] = params[key] && params[key].replace(reg, '');
});
props.match.params = { ...params };
};
props = removeQueryInRouter(props, queryReg);
const merge = {
...props,
query: queryString.parse(matchQuery(queryReg)),
};
return merge;
};
route = (r: IFMenuBase) => {
const Component = r.component && AllComponents[r.component];
return (
<Route
key={r.key}
exact
path={r.key}
render={(props: any) => {
// 重新包装组件
const wrappedComponent = (
<DocumentTitle title={r.title}>
<Component {...this.mergeQueryToProps(props)} />
</DocumentTitle>
);
// return this.requireLogin(wrappedComponent, r.requireAuth);
return wrappedComponent;
}}
/>
);
};
iterteMenu = (r: IFMenu) => {
const subRoute = (r: IFMenu): any => {
return (r.subs && r.subs.map((subR: IFMenu) => (subR.subs ? subRoute(subR) : this.route(subR))));
};
return r.component ? this.route(r) : subRoute(r);
};
createRoute = (key: string) => {
return routesConfig[key].map(this.iterteMenu);
};
register = () => {
return Object.keys(routesConfig).map((key) => this.createRoute(key))
}
render() {
const routeList = [
<Route key="/app" exact path="/app" render={() => <Redirect to="/app/shop/index" push />} />,
this.register(),
<Route key="/404" render={() => <Redirect to="/404" />} />
]
return <div className="app-content">
<Router>
<Switch>{routelist}</Switch>
</Router>
</div>
}
}
export default BaseRouter;
routes/config.ts
/*
* @Author: lxq
* @Date: 2021-01-04 17:14:31
* @LastEditors: lxq
* @LastEditTime: 2021-05-19 14:40:05
*/
/**
* Created by lxq on 2020/4/23.
*/
export interface IFMenuBase {
key: string;
title: string;
icon?: string;
component?: string;
query?: string;
requireAuth?: boolean;
route?: string;
type?: string // 'outLink' 外部链接
}
export interface IFMenu extends IFMenuBase {
subs?: IFMenu[];
}
const routesConfig: {
menus: IFMenu[];
others: IFMenu[] | [];
[index: string]: any;
} = {
// 菜单路由
menus: [
// 一级菜单
{ key: '/app/shop/index', title: '店铺利润', icon: 'dianpulirun', component: 'ShopIndex' },
{ key: '/app/shop/ProfitAnalysis', title: '利润分析', icon: 'lirunfenxi', component: 'ProfitAnalysis' },
{ key: '/app/trans/index', title: '店铺交易', icon: 'dianpujiaoyi', component: 'TransIndex' },
{ key: '/app/cost/index', title: '成本费用', icon: 'chengbenfeiyong', component: 'CostIndex' },
// 二级菜单
{
key: '/app/demo', title: 'demo案例', icon: 'shezhi',
subs: [
{ key: '/app/demo/router-demo', title: '1、动态路由示例', icon: 'shezhi',component: 'DemoRouter' },
{ key: '/app/demo/store-demo', title: '2、mbox数据流示例', icon: 'shezhi',component: 'DemoStore' },
{ key: '/app/demo/axios-demo', title: '3、axios示例', icon: 'shezhi',component: 'DemoAxios' },
{ key: '/app/demo/lodash-demo', title: '4、lodash工具函数示例', icon: 'shezhi',component: 'DemoLodash' },
{ key: '/app/demo/iconfont-demo', title: '5、主题切换、图标字体', icon: 'shezhi',component: 'DemoIconfont' },
{ key: '/app/demo/img-demo', title: '6、样式、图片引入示例', icon: 'shezhi',component: 'DemoImg' },
{ key: '/app/demo/recharts-demo', title: '7、recharts示例', icon: 'shezhi',component: 'DemoRecharts' },
{ key: '/app/demo/ants-demo', title: '8、ants示例', icon: 'shezhi',component: 'DemoAnts' },
{ key: '/app/demo/ants-animate', title: '9、animate示例', icon: 'shezhi',component: 'DemoAnimate' },
],
},
{ key: '/app/shop/ShopSetting', title: '设置', icon: 'shezhi', component: 'ShopSetting' },
{ key: 'https://doc-qdd.static.chanjet.com', title: '帮助中心', type: 'outLink' },
{ key: 'https://app.1688.com', title: '应用市场', type: 'outLink' },
],
// 非菜单相关路由
others: [
// 动态路由示例
{ key: '/app/demo/icons/:id', title: '路由返回', icon: '', component: 'DemoIcon' },
],
};
export default routesConfig;
step4、业务组件中统一处理接口返回的登录过期、跳转到/login-guide
//request拦截器
instance.interceptors.request.use()
//respone拦截器
instance.interceptors.response.use()