# Vue專案中建立progress bar ###### tags: `Vue` `progress bar` `beforeRouteEnter` `beforeRouteUpdate` `beforeRouteLeave` `beforeEnter` ## 使用套件 [NProgress](https://www.npmjs.com/package/nprogress) `npm install nprogress` --- ## include css 在main.js放NProgress的css檔 /main.js `import 'nprogress/nprogress.css'` --- 有三種方法使用NProgress.js ### Axios Interceptors 在api發送request前攔截,啟動progress bar,在api收到server的response前攔截,結束progress bar,類似middleware的概念 e.g. ```javascript import axios from 'axios' import NProgress from 'nprogress' // <--- Import the library const apiClient = axios.create({ ... }) apiClient.interceptors.request.use(config => { // Called on request NProgress.start() return config }) apiClient.interceptors.response.use(response => { // Called on response NProgress.done() return response }) ... ``` 缺點1. 只適用單一api的情況,若有複數api,會變成以下情況: 建立loading Vuex module 發出第一個api request後,NProgress.start(),計算等待傳送的api個數,遞增。開始收到response後,遞減等待傳送的api個數,為0時,NProgress.done()。 缺點2. Templates get rendered before the API call is returned: render 新的頁面時,Templates會先出現,同時progres bar在跑,等待api回傳data,等progres bar跑完後,把data放進已經render完的template, bad fo UX。 --- ### In-component route guards 在route components (the ones passed to the router configuration),有三個hooks可以使用,這三個hooks稱為Route Navigation Guards。根據[官方文件](https://router.vuejs.org/guide/advanced/navigation-guards.html#per-route-guard): ```javascript beforeRouteEnter(routeTo, routeFrom, next) beforeRouteUpdate(routeTo, routeFrom, next) beforeRouteLeave(routeTo, routeFrom, next) ``` 首先認識參數: routeTo: 指即將要被導向的route routeFrom: 指即將要被導離的route next: 在每個hook完成後一定要呼叫的function,繼續接下來的navigation 此三個hooks的定義如下: #### beforeRouteEnter(routeTo, routeFrom, next) This is called before the component is created. Since the component has not been created yet, we can’t use the this keyword here. e.g. 重新整理頁面,component再次被created時。 #### beforeRouteUpdate(routeTo, routeFrom, next) This is called when the route changes, but is still using the same component. An example here is when we paginate, and we switch from page to page but still using the same component. It does have access to “this”. component重新被使用時,會呼叫beforeRouteUpdate。 e.g. 按下next page,頁面內容的conponents被抽換data時。 #### beforeRouteLeave(routeTo, routeFrom, next) This is called when this component is navigated away from. It does have access to “this”. <br> 既然每個hook都要使用next(), next()的用法? **next()** - Called by itself will continue navigation to routeTo. **next(false)** - Cancels the navigation. **next('/')**- Redirects page to the / path. **next({ name: 'event-list' })** - Redirects to this named path e.g. ```javascript <script> import { mapState, mapActions } from 'vuex' import NProgress from 'nprogress' // <--- Include the progress bar import store from '@/store/store' // <--- Include our Vuex store export default { props: ['id'], beforeRouteEnter(routeTo, routeFrom, next) { NProgress.start() // Start the progress bar store.dispatch('event/fetchEvent', routeTo.params.id).then(() => { NProgress.done() // When the action is done complete progress bar next() // Only once this is called does the navigation continue }) }, ... } </script> ``` --- ### Global and per-route guards(simplest as far as I'm concerned) 最單純的方法就是,直接在router.js裡設定NProgress,要做到這點,必須了解global guards 和 per-router guards。 ... import EventShow from './views/EventShow.vue' import NProgress from 'nprogress' // <--- include the library Vue.use(Router) const router = new Router({ ... }) router.beforeEach((routeTo, routeFrom, next) => { // Start the route progress bar. NProgress.start() next() }) router.afterEach(() => { // Complete the animation of the route progress bar. NProgress.done() }) export default router 首先,在router.js中,因為我們要使用router.forEach() and router.afterEach(),必須define router,最後再export default router。 接著,分別在router.forEach()和router.afterEach()設定NProgress.start()和NProgress.done() 此時的Navidation Resolution flow如下: 1. global的router.beforeEach((routeTo, routeFrom, next) =>{ <br>...<br>next()}) 2. 如果在導向的component裡有beforeRouteEnter(routeTo, routeFrom, next),會來到這 3. global的router.afterEach((routeTo, routeFrom) =>{ ... }) 4. 接著才到Vue instance的life cycle hooks [完整的Navidation Resolution flow](https://router.vuejs.org/guide/advanced/navigation-guards.html#the-full-navigation-resolution-flow) 此時重新整理頁面時,會看到progress bar正常運作,但頁面並沒有等api call拿到data才render畫面。 解法之一: 在router中,使用[Per-Router](https://router.vuejs.org/guide/advanced/navigation-guards.html#per-route-guard),include Vuex store去呼叫action並取得data,然後當作props傳入component中,好處就是不需要在component中用Vuex,讓component只負責處理拿到的props就好,且之後維護和測試比較好進行。 e.g. ```javascript ... import EventShow from './views/EventShow.vue' import NProgress from 'nprogress' import store from '@/store/store' // <--- Include our store Vue.use(Router) const router = new Router({ mode: 'history', routes: [ ... { path: '/event/:id', name: 'event-show', component: EventShow, props: true, beforeEnter(routeTo, routeFrom, next) { // before this route is loaded store.dispatch('event/fetchEvent', routeTo.params.id).then(() => { next() }) } } ] ... ``` 注意:在這裏,我們需要Vuex store 中的 action fetchEvent return 回一個promise才能進行.then()之後的事,因此,若'event/fetchEvent'沒有return東西出來的話,畫面是不會如預期跑的。 此時,Navidation Resolution flow如下: 1. global的router.beforeEach((routeTo, routeFrom, next) =>{ <br>...<br>next()}) <div class="red">2. beforeEnter((routeTo, routeFrom, next) => {...})</div> 3. 如果在導向的component裡有beforeRouteEnter(routeTo, routeFrom, next),會來到這 4. global的router.afterEach((routeTo, routeFrom) =>{ ... }) 5. 接著才到Vue instance的life cycle hooks --- ### beforeEnter((routeTo, routeFrom, next) beforeEnter()只會在component created時被呼叫,當此component reused時,不會呼叫beforeEnter() <style> .red { color: red; } </style>