React Router--实战篇

本篇文章参考极客时间 React Router 教程和以下文章

前言

  上一节总结了一下 React Router 的基本架构,对于路由这个东西,大家应该已经有了一个基本的概念,本篇文章,我们就实际开发当中路由的使用情况,做一下具体分析。上一节没看的同学要补补课哈。

一、通过 URL 传参

  通过 url 传参是实际应用当中非常常见的一种情况,因为我们的 url 通常不是确定不变的,而是通过实际场景定位到某一个具体资源。

  举个🌰,大家现在可以看看本篇文章的地址栏,只要是我发布的文章,地址栏前面一定是 https://blog.csdn.net/EcbJS/article/details/ 。这一串地址定位到我的博客,最后面跟一串数字,是我的博客代码。

  当然也可以通过查询字符串的方式所以日常开发只要使用到了 React Router ,那就一定会用到 url 传参,这种模式对搜索引擎。

1.1如何通过 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;

1.2如何获取参数

   通过 this.props.match.params 进行获取。配合上方代码查看。

1.3如何运用

   什么情况下我们需要使用 url 参数,比如说有一个考勤信息的网页,网页中可以选择不同月份的考勤信息,当我们选择不同月份的时候,url 会展示成相应月份。
在这里插入图片描述
   我们之前不用 React Router 的时候,只要给组件设置一个内部 state 控制显示不同月份数据也可以达到目的。但是如果我们的页面是其他页面链接过来的,比如有一个人员信息的页面跳转到考勤页面,可能需要定位到某一具体的月份,这个时候如果月份信息只是内部 state 的话,是需要间接的把这个月份传递到页面的。

   比如说通过查询字符串把这个月份传递到页面,页面再传递到内部 state 这就会带来部分麻烦。

   我们在考虑语义化相关的参数时,可以放到 url 当中,这样页面与地址保持一致,页面内部就不用去维护当前月份信息,而是直接从 url 中获取。

   这样做可能会有一个问题,当不需要维护内部 state 的时候,会不会导致信息改变,页面却不更新,因为我们指导,react 的渲染机制就是检测到 state 变化后,render 页面。

   这一点大家放心,React Router 已经帮我们考虑到了,只要 url 变化,同样会 render 组件。

二、嵌套路由

   接下来介绍一个稍微高级点的东西,嵌套路由。这个概念是前端路由才有的,顾名思义,在一个一级菜单下,有一些二级菜单,二级菜单下可能还有三级菜单,一层一层嵌套。
在这里插入图片描述
  说白了,React Router 是官方给我们提供的一个组件,是组件那就能嵌套,之前我们使用的路由是在 Routecomponent 属性当中渲染组件。现在我们需要在这个属性中渲染子路由。

  具体代码如下。

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 文件,它会去加载每个子路由。

3.1 各模块单独配置路由

  建议使用 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}
    ]
};

3.2 顶层路由

  下面是一个顶层路由,它会去引入各个模块的单独路由,汇总,然后通过入口文件 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 函数)。

  

  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值