Vue榮耀黃金 —構建本身的Router

代碼 歡迎followhtml

vue-router實戰

先列一段最簡單的router代碼vue

  1. Vue.use安裝路由插件
  2. 對外暴露了一個new Router的對象,裏面包含全部路由的配置
import VueRouter from 'vue-router'
Vue.use(VueRouter)

export default new VueRouter({
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
      beforeEnter(from,to,next){
          console.log(`beforEnterHome from ${from} to ${to}`)
          setTimeout(()=>{
            next()
          },1000)
          // next()
      }
    },
    {
      path: '/about',
      name: 'about',
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
    }
  ]
})
複製代碼

初始化的時候,直接把這個返回的對象傳遞到new Vuewebpack

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

複製代碼

模板層自帶了router-link和router-view兩個組件git

<div id="nav">
      <button @click="about">about</button>
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <router-view/>

複製代碼

實現本身的vue-router

咱們新建vue-router文件夾 新建index.js, 你們都已是黃金水平了,就不介紹太多基礎知識了,基本上迷你的router,這幾塊就能夠構成github

export default class Router{
    constructor(){

    }
    init(){
        // 初始化
    }
    bindEvents(){
        // 綁定事件
    }
    createRouteMap(){
        // 初始化路由表
    }
    initComponent(){
        // 註冊router-link和router-view路由
    }

}


複製代碼

插件機制

咱們使用Vue.use(VueRouter)來註冊和啓動路由, 這個我們整vuex源碼的時候整過了,帶上一個install方法就能夠,先註冊一個簡單的調試信息看下web

let Vue
class Router {
    static install(_Vue) {
        Vue = _Vue
        Vue.mixin({
            beforeCreate() {
                if(this.$options.router){
                    // new Vue的時候傳遞的
                    Vue.prototype.$routerMsg = '路由安裝完畢'
                }
            }
        })
    }
}
複製代碼

app.vue使用$routerMsg能夠直接顯示出信息,bingovue-router

單頁應用原理

  1. hash模式,改變錨點
  2. history模式,利用了html的popState和pushState方法 ,url發生變化,不會刷新頁面 二者作路由跳轉和監聽的事件削微的有些不同,這裏咱們只演示一下hash,實際狀況hash和history用兩個class就成

咱們在install的時候,執行init 啓動整個路由, 監聽onload和hashchange事件,觸發後,根據當前的hash,找到須要渲染的組件,而後去渲染router-view的內容就歐克了vuex

::: tip hash變化後,通知到router-view渲染,須要借用Vue自己的響應式能力 :::typescript

static install(_Vue) {
        Vue = _Vue
        Vue.mixin({
            beforeCreate() {
                if(this.$options.router){
                    // new Vue的時候傳遞的
                    Vue.prototype.$routerMsg = '路由安裝完畢'
                    Vue.prototype.$router = this.$options.router
                    this.$options.router.init()
                }
            }
        })
    }
    init() {
        this.bindEvents()
        this.createRouteMap(this.$options)
        this.initComponent(Vue)
    }
    bindEvents(){
        window.addEventListener('load', this.onHashChange.bind(this), false)
        window.addEventListener('hashchange', this.onHashChange.bind(this), false)
    }
複製代碼

路由映射表

上面說的有一步,就是根據當前hash路由,找到須要渲染的組件,我們傳遞進來的事數組,查找起來費勁,轉成對象,方便查找和定位組件,咱們稱之爲路由映射表redux

constructor(options) {
        this.$options = options
        this.routeMap = {}
    }
    createRouteMap(options) {
        options.routes.forEach(item => {
            this.routeMap[item.path] = item
        })
    }

複製代碼

註冊組件router-view

在組件的render函數裏,根據this.app.current裏面存儲的路由,查找到組件 而後渲染便可,

::: tip 這裏的h,就是React裏面的createElement一個概念,之後給你們寫虛擬dom源碼的時候,你們會有更深入的理解 :::

Vue.component('router-view', {
    render:h=>{
        var component = this.routeMap[this.app.current].component
        return h(component)
    }
})

複製代碼

註冊組件router-link

router-link整成a標籤就能夠,記得帶上插槽 ::: tip 這裏面的寫法 就是傳說中的JSX :::

Vue.component('router-link', {
    props: {
        to: String
    },
    render(h){
        return <a href={this.to}>{this.$slots.default}</a>
    }
})

複製代碼

onHashchange

具體處理hash變化的邏輯,其實很easy,由於咱們利用Vue的響應式原理來存儲當前路由,咱們獲取當前的hash,而後直接利用Vue響應式機制通知router-view便可

constructor(options) {
        this.$options = options
        this.routeMap = {}
        this.app = new Vue({
            data: {
                current: '/'
            }
        })

    }
    onHashChange(e) {
        let hash = this.getHash()
        let router = this.routeMap[hash]
        this.app.current = this.getHash()
    }
複製代碼

路由守衛

註冊路由的時候,能夠帶上生命週期 而且生命週期的參數next,執行後才跳轉,能夠作路由守衛的工做,好比我們小小的模擬一下,2秒後再跳轉

{
      path: '/',
      name: 'home',
      component: Home,
      beforeEnter(from,to,next){
          console.log(`beforEnterHome from ${from} to ${to}`)
          setTimeout(()=>{
            next()
          },1000)
          // next()
      }
    },
複製代碼

實際的Router中,路由守衛的邏輯很複雜,是一個異步隊列的依次執行,有點像koa或者redux的中間件執行邏輯,我們只考慮一個 簡化一下邏輯

if(router.beforeEnter){
        router.beforeEnter(from, to, ()=>{
            this.app.current = this.getHash()

        })
    }else{
        this.app.current = this.getHash()
    }
複製代碼

擴展

實際的vue-router代碼,要複雜不少,不少容錯的處理,有幾個擴展點你們能夠重點關注

  1. 路由嵌套
    1. 遞歸註冊+ 層級查找渲染router-view
  2. 路由變量 (使用path-to-regexp)
  3. 路由守衛隊列
    1. 異步隊列
  4. addRouter動態添加路由

代碼

let Vue
class Router {
    static install(_Vue) {
        Vue = _Vue
        Vue.mixin({
            beforeCreate() {
                if(this.$options.router){
                    // new Vue的時候傳遞的
                    Vue.prototype.$routerMsg = '路由安裝完畢'
                    Vue.prototype.$router = this.$options.router
                    this.$options.router.init()
                }
            }
        })
    }
    constructor(options) {
        this.$options = options
        this.routeMap = {}
        this.app = new Vue({
            data: {
                current: '/'
            }
        })

    }
    // 綁定事件
    init() {
        this.bindEvents()
        this.createRouteMap(this.$options)
        this.initComponent(Vue)
    }
    bindEvents(){
        window.addEventListener('load', this.onHashChange.bind(this), false)
        window.addEventListener('hashchange', this.onHashChange.bind(this), false)
    }
    // 路由映射表
    createRouteMap(options) {
        options.routes.forEach(item => {
            this.routeMap[item.path] = item
        })
    }
    // 註冊組件
    initComponent(Vue) {
        Vue.component('router-link', {
            props: {
                to: String
            },
            render(h){
                return <a href={this.to}>{this.$slots.default}</a>
            }
        })
        Vue.component('router-view', {
            render:h=>{
                var component = this.routeMap[this.app.current].component
                return h(component)
            }
        })
    }
    getHash() {
        return window.location.hash.slice(1) || '/'
    }
    push(url){
      window.location.hash  = url
    }
    getFrom(e){
        let from, to
        if(e.newURL){
            from = e.oldURL.split('#')[1]
            to = e.newURL.split('#')[1]
        }else{
            from = ''
            to = location.hash
        }
        return {from,to}
    }
    // 設置當前路徑
    onHashChange(e) {
        let {from, to} = this.getFrom(e)
        let hash = this.getHash()
        let router = this.routeMap[hash]

        if(router.beforeEnter){
            router.beforeEnter(from, to, ()=>{
                this.app.current = this.getHash()

            })
        }else{
            this.app.current = this.getHash()
        }
    }
}
export default Router

複製代碼

各位小老弟 下次再見,瞭解完這些原理,須要的是實戰,如今Vue的最佳實踐我列個任務,慢慢寫

  1. typescript
  2. 組件化設計
  3. 自動化測試
  4. 先後端分離

歡迎你們提需求

相關文章
相關標籤/搜索