React Router

156 阅读9分钟

 React Router

  1. react-router:路由核心库,包含诸多和路由功能相关的核心代码
  2. react-router-dom:利用路由核心库,结合实际的页面,实现跟页面路由密切相关的功能

 两种模式

路由:根据不同的页面地址,展示不同的组件

url地址组成

例:www.react.com:443/news/1-2-1.…

  1. 协议名(schema):https
  2. 主机名(host):www.react.com
    1. ip地址
    2. 预设值:localhost
    3. 域名
    4. 局域网中电脑名称
  3. 端口号(port):443
    1. 如果协议是http,端口号是80,则可以省略端口号
    2. 如果协议是https,端口号是443,则可以省略端口号
  4. 路径(path):/news/1-2-1.html
  5. 地址参数(search、query):?a=1&b=2
    1. 附带的数据 2. 格式:属性名=属性值&属性名=属性值.... 6. 哈希(hash、锚点)
    2. 附带的数据

 Hash Router 哈希路由

根据url地址中的哈希值来确定显示的组件

 原因:hash的变化,不会导致页面刷新  这种模式的兼容性最好

Borswer History Router 浏览器历史记录路由

HTML5出现后,新增了History Api,从此以后,浏览器拥有了改变路径而不刷新页面的方式

History表示浏览器的历史记录,它使用栈的方式存储。

  1. history.length:获取栈中数据量

  2. history.pushState:向当前历史记录栈中加入一条新的记录

    1. 参数1:附加的数据,自定义的数据,可以是任何类型
    2. 参数2:页面标题,目前大部分浏览器不支持
    3. 参数3:新的地址
  3. history.replaceState:将当前指针指向的历史记录,替换为某个记录

    1. 参数1:附加的数据,自定义的数据,可以是任何类型
    2. 参数2:页面标题,目前大部分浏览器不支持 3. 参数3:新的地址 根据页面的路径决定渲染哪个组件

 路由组件

 Router组件

它本身不做任何展示,仅提供路由模式配置,另外,该组件会产生一个上下文,上下文中会提供一些实用的对象和方法,供其他相关组件使用

  1. HashRouter:该组件,使用hash模式匹配
  2. BrowserRouter:该组件,使用BrowserHistory模式匹配

通常情况下,Router组件只有一个,将该组件包裹整个页面

 Route组件

根据不同的地址,展示不同的组件 重要属性:

  1. path:匹配的路径
    1. 默认情况下,不区分大小写,可以设置sensitive属性为true,来区分大小写
    2. 默认情况下,只匹配初始目录,如果要精确匹配,配置exact属性为true
    3. 如果不写path,则会匹配任意路径
  2. component:匹配成功后要显示的组件
  3. children:
    1. 传递React元素,无论是否匹配,一定会显示children,并且会忽略component属性
    2. 传递一个函数,该函数有多个参数,这些参数来自于上下文,该函数返回react元素,则一定会显示返回的元素,并且忽略component属性

Route组件可以写到任意的地方,只要保证它是Router组件的后代元素


import React from 'react'
import { BrowserRouter as RouterRoute } from "react-router-dom"
//   /a
function A() {
    return <h1>组件A</h1>
}
//   /a/b
function B() {
    return <h1>组件B</h1>
}
// 任意路径
function C() {
    return <h1>
        找不到页面1111
        <Route path="/abc" exact component={D} />
    </h1>
}
function D() {
    return <span>D组件</span>
}
export default function App() {
    return (
        <Router>
            <Route path="/a" exact component={A}>
                <h1 style={{ color: "red" }}>必定会看到的内容</h1>
                <p>adfasdfasdf</p>
            </Route>
            <Route path="/a/b" component={B} />
            <Route component={C} />
        </Router>
    )
}


 Switch组件

写到Switch组件中的Route组件,当匹配到第一个Route后,会立即停止匹配

由于Switch组件会循环所有子元素,然后让每个子元素去完成匹配,若匹配到,则渲染对应的组件,然后停止循环。因此,不能在Switch的子元素中使用除Route外的其他组件。


import React from 'react'
import { BrowserRouter as RouterRouteSwitch } from "react-router-dom"
//   /a
function A() {
    return <h1>组件A</h1>
}
//   /a/b
function B() {
    return <h1>组件B</h1>
}
// 任意路径
function C() {
    return <h1>
        找不到页面
        <Route path="/abc" exact component={D} />
    </h1>
}
function D(){
    return <span>D组件</span>
}
export default function App() {
    return (
        <Router>
            <Switch>
                <Route path="/a" exact component={A} />
                <Route path="/a/b" component={B} />
                <Route component={C} />
            </Switch>
        </Router>
    )
}


 路由信息

Router组件会创建一个上下文,并且,向上下文中注入一些信息

该上下文对开发者是隐藏的,Route组件若匹配到了地址,则会将这些上下文中的信息作为属性传入对应的组件

import React from "react";
import { BrowserRouter as RouterRouteSwitch } from "react-router-dom";
function News(props) {
  console.log(props);
  return (
    <div>
      <p>
        显示{props.match.params.year}年{props.match.params.month}月
        {props.match.params.day}日的新闻
      </p>
    </div>
  );
}
function NotFound() {
  return <h1>找不到页面</h1>;
}
export default function App() {
  return (
    <Router>
      <Switch>
        <Route path="/news/:year(\d+)/:month(\d+)/:day(\d+)" component={News} />
        <Route component={NotFound} />
      </Switch>
    </Router>
  );
}

 history

它并不是window.history对象,我们利用该对象无刷新跳转地址 为什么没有直接使用history对象

  1. React-Router中有两种模式:Hash、History,如果直接使用window.history,只能支持一种模式
  2. 当使用windows.history.pushState方法时,没有办法收到任何通知,将导致React无法知晓地址发生了变化,结果导致无法重新渲染组件
  • push:将某个新的地址入栈(历史记录栈)
    1. 参数1:新的地址
    2. 参数2:可选,附带的状态数据
  • replace:将某个新的地址替换掉当前栈中的地址
  • go: 与window.history一致
  • forward: 与window.history一致
  • back: 与window.history一致

 location

与history.location完全一致,是同一个对象,但是,与window.location不同

location对象中记录了当前地址的相关信息

我们通常使用第三方库query-string,用于解析地址栏中的数据

 match

该对象中保存了,路由匹配的相关信息

  • isExact:事实上,当前的路径和路由配置的路径是否是精确匹配的

  • params:获取路径规则中对应的数据

实际上,在书写Route组件的path属性时,可以书写一个string pattern(字符串正则)

react-router使用了第三方库:Path-to-RegExp,该库的作用是,将一个字符串正则转换成一个真正的正则表达式。    向某个页面传递数据的方式:

  1. 使用state:在push页面时,加入state
  2. 利用search:把数据填写到地址栏中的?后
  3. 利用hash:把数据填写到hash后
  4. params:把数据填写到路径中

 非路由组件获取路由信息

某些组件,并没有直接放到Route中,而是嵌套在其他普通组件中,因此,它的props中没有路由信息,如果这些组件需要获取到路由信息,可以使用下面两种方式:

  1. 将路由信息从父组件一层一层传递到子组件
  2. 使用react-router提供的高阶组件withRouter,包装要使用的组件,该高阶组件会返回一个新组件,新组件将向提供的组件注入路由信息。
import React from 'react'
import { BrowserRouter as RouterRouteSwitch } from "react-router-dom"
function A(props) {
    return <div>
        <p>组件A</p>
        <button onClick={() => {
            props.history.push("/b", "状态数据");
        }}>跳转到/b</button>
    </div>
}
function B(props) {
    return <div>
        <p>组件B</p>
        <p>
            获取状态数据:{props.history.location.state}
        </p>
        <button onClick={() => {
            props.history.push("/a");
        }}>跳转到/a</button>
    </div>
}
function NotFound() {
    return <h1>找不到页面</h1>
}
export default function App() {
    return (
        <Router>
            <Switch>
                <Route path="/a" component={A} />
                <Route path="/b" component={B} />
                <Route component={NotFound} />
            </Switch>
        </Router>
    )
}

 其他组件

 Link

生成一个无刷新跳转的a元素

  • to
    • 字符串:跳转的目标地址
    • 对象:
      • pathname:url路径
      • search
      • hash
      • state:附加的状态信息
  • replace:bool,表示是否是替换当前地址,默认是false
  • innerRef:可以将内部的a元素的ref附着在传递的对象或函数参数上
    • 函数
    • ref对象
import React from "react";
import { BrowserRouter as RouterRouteLink } from "react-router-dom";
function PageA() {
  return <h1>A页</h1>;
}
function PageB() {
  return <h1>B页</h1>;
}
function NavBar() {
  return (
    <div>
      <Link
        innerRef={(node) => {
          console.log(node);
        }}
        to="/a"
        style={{ marginRight: 20 }}
      >
        去a页
      </Link>
      <Link
        replace
        to={{
          pathname: "/b",
          hash: "#abc",
          search: "?a=1&b=2",
        }}
      >
        去b页
      </Link>
    </div>
  );
}
export default function App() {
  return (
    <Router>
      <NavBar />
      <Route path="/a" component={PageA} />
      <Route path="/b" component={PageB} />
    </Router>
  );
}

 NavLink

是一种特殊的Link,Link组件具备的功能,它都有 它具备的额外功能是:根据当前地址和链接地址,来决定该链接的样式

  • activeClassName: 匹配时使用的类名
  • activeStyle: 匹配时使用的内联样式
  • exact: 是否精确匹配
  • sensitive:匹配时是否区分大小写
  • strict:是否严格匹配最后一个斜杠
import React from 'react'
import { BrowserRouter as RouterRouteNavLink } from "react-router-dom"
import "./App.css";
function PageA() {
    return <h1>A页</h1>
}
function PageB() {
    return <h1>B页</h1>
}
function NavBar() {
    return <div>
        <NavLink activeClassName="selected"
        exact
        strict
        activeStyle={{
            background:"#ccc"
        }}
        innerRef={node=>{
            console.log(node)
        }} to="/a" style={{ marginRight: 20 }}>去a页</NavLink>
        <NavLink activeClassName="selected" 
         activeStyle={{
            background:"#ccc"
        }}
        replace to={{
            pathname: "/b",
            hash: "#abc",
            search: "?a=1&b=2"
        }}>去b页</NavLink>
    </div>
}
export default function App() {
    return (
        <Router>
            <NavBar />
            <Route path="/a" component={PageA} />
            <Route path="/b" component={PageB} />
        </Router>
    )
}

 Redirect

重定向组件,当加载到该组件时,会自动跳转(无刷新)到另外一个地址

  • to:跳转的地址
    • 字符串
    • 对象
  • push: 默认为false,表示跳转使用替换的方式,设置为true后,则使用push的方式跳转
  • from:当匹配到from地址规则时才进行跳转
  • exact: 是否精确匹配from
  • sensitive:from匹配时是否区分大小写
  • strict:from是否严格匹配最后一个斜杠
import React from 'react'
import { BrowserRouter as RouterRouteNavLinkSwitchRedirect } from "react-router-dom"
import "./App.css";
// import Link from "./Link"
function PageA() {
    return <h1>A页</h1>
}
function PageB() {
    return <h1>B页</h1>
}
function NavBar() {
    return <div>
        <NavLink activeClassName="selected"
            exact
            strict
            activeStyle={{
                background: "#ccc"
            }}
            innerRef={node => {
                console.log(node)
            }} to="/a" style={{ marginRight: 20 }}>去a页</NavLink>
        <NavLink activeClassName="selected"
            activeStyle={{
                background: "#ccc"
            }}
            replace to={{
                pathname: "/b",
                hash: "#abc",
                search: "?a=1&b=2"
            }}>去b页</NavLink>
        <NavLink
            to="/abc"
            style={{ marginLeft: 20 }}>
            其他页
        </NavLink>
    </div>
}
export default function App() {
    return (
        <Router>
            <NavBar />
            <Switch>
                <Route path="/a" component={PageA} />
                <Route path="/b" component={PageB} />
                <Redirect
                from="/abc/:id"
                to="/a/:id" />
            </Switch>
        </Router>
    )
}

 导航守卫

导航守卫:当离开一个页面,进入另一个页面时,触发的事件 history对象

  • listen: 添加一个监听器,监听地址的变化,当地址发生变化时,会调用传递的函数
    • 参数:函数,运行时间点:发生在即将跳转到新页面时
      • 参数1:location对象,记录当前的地址信息
      • 参数2:action,一个字符串,表示进入该地址的方式
    • POP:出栈
      • 通过点击浏览器后退、前进
      • 调用history.go
      • 调用history.goBack
      • 调用history.goForward
    • PUSH:入栈
      • history.push
      • REPLACE:替换
      • history.replace
    • 返回结果:函数,可以调用该函数取消监听
  • block:设置一个阻塞,并同时设置阻塞消息,当页面发生跳转时,会进入阻塞,并将阻塞消息传递到路由根组件的getUserConfirmation方法。
    • 返回一个回调函数,用于取消阻塞器

路由根组件

  • getUserConfirmation
    • 参数:函数
      • 参数1:阻塞消息
        • 字符串消息
      • 函数,函数的返回结果是一个字符串,用于表示阻塞消息
        • 参数1:location对象
        • 参数2:action值
    • 参数2:回调函数,调用该函数并传递true,则表示进入到新页面,否则,不做任何操作

实现一个GuardHelper

import React, { Component } from 'react'
import { BrowserRouter as Router, withRouter } from "react-router-dom"
let prevLoaction, location, action, unBlock;
class _GuardHelper extends Component {
    componentDidMount() {
        //添加阻塞
        unBlock = this.props.history.block((newLocation, ac) => {
            prevLoaction = this.props.location;
            location = newLocation;
            action = ac;
            return "";
        });
        //添加一个监听器
        this.unListen = this.props.history.listen((location, action) => {
            if (this.props.onChange) {
                const prevLoaction = this.props.location;
                this.props.onChange(prevLoaction, location, action, this.unListen);
            }
        })
    }
    componentWillUnmount() {
        unBlock();//取消阻塞
        //卸载监听器
        this.unListen();
    }
    render() {
        return null;
    }
}
const GuardHelper = withRouter(_GuardHelper);
class RouteGuard extends Component {
    handleConfirm = (msg, commit) => {
        if (this.props.onBeforeChange) {
            this.props.onBeforeChange(prevLoaction, location, action, commit, unBlock);
        }
        else{
            commit(true);
        }
    }
    render() {
        return <Router getUserConfirmation={this.handleConfirm}>
            <GuardHelper onChange={this.props.onChange} />
            {this.props.children}
        </Router>;
    }
}
export default RouteGuard;

App.js


import React from 'react'
import { RouteLink } from "react-router-dom"
import RouteGuard from './RouteGuard';
function Page1() {
    return <h1>Page1</h1>
}
function Page2() {
    return <h1>Page2</h1>
}
export default function App() {
    return (
        <RouteGuard
            onBeforeChange={(prev, curactioncommitunBlock) => {
                console.log(`页面想要从${prev.pathname}跳转到${cur.pathname},跳转方式是${action},允许跳转`)
                commit(true);
                unBlock();//取消阻塞,仅阻塞了一次
            }}
            onChange={(prevLocation, location, action, unListen) => {
                console.log(`日志:从${prevLocation.pathname}进入页面${location.pathname},进入方式${action}`)
                unListen();//取消监听,仅监听了一次
            }}
        >
            <ul>
                <li>
                    <Link to="/page1">页面1</Link>
                </li>
                <li>
                    <Link to="/page2">页面2</Link>
                </li>
            </ul>
            <Route path="/page1" component={Page1} />
            <Route path="/page2" component={Page2} />
        </RouteGuard>
    )
}


结尾

之后还会继续追加内容,前端搞起