资讯专栏INFORMATION COLUMN

单页面路由切换原理/简单实现

yangrd / 3433人阅读

摘要:新增一条记录替换当前的记录实现核心路由绑定标签绑定事件并阻止默认事件总结本文只是简述了,路由变化原理,并未去结合去完成实现一个框架路由。

前言

最近学习react时,在使用react-router-dom的时候,对history原理与路由切换实现并不了解,经过学习后总结一下吧!

如果你只是使用react 自带history 那下面这些原理,你可能并不会用到。但当需要使用自己的history 或 使用第三方history 的时候你就需要了解其原理

在react 中,你要在非组件内可以灵活的切换路由,那么你就要使用自定义的history。

// app.jsx
import React from "react"
import "./App.scss"
import { BrowserRouter } from "react-router-dom"
import history from "./history"
import XtContent from "./layout/content"
import Login from "./components/loginModal"
function App() {
  return (
    
) } export default App // history.js // 这里使用第三方创建history import { createBrowserHistory } from "history" export default createBrowserHistory() // 在封装的fetch中使用 import axios from "axios" import history from "@/history"; // 设置axios 通用 const service = axios.create({ timeout: 5000, headers: { "Content-Type": "application/json;charset=UTF-8" }, withCredentials: true }) // 响应拦截 service.interceptors.response.use( response => { //服务端定义的响应code码为0时请求成功 if (response.data.code === 200) { //使用Promise.resolve 正常响应 return Promise.resolve(response.data) } else if (response.data.code === 1002) { //服务端定义的响应code码为1401时为未登录 message.error("登陆失效,请从新登陆!") /////////// 登陆失败切换登陆页面 **history.push("/Login")** ////////// window.localStorage.removeItem("userInfo") return Promise.reject(response.data) //使用Promise.reject 抛出错误和异常 } else { message.error(response.data.message) return Promise.reject(response.data) } }, error => { if (error && error.response) { let res = {} res.code = error.response.status res.msg = throwErr(error.response.status, error.response) //throwErr 捕捉服务端的http状态码 定义在utils工具类的方法 message.error(res.msg) return Promise.reject(res) } return Promise.reject(error) } )

模式

在React/Vue的路由时,会有两种模式 hash 和 history ,事实上他们是针对游览器的路由模式设置的。其基本原理实际就是通关游览器提供的监听这两种模式的变化,从而映射的对应路由的组件render.

基与以上理论简单实现一下路由切换 hash

hash 其兼容性好,但一般不采用只是在不支持H5 history 的情况下回退到hash。其在游览器上的路由形式 http://localhost#/test 与正常路由相比略显怪异且不美观(不推荐使用)

核心:监听hash变化触发callback // window.addEventListener("hashchange", callback)

// 模拟实现
  class Router {
      constructor() {
        // 储存 hash 与 callBack 的映射
        this.routes = {};
        // 当前 路由
        this.currentUrl = "";
        // 存储历史记录
        this.history = [];
        // 作为指针,默认指向 this.history 的末尾,根据后退前进指向 history 中不同的 hash
        this.currentIndex = this.history.length - 1;
        this.backIndex = this.history.length - 1
        this.refresh = this.refresh.bind(this);
        this.backOff = this.backOff.bind(this);
        // 默认不是后退操作
        this.isBack = false;
    
        // 监听load后加载路由
        window.addEventListener("load", this.refresh, false);
    
        // 监听hash 变化
        //window.addEventListener("hashchange", this.refresh, false);
      }
    
      //路由实例 加添路由映射
      route(path, callback) {
        this.routes[path] = callback || function() {};
      }
    
      // 根据由render
      refresh() {
        console.log("refresh")
        this.currentUrl = location.hash.slice(1) || "/";
        this.history.push(this.currentUrl);
        this.currentIndex++;
        if (!this.isBack) {
          this.backIndex = this.currentIndex
        }
        this.routes[this.currentUrl]();
        console.log("指针:", this.currentIndex, "history:", this.history);
        this.isBack = false;
      }
      // 后退功能
      backOff() {
        // 后退操作设置为true
        console.log(this.currentIndex)
        console.log(this.backIndex)
        this.isBack = true;
        this.backIndex <= 0 ?
          (this.backIndex = 0) :
          (this.backIndex = this.backIndex - 1);
        location.hash = `#${this.history[this.backIndex]}`;
      }
    }
    
    // 调用
    
    window.router = new Router()

    router.route("/", function () {
      console.log("")
      changeContent("")
    })
    router.route("/Home1", function () {
      console.log("Home1")
      changeContent("Home1")
    })
    router.route("/Home2", function () {
      console.log("Home2")
      changeContent("Home2")
    })
    router.route("/Home3", function () {
      console.log("Home3")
      changeContent("Home3")
    })
history history API (pushState replaceState)

将游览器地址上路由切换

修改历史记录

不会刷新页面

其路由形式为游览器标准,所以当切换到某一路由localhost/test 后再刷新游览器(F5),游览器会向服务器请求/test下对应的资源,然鹅路由是在前端实现的服务器会找不到资源,在这种模式需要将服务器接收到的get(contentType = text/html)请求统一返回index.html。下面已node-koa为例

  
  const Koa = require("koa")
  // 一个处理前端路由hisrtory模式的node插件
  const history = require("koa2-connect-history-api-fallback")
  const app = new Koa()
  // 将contentType 为 text/html, application/xhtml+xml 统一返回静态资源文件下的index.html
  app.use(history({
    htmlAcceptHeaders: ["text/html", "application/xhtml+xml"]
  }))
  
  * koa2-connect-history-api-fallback 见 https://github.com/davezuko/koa-connect-history-api-fallback

/**
* state :历史记录相关联的状态对象,当popstate事件触发时,会把该对象传入回调函数。不用可传null。
* title: 新页面的标题不用可传null。
* url: 要切换到的路径,必须保持与当前URL同一个域。
**/

// 新增一条记录
history.pushState(state, title, url)

// 替换当前的记录
history.replaceState(state, title, url)

#### 实现 #####

* 核心  window.addEventListener("popstate",callBack)

class Routers {
  constructor() {
    this.routes = {};
    this._bindPopState();
  }
  init(path) {
    history.replaceState({path: path}, null, path);
    this.routes[path] && this.routes[path]();
  }

  route(path, callback) {
    this.routes[path] = callback || function() {};
  }

  go(path) {
    history.pushState({path: path}, null, path);
    this.routes[path] && this.routes[path]();
  }
  _bindPopState() {
    window.addEventListener("popstate", e => {
      const path = e.state && e.state.path;
      this.routes[path] && this.routes[path]();
    });
  }
}

window.Router = new Routers();
Router.init(location.pathname);

// 路由绑定
Router.route("/", function() {
  console.log("/")
});
Router.route("/blue", function() {
  console("blue");
});
Router.route("/green", function() {
  console("green");
});

// a标签绑定事件、并阻止默认事件
a.addEventListener("click", e => {
  if (e.target.tagName === "A") {
    e.preventDefault();
    Router.go(e.target.getAttribute("href"));
  }
});


总结

本文只是简述了,路由变化原理,并未去React/Vue结合 去完成实现一个框架路由。后续学习后会补上!

参考

Hash路由

history路由

作者:易企秀——D_Q_

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/106516.html

相关文章

  • 页面路由切换原理/简实现

    摘要:新增一条记录替换当前的记录实现核心路由绑定标签绑定事件并阻止默认事件总结本文只是简述了,路由变化原理,并未去结合去完成实现一个框架路由。 前言 最近学习react时,在使用react-router-dom的时候,对history原理与路由切换实现并不了解,经过学习后总结一下吧! 如果你只是使用react 自带history 那下面这些原理,你可能并不会用到。但当需要使用自己的hist...

    DandJ 评论0 收藏0
  • vue-router实现原理

    摘要:我们知道是的核心插件,而当前项目一般都是单页面应用,也就是说是应用在单页面应用中的。原理是传统的页面应用,是用一些超链接来实现页面切换和跳转的其实刚才单页面应用跳转原理即实现原理实现原理原理核心就是更新视图但不重新请求页面。 近期面试,遇到关于vue-router实现原理的问题,在查阅了相关资料后,根据自己理解,来记录下。我们知道vue-router是vue的核心插件,而当前vue项目...

    vibiu 评论0 收藏0
  • 你需要知道的页面路由实现原理

    摘要:显示为显示为显示为单页面应用用户访问轨迹埋点开发过单页面应用的同学,一定比较清楚,单页面应用的路由切换是无感知的,不会重新进行请求去获取页面,而是通过改变页面渲染视图来实现。 前言 最近开发的埋点项目,需要记录用户行为轨迹即用户页面访问顺序。需要在页面跳转的时候,记录用户访问的信息(比如 url ,请求头部等),非单页面应用可以给 window 对象加上一个 beforeunload ...

    light 评论0 收藏0
  • 【Vue.js 牛刀小试】:第十二章 - 使用 Vue Router 实现 Vue 中的前端路由控制

    摘要:而路由则是使用了中新增的事件和事件。总结这一章主要是介绍了如何使用在中构建我们的前端路由。 系列目录地址 一、基础知识概览 第一章 - 一些基础概念(posted at 2018-10-31) 第二章 - 常见的指令的使用(posted at 2018-11-01) 第三章 - 事件修饰符的使用(posted at 2018-11-02) 第四章 - 页面元素样式的设定(posted a...

    cpupro 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<