本篇文章参考极客时间 React Router 教程和以下文章
上一节总结了一下 React Router 的基本架构,对于路由这个东西,大家应该已经有了一个基本的概念,本篇文章,我们就实际开发当中路由的使用情况,做一下具体分析。上一节没看的同学要补补课哈。
通过 url 传参是实际应用当中非常常见的一种情况,因为我们的 url 通常不是确定不变的,而是通过实际场景定位到某一个具体资源。
举个,大家现在可以看看本篇文章的地址栏,只要是我发布的文章,地址栏前面一定是 https://blog.csdn.net/EcbJS/article/details/ 。这一串地址定位到我的博客,最后面跟一串数字,是我的博客代码。
当然也可以通过查询字符串的方式所以日常开发只要使用到了 React Router ,那就一定会用到 url 传参,这种模式对搜索引擎。
通过 Route 的 path 属性设置要传递的参数。在 Route 中需要设置参数的地方,设置成 :属性名
的格式,如下图。
下面是一个完整实例,帮助大家理解。
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom';
//这里提供了一个match的参数,里面的params属性可以让我们访问到在 patch 中定义的参数
const Topic = ({ match }) => {
<h1>Tpoic {match.params.id}</h1>
};
class RouterParams extends Component {
render() {
return (
<Router>
<div id="menu">
<ul>
<li><Link to="/topic/1">Topic 1</Link></li>
<li><Link to="/topic/2">Topic 2</Link></li>
<li><Link to="/topic/3">Topic 3</Link></li>
</ul>
<div id="page_container">
//通过:id的方式可定义参数
<Route patch="/topic/:id" component={Topic} />
</div>
</div>
</Router>
);
}
}
export default RouterParams;
通过 this.props.match.params 进行获取。配合上方代码查看。
什么情况下我们需要使用 url 参数,比如说有一个考勤信息的网页,网页中可以选择不同月份的考勤信息,当我们选择不同月份的时候,url 会展示成相应月份。
我们之前不用 React Router 的时候,只要给组件设置一个内部 state 控制显示不同月份数据也可以达到目的。但是如果我们的页面是其他页面链接过来的,比如有一个人员信息的页面跳转到考勤页面,可能需要定位到某一具体的月份,这个时候如果月份信息只是内部 state 的话,是需要间接的把这个月份传递到页面的。
比如说通过查询字符串把这个月份传递到页面,页面再传递到内部 state 这就会带来部分麻烦。
我们在考虑语义化相关的参数时,可以放到 url 当中,这样页面与地址保持一致,页面内部就不用去维护当前月份信息,而是直接从 url 中获取。
这样做可能会有一个问题,当不需要维护内部 state 的时候,会不会导致信息改变,页面却不更新,因为我们指导,react 的渲染机制就是检测到 state 变化后,render 页面。
这一点大家放心,React Router 已经帮我们考虑到了,只要 url 变化,同样会 render 组件。
接下来介绍一个稍微高级点的东西,嵌套路由。这个概念是前端路由才有的,顾名思义,在一个一级菜单下,有一些二级菜单,二级菜单下可能还有三级菜单,一层一层嵌套。
说白了,React Router 是官方给我们提供的一个组件,是组件那就能嵌套,之前我们使用的路由是在 Route 的 component 属性当中渲染组件。现在我们需要在这个属性中渲染子路由。
具体代码如下。
import React, { Component } from "react";
import { BrowerRouter as Router, Route, Link } from "react-router-dom";
const Category = ({ match }) => {
<h1>Sub Category {match.params.subId}</h1>;
};
const SubCategory = ({ match }) => {
<div>
<h1>Category {match.params.id}</h1>
<ul id="menu2">
<li><Link to={`/category/${match.params.id}/sub/1`}>Sub Category 1</Link></li>
<li><Link to={`/category/${match.params.id}/sub/2`}>Sub Category 2</Link></li>
<li><Link to={`/category/${match.params.id}/sub/3`}>Sub Category 3</Link></li>
</ul>
<div>
<Route
//需要注意,子路由的匹配规则要对应父的规则
path="/category/:id/sub/:subId"
component={Category}
/>
</div>
</div>;
};
class NestedRoute extends Component {
render() {
renturn (
<Router>
<div>
<ul id="menu">
<li><Link to={`/category/1`}>Category 1</Link></li>
<li><Link to={`/category/2`}>Category 2</Link></li>
<li><Link to={`/category/3`}>Category 3</Link></li>
</ul>
<div>
<Route path="/category/:id" component={SubCategory} />
</div>
</div>
</Router>
);
}
}
export default NestedRoute;
上面写法没有任何问题,代码可以正常运行,但是运用到实际开发当中就不太方便维护。路由一层一层嵌套,当要定位某一子路由时,要从它的祖宗辈一层一层往下找,维护成本很高。
我们希望像后端路由一样,专门写一个文件只保存路由配置,用到的时候调用一下就好。
如上图所示,我们需要有一个根文件,routeConfig.js 文件,它会去加载每个子路由。
建议使用 JSON 定义路由,因为 React Router 是基于声明式的方式定义的,这种方式虽然直观,但是如果把路由都定义到页面布局里面,每个组件的功能就会变得复杂,反而不容易理解。
用 JSON 的方式把路由抽象出来,纯粹就是路由配置部分,而这些配置用在什么布局页面,是有布局页面自己决定,这样职能单一化,开发模块化的思想,才符合现在大型开发的发展方向。下面就是一个路由的实例配置文件。
import { WelcomePage, CounterPage, RedditListPage, Layout } from './';
export default {
path: 'examples',
name: 'Examples',
component: Layout,
childRoutes: [
{path: '', name: 'Welcome page', component: WelcomePage},
{path: 'counter', name: 'Counter page', component: CounterPage},
{path: 'reddit', name: 'Reddit list page', component: RedditListPage}
]
};
下面是一个顶层路由,它会去引入各个模块的单独路由,汇总,然后通过入口文件 App.js 进行加载。
import homeRoute from "../homeRoute/route";
import componentRoute from "../componentRoute/route";
import examplesRoute from "../examples/route";
import App from "../App";
import pageNotFound from "../pageNotFound";
const childRoutes = [
homeRoute,
componentRoute,
examplesRoute
];
const routes = [{
path: '/',
component: App,
childRoutes: [
...childRoutes,
{ path: '*', name: 'Page not found', component: pageNotFound }
]
}]
export default routes
React Router 4 里面需要我们自己手动实现路由守卫,守卫的意思是在进入路由前进行一些校验,比如校验是否登陆,如果未登录,就跳转到重定向页面。我们需要给路由表设置一个用于校验的属性
import { WelcomePage, CounterPage, RedditListPage, Layout, ErrorPage } from './';
export default {
path: 'examples',
name: 'Examples',
component: Layout,
childRoutes: [
{
path: '',
name: 'Welcome page',
component: WelcomePage,
auth: true
},
{
path: 'counter',
name: 'Counter page',
component: CounterPage,
auth: true
},
{
path: 'reddit',
name: 'Reddit list page',
component: RedditListPage
},
{
path: '/404',
name: 'No page',
component: ErrorPage
}
]
};
上面的 auth 属性用来判断是否需要权限校验
然后,定义 Router 组件,由于每个路由都需要检验是否登录,他们之间的这些共性的逻辑,可以抽象成高阶组件,不用对每个路由单独写一次。
import * as React from 'react';
import { HashRouter, Switch } from 'react-router-dom';
import { FrontendAuth } from '../components/frontend-auth/frontend-auth.component'
import { routerConfig } from './router.config'
export class Router extends React.Component{
render(){
return(
<HashRouter>
<Switch>
<FrontendAuth config={routerConfig} />
</Switch>
</HashRouter>
);
}
}
上面 FrontendAuth 是一个高阶组件,用来渲染各个路由组件,引入 routerConfig 作为 props 传递给高阶组件,供渲染子组件使用。 FrontendAuth 实现如下。
import * as React from 'react';
import { Route,Redirect } from 'react-router-dom';
export class FrontendAuth extends React.Component{
render(){
const { location,config } = this.props;
const { pathname } = location;
const isLogin = localStorage.getItem('__config_center_token')
// 如果该路由不用进行权限校验,登录状态下登陆页除外
// 因为登陆后,无法跳转到登陆页
// 这部分代码,是为了在非登陆状态下,访问不需要权限校验的路由
const targetRouterConfig = config.find((v) => v.path === pathname);
if(targetRouterConfig && !targetRouterConfig.auth && !isLogin){
const { component } = targetRouterConfig;
return <Route exact path={pathname} component={component} />
}
if(isLogin){
// 如果是登陆状态,想要跳转到登陆,重定向到主页
if(pathname === '/login'){
return <Redirect to='/' />
}else{
// 如果路由合法,就跳转到相应的路由
if(targetRouterConfig){
return <Route path={pathname} component={targetRouterConfig.component} />
}else{
// 如果路由不合法,重定向到 404 页面
return <Redirect to='/404' />
}
}
}else{
// 非登陆状态下,当路由合法时且需要权限校验时,跳转到登陆页面,要求登陆
if(targetRouterConfig && targetRouterConfig.auth){
return <Redirect to='/login' />
}else{
// 非登陆状态下,路由不合法时,重定向至 404
return <Redirect to='/404' />
}
}
}
}
由于 FrontendAuth 组件放在了 Switch 组件内部, React Router 还自动为 FrontendAuth 注入了 location 属性,当地址栏的路由发生变化时,就会触发 location 属性对象上的 pathname 属性发生变化,从而触发 FrontendAuth 的更新(调用 render 函数)。