乐趣区

关于vue.js:手写简易的-Vue-Router

前言

还是那样,懂得如何应用一个罕用库,还得理解其原理或者怎么模仿实现,明天实现一下 vue-router

有一些常识我这篇文章提到了,这里就不具体一步步写,请看我 手写一个繁难的 Vuex

根本骨架

  • Vue 外面应用插件的形式是Vue.use(plugin),这里贴出它的用法:

装置 Vue.js 插件。如果插件是一个对象,必须提供 install 办法。如果插件是一个函数,它会被作为 install 办法。install 办法调用时,会将 Vue 作为参数传入。这个办法的第一个参数是 Vue 结构器,第二个参数是一个可选的选项对象。

  • 全局混入

应用 Vue.mixin(mixin)

全局注册一个混入,影响注册之后所有创立的每个 Vue 实例。能够应用混入向组件注入自定义的行为,它将影响每一个之后创立的 Vue 实例。

  • 路由用法

比方简略的:

// 路由数组
const routes = [
  {
    path: '/',
    name: 'Page1',
    component: Page1,
  },
  {
    path: '/page2',
    name: 'Page2',
    component: Page2,
  },
]

const router = new VueRouter({
  mode: 'history', // 模式
  routes,
})

它是传入了 moderoutes,咱们实现的时候须要在 VueRouter 构造函数中接管。

在应用路由题目的时候是这样:

<p>
  <!-- 应用 router-link 组件来导航. -->
  <!-- 通过传入 `to` 属性指定链接. -->
  <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
  <router-link to="/page1">Go to Foo</router-link>
  <router-link to="/page2">Go to Bar</router-link>
</p>
<!-- 路由进口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>

故咱们须要应用 Vue.component(id, [definition] ) 注册一个全局组件。

理解了大略,咱们就能够写出一个根本骨架

let Vue = null

class VueRouter {constructor(options) {
    this.mode = options.mode || 'hash'
    this.routes = options.routes || []}
}

VueRouter.install = function (_Vue) {
  Vue = _Vue

  Vue.mixin({beforeCreate() {
      // 根组件
      if (this.$options && this.$options.router) {
        this._root = this // 把以后 vue 实例保留到_root 上
        this._router = this.$options.router // 把 router 的实例挂载在_router 上
      } else if (this.$parent && this.$parent._root) {
        // 子组件的话就去继承父组件的实例,让所有组件共享一个 router 实例
        this._root = this.$parent && this.$parent._root
      }
    },
  })

  Vue.component('router-link', {
    props: {
      to: {type: [String, Object],
        required: true,
      },
      tag: {
        type: String,
        default: 'a', // router-link 默认渲染成 a 标签
      },
    },
    render(h) {
      let tag = this.tag || 'a'
      return <tag href={this.to}>{this.$slots.default}</tag>
    },
  })

  Vue.component('router-view', {render(h) {return h('h1', {}, '视图显示的中央') // 临时置为 h1 标签,上面会改
    },
  })
}

export default VueRouter

mode

vue-router有两种模式,默认为 hash 模式。

history 模式

通过 window.history.pushStateAPI 来增加浏览器历史记录,而后通过监听popState 事件,也就是监听历史记录的扭转,来加载相应的内容。

  • popstate 事件

当流动历史记录条目更改时,将触发 popstate 事件。如果被激活的历史记录条目是通过对 history.pushState()的调用创立的,或者受到对 history.replaceState()的调用的影响,popstate 事件的 state 属性蕴含历史条目标状态对象的正本。

  • History.pushState()办法
window.history.pushState(state, title, url)

该办法用于在历史中增加一条记录,接管三个参数,顺次为:

state:一个与增加的记录相关联的状态对象,次要用于 popstate 事件。该事件触发时,该对象会传入回调函数。也就是说,浏览器会将这个对象序列化当前保留在本地,从新载入这个页面的时候,能够拿到这个对象。如果不须要这个对象,此处能够填 null。title:新页面的题目。然而,当初所有浏览器都漠视这个参数,所以这里能够填空字符串。url:新的网址,必须与以后页面处在同一个域。浏览器的地址栏将显示这个网址。

hash 模式

应用 URL 的 hash 来模仿一个残缺的 URL。,通过监听 hashchange 事件,而后依据 hash 值(可通过 window.location.hash 属性读取)去加载对应的内容的。

持续减少代码,

let Vue = null

class HistoryRoute {constructor() {this.current = null // 以后门路}
}

class VueRouter {constructor(options) {
    this.mode = options.mode || 'hash'
    this.routes = options.routes || []
    this.routesMap = this.createMap(this.routes)
    this.history = new HistoryRoute() // 以后路由
    this.initRoute() // 初始化路由函数}

  createMap(routes) {return routes.reduce((pre, current) => {pre[current.path] = current.component
      return pre
    }, {})
  }

  initRoute() {if (this.mode === 'hash') {
      // 先判断用户关上时有没有 hash 值,没有的话跳转到 #/
      location.hash ? '': (location.hash ='/')
      window.addEventListener('load', () => {this.history.current = location.hash.slice(1)
      })
      window.addEventListener('hashchange', () => {this.history.current = location.hash.slice(1)
      })
    } else {
      // history 模式
      location.pathname ? '': (location.pathname ='/')
      window.addEventListener('load', () => {this.history.current = location.pathname})
      window.addEventListener('popstate', () => {this.history.current = location.pathname})
    }
  }
}

VueRouter.install = function (_Vue) {
  Vue = _Vue

  Vue.mixin({beforeCreate() {if (this.$options && this.$options.router) {
        this._root = this
        this._router = this.$options.router
        Vue.util.defineReactive(this, '_route', this._router.history) // 监听 history 门路变动
      } else if (this.$parent && this.$parent._root) {this._root = this.$parent && this.$parent._root}
      // 当拜访 this.$router 时即返回 router 实例
      Object.defineProperty(this, '$router', {get() {return this._root._router},
      })
      // 当拜访 this.$route 时即返回以后页面路由信息
      Object.defineProperty(this, '$route', {get() {return this._root._router.history.current},
      })
    },
  })
}

export default VueRouter

router-link 和 router-view 组件

VueRouter.install = function (_Vue) {
  Vue = _Vue

  Vue.component('router-link', {
    props: {
      to: {type: [String, Object],
        required: true,
      },
      tag: {
        type: String,
        default: 'a',
      },
    },
    methods: {handleClick(event) {
        // 阻止 a 标签默认跳转
        event && event.preventDefault && event.preventDefault()
        let mode = this._self._root._router.mode
        let path = this.to
        this._self._root._router.history.current = path
        if (mode === 'hash') {window.history.pushState(null, '','#/' + path.slice(1))
        } else {window.history.pushState(null, '', path.slice(1))
        }
      },
    },
    render(h) {
      let mode = this._self._root._router.mode
      let tag = this.tag || 'a'
      let to = mode === 'hash' ? '#' + this.to : this.to
      console.log('render', this.to)
      return (<tag on-click={this.handleClick} href={to}>
          {this.$slots.default}
        </tag>
      )
      // return h(tag, { attrs: { href: to}, on: {click: this.handleClick} }, this.$slots.default)
    },
  })

  Vue.component('router-view', {render(h) {
      let current = this._self._root._router.history.current // current 曾经是动静响应
      let routesMap = this._self._root._router.routesMap
      return h(routesMap[current]) // 动静渲染对应组件
    },
  })
}

至此,一个繁难的 vue-router 就实现完了,案例残缺代码附上:mini-vue-router

  • ps:集体技术博文 Github 仓库,感觉不错的话欢送 star,给我一点激励持续写作吧~
退出移动版