路由

不使用vue-router的狀況

代碼
官方給出下面的例子在不使用vue-router的狀況下來實現一個路由。
該示例結合了H5歷史管理API、單文件組件、JS模塊相關內容來實現路由javascript

clipboard.png

後面說的頁面只是一條瀏覽器歷史記錄,關於歷史記錄管理見歷史記錄管理(window.history)
simple_router.js爲入口,進行組件的加載,和其餘一些功能初始化工做。css

//simple_router.js
data: {
    currentRoute: window.location.pathname
},
computed: {
    ViewComponent() {
        const matchingView = routes[this.currentRoute]
        return matchingView
            ? require('./pages/' + matchingView + '.vue').default
            : require('./pages/404.vue').default
    }
},
render(h) {
    return h(this.ViewComponent)
}

屬性currentRoute是一個狀態轉換器(在VLink.vue中改變狀態),在下層組件中經過改變它來隨時切換頁面。它在轉換頁面前,會去路由表中查找對應的頁面路徑。html

routes.js爲路由表vue

//routes.js
export default {
    '/':'Home',
    '/about':'About'
}

每一個路由的頁面都是一個組件,切換頁面就是在這些組件中切換。要來回切換這些組件須要先加載它們,咱們在ViewComponent計算屬性中完成這個工做,經過currentRoute定位好對應的組件,在經過require(組件路徑).default在運行時動態的加載它,並在渲染函數中使用它。java

特別注意的是,若是你使用vue-loader@1.4+版本,這個require後面要加上default,不然報個加載模板失敗的錯誤,由於在這個版本及更高版本上,vue文件導出的全是esModule,而require加載的是commonjs格式的模塊,在這裏具體就是它返回的是一個包含default的對象的對象。
具體查看vue-loader API esModule選項ES6入門模塊部分記升級vue-loader版本時遇到的一個坑webpack

這裏入口中最重要的是別忘了,使用window.onpopstate管理歷史記錄,激活後退按鈕的功能ios

window.addEventListener('popstate', () => {
    app.currentRoute = window.location.pathname
})

以後就是定義須要切換的幾個組件,Home.vueAbout.vue404.vue,它們大同小異git

<!--Home.vue-->
<template>
  <main-layout>
    <p>Welcome home</p>
  </main-layout>
</template>

<script>
  import MainLayout from '../layouts/Main.vue'

  export default {
    components: {
      MainLayout
    }
  }
</script>

這些組件經過Main.vue完成具體的佈局es6

<!--Main.vue-->
<template>
  <div class="container">
    <ul>
      <li>
        <v-link href="/">Home</v-link>
        <v-link href="/about">About</v-link>
      </li>
    </ul>

    <slot></slot>
  </div>
</template>

<script>
  import VLink from '../components/VLink.vue'

  export default {
    components: {
      VLink
    }
  }
</script>

接着就是在Main.vue中使用子組件VLink.vuegithub

<!--VLink.vue-->
<template>
  <a
    v-bind:href="href"
    v-bind:class="{ active: isActive }"
    v-on:click="go"
  >
    <slot></slot>
  </a>
</template>

<script>
  import routes from '../routes'

  export default {
    props: {
      href: {
        type:String,
        required: true 
      }
    },
    computed: {
      isActive () {
        return this.href === this.$root.currentRoute
      }
    },
    methods: {
      go (event) {
        event.preventDefault()
        this.$root.currentRoute = this.href
        window.history.pushState(
          null,
          routes[this.href],
          this.href
        )
      }
    }
  }
</script>

VLink.vue很也是很關鍵,定義了一個連接,在開始入口文件中的currentRoute 這個狀態轉換器,就是經過點擊連接改變狀態的,而這個改變值this.href是經過佈局組件Main.vueProps數據下發下來的(<v-link href="/about">),最後別忘記把這個新狀態做爲URL推入歷史記錄管理的狀態棧中。

總結下就是 經過 simple_router.js定義如何加載頁面(運行時動態加載),經過 VLink.vue觸發這個加載,經過佈局文件將這兩部分結合在一塊兒。其中夾雜了歷史記錄的管理。

使用vue-router的狀況

該插件總體結構比較簡單,而細節很是繁瑣(用來解決各類各樣的缺陷和bug)。

命名路由子路由構成總體結構,咱們用它構建以下頁面。

clipboard.png

目錄結構以下

clipboard.png

代碼在這vue-router v0.1

//router.js
export default new VueRouter({
    routes: [
        { path: '/', component: Home },
        {
            path: '/settings', component: UserSettings,
            children: [
                { 
                    path: 'userinfo', 
                    component:UserInfo
                },
                {   
                    path: 'useremail', 
                    component:UserEmail
                }
            ]
        },
        {
            path: '/interaction', component: UserInteraction,
            children: [
                { path: 'userfriend', component: UserFriend },
                { path: 'userFollow', component: UserFollow}
            ]
        }
    ]
})

頂層路由及其對應的組件

<!-- index.html -->
<div id="app">
    <h1>vue-router 測試頁</h1>
    <div class="nav-header">
        <router-link to="/">用戶首頁</router-link>
        <router-link to="/settings">用戶設置</router-link>
        <router-link to="/interaction">社交管理</router-link>
    </div>
    <router-view></router-view>
</div>
<!-- UserSettings.vue -->
<template>
    <div class="container">
        <ul class="nav-left">
            <router-link tag="li" to="/settings/userinfo"><a>基本信息</a></router-link>
            <router-link tag="li" to="/settings/useremail"><a>更換郵箱</a></router-link>
        </ul>
        <div class="main-right">
            <router-view></router-view>
        </div>
    </div>
</template>
<!-- UserInteraction.vue -->
<template>
    <div class="container">
        <ul class="nav-left">
            <router-link tag="li" to="/interaction/userfriend"><a>個人好友</a></router-link>
            <router-link tag="li" to="/interaction/userfollow"><a>關注的人</a></router-link>
        </ul>
        <div class="main-right">
            <router-view></router-view>
        </div>
    </div>
</template>

第二層路由(子路由)及其對應組件,這裏

如下兩張圖說明路由和子路由是如何工做的。

clipboard.png

clipboard.png

第一張圖說明當咱們點擊連接,通過路由就能夠把對應的組件,放到頁面指定的<router-view>中。而一樣通過子路由就能夠把對應的組件,放到頂層組件中指定的<router-view>中。

而第二張,就是對路由過程的補充,經過路由或子路由去尋找對應組件,找到的組件再反過來放入視圖中。

命名路由

有時候咱們須要在一個組件中使用多塊<router-view>,以便增長組件的重用,就可使用命名路由。

好比咱們須要在用戶設置中的基本信息更換郵箱中都加一段廣告。

增長一個廣告組件Advertisement.vue

<!-- pages/common/Advertisement.vue -->
<template>
    <div class="adver">
        <h1>某廣告</h1>
    </div>
</template>

UserSettings.vue中添加一段命名的<router-view>

<!-- UserSettings.vue -->
<div class="main-right">
    <router-view></router-view>
    <!-- 添加一段視圖 -->
    <router-view name="adver"></router-view>
</div>

在路由文件中爲命名的<router-view>添加命名路由。default路由對應那些未命名的<router-view>

//router.js
//...
children: [
    { 
        path: 'userinfo', 
        components: {
            default:UserInfo,
            //添加命名路由
            adver:Advertisement
        }
    },
    {   
        path: 'useremail', 
        components: {
            default:UserEmail,
            //添加命名路由
            adver:Advertisement
        }
    }
]
//...

clipboard.png

具體代碼 添加命名路由

爲激活的連接添加樣式

被激活的連接,vue-router會爲其添加樣式類router-link-active,咱們在這個class中能夠爲其添加具體樣式,上面代碼中已經被添加了樣式。

a.router-link-active,
li.router-link-active>a {
    background-color: gainsboro;
    color: #37C6C0;
}

這裏被分紅兩種狀況
當這種不帶tag的寫法

<router-link to="/">用戶首頁</router-link>

被解析成

clipboard.png

tag的寫法

<router-link tag="li" to="/settings/userinfo"><a>基本信息</a></router-link>

被解析成

clipboard.png

router-link-active被添加在tag上。

細心的你能夠發現無論哪一個連接被激活,用戶首頁上始終存在着router-link-active,這是由於它的路由是/,因此在其餘路由被解析時,/也會被匹配。咱們能夠用exact解決這個問題,它使路徑字符串要徹底匹配。

clipboard.png

<router-link to="/" exact>用戶首頁</router-link>

clipboard.png

動態路由模糊匹配

代碼 v0.3 動態路由匹配與Props屬性

有時候一堆同級路由它們所對應的組件基本相同,咱們就可使用動態路由,匹配到同一個組件。
咱們在個人好友中有一個好友列表。當點擊某好友會顯示他的信息。

下面只使用了最簡單的匹配模式,關於更復雜的匹配,vue-router使用path-to-regexp動態匹配請求路徑,具體查看PocketLibs(2)—— 請求相關 path-to-regexp

clipboard.png

//router.js
//......
{
    path: '/interaction', component: UserInteraction,
    children: [
        { 
            path: 'userfriend', component: UserFriend,
            //添加好友信息 爲組件FriendInfo添加動態路由 
            children: [
                { path:'fd/:id',component:FriendInfo}
            ]
        },
        { path: 'userFollow', component: UserFollow}
    ]
}
//......

修改組件UserFriend.vue,添加好友列表與好友信息視圖

<!-- UserFriend.vue -->
<template>
    <div class="friend-list">
        <h3>好友列表</h3>
        <ul>
            <li v-for="item in 10" :key="item">
                <router-link :to="'/interaction/userfriend/fd/' + item ">好友{{item}}</router-link>
            </li>
            
        </ul>
        <div>
            <!--好友信息視圖-->
            <router-view></router-view>
        </div>
    </div>
</template>

添加好友信息組件FriendInfo.vue

<!-- FriendInfo.vue -->
<template>
    <div>好友{{$route.params.id}}信息</div>
</template>

使用命名組件和Props改進個人好友模塊

在剛纔的代碼中,UserFriend.vue中有如下代碼

<li v-for="item in 10" :key="item">
    <router-link :to="'/interaction/userfriend/fd/' + item ">好友{{item}}</router-link>
</li>

:to="'/interaction/userfriend/fd/' + item "有點違和。咱們能夠用命名路由改造一下。
先在路由文件中爲用戶信息對應的路由添加成name屬性,切換成命名路由。

//router.js
{ 
    path: 'userfriend', component: UserFriend,
    //添加好友信息 爲組件FriendInfo添加動態路由 
    children: [
        //爲動態路由添加name
        { path:'fd/:id',name:'fd',component:FriendInfo}
    ]
}

而後修改UserFriend.vue中的<router-link>

<!-- UserFriend.vue -->
<router-link :to="{name:'fd',params:{id:item}}">好友{{item}}</router-link>

效果與上面的同樣。

咱們再仔細看看組件FriendInfo.vue

<!--FriendInfo.vue-->
<template>
    <div>好友{{$route.params.id}}信息</div>
</template>

在組件中使用 $route 會使之與其對應路由造成高度耦合,從而使組件只能在某些特定的 URL 上使用,限制了其靈活性。咱們能夠爲路徑參數添加Props數據下發。
繼續修改好友信息的路由部分

//router.js
{ 
    path: 'userfriend', component: UserFriend,
    //添加好友信息 爲組件FriendInfo添加動態路由 
    children: [
        //爲動態路由添加name
        //爲路徑參數添加Props數據下發
        { path:'fd/:id',name:'fd',component:FriendInfo,props:true}
    ]
}

爲組件FriendInfo添加Props,並使用它。

<template>
    <!-- <div>好友{{$route.params.id}}信息</div> -->
    <div>好友{{id}}信息</div>
</template>
<script>
export default {
    props: ['id']
}
</script>

在路由中使用Props的其餘模式

若是在命名路由中使用Props數據下發,要爲每個對應組件,都設置Props。

{
  path: '/user/:id',
  components: { default: User, sidebar: Sidebar },
  props: { default: true, sidebar: false }
}

Props還能夠是一個對象

{ path:'fd/:id',name:'fd',component:FriendInfo,props:{id:1}}

還能夠是一個函數,函數接收一個$route對象

function dynamicPropsFn (route) {
  const now = new Date()
  return {
    name: (now.getFullYear() + parseInt(route.params.years)) + '!'
  }
}
//...
{ path: '/dynamic/:years', component: Hello, props: dynamicPropsFn }

鉤子函數、請求數據和進度條

代碼 vue-router 0.4 鉤子、請求數據與全局進度條
咱們使用axios發送請求,http://schematic-ipsum.heroku... 能夠做爲響應模擬一些基本的數據。
改造好友信息組件FriendInfo.vue,咱們在組件級鉤子beforeRouteUpdate中發送請求。

<template>
    <!-- <div>好友{{$route.params.id}}信息</div> -->
    <div class="friend-info">
        好友{{id}}信息
        <ul>
            <li v-if="name">姓名:{{name}}</li>
            <li v-if="phone">電話:{{phone}}</li>
            <li v-if="email">郵件:{{email}}</li>
        </ul>
    </div>
</template>
<script>
import axios from 'axios'
const reqObject = {
    "type": "object",
    "properties": {
        "name": {
          "type": "string",
          "ipsum": "name"
        },
        "phone": {
          "type": "string",
          "format": "phone"
        },
        "email": {
          "type": "string",
          "format": "email"
        }
    }
}
export default {
    props: ["id"],
    data(){
        return {
            name:null,
            phone:null,
            email:null,
        }
    },
    beforeRouteEnter(to, from, next) {
        console.log('beforeRouteEnter running..........')
        axios.post("http://schematic-ipsum.herokuapp.com/", reqObject)
            .then(response => {
                next(vm => {
                    vm.name = response.data.name;
                    vm.phone = response.data.phone;
                    vm.email = response.data.email;
                });
            });
    }
};
</script>

beforeRouteEnter鉤子中第三個參數next(),調用它時,才能夠繼續其餘操做(此時系統處於等待),咱們在獲取到響應時再調用它,所以在獲取響應後纔會看渲染效果。

clipboard.png

仔細觀察能夠發現,屢次查看好友信息,信息不會改變,查看控制檯發現beforeRouteEnter只調用了一次,只發送了一次信息。

clipboard.png

首次查看信息時,解析路由並使組件FriendInfo激活,咱們調用beforeRouteEnter,以後每次查看,組件不會從新被渲染,只會被重複利用,所以不會再調用beforeRouteEnter。注意該鉤子中不能使用this,由於在執行它時組件尚未被實例,可是next()的回調執行時,組件已被實例,它接收組件實例做爲參數(示例中的vm)

解決這個問題就靠beforeRouteUpdate,該鉤子在當前路由改變,組件被複用時調用。好比從/interaction/userfriend/fd/1/interaction/userfriend/fd/2時它就會調用。下面咱們就在FriendInfo中添加這個鉤子。

beforeRouteUpdate(to, from, next) {
    axios.post("http://schematic-ipsum.herokuapp.com/", reqObject)
        .then(response => {
            this.name = response.data.name;
            this.phone = response.data.phone;
            this.email = response.data.email;
            next();
        });
},

注意它是可使用this的,它的next()不接收回調。

clipboard.png

咱們看下youtube

clipboard.png

每次加載頁面會有個進度條

clipboard.png

咱們如今就使用全局鉤子函數和NProgress實現這個功能,在入口app.js文件中添加全局路由鉤子。

import Vue from 'Vue'
import nprogress from 'nprogress'
import 'nprogress/nprogress.css'
import router from './router'

const app = function () {
  //在任何導航被觸發前執行
  router.beforeEach((to, from, next) => {
    console.log('beforeEach')
    nprogress.start()
    next()
  })
  //導航中的最後一個鉤子
  router.afterEach((to, from) => {
    console.log('afterEach')
    nprogress.done()
  })

  new Vue({
    router,
    el: '#app'
  })
}

export { app }

clipboard.png

給路由視圖添加動畫

代碼 vue-router 0.5 爲<router-view>添加動畫
爲顯示好友信息添加動畫
修改組件UserFriend

<!-- UserFriend.vue -->
<div>
    <transition name="slide-left" mode="out-in">
        <router-view class="child-view"></router-view>
    </transition>
</div>

添加過渡類

.child-view {
  position: absolute;
  transition: all 1s cubic-bezier(.55,0,.1,1);
}
.slide-left-enter {
  opacity: 0;
  -webkit-transform: translate(60px, 0);
  transform: translate(60px, 0);
}
.slide-left-leave-active {
  opacity: 0;
  -webkit-transform: translate(-60px, 0);
  transform: translate(-60px, 0);
}

因爲在切換查看信息時,組件FriendInfo不會從新渲染,即除第一次外不會有動畫,所以須要設置key這裏有個不可預期的錯誤,即在<router-view>上設置key。

<!-- UserFriend.vue -->
<router-view class="child-view" :key="$route.params.id"></router-view>

千萬不能這麼作,key沒法傳遞給FriendInfo的根節點。

咱們須要在組件FriendInfo上設置key

//FriendInfo.vue
<div class="friend-info" :key="id">

clipboard.png

重定向

代碼 vue-router 0.6 重定向和別名
仿造個人好友,構造關注的人

clipboard.png

當點擊單數用戶時,顯示用戶信息,點擊雙數用戶時,顯示用戶被銷燬。
用戶信息爲一個組件,銷燬爲另外一個組件。
路由配置以下:

{ 
    path: 'userFollow', component: UserFollow,
    children: [
        {
            path:'fw/:id', name:'fw',
            redirect: to => {
                if(to.params.id%2===0)
                    return 'fd/:id'
                else
                    return 'fi/:id'
            },
            beforeEnter:(to, from, next) => {
                console.log(from)
                console.log(to)
                next()
            }
        },
        {
            path:'fd/:id', component:UserDestroy, props:true
        },
        {
            path:'fi/:id', component:FollowInfo, props:true
        }
    ]
}

每一個routerLink的連接路徑爲fw/:id,在redirect中配置重定向函數,路徑參數id爲單數重定向到路由fi/:id,雙數時,重定向到fd/:idredirect還能夠是個表示路由路徑的字符串或命名路由對象({name:'foo',params:{bar:'baz'}})。

另外在redirect屬性所在的路由中,導航守衛不會執行,如上面的beforeEnter不會執行(全局的守衛也不會執行)。

別名

咱們增長一個頂級導航系統公告

<div class="nav-header">
    <router-link to="/" exact>用戶首頁</router-link>
    <router-link to="/settings">用戶設置</router-link>
    <router-link to="/interaction">社交管理</router-link>
    <router-link to="/notification">系統公告</router-link>
</div>

爲它添加路由

{
    path: '/notification', component: SystemNotification
}

SystemNotification組件只是一個簡單的標題

<template>
    <div>
        <H3>系統公告</H3>
    </div>
</template>

clipboard.png

以後我想把這個頁面改爲個人消息頁面

clipboard.png

爲此咱們修改路由路徑/usermessage,將原來的名字配置爲別名alias:'/notification',並保持routerLink的路徑不變。

這樣作一是其餘網站引用該頁面不會產生404,二是路由內部配置引用該路由也不會找不到。

{
    path: '/usermessage', component: SystemNotification, alias:'/notification'
},

滾動行爲

代碼 vue-router 0.7 滾動行爲
個人消息中配置路由及其視圖,以下圖:

clipboard.png

配置4個路由及其對應組件

<H3>個人消息</H3>
<router-link to="/notification/system-msg">系統消息</router-link>
<router-link to="/notification/friend-msg">好友消息</router-link>
<router-link to="/notification/group-msg">用戶組消息</router-link>
<router-link to="/notification/other-msg">其餘消息</router-link>
<router-view></router-view>
path: '/usermessage', component: SystemNotification, alias: '/notification',
children: [
    { path: 'system-msg', component: SystemMessage, meta: { scrollToTop: false } },
    { path: 'friend-msg', component: FriendMessage, meta: { scrollToTop: true } },
    { path: 'group-msg', component: GroupMessage, meta: { scrollToTop: true } },
    { path: 'other-msg', component: OtherMessage, meta: { scrollToTop: true } }
]

四個組件中定義一系列字符串列表。在其中插入下一個消息列表的routerLink,如

clipboard.png

效果以下

clipboard.png

使用歷史記錄回退時,保持原紀錄位置,是正常的。但點擊連接,跳到新頁面,也是原來位置,這不是咱們預期的行爲,咱們使用路由屬性scrollBehavior解決這個問題。它與屬性routes是同一級別的屬性。

//router.js
scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
        return savedPosition
    } else {
        const position = {}
        if(to.hash) {
            position.selector = to.hash
            if(to.hash === "#anchor") {
                position.offset = { y: 100 }
            }
        }
        if (to.matched.some(m => m.meta.scrollToTop)) {
            position.x = 0
            position.y = 0
        }
        return position;
    }
}

爲了後面定位到錨點,咱們改造FriendMessage,爲連接到下一個消息列表的鏈接添加hash#anchor

<li><router-link to="/notification/group-msg#anchor">用戶組消息</router-link></li>

修改GroupMessage

<ul>
    <li v-for="item in 29" :key="item">用戶組消息{{item}}</li>
    <li id="anchor" style="border:1px solid red;" key="30">用戶組消息30</li>
    <li v-for="item in 20" :key="item+30">用戶組消息{{item+30}}</li>
    <li><router-link to="/notification/system-msg">其餘消息</router-link></li>
    <li v-for="item in 30" :key="item+50">用戶組消息{{item+50}}</li>
</ul>

scrollBehavior接收3個參數tofromsavedPosition。以上函數中savedPosition當且僅當 popstate 導航 (經過瀏覽器的 前進/後退 按鈕觸發) 時可用,這裏與默認的效果沒區別。咱們獲取to路由的元數據meta.scrollToTop(保存自定義的數據),當爲true時咱們切換到to對應的頁面時,咱們定位到{x:0,y:0},不然保持默認行爲。若是to存在hash,設置{selector:to.hash}定位到錨點,能夠具體定位錨點定位的偏移量{selector:to.hash,offset:{y:100}}

clipboard.png

History模式

code 0.8

在以上全部的請求路徑都帶#,這不是咱們所指望的,可是可用於全部的瀏覽器,這種模式爲默認的hash模式,如:

http://localhost:8080/#/settings/useremail

如今使用history模式,設置屬性mode

mode:"history",
routes: [/*...*/],
scrollBehavior(to, from, savedPosition) {/*...*/}

如今路徑就正常了,但它只能用於支持H5 History API的瀏覽器。

http://localhost:8080/settings/useremail

History API須要服務器的支持,不然當重載頁面時,會發生404頁面找不到,就像下面這樣。

clipboard.png

這裏使用webpack-dev-server,設置webpack的devServer.historyApiFallback爲true,使其支持History API。如

devServer: {
    historyApiFallback:true,
    noInfo: true
},

其餘服務器的配置見官方文檔後端配置例子

而後又發現一個bug,像下面這樣,顯示http://localhost:8080/settings/app.js找不到

clipboard.png

其實這是個webpack的問題,插入js資源時,像下面這樣

clipboard.png

它是相對於當前請求的路徑,爲了解決這個問題,咱們要在webpack中設置output中的publicPath屬性爲/

output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/',
    filename: '[name].js',
},

它在全部資源前加上虛擬路徑/app.js就變爲絕對路徑localhost:8080/app.js,這下就沒什麼問題了。

相關文章
相關標籤/搜索