单页面应用跳转,并刷新页面的实现原理

400 阅读2分钟

1.什么是单页面应用(SPA)

  • 概念:单页面应用是指,一次性加载完所需要的资源,如HTML,css,js,不需要动态的加载资源,对SPA来说页面的切换就是指组件的切换。简单来说,SPA只有一个html页面,可以局部的刷新页面,而非加载整个页面。

2.SPA最终实现的效果

  • url的地址发生变化,但不会向后端发送请求。
  • 根据配置的路由信息,每次点击切换路由,会改变url地址栏,并切换到不同的组件显示。

3.SPA两种模式

  • hash路由
  • browser路由

4.SPA实现原理

  1. hash模式
  • 浏览器的hashchange事件
  • 发布订阅者模式
// 手写一个HashRouter
class HashRouter {
  constructor() {
    this.routes = {};
    this.currentUrl = null;
  }

  // 一个hash值对应一个方法,发布订阅者模式
  route(hash, callBack) {
    this.routes[hash] = callBack || function () {};
  }

  updateCurrentUrl() {
    this.currentUrl = window.location.hash || "/";
    this.routes[this.currentUrl] && this.routes[this.currentUrl]();
  }

  init() {
    // 注册监听事件
    window.addEventListener("load", this.updateCurrentUrl.bind(this), false);
    window.addEventListener(
      "hashchange",
      this.updateCurrentUrl.bind(this),
      false
    );
  }
}

export default HashRouter;
// 案例
import React, { Component } from "react";
import HashRouter from "./handleRouter/HashRouter";

class App extends Component {
  hashRouter = new HashRouter();
  render() {
    this.hashRouter.init();
    this.hashRouter.route("#page", () => {
      console.log("hashRouter");
    });
    return (
      <>
          <a href="/#page">page</a>
      </>
    );
  }
}

export default App;

20230907112131.gif 2. history模式

  • 浏览器的popstate事件
  • popstate事件触发依赖history.pushState()和history.replaceState(),但是只执行这两个方法并不会触发popState事件,只有在做出浏览器动作时,才会触发该事件,如浏览器的后退history.back()或者history.forward()加载历史列表中的下一个url
  • 其他的原理和hashRouter基本一样,都采用了发布订阅者模式
// 手写一个BrowserRoter
class BrowserRouter {
  constructor() {
    this.routes = {};
    this.currentUrl = null;
  }

  // 一个值对应一个方法,发布订阅者模式
  route(hash, callBack) {
    this.routes[hash] = callBack || function () {};
  }

  updateCurrentUrl(url) {
    this.currentUrl = url;
    this.routes[this.currentUrl] && this.routes[this.currentUrl]();
  }

  init() {
    this.linkBind();
    window.addEventListener(
      "load",
      this.updateCurrentUrl.bind(this, "/"),
      false
    );
    window.addEventListener(
      "popstate",
      this.updateCurrentUrl.bind(this, window.location.pathname),
      false
    );
  }
}

export default BrowserRouter;
import React, { Component } from "react";
import { Button } from "antd";
import BrowserRouter from "./handleRouter/BrowserRouter";

class App extends Component {
  browserRouter = new BrowserRouter();
  handleClickBrowser = (e) => {
    e.preventDefault();
    window.history.pushState({}, null, "/page1");
    window.history.back();
  };
  handleClickBrowser2 = (e) => {
    e.preventDefault();
    window.history.pushState({}, null, "/page2");
    window.history.back();
  };
  render() {
    this.browserRouter.init();
    this.browserRouter.route("/page1", () => {
      console.log("browserRouter1");
    });
    this.browserRouter.route("/page2", () => {
      console.log("browserRouter2");
    });
    return (
      <>
        <a href="#" onClick={this.handleClickBrowser}>
          page1
        </a>
        <a href="#" onClick={this.handleClickBrowser2}>
          page2
        </a>
        <div id="content"></div>
      </>
    );
  }
}

export default App;

20230920134739.gif