深入Vue-router最佳實踐

前言

最近再刷Vue周邊生態(tài)的官方文檔,因為之前的學(xué)習(xí)都是看視頻配合著文檔唇跨,但主要還是通過視頻學(xué)習(xí)稠通,所以很多知識點都沒有了解衬衬,至從上次刷了Vuex的官方文檔就體會到了通讀文檔的好處,學(xué)習(xí)一門技術(shù)最好的還是去看它的官方文檔改橘,這樣對于這門技術(shù)你就會了解的比較透徹滋尉,知識點也比較全面,所以在刷完Vuex文檔之后寫了篇《深入Vuex最佳實踐》飞主,然后花了兩天(上班沒時間摸魚狮惜,都是晚上學(xué)習(xí))的時間刷完了Vue-router官方文檔,所以有了這篇文章碌识,所以后續(xù)還會一篇關(guān)于Vue-cli相關(guān)的配置文章碾篡,所以整篇文章主要從實踐角度切入,可能不會有那么多源碼解析(有點標題黨的味道筏餐,哈哈~??)开泽,但也會涉及到核心功能的源碼解讀

在線卑微,如果覺得這篇文章對你有幫助的話歡迎大家點個贊??

tip: 文章首發(fā)于掘金并做了排版美化推薦掘金閱讀體驗更好 戳我跳轉(zhuǎn)

簡介

Vue-router 是 Vue.js官方的路由管理器魁瞪。它和 Vue.js的核心深度集成穆律,讓構(gòu)建單頁面應(yīng)用變得易如反掌

先來了解兩點

  • 單頁面應(yīng)用(SPA)

  • 路由管理器

單頁面應(yīng)用

單頁面應(yīng)用程序?qū)⑺械幕顒泳窒抻谝粋€Web頁面中,僅在該Web頁面初始化時加載相應(yīng)的HTML导俘、JavaScript 和 CSS峦耘。一旦頁面加載完成了,SPA不會因為用戶的操作而進行頁面的重新加載或跳轉(zhuǎn)趟畏。取而代之的是利用 JavaScript 動態(tài)的變換HTML的內(nèi)容贡歧,從而實現(xiàn)UI與用戶的交互滩租。

路由管理器

這里的路由管理器并不是我們并時生活中的硬件路由器赋秀,這里的路由就是單頁應(yīng)用(SPA)的路徑管理器,就是為了解決Vue.js開發(fā)單頁面應(yīng)用不能進行鏈接跳轉(zhuǎn)律想,我們通過路徑的的方式來管理不同的頁面

了解Vue-router所解決的問題之后猎莲, 我們開始學(xué)習(xí)Vue-router在項目中常用的一些功能

  • 嵌套的路由/視圖表

  • 模塊化的、基于組件的路由配置

  • 路由參數(shù)技即、查詢著洼、通配符

  • 細粒度的導(dǎo)航控制

起步

在開始我們先體會下Vue-router的一些功能:

  • 動態(tài)路由匹配

  • 嵌套路由

  • 聲明式/編程式導(dǎo)航

  • 命名路由/命名視圖

  • 重定向和別名

  • 路由組件傳參

tip:本文章所有實例代碼倉庫地址在文章最后有給出

動態(tài)路由匹配

router.js

import Vue from 'vue' // 引入Vue
import Router from 'vue-router' // 引入vue-router
import Home from '@/pages/Home' //引入根目錄下的Hello.vue組件
 
// Vue全局使用Router
Vue.use(Router)

/*
    使用 Vue.js + Vue-router構(gòu)建單頁面應(yīng)用, 只要通過組合組件來組成我們的應(yīng)用程序, 我們引入Vue-router,只要    將組件映射到路由,告訴Vue-router在那里渲染它們
*/

let routes = [ // 配置路由,這里是個數(shù)組
  { // 每一個鏈接都是一個對象
    path: '/', // 鏈接路徑
    name: 'Home', // 路由名稱而叼,
    component: Home // 對應(yīng)的組件模板
  },
  // 動態(tài)路徑參數(shù) 以冒號開頭
  { path: '/user/:username', // 動態(tài)路由
    component: () => import('../pages/User1'), // 按需加載路由對應(yīng)的組件, 需要下載polyfill兼容ES6語法
  },
  {   // 多段路徑參數(shù)
    path: '/user/:id/post/:post_id', // 動態(tài)路由
    component: () => import('../pages/User2'), // 按需加載路由對應(yīng)的組件, 需要下載polyfill兼容ES6語法
  },
]

export default new Router({
  routes
})

User1

用戶訪問 /#/user/xxx的時候展示該組件

<template>
  <div class="User1">
    User1 - 單個路徑參數(shù)
  </div>
</template>

User2

用戶訪問 /#/user/xxx/post/xxx的時候展示該組件

<template>
  <div class="User2">
    User2 - 多段路徑參數(shù)路由
  </div>
</template

那么問題來了身笤,我們怎么知道用戶訪問的是那個動態(tài)參數(shù)路由呢?這個時候就要用到響應(yīng)路由參數(shù)的變化

兩種方式:watch (監(jiān)測變化) $route 對象, beforeRouteUpdate導(dǎo)航守衛(wèi)

user1.vue增加下面代碼

<template>
  <div class="User1">
    <!-- 通過router對象可以獲取到路由屬性葵陵, 這種方式耦合了液荸,后面會講路由組件傳參的方式 -->
    User1 -{{$route.params.username}} 單個路徑參數(shù)
  </div>
</template>

<script>
export default {
  name: 'User1',
  // 偵聽route對象方式
  watch: {
    $route (to, from) {
      this.$message.success(`watch -> ${to.path}, ${from.path}`)
    },
    
  },
  // vue2.2引入的導(dǎo)航守衛(wèi),當(dāng)路由參數(shù)發(fā)生變化調(diào)用
  beforeRouteUpdate (to, from, next) {
    this.$message.info(`導(dǎo)航守衛(wèi) -> ${to.path}, ${from.path}`)
    // 一定要調(diào)用next讓其繼續(xù)解析下一個管道中的路由組件
    next()
  }
}
</script>

演示

image

注意上面從ck->ks路由參數(shù)變化時兩種方式都監(jiān)聽到了脱篙,我們可以在這兩個函數(shù)中做一些路由狀態(tài)變化時的操作

路由組件傳參

上面在<tempate>模板中通過$router.prarams.username方式獲取路由傳遞的參數(shù)已經(jīng)于其對應(yīng)路由形成高度耦合娇钱,限制了其靈活性伤柄, 我們可以通過props將組件和路由進行解耦

props傳遞路由組件參數(shù)有三種方式:

  • 布爾模式

  • 對象模式

  • 函數(shù)模式

代碼

router.js

import Vue from 'vue'
import Router from 'vue-router'
import home from '@/pages/Home'

Vue.use(Router)

let routes = [
  {
    path: '/',
    name: 'Home',
    component: home
  },
  {  // 動態(tài)路徑參數(shù) 以冒號開頭
    path: '/user1/:username', // 動態(tài)路由
    component: () => import('../pages/User1'),
    props: true  // 布爾模式: 如果 props 被設(shè)置為 true,route.params 將會被設(shè)置為組件屬性文搂。
  },
  { 
    path: '/user2', 
    component: () => import('../pages/User2'),
    props: {username: 'ck'} // 對象模式: 只有靜態(tài)路由才能有效, 并且參數(shù)是寫死的
  },
  {
    path: '/user3/:username', 
    component: () => import('../pages/User3'),
    // 返回了用戶url中的參數(shù) 比如 /user3?username='ck' => {username: 'ck} 最終還是以對象模式的方式返回參數(shù)
    props: (route) => ({username: route.query.username}) // 函數(shù)模式
  }
]

export default new Router({
  routes
})

User2

對象模式

<template>
  <div class="User2">
    User2 - {{username}} 
  </div>
</template>

<script>
export default {
  name: 'User2',
  props: ['username']  // 通過props獲取路由傳遞給對應(yīng)組件的參數(shù)
}
</script>

User3

函數(shù)模式

<template>
  <div class="User3">
    User3 - {{username}}
  </div>
</template>

<script>
export default {
  name: 'User3',
  props: ['username']  // 通過props獲取路由傳遞給對應(yīng)組件的參數(shù)
}
</script>

演示

image

從上面我們可以看出因為user2是靜態(tài)路由所以不支持動態(tài)參數(shù)而且其對應(yīng)的路由組件傳遞過來的參數(shù)也是寫死的

嵌套路由

實際生活中的應(yīng)用界面适刀,通常由多層嵌套的組件組合而成。同樣地煤蹭,URL 中各段動態(tài)路徑也按某種結(jié)構(gòu)對應(yīng)嵌套的各層組件笔喉,例如:

+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------

router.js

import Vue from 'vue'
import Router from 'vue-router'
import home from '@/pages/Home'

Vue.use(Router)

let routes = [
  {
    path: '/',
    name: 'Home',
    component: home,
  },
  {
    path: '/user/:username', // 動態(tài)路由
    name: 'User',
    component: () => import('../components/User'),
    children: [
      {
       // 當(dāng) '/user/:username/profile' 匹配成功, UserProfile 會被渲染在 User 的 <router-view> 中
        path: 'profile', // 可以匹配 /user/ks/profile
        name: 'Profile',
        component: () => import('../components/Profile')
      },
      {
        path: '/user/:usrname/posts', // 這樣也可以匹配 /user/ks/posts硝皂, 但其實是將其匹配為了根組件的/user:username動態(tài)組件下的 posts
        name: 'Posts',
        component: () => import('../components/Posts')
      },
      {
        path: '',
        name: 'UserHome',
        // 當(dāng) /user/:username 匹配成功然遏,比如 /user/ks || /user/ck
        // UserHome 會被渲染在 User 的 <router-view> 中
        component: () => import('../components/UserHome')
      },
    ]
  },
  {
    path: '/footer',
    name: 'Foo',
    component: () => import('../components/Footer')
  }
]

export default new Router({
  routes
})

演示

image

聲明式/編程式導(dǎo)航

聲明式 編程式
<router-link :to="..." replace> router.replace(...)
<template>
  <div class="home">
       <!-- 聲明式 -->
    <router-link
      to="footer"
      tag="button"
    >
        to footer
    </router-link>  
      
    <!-- 編程式 -->
    <button @click="$router.push('footer')">字符串-寫路由路徑的方式</button>
    <button @click="$router.push({path: '/footer'})">對象-寫路徑的方式</button>
    <button @click="$router.push({name: 'Foo', params: {'userId': '123'}})">name和params - 寫路由名稱攜帶參數(shù)的方式</button>
    <button @click="$router.push({path: '/footer', query: {'userId': '456'}})">queyr和path - 寫路由路徑攜帶參數(shù)的方式</button>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'home',
  data () {
    return {
    }
  },
  methods: {
  }
}
</script>
<style>
button {
  display: block;
}
</style>
  • router.push(location, onComplete?, onAbort?)

  • router.replace(location, onComplete?, onAbort?)

這兩種的方式一樣, 唯一區(qū)別在于 push會產(chǎn)生路由的歷史記錄, 而repalce不會產(chǎn)生, 這對于window中的history是一致的

<!-- router.go方法 -->
<template>
     <button @click="goForward">go(1)-前進一步</button>
    <button @click="goBack">go(-1)-后退一步</button>
    <button @click="gogogo">go(100)-前進一白步</button>   
 </template>

<script>
 export default {
    name: 'home'
    methods: {
        goForward () {
          // 從歷史路由中前進一步相當(dāng)于 window.history.forward
          this.$router.go(1);
        },
        goBack () {
              // 從歷史路由中后退一步相當(dāng)于 window.history.back
              this.$router.go(-1);
        },
        gogogo () {
              // 歷史路由中沒有100步, 就啥也不干
              this.$router.go(100);
        }
    }  
 }
</script>

演示

image

命名路由/命名視圖/重定向和別名

router.js

import Vue from 'vue'
import Router from 'vue-router'
import UserSettings from '@/pages/UserSettings'

Vue.use(Router)

let routes = [
  {
    path: '/',
    redirect: '/settings' // 重定向
  },
  {
    path: '/settings',
    name: 'Settings', // 命名路由
    alias: '/a', // 取別名,當(dāng)url中訪問 /a -> 也是訪問的 settings組件但是路由匹配的是/a, 就相當(dāng)于用戶訪問 /a一樣
    // 你也可以在頂級路由就配置命名視圖
    component: UserSettings,
    children: [
      {
        path: 'emails',
        component: () => import('../pages/UserEmails')
      }, 
      {
        path: 'profile',
        components: {
          default: () => import('../pages/UserProfile'),
          helper: () => import('../pages/UserProfilePreview')
        }
      }
    ]
  }
]

export default new Router({
  routes
})

UserSetttings

<template>
  <div class="UserSettings">
    <h1>User Settings</h1>
    <NavBar/>
    <router-view/>
    <!-- 命名視圖 -->
    <router-view name="helper"/>  
  </div>
</template>

<script>
import NavBar from '../components/NavBar'
export default {
  name: 'UserSettings',
  components: {
    NavBar
  }
}
</script>

通過上面的學(xué)習(xí)相信大家已經(jīng)撐握了Vue-router在項目中所常用的功能秧倾,下面我們開始學(xué)習(xí)Vue-router的導(dǎo)航守衛(wèi)

進階

導(dǎo)航守衛(wèi)

“導(dǎo)航”表示路由正在發(fā)生改變赡艰。記住參數(shù)或查詢的改變并不會觸發(fā)進入/離開的導(dǎo)航守衛(wèi)。你可以通過觀察 $route 對象響應(yīng)路由參數(shù)的變化來應(yīng)對這些變化,或使用 beforeRouteUpdate 的組件內(nèi)守衛(wèi)贮泞。

全局的守衛(wèi)

  • 全局前置守衛(wèi) (router.beforeEach)

  • 全局解析守衛(wèi) (router.breforeResolve)

  • 全局后置鉤子 (router.afterEach) 注:這個鉤子中不存在next

路由獨享的守衛(wèi)

你可以在路由配置上直接定義 beforeEnter 守衛(wèi):

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // to -> 要跳轉(zhuǎn)過去的路由信息
        // from -> 當(dāng)前的路由信息
        // next() => 一個函數(shù)令蛉,表示解析下一個管道中的路由記錄
      }
    }
  ]
})

組件內(nèi)的守衛(wèi)

最后运杭,你可以在路由組件內(nèi)直接定義以下路由導(dǎo)航守衛(wèi):

  • beforeRouteEnter

  • beforeRouteUpdate (2.2 新增)

  • beforeRouteLeave

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染該組件的對應(yīng)路由被 confirm 前調(diào)用
    // 不!能!獲取組件實例 `this`
    // 因為當(dāng)守衛(wèi)執(zhí)行前,組件實例還沒被創(chuàng)建
  },
  beforeRouteUpdate (to, from, next) {
    // 在當(dāng)前路由改變,但是該組件被復(fù)用時調(diào)用
    // 舉例來說,對于一個帶有動態(tài)參數(shù)的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉(zhuǎn)的時候,
    // 由于會渲染同樣的 Foo 組件,因此組件實例會被復(fù)用飘痛。而這個鉤子就會在這個情況下被調(diào)用塑猖。
    // 可以訪問組件實例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 導(dǎo)航離開該組件的對應(yīng)路由時調(diào)用
    // 可以訪問組件實例 `this`
  }
}

beforeRouteEnter 守衛(wèi) 不能 訪問 this令花,因為守衛(wèi)在導(dǎo)航確認前被調(diào)用扮碧,因此即將登場的新組件還沒被創(chuàng)建搔啊。不過柬祠,你可以通過傳一個回調(diào)給 next來訪問組件實例漫蛔。在導(dǎo)航被確認的時候執(zhí)行回調(diào),并且把組件實例作為回調(diào)方法的參數(shù)脑奠。

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 在實例創(chuàng)建好之后會調(diào)用next傳遞過去的回調(diào)并且將實例當(dāng)做參數(shù)傳遞進來,所以通過 `vm` 可以訪問組件實例
  })
}

注意 beforeRouteEnter 是支持給 next 傳遞回調(diào)的唯一守衛(wèi)齿诞。對于 beforeRouteUpdatebeforeRouteLeave 來說斑司,this 已經(jīng)可用了,所以不支持傳遞回調(diào)吠式,因為沒有必要了陡厘。

beforeRouteUpdate (to, from, next) {
  // just use `this`
  this.name = to.params.name
  next()
}

這個離開守衛(wèi)通常用來禁止用戶在還未保存修改前突然離開。該導(dǎo)航可以通過 next(false) 來取消特占。

beforeRouteLeave (to, from, next) {
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (answer) {
    next()
  } else {
    next(false)
  }
}

實踐

上面講了那么多相信大家也是懵懵的糙置,這些路由調(diào)用的時機是怎么樣的,順序又是怎么樣的是目,下面我們按照官方給的解釋實踐一下

完整的導(dǎo)航解析流程

  1. 導(dǎo)航被觸發(fā)谤饭。

  2. 在失活的組件里調(diào)用 beforeRouteLeave 守衛(wèi)。

  3. 調(diào)用全局的 beforeEach 守衛(wèi)懊纳。

  4. 在重用的組件里調(diào)用 beforeRouteUpdate 守衛(wèi) (2.2+)揉抵。

  5. 在路由配置里調(diào)用 beforeEnter

  6. 解析異步路由組件嗤疯。

  7. 在被激活的組件里調(diào)用 beforeRouteEnter冤今。

  8. 調(diào)用全局的 beforeResolve 守衛(wèi) (2.5+)。

  9. 導(dǎo)航被確認茂缚。

  10. 調(diào)用全局的 afterEach 鉤子戏罢。

  11. 觸發(fā) DOM 更新。

  12. 用創(chuàng)建好的實例調(diào)用 beforeRouteEnter 守衛(wèi)中傳給 next 的回調(diào)函數(shù)脚囊。

router.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '@/pages/Home'
import {message} from 'ant-design-vue'

Vue.use(VueRouter)

let routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/index',
    name: 'Index',
    component: () => import('../pages/Index'),
  },
  {
    path: '/user/:id',
    name: 'User',
    props: true,
    component: () => import('../pages/User'),
    beforeEnter: (to, from, next) => {
      message.success(`路由獨享守衛(wèi)[beforeEnter] -> 從${from.path} 到 ${to.path}`);
      next()
    }
  }
]

let router = new VueRouter({
  routes
})
router.beforeEach((to, from, next) => {
  message.success(`全局前置守衛(wèi)[beforeEach] -> 從${from.path} 到 ${to.path}`, 5)
  next();
})

router.beforeResolve((to, from, next) => {
  message.success(`全局解析守衛(wèi)[beforeResolve] -> 從${from.path} 到 ${to.path}`, 5)
  next();
})

router.afterEach((to, from) =>  {
  // 鉤子沒有next, 也不會改變導(dǎo)航本身
  message.success(`全局后置鉤子[afterEach] -> 從${from.path} 到 ${to.path}`, 5)
})


export default router

Home.vue

<template>
  <div class="Home">
    <div class="title">Home</div>
    <a-button type="primary" @click="toIndexHanlder">
      to Index
    </a-button>
  </div>
</template>

<script>
export default {
  name: 'Home',
  beforeRouteLeave(to, from, next) {
    this.$message.success(`組件內(nèi)守衛(wèi)[leave] -> 從${from.path} 到 ${to.path}`, 5);
    next();
  },
  methods: {
    toIndexHanlder() {
      this.$router.push({ path: '/index' });
    },
  },
};
</script>

Index.vue

<template>
  <div class="Index">
    <div class="title">Index</div>
    <a-button class="my-btn" type="primary" @click="BackHanlder">
      返回
    </a-button>
    <a-button class="my-btn" type="primary" @click="toUserHanlder">
      toUser
    </a-button>
  </div>
</template>

<script>
export default {
  name: 'Index',
  beforeRouteLeave (to, from, next) {
    console.log(to);
    next()
  },
  methods: {
    BackHanlder () {
      this.$router.go(-1)
    },
    toUserHanlder () {
      this.$router.push({path: 'user/ck'})
    }
  }
}
</script>

User.vue

<template>
  <div class="User">
    <div class="title">User - {{id}}</div>
    <a-button class="my-btn" type="primary" @click="toUserJump">
      跳轉(zhuǎn)動態(tài)路由
    </a-button>
  </div>
</template>

<script>
export default {
  name: 'User',
  data () {
    return {
      names: ['a', 'b', 'c', 'd', 'e', 'f'], // 隨機路由
      curNameIndex: 0,
      lastNameIndex: 0,
    }
  },
  props: ['id'],
  beforeRouteUpdate (to, from, next) {
    this.$message.success(`組件內(nèi)的守衛(wèi)[beforeRouteUpdate] -> 從${from.path} 到 ${to.path}`, 5)
    next()
  },
  beforeRouteEnter (to, from, next) {
    // 不能獲取this, 因為當(dāng)守衛(wèi)執(zhí)行前龟糕,組件實例還沒被創(chuàng)建, 
    // 但是在這個守衛(wèi)中next支持傳遞回調(diào), 在實例創(chuàng)建完畢后調(diào)用 
    next((vm) => {
      // vm => 創(chuàng)建好的Vue實例
      vm.$message.success(`組件內(nèi)的守衛(wèi)[beforeRouteEnter] -> 從${from.path} 到 ${to.path}`, 5)
    })
  },
  methods: {
    // 獲取隨機路由的方法
   getRandomArbitrary (min, max) {
        this.curNameIndex = Math.round(Math.random() * (max - min) + min);
        return this.curNameIndex === this.lastNameIndex 
        ? this.getRandomArbitrary(min, max) 
        : this.curNameIndex;
    },
    toUserJump () {
      this.getRandomArbitrary(0, this.names.length -1)
      this.lastNameIndex = this.curNameIndex;
      this.$router.push({path: `/user/${this.names[this.curNameIndex]}`})
    }
  }
}
</script>

5.gif

演示

上面動圖可能過于快了, 將其截圖下來每一步做下分析


![4.png](https://upload-images.jianshu.io/upload_images/20740092-b6c202da9f27b8e6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

上面標的數(shù)子是對應(yīng)官方給的順序

從Home.vue跳轉(zhuǎn)到Index.vue觸發(fā)的路由守衛(wèi)

    1. 點擊按鈕導(dǎo)航被觸發(fā)
    1. 在失活的組件守衛(wèi)中(Home.vue)調(diào)用的beforeRouterLeave, 表示離開該組件
    1. 調(diào)用全局前置守衛(wèi)beforeEach, 從route對象中可以獲取我們跳轉(zhuǎn)前和跳轉(zhuǎn)后的路由信息
    1. 路由解析完畢調(diào)用


      4.png

上面標的數(shù)子是對應(yīng)官方給的順序

Index.vue跳轉(zhuǎn)到user/ck觸發(fā)的路由守衛(wèi)

    1. 調(diào)用全局前置守衛(wèi)beforeEach
    1. 在路由配置(User.vue)中調(diào)用befreEnter
    1. 調(diào)用全局的 afterEach 鉤子。
    1. 用創(chuàng)建好的實例調(diào)用 beforeRouteEnter 守衛(wèi)中傳給 next 的回調(diào)函數(shù)并且將創(chuàng)建好的實例傳遞進去了

5.png

user/ck跳轉(zhuǎn)到user/c觸發(fā)的路由守衛(wèi)

    1. 因為這個組件是動態(tài)路由, 在/user/ck -> user/c重用同一個組件所以觸發(fā)beforeRoteUpdate

案列

[圖片上傳失敗...(image-147e46-1594365992693)]

該案列涉及到到了

  • 動態(tài)修改路由元信息,修改文檔標題

  • 基于路由的動態(tài)過渡

  • 基于導(dǎo)航守衛(wèi)對用戶登錄狀態(tài)進行攔截

  • 對于沒有定義的組件投統(tǒng)一返回404頁面

  • 使用路由的離開守衛(wèi)判對于用戶跳轉(zhuǎn)登錄頁面進行確認

戳我去GitHub倉庫地址,歡迎大家點個Start??

源碼解讀

6.gif

vue-router 實現(xiàn)原理

vue-router 實例化時會初始化 this.history,傳入不同 mode 會對應(yīng)不同的 history颂鸿,下面來看下代碼

constructor (options: RouterOptions = {}) {
    this.mode = mode // 不傳mode, 默認就是hash
    
    switch (mode) {
      case 'history':
        this.history = new HTML5History(this, options.base)
        break
      case 'hash': 
        this.history = new HashHistory(this, options.base, this.fallback)
        break
      case 'abstract':
        this.history = new AbstractHistory(this, options.base)
        break
      default:
        if (process.env.NODE_ENV !== 'production') {
          assert(false, `invalid mode: ${mode}`)
        }
    }
}
// => 上面通過HashHistory初始化之后會得到其實例缓艳,我們調(diào)用的一些 push、replace泄鹏、go都是this.history上的方法

這里以 HashHistory 為例郎任,vue-router 的 push 方法實現(xiàn)如下:

push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    // $flow-disable-line
    if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
      return new Promise((resolve, reject) => {
        this.history.push(location, resolve, reject)
      })
    } else {
      this.history.push(location, onComplete, onAbort)
    }
}
// 在調(diào)用push方法之后調(diào)用了this.history的push方法

HashHistory 具體實現(xiàn)了 push 方法:

function pushHash (path) {
  if (supportsPushState) {
    pushState(getUrl(path))
  } else {
    window.location.hash = path // 本質(zhì)上還是通過對window.location.hash方法進行的封裝
  }
}

對路由的監(jiān)聽通過 hash 相應(yīng)的事件監(jiān)聽實現(xiàn):

window.addEventListener(
  supportsPushState ? 'popstate' : 'hashchange',
  () => {
    const current = this.current
    if (!ensureSlash()) {
      return
    }
    this.transitionTo(getHash(), route => {
      if (supportsScroll) {
        handleScroll(this.router, route, current, true)
      }
      if (!supportsPushState) {
        replaceHash(route.fullPath)
      }
    })
  }
) 

// 對于路由的監(jiān)聽也是通過監(jiān)聽window對象提供的 popstate、hashchange兩個事件對于hash的監(jiān)聽來做出不同的響應(yīng)

所以备籽,Vue-router最核心的也是通過History來實例相應(yīng)的功能,而History是由傳遞進去的mode決定,不同的History調(diào)用的底層方法不一樣车猬,但底層都是通過window.location提供的一些方法進行實例霉猛,比如hash改變就是通過hashchange這個事件監(jiān)聽來支持的,所以Vue-router本質(zhì)上就是對于原生事件的封裝

除此之外珠闰,vue-router 還提供了兩個組件:

Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
// => 所以我們就可以在全局上使用 <router-view> <router-link> 這兩個內(nèi)置組件

寫在最后

因為是是實踐文惜浅,所以這整篇文章都是通過代碼的方式來講的,對于一些概念性和基礎(chǔ)性語法的東西講的比較少伏嗜。如果 這篇文章對你有幫助請點個贊??

看完兩件小事

如果你覺得我的文章對你挺有幫助坛悉,我想請你幫我兩個小忙:

  1. 關(guān)注我的 GitHub 博文,讓我們成為長期關(guān)系

  2. 關(guān)注公眾號「前端自學(xué)驛站」承绸,所有文章裸影、資料第一時間首發(fā)公眾號,公眾號后臺回復(fù)「教程」 免費領(lǐng)取我精心整理的前端視頻教程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末军熏,一起剝皮案震驚了整個濱河市轩猩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荡澎,老刑警劉巖均践,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異摩幔,居然都是意外死亡彤委,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門或衡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來焦影,“玉大人,你說我怎么就攤上這事薇宠⊥蛋欤” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵澄港,是天一觀的道長椒涯。 經(jīng)常有香客問我废岂,道長湖苞,這世上最難降的妖魔是什么财骨? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己砚亭,他們只是感情好寻仗,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布曹体。 她就那樣靜靜地躺著滞谢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪喘漏。 梳的紋絲不亂的頭發(fā)上垦江,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播洞翩,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼虑椎,長吁一口氣:“原來是場噩夢啊……” “哼震鹉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤泥技,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后丛楚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡喧务,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年庐冯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坎穿。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡展父,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出玲昧,到底是詐尸還是另有隱情栖茉,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布孵延,位于F島的核電站吕漂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏尘应。R本人自食惡果不足惜惶凝,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一吼虎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧苍鲜,春花似錦思灰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至遍坟,卻和暖如春拳亿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背愿伴。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留电湘,地道東北人隔节。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像寂呛,于是被迫代替她去往敵國和親怎诫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353