前言

  • 最近学习react时,在使用react-router-dom的时候,对history原理与路由切换实现并不了解,经过学习后总结一下吧!
  • 如果你只是使用react 自带history 那下面这些原理,你可能并不会用到。但当需要使用自己的history 或 使用第三方history 的时候你就需要了解其原理
  • 在react 中,你要在非组件内可以灵活的切换路由,那么你就要使用自定义的history。

    // app.jsximport 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 (    <div className="App">      <BrowserRouter history={history} basename="/xiutui">        <Login />        <XtContent />      </BrowserRouter>    </div>  )}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_