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

31次阅读

共计 4987 个字符,预计需要花费 13 分钟才能阅读完成。

前言

  • 最近学习 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 (
        <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_

正文完
 0

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

31次阅读

共计 4982 个字符,预计需要花费 13 分钟才能阅读完成。

前言

  • 最近学习 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 (
        <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 路由

正文完
 0