【源码】vue-rotuer1 原理

作者: woow_wu7 | 来源:发表于2019-07-04 21:09 被阅读0次

大纲
hash路由
history路由
vue-router简单实现
vue中的数组,对象添加属性的监听

(1)Hash

  • url 的 hash 以 # 开头,原本是用来作为锚点,从而定位到页面的特点区域
  • 当 hash 改变时,页面不会刷新,浏览器也不会向服务器发送请求
  • 注意:hash 改变时,会触发 hashchange 事件,hashchange回调函数中,可以请求数据,实现页面的更新操作
Hash

1. 作为锚点
  - 点击a2会跳转到div2上,同时url中的hash部分会变化
<a href="#anchor1">锚点1</a>
<a href="#anchor2">锚点2</a>
<div id="anchor1" class="anchor1">锚点1</div>
<div id="anchor2" class="anchor2">锚点2</div>


2. 当页面的url的hash改变时,会触发 hashchange 事件,可以事件更新的更新操作
<body>
  <a href="#anchor1">锚点1</a>
  <a href="#anchor2">锚点2</a>
  <script>
    window.addEventListener('hashchange', function() {
      console.log('111111111')
    }, false)
  </script>
</body>




3. 手动实现 hash-router

原理:
(1)url的hash改变时,触发hashchange事件
(2)hashchange事件触发时,更新视图

- html部分
  <a href="#/home">home</a>
  <a href="#/other">other</a>
  <div id="content">内容部分</div>

- js部分
const routes = [{ // 路由配置数组
  path: '/home',
  component: '<h1>home页面<h1/>'
}, {
  path: '/other',
  component: '<h1>other页面</h1>'
}]

class Router {
  constructor(routes) {
    this.route = {}
    routes.forEach(item => { 
      // 循环配置项,path作为key, value是一个更新html的函数
      this.route[item.path] = () => { document.getElementById('content').innerHTML = item.component }
    })
    this.init()
  }

  init () {
    window.addEventListener('load', this.updateView, false) // 内容加载完成触发
    window.addEventListener('hashchange', this.updateView, false) // hash改变触发,更新视图
  }
  updateView = () => {
    const hash = window.location.hash.slice(1) || '/'   // 截取hash
    this.route[hash] && this.route[hash]() // 如果该路由存在,就加载路由对应的组件
  }
}

const router = new Router(routes)

(2) History

  • History对象的属性
    (1)History.length:当前窗口访问过的网址数量,包括当前页面
    (2)History.state:History堆栈最上层的状态值

  • History对象的方法:back() forward() go()

  • History.pushState(state, title, url) ------- 用于在历史中添加一条记录
    (1)state:一个与添加的记录相关联的 对象,主要用于 popstate 事件。在popstate事件触发时,浏览器会将这个对象序列化后保存在本地,从新载入这个页面时,能拿到这个对象
    (2)title:新页面的标题,现在所有浏览器都忽略这个参数,可以填空字符串
    (3)url:新的网址,必须与当前页面在同一个域内,浏览器的地址栏将显示这个网址
    (4)pushState()方法不会触发页面刷新,只是导致 History 对象发生变化,地址栏会有反应。

  • History.replaceState() ------------------- 用来修改 History对象 的当前记录

  • popstate 事件 ------- 每当同一个文档的浏览历史出现变化时,就会触发popstate事件
    (1)注意:popstate事件触发的时机:如果仅仅是改变 History.pushState 和 History.replaceState 不会触发 popstate,但是 History.pushState 和 History.replaceState 可以改变url,(不会触发 popstate 但会 修改地址栏的url)
    (2)popstate触发的条件:

    1. 浏览器的进退按钮,
    2. 或者 History.go(),History.back(),History.forward()等
  • html5中通过 History.pushState()History.replaceState()来进行路由控制,通过这两个方法可以实现改变url,且实现不向服务器发送请求,不存在#号,比hash路由更美观

  • 但是 History 路由需要服务器的支持,并且需将所有的路由重定向到根页面

History


前置知识点:
1. history
  - html5中通过 history.pushState 和 hitstory.repalceState 来控制路由
  - history.pushState 和 history.replaceState 只会改变 History对象,会改变url,但不会更新页面
    - 所以当history对象变化时,需要手动触发一个事件
      - 1. 封装一个方法,在 pushState()或者replaceState()之后调用一个方法
      - 2. 通过浏览器history.back() go() forward()等方法触发 popState 事件

2. location对象
  - host 主机名和端口号
  - hostname 主机名
  - pathname 返回url的路径部分,----------------  /之后  ?之前
  - href 返回完整的url ------------------------- <a href=""> a标签中也有href属性
  - search 以 ? 开始至行尾 或 # --------------- ?开始到#号之前,如果没有#就到url结束(包括?号)
  - hash 以 # 开始至行尾 ----------------------- hash部分不会上传到服务器(包括#号)
  - port 端口号
  - protocol 协议 ------------------------------ 包括结尾的:  (http:) 
  - origin 协议域名端口
  


<body>
  <div>
    <a href="javascript:void(0)" data-href="/home">home</a>
    <a href="javascript:void(0)" data-href="/other">other</a>

    <div id="content">content</div>
  </div>
  <script>
    const routes = [{ // 路由配置数组
      path: '/home',
      component: '<h1>home页面1<h1/>'
    }, {
      path: '/other',
      component: '<h1>other页面2</h1>'
    }]
    class Router {
      constructor (toutes) {
        this.route = {}

        toutes.forEach(item => {
          this.route[item.path] = () => {
            document.getElementById('content').innerHTML = item.component
          }
        })

        this.bindEvent()

        this.init()
      }
      init () {
        window.addEventListener('load', this.updateView, false)
        window.addEventListener('popstate', this.updateView, false)
        // 监听popstate事件
        // popstate事件触发的条件
        // 1. history.go()    history.foward()   history.back() 等history上的事件
        // 2. 浏览器的前进,后退按钮
        // 注意:本代码中监听popstate,只要是为了浏览器的前进后退时,触发
      }

      updateView = () => {
        const path = window.location.pathname || '/'   // 获取pathname
        this.route[path] && this.route[path]()
      }

      bindEvent = () => {
        const that = this

        const a = document.getElementsByTagName('a')
        Array.prototype.forEach.call(a, item => {
          //  循环a标签,添加点击事件,拿到 data-href属性
          item.addEventListener('click', function () {
            that.push(item.getAttribute('data-href'))
          }, false)
        })
      }

      push = (url) => {
        window.history.pushState({}, null, url) 
        // 修改 History 对象,可以改变地址栏url
        // 这里 pushState 主要是为了改变 url,当url改变后,调用updateView() 拿到更新后的url,再更新视图
        this.updateView()
      }
    }
    new Router(routes)
  </script>
<body>

实现一个简单的 vue-router

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <!-- 引入vue-cdn -->
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body >
  <div id="app">
    <p>
      <!-- 注意 router-link组件具有 to 属性 -->
      <router-link to="#/home">home</router-link>
      <router-link to="#/other">route</router-link>
    </p>
    <router-view></router-view>
  </div>

  <script>
    // 创建两个组件
    const Home = { template: '<h1>home组件</h1>' }
    const Other = { template: '<h1>ohter组件</h1>' }

    const routes = [{
      path: '/home',
      component: Home
    }, {
      path: '/other',
      component: Other
    }]

    class VueRouter {
      constructor(Vue, routes) {
        this.$options = routes
        this.routeMap = {}  // 路由映射对象
        this.createRouteMap(this.$options) // 创建路由映射关系

        // 创建app属性,是vue实列对象,挂载 data
        // this.app.current 可以访问到 vue data中的 currentHash

        // var data = { a: 1 }
        // var vm = new Vue({
        //   data: data
        // })
        // vm.a = data.a
        this.app = new Vue({
          data: {
            currentHash: '#/'
          }
        })

        this.init() // 初始化监听函数

        this.initComponent(Vue)
      }

      // createRouteMap 创建路由映射
      // 当地址栏的url的 hash 变化时,找到对应的组件
      // 注意 otpions是一个对象
      createRouteMap = (options) => {
        options.routes.forEach(item => {
          this.routeMap[item.path] = item.component
        })
      }

      init = () => {
        window.addEventListener('load', this.onHashChange, false)
        window.addEventListener('hashchange', this.onHashChange, false) // window绑定hashchange事件监听函数
      }

      // 当url中的hash改变时,触发hashchange事件
      // 更新state中的数据 currentHash
      // 更新后,会触发vue中的render方法,更新视图
      onHashChange = () => {
        this.app.currentHash = window.location.hash.slice(1) || '/'
      }

      initComponent = (Vue) => {
        // router-link组件
        // props to属性
        // template 本质上会被处理成a标签,href属性是传入的 to 属性,内容是 slot 插入的内容
        Vue.component('router-link', {
          props: {
            to: {
              type: String,
              value: ''
            }
          },
          template: '<a :href="to"><slot></slot></a>'
        })

        const that = this

        Vue.component('router-view', {
          render(h) {
            const component = that.routeMap[that.app.currentHash] // 拿到最新hash对应的组件
            return h(component)
            // h(component) 相当于 createElement(component)
            // render: function(createElement) { return createElement(App); }
          }
        })
      }
    }

    // 调用 VueRouter
    // 注意 VueRouter 第二个参数是一个对象
    new VueRouter(Vue, {
      routes
    })

    new Vue({
      el: '#app' // 将vue的控制范围限制到 id=app 的范围内
    })
  </script>
</body>
</html>

https://juejin.im/post/5cf9f75ef265da1bbf690ec7

https://juejin.im/post/5cb2c1656fb9a0688360fb2c

基础 https://juejin.im/post/5c7160d46fb9a049d236ae79

hash history 路由 模拟实现 https://juejin.im/post/5b330142e51d4558b10a9cc5

vue-router模拟实现 https://juejin.im/post/5b35dcb5f265da59a117344d

url组成 https://blog.csdn.net/hongtaochi0464/article/details/90649340

数组和对象的监听

https://cn.vuejs.org/v2/guide/list.html#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9

相关文章

网友评论

    本文标题:【源码】vue-rotuer1 原理

    本文链接:https://www.haomeiwen.com/subject/glhgcctx.html