JavaScript 进阶营让前端飞工作生活

【源码】vue-rotuer1 原理

2019-07-04  本文已影响0人  woow_wu7

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

(1)Hash

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

上一篇 下一篇

猜你喜欢

热点阅读