从 React-Router 切入,系统学习前端路由解决方案

从 React-Router 切入,系统学习前端路由解决方案

  React-Router 是 React 场景下的路由解决方案,本篇将学习 React-Router 的实现机制,并基于此提取和探讨通用的前端路由解决方案。

注:没有使用过 React-Router 的同学,可以点击这里完成快速上手。

认识 React-Router

  本着尽快进入主题的原则,这里用一个尽可能简单的 Demo 作为引子,快速地把握 React-Router 的核心功能。请看下面代码(解析在注释里):

import React from "react";
// 引入 React-Router 中的相关组件
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
// 导出目标组件
const BasicExample = () => (
  // 组件最外层用 Router 包裹
  <Router>
    <div>
      <ul>
        <li>
          // 具体的标签用 Link 包裹
          <Link to="/">Home</Link>
        </li>
        <li>
          // 具体的标签用 Link 包裹
          <Link to="/about">About</Link>
        </li>
        <li>
          // 具体的标签用 Link 包裹
          <Link to="/dashboard">Dashboard</Link>
        </li>
      </ul>
      <hr />
      // Route 是用于声明路由映射到应用程序的组件层
      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
      <Route path="/dashboard" component={Dashboard} />
    </div>
  </Router>
);

// Home 组件的定义
const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
);

// About 组件的定义
const About = () => (
  <div>
    <h2>About</h2>
  </div>
);

// Dashboard 的定义
const Dashboard = () => (
  <div>
    <h2>Dashboard</h2>
  </div>
);

export default BasicExample;

  当点击不同的链接时,ul 元素内部就会展示不同的组件内容。比如当点击“About”链接时,就会展示 About 组件的内容。注意,点击 About 后,界面中发生变化的地方有两处:除了 ul 元素的内容改变了之外,路由信息也改变了。

  在 React-Router 中,各种细碎的功能点有不少,但作为 React 框架的前端路由解决方案,它最基本也是最核心的能力,其实正是你刚刚所见到的这一幕——路由的跳转。这也是我们接下来讨论的重点。

React-Router 是如何实现路由跳转的?

  首先需要回顾下 Demo 中的第一行代码:

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

  这行代码告诉我们,为了实现一个简单的路由跳转效果,一共从 React-Router 中引入了以下 3 个组件:

  • BrowserRouter
  • Route
  • Link

  这 3 个组件也就代表了 React-Router 中的 3 个核心角色:

  • 路由器,比如 BrowserRouterHashRouter
  • 路由,比如 RouteSwitch
  • 导航,比如 LinkNavLinkRedirect

  路由(以 Route 为代表)负责定义路径与组件之间的映射关系,而导航(以 Link 为代表)负责触发路径的改变,路由器(包括 BrowserRouter 和 HashRouter)则会根据 Route 定义出来的映射关系,为新的路径匹配它对应的逻辑。

  以上便是 3 个角色“打配合”的过程。这其中,最需要你注意的是路由器这个角色,React Router 曾在说明文档中官宣它是“React Router 应用程序的核心”。因此学习 React Router,最要紧的是搞明白路由器的工作机制。

路由器:BrowserRouter 和 HashRouter

  路由器负责感知路由的变化并作出反应,它是整个路由系统中最为重要的一环。React-Router 支持我们使用 hash(对应 HashRouter)和 browser(对应 BrowserRouter) 两种路由规则,这里我们把两种规则都讲一下。

  HashRouterBrowserRouter,这俩人名字这么像,该不会底层逻辑区别也不大吧?别说,还真是如此。我们首先来瞟一眼 HashRouter的源码:

在这里插入图片描述

  再瞟一眼 BrowserRouter的源码:

在这里插入图片描述

  这两个文件惊人的相似,而最关键的区别我也已经在图中分别标出,即它们调用的 history 实例化方法不同:HashRouter调用了 createHashHistoryBrowserRouter调用了 createBrowserHistory

  这两个 history 的实例化方法均来源于 history 这个独立的代码库,关于它的实现细节,倒不必纠结。对于 createHashHistorycreateBrowserHistory 这两个 API,我们最要紧的是掌握它们各自的特征。

  • createBrowserHistory:它将在浏览器中使用 HTML5 history API 来处理 URL(见下图标红处的说明),它能够处理形如这样的 URL,example.com/some/path。由此可得,BrowserRouter 是使用 HTML 5 的 history API 来控制路由跳转的。

    在这里插入图片描述

  • createHashHistory:它是使用 hash tag (#) 处理 URL 的方法,能够处理形如这样的 URL,example.com/#/some/path。我们可以看到它的源码中对各种方法的定义基本都围绕 hash 展开(如下图所示),由此可得,HashRouter 是通过 URL 的 hash 属性来控制路由跳转的

    在这里插入图片描述

理解前端路由——是什么?解决什么问题?

背景——问题的产生

  在前端技术早期,一个 URL 对应一个页面,如果你要从 A 页面切换到 B 页面,那么必然伴随着页面的刷新。这个体验并不好,不过在最初也是无奈之举——毕竟用户只有在刷新页面的情况下,才可以重新去请求数据。

  后来,改变发生了——Ajax 出现了,它允许人们在不刷新页面的情况下发起请求;与之共生的,还有“不刷新页面即可更新页面内容”这种需求。在这样的背景下,出现了SPA(单页面应用)。

  SPA 极大地提升了用户体验,它允许页面在不刷新的情况下更新页面内容,使内容的切换更加流畅。但是在 SPA 诞生之初,人们并没有考虑到“定位”这个问题——在内容切换前后,页面的 URL 都是一样的,这就带来了两个问题:

  • SPA 其实并不知道当前的页面“进展到了哪一步”,可能你在一个站点下经过了反复的“前进”才终于唤出了某一块内容,但是此时只要刷新一下页面,一切就会被清零,你必须重复之前的操作才可以重新对内容进行定位——SPA 并不会“记住”你的操作;
  • 由于有且仅有一个 URL 给页面做映射,这对 SEO 也不够友好,搜索引擎无法收集全面的信息。

前端路由——SPA“定位”解决方案

  前端路由可以帮助我们在仅有一个页面的情况下,“记住”用户当前走到了哪一步——为 SPA 中的各个视图匹配一个唯一标识。这意味着用户前进、后退触发的新内容,都会映射到不同的 URL 上去。此时即便他刷新页面,因为当前的 URL 可以标识出他所处的位置,因此内容也不会丢失。

  那么如何实现这个目的呢?首先我们要解决以下两个问题。

  • 当用户刷新页面时,浏览器会默认根据当前 URL 对资源进行重新定位(发送请求)。这个动作对 SPA 是不必要的,因为 SPA 作为单页面,无论如何也只会有一个资源与之对应。此时若走正常的请求-刷新流程,反而会使用户的前进后退操作无法被记录。
  • 单页面应用对服务端来说,就是一个 URL、一套资源,那么如何做到用“不同的 URL”来映射不同的视图内容呢?

  从这两个问题来看,服务端已经救不了 SPA 这个场景了。所以要靠咱们前端自力更生,不然怎么叫“前端路由”呢?作为前端,我们可以提供以下这样的解决思路。

  • 拦截用户的刷新操作,避免服务端盲目响应、返回不符合预期的资源内容,把刷新这个动作完全放到前端逻辑里消化掉;
  • 感知 URL 的变化。这里不是说要改造 URL、凭空制造出 N 个 URL 来。而是说 URL 还是那个 URL,只不过我们可以给它做一些微小的处理,这些处理并不会影响 URL 本身的性质,不会影响服务器对它的识别,只有我们前端能感知到。一旦我们感知到了,我们就根据这些变化、用 JS 去给它生成不同的内容。

实践思路——hash 与 history

hash 模式

  hash 模式是指通过改变 URL 后面以“#”分隔的字符串(这货其实就是 URL 上的哈希值),从而让页面感知到路由变化的一种实现方式。举个例子,比如这样的一个 URL:

https://www.imooc.com/

  我就可以通过增加和改变哈希值,来让这个 URL 变得有那么一点点不一样:

// 主页
https://www.imooc.com/#index
// 活动页
https://www.imooc.com/#activePage

  这个“不一样”是前端完全可感知的——JS 可以帮我们捕获到哈希值的内容。在 hash 模式下,我们实现路由的思路可以概括如下:

  1. hash 的改变:我们可以通过 location暴露出来的属性,直接去修改当前 URLhash值:
window.location.hash = 'index';
  1. hash的感知:通过监听 “hashchange”事件,可以用 JS 来捕捉 hash值的变化,进而决定我们页面内容是否需要更新:
// 监听hash变化,点击浏览器的前进后退会触发
window.addEventListener('hashchange', function(event){ 
    // 根据 hash 的变化更新内容
},false)
history 模式

  大家知道,在我们浏览器的左上角,往往有一对左右箭头操作点: 通过点击前进后退箭头,就可以实现页面间的跳转。这样的行为,其实是可以通过 API 来实现的。

  浏览器的 history API 赋予了我们这样的能力,在 HTML 4 时,就可以通过下面的接口来操作浏览历史、实现跳转动作:

window.history.forward()  // 前进到下一页
window.history.back() // 后退到上一页
window.history.go(2) // 前进两页
window.history.go(-2) // 后退两页

  很有趣吧?遗憾的是,在这个阶段,我们能做的只是“切换”,而不能“改变”。好在从 HTML 5 开始,浏览器支持了 pushStatereplaceState两个 API,允许我们对浏览历史进行修改和新增:

history.pushState(data[,title][,url]); // 向浏览历史中追加一条记录

history.replaceState(data[,title][,url]); // 修改(替换)当前页在浏览历史中的信息

  这样一来,修改动作就齐活了。

  有修改,就要有对修改的感知能力。在 history模式下,我们可以通过监听 popstate事件来达到我们的目的:

window.addEventListener('popstate', function(e) {
  console.log(e)
});

  每当浏览历史发生变化,popstate事件都会被触发。

:go、forward 和 back 等方法的调用确实会触发 popstate,但是pushState 和 replaceState 不会。不过这一点问题不大,我们可以通过自定义事件和全局事件总线来手动触发事件。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值