React Router

2019/11/20 JSReact

React Router API (opens new window)学习笔记

🌙 一、快速开始

🌙 1.配置环境

// 全局安装create-react-app
npm install -g create-react-app
// 启动项目
create-react-app demo-app
cd demo-app
// 启动项目
npm  start 

// 安装react-router-dom 依赖
npm install react-router-dom --save
1
2
3
4
5
6
7
8
9
10

🌙 2.简单Demo

See the Pen React-router demo1 by zkkysqs (@zkkysqs) on CodePen.

  • react-router是核心部分,是浏览器和原生应用的通用部分 。

  • react-router-dom提供了浏览器使用需要的定制组件。

  • react-router-native则专门提供了在原生移动应用中需要用到的部分。所以,如果在浏览器开发环境就只需要安装react-router-dom

本文主要是学习react-router-dom部分。

🌙 二、React Router组件

React Router组件主要分为三大类:

  • Routers:如 <BrowserRouter><HashRouter>组件,React Router的父组件,每个React Router组件都是Routers组件。
  • Route Matchers :如 <Route><Switch>组件,我称之为路由出口,渲染UI部分。
  • Navigation:如 <Link><NavLink><Redict>组件,即链接跳转,一般会被渲染为<a>标签。

🌙 1.Routers路由

🌙 1.1 hash和history两种模式的区别 (opens new window)

Router组件,根据url模式的不同,分为history模式(对应<BrowserRouter>)和hash模式(对应<HashRouter>)。

先来理解这两种模式:

对比 hash模式 (opens new window) history模式 (opens new window)
URL例子 https://music.163.com/#/friendhttps://pan.baidu.com/disk/home#list/vmode=list https://www.cnblogs.com/stdzz/p/11703829.html
特点 有特殊标识#
对后端的影响 与后端配合疏松
hash 虽然出现在 URL 中,不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面
hash 模式下,仅 hash 符号之前的内容会被包含在请求中,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
与后端配合紧密
history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.abc.com/book/id。如果后端缺少对 /book/id 的路由处理,将返回 404 错误。
各自特点 1.hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中。
2.hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URLhash
3.只可添加短字符串
1.history.go(-2);//后退两次 2.history.go(2);//前进两次 3.history.back(); //后退 4.history.forward();//前进
5.history.pushState() (opens new window)
6.history.replaceState() (opens new window)

🌙 1.2 BrowserRouter 组件

使用HTML5 history API更新页面,组件API:

<BrowserRouter
  basename={optionalString}
  forceRefresh={optionalBool}
  getUserConfirmation={optionalFunc}
  keyLength={optionalNumber}
>
  <Children />
</BrowserRouter>
1
2
3
4
5
6
7
8
  • basename:string,为路由添加root url,需要以/开头,但是尾部不需要/

    <BrowserRouter basename="/calendar" />
    <Link to="/today"/> // renders <a href="/calendar/today">
    
    1
    2
  • getUserConfirmation:func,使用window.confirm (opens new window),会出现一个路由离开确认对话框:

    <BrowserRouter
      getUserConfirmation={(message, callback) => {
        // this is the default behavior
        const allowTransition = window.confirm(message);
        callback(allowTransition);
      }}
    />
    
    1
    2
    3
    4
    5
    6
    7
  • forcerefresh:bool,如果值为true会强制刷新整个页面:

    <BrowserRouter forceRefresh={true} />
    
    1
  • keyLength:number,location.key的长度,默认为6:

    <BrowserRouter keyLength={12} />
    
    1
  • children:node,需要被渲染的子组件,在react16版本之前,只能有一个子节点,若想包裹多个,需要使用<div>包裹。react16版本之后,可以有多个子节点。

    <BrowserRouter
      basename={''}
      forceRefresh={false}
      getUserConfirmation={() => {}}
      keyLength={6}
    >
        <div>
        	<Children1 />
            <Children2 />
        </div>
    </BrowserRouter>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

🌙 1.3 HashRouter组件

使用URL的hash部分来更新页面,组件API:

<HashRouter
  basename={optionalString}
  getUserConfirmation={optionalFunc}
  hashType={optionalString}
>
  <App />
</HashRouter>
1
2
3
4
5
6
7
  • basename: string,为路由添加root url,需要以/开头,但是尾部不需要/

    <HashRouter basename="/calendar"/>
    <Link to="/today"/> // renders <a href="#/calendar/today">
    
    1
    2
  • getuserConfirmation:func,同<BrowserRouter >

  • hashType:string,根据值的不同,使#在URL位置不同,默认slash模式:

    • slash:如 #/#/sunshine/lollipops
    • noslash:如 ##sunshine/lollipops
    • hashbang:如 #!/#!/sunshine/lollipops

See the Pen React-router HashRouter和BrowserRouter by zkkysqs (@zkkysqs) on CodePen.

🌙 2.Route路由匹配

🌙 1.Route组件

即路由出口,用来将匹配的URL对应的子组件UI渲染出来。

🌙 1.1 Route路由渲染方式

以上三种方式,在同一个<Route>中只能选择一种,如果写了多个,会被覆盖,<Route children> 的优先级更高。

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

function Hello() {
  return <h1>Hello World!</h1>;
}

// component方式
ReactDOM.render(
  <Router>
    <Route path="/hello" component={Hello} />
  </Router>,
  document.body
);

// render方式
ReactDOM.render(
  <Router>
    <Route path="/hello" render={() => <Home />} />
  </Router>,
  document.body
);

// children方式
ReactDOM.render(
  <Router>
    <Route path="/hello" children={({ match }) => <Home />} />
  </Router>,
  document.body
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

🌙 1.2 Route路由匹配规则

  • path:string | string[] (opens new window)匹配的URL字符串

    // 匹配一个
    <Route path="/users/:id">
      <User />
    </Route>
    
    // 匹配多个
    <Route path={["/users/:id", "/profile/:id"]}>
      <User />
    </Route>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • exact: bool (opens new window)是否精准匹配还是模糊匹配:

    <Route exact path="/one">
      <About />
    </Route>
    
    1
    2
    3

    根据exact的值匹配:

    URL path 目标URL exact的值 匹配与否
    /one /one/two true no
    /one /one/two false yes
  • strict: bool (opens new window)是否开启严格模式,当值为true时,必须后面带斜杠才能匹配:

    <Route strict path="/one/">
      <About />
    </Route>
    
    1
    2
    3

    那么可以匹配:

    URL path 目标URL 匹配与否
    /one/ /one no
    /one/ /one/ yes
    /one/ /one/two yes

    如果必须匹配尾部没有斜杠的,那么 strict and exact 都得为true

    <Route exact strict path="/one">
      <About />
    </Route>
    
    1
    2
    3
    /one /one yes
    /one /one/ no
    /one /one/two no
  • sensitive: bool (opens new window)是否区分大小写,当为true时,区分大小写:

    <Route sensitive path="/one">
      <About />
    </Route>
    
    1
    2
    3
    URL path 目标URL sensitive值 匹配与否
    /one /one true yes
    /One /one true no
    /One /one false yes

🌙 2.Switch组件

渲染第一个匹配的URL对应的 <Route> (opens new window)<Redirect> (opens new window)

import { Route } from "react-router";

let routes = (
  <div>
    <Route path="/about">
      <About />
    </Route>
    <Route path="/:user">
      <User />
    </Route>
    <Route>
      <NoMatch />
    </Route>
  </div>
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

如果URL为/,只使用<Route>,那么上面的两个路由都能匹配,会被同时渲染出来,如果加上<Switch>则只会渲染第一个匹配的URL:

import { Route, Switch } from "react-router";

let routes = (
  <Switch>
    <Route exact path="/">
      <Home />
    </Route>
    <Route path="/about">
      <About />
    </Route>
    <Route path="/:user">
      <User />
    </Route>
    <Route>
      <NoMatch />
    </Route>
  </Switch>
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

🌙 3.Navigation路由跳转

🌙 1.Link组件

提供路由跳转的链接,会被渲染为<a>标签:

<Link to="/about">About</Link>
1

🌙 1.1 to属性

  • to: string (opens new window)直接跟URL字符串:

    <Link to="/courses?sort=name" />
    
    1
  • to: object (opens new window)对象中包含一系列属性

    • pathname: 跳转的路由.
    • search: url查询参数
    • hash: url的hash部分
    • state: State to persist to the location.
    <Link
      to={{
        pathname: "/courses",
        search: "?sort=name",
        hash: "#the-hash",
        state: { fromDashboard: true }
      }}
    />
    
    1
    2
    3
    4
    5
    6
    7
    8
  • to: function (opens new window)以当前的location作为参数,需要返回一个url字符串或者object来匹配to的合法参数。

    <Link to={location => ({ ...location, pathname: "/courses" })} />
    <Link to={location => `${location.pathname}?sort=name`} />
    
    1
    2

🌙 1.2 replace属性

<Link to="/courses" replace />
1

🌙 2.NavLink组件

这是<Link>组件的升级版,可以给链接加上激活的样式或类名。

🌙 2.1 activeClassName属性

给激活的URL加上类名:

<NavLink to="/faq" activeClassName="selected">
  FAQs
</NavLink>
1
2
3

🌙 2.2 activeStyle属性

🌙 2.3 exact属性

<NavLink exact to="/profile">
  Profile
</NavLink>
1
2
3

🌙 2.4 strict属性

<NavLink strict to="/events/">
  Events
</NavLink>
1
2
3

🌙 2.5 isActive属性

  • isActive: func (opens new window)返回布尔值,用来额外判断链接是否是激活的状态:

    <NavLink
      to="/events/123"
      isActive={(match, location) => {
        if (!match) {
          return false;
        }
    
        // only consider an event active if its event id is an odd number
        const eventID = parseInt(match.params.eventID);
        return !isNaN(eventID) && eventID % 2 === 1;
      }}
    >
      Event 123
    </NavLink>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

🌙 3.Redirect组件

路由重定向到新的URL,会在历史记录中替换掉当前的URL,和HTTP 3xx表现类似:

<Route exact path="/">
  {loggedIn ? <Redirect to="/dashboard" /> : <PublicHomePage />}
</Route>
1
2
3

🌙 3.1 to属性

🌙 3.2 push属性

<Redirect push to="/somewhere/else" />
1

🌙 3.3 from属性

  • from: string (opens new window)

    <Switch>
      <Redirect from='/old-path' to='/new-path' />
      <Route path='/new-path'>
        <Place />
      </Route>
    </Switch>
    
    // Redirect with matched parameters
    <Switch>
      <Redirect from='/users/:id' to='/users/profile/:id'/>
      <Route path='/users/profile/:id'>
        <Profile />
      </Route>
    </Switch>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

Redirect组件也有strictsensitiveexact属性,他们和Route组件对应的属性表现一致。

🌙 4.Router Hooks

通过Hooks函数获取路由状态以及路由相关信息,可以实现在组件内部路由跳转。

🌙 4.1 useHistroy

useHistroy提供路由history (opens new window)历史记录,可以用来跳转历史纪录,前进或回退。

🌙 4.2 useLocation

useLocation获取路由location (opens new window)相关信息,如可以获取查询参数:

// location的实例
{
  key: 'ac3df4', // not with HashHistory!
  pathname: '/somewhere',
  search: '?some=search-string',
  hash: '#howdy',
  state: {
    [userDefined]: true
  }
}
1
2
3
4
5
6
7
8
9
10

See the Pen React-router useLocation获取url的location信息 by zkkysqs (@zkkysqs) on CodePen.

🌙 4.3 useParams

useParams获取路由path(路径参数)的一系列key/value值。

See the Pen React-router useParams by zkkysqs (@zkkysqs) on CodePen.

🌙 4.4 useRouteMatch

useRouteMatch像<Route>方式一样,使用match (opens new window)匹配当前URL,但是可以不用正真渲染<route>UI,就可以获取匹配到的URL数据。

See the Pen React-router hooks useRouteMatch by zkkysqs (@zkkysqs) on CodePen.

🌙 二、React路由原理

🌙 1、BrowserHistory模式下

🌙 1.1 改变路由

改变路由,指的是通过调用 api 实现的路由跳转,比如开发者在 React 应用中调用 history.push 改变路由,本质上是调用window.history.pushState 方法。

window.history.pushState

history.pushState(state,title,path)
1
  • 1 state:一个与指定网址相关的状态对象, popstate 事件触发时,该对象会传入回调函数。如果不需要可填 null。
  • 2 title:新页面的标题,但是所有浏览器目前都忽略这个值,可填 null 。
  • 3 path:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个地址。

window.history.replaceState:

history.replaceState(state,title,path)
1

参数和 pushState 一样,这个方法会修改当前的 history 对象记录, 但是 history.length 的长度不会改变。

🌙 1.2 监听路由 popstate

window.addEventListener('popstate',function(e){
    /* 监听改变 */
})
1
2
3

同一个文档的 history 对象出现变化时,就会触发 popstate 事件 history.pushState 可以使浏览器地址改变,但是无需刷新页面。注意⚠️的是:用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。 popstate 事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮或者调用 history.back()、history.forward()、history.go()方法。

总结: BrowserHistory 模式下的 history 库就是基于上面改变路由,监听路由的方法进行封装处理,最后形成 history 对象,并传递给 Router。

🌙 2.HashHistory模式下

🌙 2.1 改变路由 window.location.hash

通过 window.location.hash 属性获取和设置 hash 值。开发者在哈希路由模式下的应用中,切换路由,本质上是改变 window.location.hash 。

🌙 2.2 监听路由 onhashchange

hash 路由模式下,监听路由变化用的是 hashchange

window.addEventListener('hashchange',function(e){
    /* 监听改变 */
})
1
2
3

「源码解析 」这一次彻底弄懂react-router路由原理 (opens new window)