专栏首页WebJ2EE【React】:路由(Routing)

【React】:路由(Routing)

目录
1. 知识地图
2. 前端路由
3. 路由库——React Router
  3.1. 库结构
  3.2. 示例:基础
  3.3. 示例:传参数
  3.4. 示例:嵌套路由
4. 路由实践   

在现代前端开发中,路由是非常重要的一环

1. 知识地图

2. 前端路由

前端路由起源于 SPA 单页应用架构(现代前端开发中最流行的页面模型):

  • 单页面应用指的是应用实际只有一个主页面,页面间的切换实际是 DOM 结构的动态替换。(优点:无刷新,用户体验好)
  • 对基于 React 的 SPA 应用,所有页面由不同的组件构成,页面的切换其实就是不同组件的切换。
  • 然后,我们把前端页面间(即组件间)的切换与浏览器地址栏中 URL 的变换关联起来(例如:根据浏览器地址栏的变化切换页面),这就是前端路由。

人话就是

浏览器地址变化=>视觉上的页面切换=>实际上的组件切换

前端路由就是用来完成这个任务的技术

3. 路由库——React Router

3.1. 库结构

3.2. 示例:基础

描述:

  • 将应用的路由拆分为:/home、/login、/error/404

效果图:

关键代码:

import React from "react";
import {
    BrowserRouter as Router,
    Route,
    Link
} from "react-router-dom";

import Login from "./pages/Login"
import Home from "./pages/Home"
import Error404 from "./pages/Error404";

export default function BasicExample() {
    return (
        <Router>
            <div>
                <ul>
                    <li>
                        <Link to="/home">Home</Link>
                    </li>
                    <li>
                        <Link to="/login">Login</Link>
                    </li>
                    <li>
                        <Link to="/error/404">Error404</Link>
                    </li>
                </ul>

                <hr/>

                <div>
                    <Route path="/home">
                        <Home/>
                    </Route>
                    <Route path="/login">
                        <Login/>
                    </Route>
                    <Route path="/error/404">
                        <Error404/>
                    </Route>
                </div>
            </div>
        </Router>
    );
}

3.3. 示例:传参数

描述:

  • 通过 /person/:empno 将 /person/001、/person/002 等 URL 中的 001、002 接收为 empno 参数

效果图:

关键代码:

App.tsx

import React from "react";
import {
    BrowserRouter as Router,
    Route,
    Link,
} from "react-router-dom";

import Person from "./pages/Person"

export default function ParamsExample() {
    return (
        <Router>
            <div>
                <h2>Person</h2>
                <ul>
                    <li>
                        <Link to="/person/001">Person-001</Link>
                    </li>
                    <li>
                        <Link to="/person/002">Person-002</Link>
                    </li>
                    <li>
                        <Link to="/person/003">Person-003</Link>
                    </li>
                    <li>
                        <Link to="/person/004">Person-004</Link>
                    </li>
                </ul>

                <Route path="/person/:empno" component={Person} />
            </div>
        </Router>
    );
}

Person.tsx

import React from "react";
import {useParams} from "react-router-dom"

export default function Person() {
    // We can use the `useParams` hook here to access
    // the dynamic pieces of the URL.
    let { empno } = useParams();

    return (
        <div>
            <h3>Empno: {empno}</h3>
        </div>
    );
}

3.4. 示例:嵌套路由

描述:

  • 一级路由:/、/login、/error/404
    • 注1:/ 路由负责布局,/home、/person、/orgn 是它的子路由
    • 注2:/ 路由必须放在最后,要留意

关键代码:

App.tsx

import React from "react";
import {
    BrowserRouter as Router,
    Switch,
    Route,
    Link,
    useParams,
    useRouteMatch
} from "react-router-dom";

import Login from "./pages/Login"
import Error404 from "./pages/Error404";
import Layout from "./layout/Layout";

export default function NestingExample() {
    return (
        <Router>
            <div>
                <ul>
                    <li>
                        <Link to="/">Index</Link>
                    </li>
                    <li>
                        <Link to="/login">Login</Link>
                    </li>
                    <li>
                        <Link to="/error/404">Error404</Link>
                    </li>
                </ul>

                <hr />

                <Switch>
                    <Route path="/login">
                        <Login />
                    </Route>
                    <Route path="/error/404">
                        <Error404 />
                    </Route>
                    <Route path="/">
                        <Layout />
                    </Route>
                </Switch>
            </div>
        </Router>
    );
}

Layout.tsx

import React from "react";
import {
    Route,
    Link,
    Redirect,
    Switch,
} from "react-router-dom";

import Home from "../pages/Home";
import PersonMng from "../pages/PersonMng";
import OrgnMng from "../pages/OrgnMng";

export default function Layout() {
    return (
        <div>
            <Redirect to={`/home`} />

            <ul>
                <li>
                    <Link to={`/home`}>首页</Link>
                </li>
                <li>
                    <Link to={`/person`}>个人</Link>
                </li>
                <li>
                    <Link to={`/orgn`}>单位</Link>
                </li>
            </ul>

            <Switch>
                <Route path={`/home`} component={Home}/>
                <Route path={`/person`} component={PersonMng}/>
                <Route path={`/orgn`} component={OrgnMng}/>
            </Switch>
        </div>
    );
}

4. 路由实践

  • 采用静态路表由形式描述路由。
  • 静态路由表结构采用的是 react-router-config 的官方建议结构。
  • 支持嵌套路由。
    • 抽离布局组件。
  • 支持路由重定向。
  • 支持路由级别鉴权。
  • Typescript 开发。
  • HTML5 History API 做底层实现。

效果图:

关键代码:

1. 路由表配置示例:

  • 注意/login、/error/404 与 / 路由的顺序,不能反过来。
  • / 路由控制总体布局,/home、/orgn、/person 则是 / 的子路由。
import {MxRoutes} from "dw-mx-static-config-router"

/**
 * Static import
 */
import BasicLayout from "@/layouts/BasicLayout";
import Home from "@/pages/Home";
import PersonMng from "@/pages/PersonMng";
import OrgnMng from "@/pages/OrgnMng";
import Login from "@/pages/Login";
import Error404 from "@/pages/Error404";
import NeedUserAuthorized from "@/pages/NeedUserAuthorized";

/**
 * Config
 */
const config: {
    routes: MxRoutes
} = {
    routes: [
        {
            path: '/login',
            component: Login,
            exact: true,
        }, {
            path: '/error/404',
            component: Error404,
            exact: true,
        },{
            path: '/',
            component: BasicLayout,
            wrappers: [NeedUserAuthorized],
            routes: [{
                path: '/',
                redirect: '/home',
                exact: true,
            },{
                path: '/home',
                component: Home,
                exact: true,
                name: "首页"
            }, {
                path: '/person',
                component: PersonMng,
                exact: true,
                name: "个人"
            }, {
                path: '/orgn',
                component: OrgnMng,
                exact: true,
                name: "单位"
            }],
        }
    ]
};

export default config;

备注:路由表中的 name 字段,是供布局组件使用的,用于菜单展示。

2. 布局组件示例:

import React from "react";
import {mxBrowserHistory, Link} from "dw-mx-static-config-router"
import {Menu, Dropdown, Avatar} from 'antd';
import {HeartTwoTone, UserOutlined, LockOutlined, LogoutOutlined} from 'dw-mx-icons';
import ProLayout from 'dw-mx-layout-antdpro';
import CurrentUser from "@/auth/CurrentUser";

export default function BasicLayout(props) {
    const {children, route} = props;

    const menu = (
        <Menu
            className={'app-top-user-menu'}
            onClick={() => {
                // this.changeUserStyle(false);
            }}
        >
            <Menu.Item key={'person'} onClick={() => {
            }}>
                <UserOutlined
                    style={{
                        fontSize: 10
                    }}
                />
                <span onClick={() => {
                }}>个人中心</span>
            </Menu.Item>
            <Menu.Item key={'password'} onClick={() => {
            }}>
                <LockOutlined
                    style={{
                        fontSize: 10
                    }}
                />
                <span>修改密码</span>
            </Menu.Item>
            <Menu.Divider/>
            <Menu.Item key={'logout'} onClick={() => {
                CurrentUser.logOut();
                mxBrowserHistory.replace("/login")
            }}>
                <LogoutOutlined
                    style={{
                        fontSize: 10
                    }}
                />
                <span>退出登录</span>
            </Menu.Item>
        </Menu>
    );

    return (
        <ProLayout route={route}
                   title={"账户管理中心"}
                   rightContentRender={() => <div style={{display: 'flex', flexDirection: 'row'}}>
                       <Dropdown
                           overlay={menu}
                       >
                            <span style={{marginRight: 16}}>
                                <Avatar icon={<UserOutlined/>} style={{marginRight: 8, color: "#1890ff"}}/>
                                <span className={'app-top-userInfo'}>
                                    Admin
                                </span>
                            </span>
                       </Dropdown>
                   </div>}
                   menuItemRender={(menuItemProps, defaultDom) => {
            return <Link to={menuItemProps.path}>{defaultDom}</Link>;
        }}>
            {children}
        </ProLayout>
    );
}

3. 路由鉴权示例:

import React from "react";
import {Redirect} from "dw-mx-static-config-router";
import CurrentUser from "@/auth/CurrentUser";

export default function NeedUserAuthorized(props) {
    const {children} = props;

    if (CurrentUser.isLoggedIn()) {
        return children;
    } else {
        return <Redirect to={"/login"}></Redirect>;
    }
}

4. 内部页面示例:

import React from "react";

export default function Home() {
    return (
        <div>
            <h1>这是首页</h1>
        </div>
    );
}

5. 关键实现:静态路由表渲染组件

import React from "react";
import {Router, Switch, Redirect, Route, RouteComponentProps} from "react-router-dom"

type MxRoutes = MxRoute[];

interface MxRoute {
    path: string; // 配置 path
    exact?: boolean; // 表示是否严格匹配,即 location 是否和 path 完全对应上
    strict?: boolean; // 是否严格匹配结尾'/'
    sensitive?: boolean; // 是否大小写敏感匹配
    component?: any; // 配置 location 和 path 匹配后用于渲染的 React 组件路径
    redirect?: string; // 配置路由跳转
    routes?: MxRoute[]; // 配置子路由,通常在需要为多个路径增加 layout 组件时使用
    wrappers?: any[]; // 配置路由的高阶组件封装
    key?: any;
    [key: string]: any; // 用户自定义的字段
}

interface MxBrowserRouterProps {
    routes: MxRoutes;
    history: any;
}

function render({route, props}: { route: MxRoute, props: RouteComponentProps<any> }) {
    const routes = renderRoutes(route.routes || []);

    const {component: Component, wrappers} = route;
    if (Component) {
        const newProps = {
            ...props, // 路由组件参数
            route,
        };

        // @ts-ignore
        let wrappedComponent = <Component {...newProps}>{routes}</Component>;

        if (wrappers) {
            wrappers.forEach((wrapper)=>{
                wrappedComponent = React.createElement(wrapper, newProps, wrappedComponent);
            });
        }

        return wrappedComponent;
    } else {
        return routes;
    }
}

function getRouteElement({route, index}: { route: MxRoute, index: number }) {
    const routeProps = {
        key: route.key || index,
        exact: route.exact,
        strict: route.strict,
        sensitive: route.sensitive,
        path: route.path,
    };
    if (route.redirect) {
        return <Redirect {...routeProps} from={route.path} to={route.redirect}/>;
    } else {
        return (
            <Route
                {...routeProps}
                render={(props: RouteComponentProps<any>) => {
                    return render({route, props});
                }}
            />
        );
    }
}

function renderRoutes(routes: MxRoutes) {
    return routes ? (
        <Switch>
            {routes.map((route, index) =>
                getRouteElement({
                    route,
                    index
                }),
            )}
        </Switch>
    ) : null;
}

const MxStaticConfigBrowserRouter:React.FunctionComponent<MxBrowserRouterProps> = (props) =>{
    const {history, routes} = props;

    return <Router history={history}>{renderRoutes(routes)}</Router>;
};

export default MxStaticConfigBrowserRouter;

参考:

React 路由库:React router https://reacttraining.com/react-router/ https://github.com/ReactTraining/react-router/ React router 的底层依赖库: https://github.com/ReactTraining/history UmiJS 对路由的管理: https://umijs.org/zh-CN/docs/routing Antdesign Pro 对路由的管理: https://pro.ant.design/docs/router-and-nav-cn


Vue 路由库:Vue Router https://router.vuejs.org/zh/ iview-admin 对路由的管理: https://lison16.github.io/iview-admin-doc/#/%E8%B7%AF%E7%94%B1%E9%85%8D%E7%BD%AE

本文分享自微信公众号 - WebJ2EE(WebJ2EE),作者:WEBJ2EE

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • WEB:文件上传 —— 看这篇就够了

    HTML 表单最初只支持 application/x-www-form-urlencoded 形式编码(key=value&key=value...),但它不适...

    WEBJ2EE
  • React:redux-form 应用示例

    redux-form 提供了一堆 selector,便于我们从state中获取 form 表单的各种状态数据...

    WEBJ2EE
  • React:Table 那些事(3-2)—— 斑马纹、固定表头

    《React:Table 那些事》系列文章,会逐渐给大家呈现一个基于 React 的 Table 组件的定义、设计、开发过程。每篇文章都会针对 Table 的某...

    WEBJ2EE
  • 4. Navigation实战

    本来想写一个应用redux的Navigation实战,但是发现react-native有又新的更新,新手怕误导大家,就直接用了别人的组件,看看怎么应用吧。本次在...

    MasterVin
  • 2、基本方法(Basic Recipes)

    官网地址:http://gpiozero.readthedocs.io/en/stable/recipes.html

    墨文
  • SQL2012_创建数据库,创建表,表的内外链接

    create table a1( aid int constraint PK_a primary key, age int check(age ...

    赵腰静
  • 数据库操作中需要注意的问题

    如果你用cmd窗口向一张表插入数据的时候,插入的数据是中文,会出现错误提示,用软件操作的请忽略。。。 出现错误的原因是cmd窗口采用的是gbk编码,所以你在c...

    wangweijun
  • 脑信号分析系列(1)-听觉P300实验

    刺激时间为200ms,时间间隔400ms,随机抖动±100ms, 任务是计算玩奇数球刺激的次数,记录单个参与者进行的6次2分钟的实验。

    脑机接口社区

扫码关注云+社区

领取腾讯云代金券