React05:react-router

SPA

Single Page Application : 单页面应用,整个应用只加载一个页面(入口页面),后续在与用户的交互过程中,通过 DOM 操作在这个单页上动态生成结构和内容

优点:

  • 有更好的用户体验(减少请求和渲染和页面跳转产生的等待与空白),页面切换快
  • 重前端,数据和页面内容由异步请求(AJAX)+ DOM 操作来完成,前端处理更多的业务逻辑

缺点:

  • 首次进入处理慢
  • 不利于 SEO

SPA 的页面切换机制

虽然 SPA 的内容都是在一个页面通过 JavaScript 动态处理的,但是还是需要根据需求在不同的情况下分内容展示,如果仅仅只是依靠 JavaScript 内部机制去判断,逻辑会变得过于复杂

通过把 JavaScript 与 URL 进行结合的方式:JavaScript 根据 URL 的变化,来处理不同的逻辑,交互过程中只需要改变 URL 即可。

这样把不同 URL 与 JavaScript 对应的逻辑进行关联的方式就是路由,其本质上与后端路由的思想是一样的。

前端路由

前端路由只是改变了 URL 或 URL 中的某一部分,但一定不会直接发送请求,可以认为仅仅只是改变了浏览器地址栏上的 URL 而已,JavaScript 通过各种手段处理这种 URL 的变化,然后通过 DOM 操作动态的改变当前页面的结构

  • URL 的变化不会直接发送 HTTP 请求
  • 业务逻辑由前端 JavaScript 来完成

目前前端路由主要的模式:

  • 基于 URL Hash 的路由
  • 基于 HTML5 History API 的路由
    https://developer.mozilla.org/zh-CN/docs/Web/API/History_API

React Router

理解了路由基本机制以后,也不需要重复造轮子,我们可以直接使用 React Router 库

React Router 提供了多种不同环境下的路由库

  • web
  • native

基于 Web 的 React Router

基于 web 的 React Router 为:react-router-dom

安装

npm i -S react-router-dom

路由组件

BrowserRouter 组件 – history

基于 HTML5 History API 的路由组件

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, HashRouter } from "react-router-dom";
import App from './App';

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

HashRouter 组件 – hash

基于 URL Hash 的路由组件

Route 组件

通过该组件来设置应用单个路由信息,
Route 组件所在的区域就是就是当 URL 与当前 Route 设置的 path 属性匹配的时候,后面 component 将要显示的区域

属性

  1. path

    1. path 匹配规则: 默认情况下,path是一种模糊匹配,以path为开始时,则认定匹配
    2. 想要精确匹配时,要设置exact 为 true
    3. 严格匹配:非严格匹配下,当url为/about/也能匹配成功
      如果要严格匹配需要设置strict属性
      必须与精确匹配同时存在
    4. 多路径匹配时:[路径1, 路径2]
    5. 当path为空或不写path时 Router 代表所有组件都匹配
  2. component

<Fragment>
    <Route path="/" exact component={IndexView} />
    <Route path={["/", "/home", "/h"]} exact component={IndexView} />
    <Route path="/about" exact strict component={AboutView} />
    <Route path="/join" component={JoinView} />
</Fragment>

传递 props

<Route exact path='/' component={Home}>

如果 Route 使用的是 component 来指定组件,那么不能使用 props

Route的 render 属性

接收函数,函数的返回值必须是组件

 <Route path="/message" render={() => {
     return <MessageView userName={userName} />;
 }} />

子组件打印props

import React from 'react';

const MessageView = (props) => {
    console.log(props);
    return (
        <h3>留言板</h3>
    );
};

export default MessageView;

通过 render 属性来指定渲染函数,render 属性值是一个函数,当路由匹配的时候指定该函数进行渲染

Switch 组件

功能类似于switch语句
一项匹配成功不再匹配后续路径

例如显示404页面

<Switch>
    <Route path="/" exact component={IndexView} />
    <Route path={["/", "/home", "/h"]} exact component={IndexView} />
    <Route path="/about" exact strict component={AboutView} />
    <Route path="/join" component={JoinView} />
    <Route component={NotFound} />
</Switch>

Link 组件

Link 组件用来处理 a 链接 类似的功能(它会在页面中生成一个 a 标签),但设置这里需要注意的,react-router-dom 拦截了实际 a 标签的默认动作,然后根据所有使用的路由模式(Hash 或者 HTML5)来进行处理,改变了 URL,但不会发生请求,同时根据 Route 中的设置把对应的组件显示在指定的位置

to 属性

to 属性类似 a 标签中的 href

import React from 'react';
import { Link } from "react-router-dom";

const NavView = () => {
    return (
        <nav>
            <Link to="/">首页</Link>
            <span> | </span>
            <Link to="/about">关于</Link>
            <span> | </span>
            <Link to="/join">加入</Link>
        </nav>
    );
};

export default NavView;

Redirect 组件

设置跳转的 URL

<Route path="/login" component={LoginView} />
<Route path="/message" render={() => {
    // return <MessageView userName={userName} />;
    return <Redirect to="/login" />;
}} />

Route组件 的路由信息

被Router组件的component 直接调用的组件称之为路由组件
Route在调用路由组件时会传递一些相关的路由信息
在这里插入图片描述

history: 当前URL信息,及操作URL的相关方法

  • go: ƒ go(n) 历史记录跳转几项,正值向前跳转,负值向后跳转
  • goBack: ƒ goBack() 返回上一步
  • goForward: ƒ goForward() 前进到下一步
  • length: 40 历史记录中共记录几项
  • location: {pathname: “/”, search: “”, hash: “”, state: undefined}
    url 信息 pathname
    • 当前URL,
    • search 当前 search 值,
    • hash当前 hash 值,
    • 历史记录传递的信息 state
  • push: ƒ push(path, state) 跳转
  • replace: ƒ replace(path, state) 跳转

location: URL信息

{pathname: "/", search: "", hash: "", state: undefined}

match:

 {path: "/", url: "/", isExact: true, params: {}} 匹配信息,path 当前 route 的 path
  params path 传递的参数

render 属性中的路由信息

<Route path="/login" render={(routeProps) => {
    return <LoginView setUserName={setUserName} {...routeProps} />;
}} />

在这里插入图片描述
其中的history.push()并不会触发浏览器的刷新,知识同步路由更新视图

import React, { Fragment, useEffect } from 'react';

const LoginView = (props) => {
    console.log(props);
    let {history} = props;
    useEffect(() => {
        new Promise((resolve) => {
            setTimeout(() => {
                props.setUserName("myName");
                resolve();
            }, 1000);
        }).then(() => {
            console.log("登陆成功,回到首页/留言页");
            history.push("/")
        });
    }, []);
    return (
        <Fragment>
            <h2>登陆</h2>
            <button >login</button>
        </Fragment>
    );
};

export default LoginView;

高阶路由 withRouter函数

如何在非路由组件中使用路由信息?

  • 一步一步传递
  • 可以使用高阶路由withRouter

如果一个组件不是路由绑定组件,那么该组件的 props 中是没有路由相关对象的,虽然我们可以通过传参的方式传入,
但是如果结构复杂,这样做会特别的繁琐。幸好,我们可以通过 withRouter 方法来注入路由对象

类组件使用

  • 调用withRouter时 必须传入一个组件,withRouter会返回一个新的组件
  • 其他地方调用新组建时,新组件内部会调用传入的组件,并将路由信息传递给该组件
    非路由类组件,使用withRouter函数
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';

class AboutDetails1 extends Component {
    render() {
        return (
            <h2>关于详情1</h2>
        );
    }
}

AboutDetails1 = withRouter(AboutDetails1)

export default AboutDetails1;
// export default withRouter(AboutDetails1);

父组件调用子组件时,接收到路由

import React, { Fragment } from 'react';
import AboutDetails1 from './aboutDetails1';

export default function AboutView(props) {
    console.log(props);
    return (
        <Fragment>
            <h1>
                about
        </h1>
            <AboutDetails1 />
        </Fragment>
    );
}

函数式组件使用 withRouter

import React from 'react';
import { withRouter } from 'react-router-dom';

const AboutDetails2 = (props) => {
    console.log(props);
    return (
        <h2>关于详情2</h2>
    );
};

export default withRouter(AboutDetails2);

函数式组件通过 hooks 获取路由信息

  1. useHistory()
  2. useParams()
  3. useRouteMatch()
  4. useLocation()
import React from 'react';
import { useHistory, useRouteMatch, useLocation } from 'react-router-dom';

const AboutDetails2 = (props) => {
    let history = useHistory();
    console.log(history);
    console.log(useRouteMatch());
    console.log(useParams());
    console.log(useLocation());
    return (
        <h2>关于详情2</h2>
    );
};

export default AboutDetails2;


URL参数匹配的情况

  • 在 path 中 通过:paramsName
  • 在URL中截取对应值paramsValue
    在这里插入图片描述
<Route path="/about/:type/:page" exact strict component={AboutView} />

在这里插入图片描述

NavLink 组件

NavLink 与 Link 类似,但是它提供了两个特殊属性用来处理页面导航

activeClass/activeClassName

  • activeClass:当前项被选中时的class,默认为active
  • 可通过activeClassName属性手动设置class
import React from 'react';
import { NavLink } from "react-router-dom";

const NavView = () => {
    return (
        <nav>
            <NavLink to="/" strict exact activeClassName="selected">首页</NavLink>
            <span> | </span>
            <NavLink to="/about" activeClassName="selected">关于</NavLink>
            <span> | </span>
            <NavLink to="/join" activeClassName="selected">加入</NavLink>
            <span> | </span>
            <NavLink to="/message" activeClassName="selected">留言</NavLink>
            <span> | </span>
            <NavLink to="/login" activeClassName="selected">登陆</NavLink>
        </nav>
    );
};

export default NavView;

activeStyle

定义当前项被选中时的style
当 URL 与 NavLink 中的 to 匹配的时候,激活 activeStyle 中的样式

<nav>
    <NavLink to="/" strict exact activeStyle={{
        color: "orange"
    }}>首页</NavLink>
    <span> | </span>
    <NavLink to="/about" activeStyle={{
        color: "orange"
    }}>关于</NavLink>
    <span> | </span>
    <NavLink to="/join" activeStyle={{
        color: "orange"
    }}>加入</NavLink>
    <span> | </span>
    <NavLink to="/message" activeStyle={{
        color: "orange"
    }}>留言</NavLink>
    <span> | </span>
    <NavLink to="/login" activeStyle={{
        color: "orange"
    }}>登陆</NavLink>
</nav>

isActive

  • isActive 是一个函数,返回布尔值
  • 默认情况下,匹配的是 URL 与 to 的设置,
  • 通过 isActive 可以自定义激活逻辑,

永远持有activeStyle

  <NavLink
      to="/join"
      activeStyle={{
          color: "orange"
      }}
      isActive={() => {
          return true;
      }}
  >加入</NavLink>

返回false 就是永远不持有activeStyle

路由的开发技巧

自定义动态的路由数据

路由数据

import React from "react";
import IndexView from "../view/index/index";
import ListView from "../view/list/index";
import AboutView from "../view/about/index";
import View404 from "../view/404/index";
/*
主导航:
    首页
    列表页
        全部
        新闻
        问题
        作品
    关于我们
*/
const route_list = [
    {
        path: ["/", "/index"],
        exact: true,
        render(props) {
            return <IndexView {...props} />;
        }
    }, {
        path: ["/list/:type/:page", "/list/:type", "/list"],
        exact: true,
        render(props) {
            return <ListView  {...props} />;
        }
    }, {
        path: "/about",
        exact: true,
        render(props) {
            return <AboutView  {...props} />;
        }
    }, {
        path: "",
        exact: true,
        render(props) {
            return <View404  {...props} />;
        }
    }
];
const menu = [
    {
        title: "首页",
        to: "/",
        exact: true,
        activeClass: "active",
        active(url) {
            // 接收当前 url ,自己定义规则是否需要选中当前项
            return url === "/index" || url === "/";
        }
    }, {
        title: "列表页",
        to: "/list",
        exact: true,
        activeClass: "active",
        active(url) {
            return url.split("/")[1] === "list";
        }
    }, {
        title: "关于我们",
        to: "/about",
        exact: true,
        activeClass: "active"
    }
];
/*
    全部
         /list  ""/list/all  /list/all/pageNub
    新闻 
        /list/news   /list/news/pageNub
    问题
        /list/ask   /list/ask/pageNub
    作品
        /list/example   /list/example/pageNub
*/
const listMenu = [
    {
        title: "全部",
        to: "/list",
        exact: true,
        activeClass: "listActive",
        active(url) {
            if (url === "/list") {
                return true;
            }
            let urls = url.split("/");
            return urls[2] === "all";
        }
    }, {
        title: "新闻",
        to: "/list/news",
        exact: true,
        activeClass: "listActive",
        active(url) {
            let urls = url.split("/");
            return urls[2] === "news";
        }
    }, {
        title: "问题",
        to: "/list/ask",
        exact: true,
        activeClass: "listActive",
        active(url) {
            let urls = url.split("/");
            return urls[2] === "ask";
        }
    }, {
        title: "作品",
        to: "/list/example",
        exact: true,
        activeClass: "listActive",
        active(url) {
            let urls = url.split("/");
            return urls[2] === "example";
        }
    }
];
export { route_list, menu, listMenu };

利用路由数据和es6自动生成路由逻辑

路由主视图

import React from "react";
import { Switch, Route } from "react-router-dom";
import { route_list } from "./route_list";
function IndexRoute(props) {
    return <Switch>
        {
            route_list.map((item, index) => {
                return <Route
                    key={index}
                    path={item.path}
                    exact={item.exact}
                    render={(routerProps) => {
                        return item.render({ ...routerProps, ...props });
                    }}
                />;
            })
        }
    </Switch>;
}

export default IndexRoute;

nav按钮视图

import React, { Fragment } from "react";
import { menu } from "../router/route_list";
import { NavLink, useLocation } from "react-router-dom";
function Nav() {
    let { pathname } = useLocation();
    return <Fragment>
        <nav>
            {menu.map((item, index) => {
                return <NavLink
                    to={item.to}
                    activeClassName={item.activeClass}
                    key={index}
                    exact={item.exact}
                    isActive={item.active ? () => {
                        return item.active(pathname);
                    } : null}
                >{item.title}</NavLink>;
            })}
        </nav>
        <hr />
    </Fragment>;
}

export default Nav;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值