【源码】vue-rotuer1 原理

大纲
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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 目录 - 1.vue-router 动态路由匹配 - 2.router-link组件及其属性 - 3.vue-ro...
    我跟你蒋阅读 1,098评论 0 7
  • 路由实现原理,就是根据不同的 url ,在页面上显示相应的内容。而浏览器 url 变化时,会造成页面的刷新。前端路...
    极客传阅读 188评论 0 1
  • 通常 SPA 中前端路由有2种实现方式: window.history location.hash 下面就来介绍下...
    好奇男孩阅读 1,132评论 0 18
  • 编程式导航 1 .用在可复用的路由视图里面,比如所有的需要跳转到一个文章具体内容的路由,每一次跳转到新路由的时候,...
    skoll阅读 605评论 0 1
  • 远见(预测未来) 创新 领导变革 管理不确定性 确信与坚持 教练他人 …… 杰克韦尔奇、乔布斯、任正非、张瑞敏…....
    任建平_海问联合阅读 860评论 2 5