簡單的路由原理,基于組件
const NotFound = { template: '<p>Page not found</p>' }
const Home = { template: '<p>home page</p>' }
const About = { template: '<p>about page</p>' }
const routes = {
'/': Home,
'/about': About
}
new Vue({
el: '#app',
data: {
currentRoute: window.location.pathname
},
computed: {
ViewComponent () {
return routes[this.currentRoute] || NotFound
}
},
render (h) { return h(this.ViewComponent) }
})
vue-router學習
1.使用
npm install vue-router
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
2.入口
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 組件來導(dǎo)航. -->
<!-- 通過傳入 `to` 屬性指定鏈接. -->
<!-- <router-link> 默認會被渲染成一個 `<a>` 標簽 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的組件將渲染在這里 -->
<router-view></router-view>
</div>
3.JavaScript
// 0. 如果使用模塊化機制編程, 要調(diào)用 Vue.use(VueRouter)
// 1. 定義(路由)組件。
// 可以從其他文件 import 進來
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 2. 定義路由
// 每個路由應(yīng)該映射一個組件。 其中"component" 可以是
// 通過 Vue.extend() 創(chuàng)建的組件構(gòu)造器,
// 或者,只是一個組件配置對象。
// 我們晚點在討論嵌套路由。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
// 3. 創(chuàng)建 router 實例,然后傳 `routes` 配置
// 你還可以傳別的配置參數(shù), 不過先這么簡單著吧。
const router = new VueRouter({
routes // (縮寫)相當于 routes: routes
})
// 4. 創(chuàng)建和掛載根實例。
// 記得要通過 router 配置參數(shù)注入路由,
// 從而讓整個應(yīng)用都有路由功能
const app = new Vue({
router
}).$mount('#app')
// 現(xiàn)在,應(yīng)用已經(jīng)啟動了!
動態(tài)路由監(jiān)聽
$route.params.
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 對路由變化作出響應(yīng)...
}
}
}
4.嵌套路由
<div id="app">
<p>
<router-link to="/user/foo">/user/foo</router-link>
<router-link to="/user/foo/profile">/user/foo/profile</router-link>
<router-link to="/user/foo/posts">/user/foo/posts</router-link>
</p>
<router-view></router-view>
</div>
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`
}
const UserHome = { template: '<div>Home</div>' }
const UserProfile = { template: '<div>Profile</div>' }
const UserPosts = { template: '<div>Posts</div>' }
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
// UserHome will be rendered inside User's <router-view>
// when /user/:id is matched
{ path: '', component: UserHome },
// UserProfile will be rendered inside User's <router-view>
// when /user/:id/profile is matched
{ path: 'profile', component: UserProfile },
// UserPosts will be rendered inside User's <router-view>
// when /user/:id/posts is matched
{ path: 'posts', component: UserPosts }
]
}
]
})
5.編程式的導(dǎo)航
// 字符串
router.push('home')
// 對象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})
// 帶查詢參數(shù),變成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
router.replace(location)
跟 router.push
很像,唯一的不同就是,它不會向 history 添加新記錄,而是跟它的方法名一樣 —— 替換掉當前的 history 記錄。
router.go(n)
// 在瀏覽器記錄中前進一步,等同于 history.forward()
router.go(1)
// 后退一步記錄,等同于 history.back()
router.go(-1)
// 前進 3 步記錄
router.go(3)
// 如果 history 記錄不夠用,那就默默地失敗唄
router.go(-100)
router.go(100)
6.命名路由
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const Home = { template: '<div>This is Home</div>' }
const Foo = { template: '<div>This is Foo</div>' }
const Bar = { template: '<div>This is Bar {{ $route.params.id }}</div>' }
const router = new VueRouter({
mode: 'history',
base: __dirname,
routes: [
{ path: '/', name: 'home', component: Home },
{ path: '/foo', name: 'foo', component: Foo },
{ path: '/bar/:id', name: 'bar', component: Bar }
]
})
new Vue({
router,
template: `
<div id="app">
<h1>Named Routes</h1>
<p>Current route name: {{ $route.name }}</p>
<ul>
<li><router-link :to="{ name: 'home' }">home</router-link></li>
<li><router-link :to="{ name: 'foo' }">foo</router-link></li>
<li><router-link :to="{ name: 'bar', params: { id: 123 }}">bar</router-link></li>
</ul>
<router-view class="view"></router-view>
</div>
`
}).$mount('#app')
7.命名視圖
(同級)展示多個視圖,而不是嵌套展示,例如創(chuàng)建一個布局,有 sidebar
(側(cè)導(dǎo)航) 和main
(主內(nèi)容) 兩個視圖,這個時候命名視圖就派上用場了
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<div id="app">
<h1>Named Views</h1>
<ul>
<li><router-link to="/">/</router-link></li>
<li><router-link to="/other">/other</router-link></li>
</ul>
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
</div>
</div>
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
const Baz = { template: '<div>baz</div>' }
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/',
// a single route can define multiple named components
// which will be rendered into <router-view>s with corresponding names.
components: {
default: Foo,
a: Bar,
b: Baz
}
},
{
path: '/other',
components: {
default: Baz,
a: Bar,
b: Foo
}
}
]
})
new Vue({
router,
el: '#app'
})
8.重定向 和 別名
重定向也是通過 routes 配置來完成,下面例子是從 /a 重定向到 /b:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
重定向的目標也可以是一個命名的路由:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
甚至是一個方法,動態(tài)返回重定向目標:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目標路由 作為參數(shù)
// return 重定向的 字符串路徑/路徑對象
}}
]
})
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const Home = { template: '<div><h1>Home</h1><router-view></router-view></div>' }
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
const Baz = { template: '<div>baz</div>' }
const router = new VueRouter({
mode: 'history',
base: __dirname,
routes: [
{ path: '/home', component: Home,
children: [
// absolute alias
{ path: 'foo', component: Foo, alias: '/foo' },
// relative alias (alias to /home/bar-alias)
{ path: 'bar', component: Bar, alias: 'bar-alias' },
// multiple aliases
{ path: 'baz', component: Baz, alias: ['/baz', 'baz-alias'] }
]
}
]
})
new Vue({
router,
template: `
<div id="app">
<h1>Route Alias</h1>
<ul>
<li><router-link to="/foo">
/foo (renders /home/foo)
</router-link></li>
<li><router-link to="/home/bar-alias">
/home/bar-alias (renders /home/bar)
</router-link></li>
<li><router-link to="/baz">
/baz (renders /home/baz)</router-link>
</li>
<li><router-link to="/home/baz-alias">
/home/baz-alias (renders /home/baz)
</router-link></li>
</ul>
<router-view class="view"></router-view>
</div>
`
}).$mount('#app')
1.導(dǎo)航鉤子
vue-router
提供的導(dǎo)航鉤子主要用來攔截導(dǎo)航,讓它完成跳轉(zhuǎn)或取消。有多種方式可以在路由導(dǎo)航發(fā)生時執(zhí)行鉤子:全局的, 單個路由獨享的, 或者組件級的。
(1)全局鉤子
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
每個鉤子方法接收三個參數(shù):
**to: Route
**: 即將要進入的目標 路由對象
**from: Route
**: 當前導(dǎo)航正要離開的路由
**next: Function
**: 一定要調(diào)用該方法來 resolve 這個鉤子。執(zhí)行效果依賴 next
方法的調(diào)用參數(shù)。
**next()
**: 進行管道中的下一個鉤子。如果全部鉤子執(zhí)行完了,則導(dǎo)航的狀態(tài)就是 **confirmed**
(確認的)。
**next(false)
**: 中斷當前的導(dǎo)航。如果瀏覽器的 URL 改變了(可能是用戶手動或者瀏覽器后退按鈕),
那么 URL 地址會重置到 from 路由對應(yīng)的地址。
**next('/')
或者 next({ path: '/' })
**: 跳轉(zhuǎn)到一個不同的地址。當前的導(dǎo)航被中斷,然后進行一個新的導(dǎo)航。
(2)某個路由獨享的鉤子
你可以在路由配置上直接定義 beforeEnter 鉤子:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
(3)組件內(nèi)的鉤子
最后,你可以使用 beforeRouteEnter 和 beforeRouteLeave,在路由組件內(nèi)直接定義路由導(dǎo)航鉤子,
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) => {
// 在渲染該組件的對應(yīng)路由被 confirm 前調(diào)用
// 不!能!獲取組件實例 `this`
// 因為當鉤子執(zhí)行前,組件實例還沒被創(chuàng)建
},
beforeRouteLeave (to, from, next) => {
// 導(dǎo)航離開該組件的對應(yīng)路由時調(diào)用
// 可以訪問組件實例 `this`
}
}
beforeRouteEnter 鉤子 不能 訪問 this,因為鉤子在導(dǎo)航確認前被調(diào)用,因此即將登場的新組件還沒被創(chuàng)建。
不過,你可以通過傳一個回調(diào)給 next來訪問組件實例。在導(dǎo)航被確認的時候執(zhí)行回調(diào),并且把組件實例作為回調(diào)方法的參數(shù)。
beforeRouteEnter (to, from, next) => {
next(vm => {
// 通過 `vm` 訪問組件實例
})
}
可以 在 beforeRouteLeave 中直接訪問 this。這個 leave 鉤子通常用來禁止用戶在還未保存修改前突然離開??梢酝ㄟ^ next(false) 來取消導(dǎo)航。
2.路由元信息
定義路由的時候可以配置 meta 字段:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: { requiresAuth: true }
}
]
}
]
})
下面例子展示在全局導(dǎo)航鉤子中檢查 meta 字段:
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!auth.loggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // 確保一定要調(diào)用 next()
}
})
3.滾動特效
<router-view> 是基本的動態(tài)組件,所以我們可以用 <transition>
組件給它添加一些過渡效果:
<transition>
<router-view></router-view>
</transition>
單個路由的過渡
const Foo = {
template: `
<transition name="slide">
<div class="foo">...</div>
</transition>
`
}
const Bar = {
template: `
<transition name="fade">
<div class="bar">...</div>
</transition>
`
}
動態(tài)路由
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const Home = {
template: `
<div class="home">
<h2>Home</h2>
<p>hello</p>
</div>
`
}
const Parent = {
data () {
return {
transitionName: 'slide-left'
}
},
// dynamically set transition based on route change
watch: {
'$route' (to, from) {
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
}
},
template: `
<div class="parent">
<h2>Parent</h2>
<transition :name="transitionName">
<router-view class="child-view"></router-view>
</transition>
</div>
`
}
const Default = { template: '<div class="default">default</div>' }
const Foo = { template: '<div class="foo">foo</div>' }
const Bar = { template: '<div class="bar">bar</div>' }
const router = new VueRouter({
mode: 'history',
base: __dirname,
routes: [
{ path: '/', component: Home },
{ path: '/parent', component: Parent,
children: [
{ path: '', component: Default },
{ path: 'foo', component: Foo },
{ path: 'bar', component: Bar }
]
}
]
})
new Vue({
router,
template: `
<div id="app">
<h1>Transitions</h1>
<ul>
<li><router-link to="/">/</router-link></li>
<li><router-link to="/parent">/parent</router-link></li>
<li><router-link to="/parent/foo">/parent/foo</router-link></li>
<li><router-link to="/parent/bar">/parent/bar</router-link></li>
</ul>
<transition name="fade" mode="out-in">
<router-view class="view"></router-view>
</transition>
</div>
`
}).$mount('#app')
4.導(dǎo)航完成后獲取數(shù)據(jù)
假設(shè)我們有一個 Post 組件,需要基于 $route.params.id 獲取文章數(shù)據(jù):
導(dǎo)航完成之后獲取:先完成導(dǎo)航,然后在接下來的組件生命周期鉤子中獲取數(shù)據(jù)。在數(shù)據(jù)獲取期間顯示『加載中』之類的指示。
導(dǎo)航完成之前獲取:導(dǎo)航完成前,在路由的 enter
鉤子中獲取數(shù)據(jù),在數(shù)據(jù)獲取成功后執(zhí)行導(dǎo)航。
<template>
<div class="post">
<div class="loading" v-if="loading">
Loading...
</div>
<div v-if="error" class="error">
{{ error }}
</div>
<div v-if="post" class="content">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
</div>
</template>
export default {
data () {
return {
loading: false,
post: null,
error: null
}
},
created () {
// 組件創(chuàng)建完后獲取數(shù)據(jù),
// 此時 data 已經(jīng)被 observed 了
this.fetchData()
},
watch: {
// 如果路由有變化,會再次執(zhí)行該方法
'$route': 'fetchData'
},
methods: {
fetchData () {
this.error = this.post = null
this.loading = true
// replace getPost with your data fetching util / API wrapper
getPost(this.$route.params.id, (err, post) => {
this.loading = false
if (err) {
this.error = err.toString()
} else {
this.post = post
}
})
}
}
}
在導(dǎo)航完成前獲取數(shù)據(jù)
export default {
data () {
return {
post: null,
error: null
}
},
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) =>
if (err) {
// display some global error message
next(false)
} else {
next(vm => {
vm.post = post
})
}
})
},
// 路由改變前,組件就已經(jīng)渲染完了
// 邏輯稍稍不同
watch: {
$route () {
this.post = null
getPost(this.$route.params.id, (err, post) => {
if (err) {
this.error = err.toString()
} else {
this.post = post
}
})
}
}
}
5.滾動行為
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const Home = { template: '<div>home</div>' }
const Foo = { template: '<div>foo</div>' }
const Bar = {
template: `
<div>
bar
<div style="height:500px"></div>
<p id="anchor">Anchor</p>
</div>
`
}
// scrollBehavior:
// - only available in html5 history mode
// - defaults to no scroll behavior
// - return false to prevent scroll
const scrollBehavior = (to, from, savedPosition) => {
if (savedPosition) {
// savedPosition is only available for popstate navigations.
return savedPosition
} else {
const position = {}
// new navigation.
// scroll to anchor by returning the selector
if (to.hash) {
position.selector = to.hash
}
// check if any matched route config has meta that requires scrolling to top
if (to.matched.some(m => m.meta.scrollToTop)) {
// cords will be used if no selector is provided,
// or if the selector didn't match any element.
position.x = 0
position.y = 0
}
// if the returned position is falsy or an empty object,
// will retain current scroll position.
return position
}
}
const router = new VueRouter({
mode: 'history',
base: __dirname,
scrollBehavior,
routes: [
{ path: '/', component: Home, meta: { scrollToTop: true }},
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar, meta: { scrollToTop: true }}
]
})
new Vue({
router,
template: `
<div id="app">
<h1>Scroll Behavior</h1>
<ul>
<li><router-link to="/">/</router-link></li>
<li><router-link to="/foo">/foo</router-link></li>
<li><router-link to="/bar">/bar</router-link></li>
<li><router-link to="/bar#anchor">/bar#anchor</router-link></li>
</ul>
<router-view class="view"></router-view>
</div>
`
}).$mount('#app')
6.路由懶加載
結(jié)合 Vue 的 異步組件 和 Webpack 的 code splitting feature, 輕松實現(xiàn)路由組件的懶加載。
我們要做的就是把路由對應(yīng)的組件定義成異步組件:
const Foo = resolve => {
// require.ensure 是 Webpack 的特殊語法,用來設(shè)置 code-split point
// (代碼分塊)
require.ensure(['./Foo.vue'], () => {
resolve(require('./Foo.vue'))
})
}
這里還有另一種代碼分塊的語法,使用 AMD 風格的 require,于是就更簡單了:
const Foo = resolve => require(['./Foo.vue'], resolve)
不需要改變?nèi)魏温酚膳渲?,跟之前一樣使?Foo:
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
把組件按組分塊
const Foo = r => require.ensure([], () => r(require('./Foo.vue')), 'group-foo')
const Bar = r => require.ensure([], () => r(require('./Bar.vue')), 'group-foo')
const Baz = r => require.ensure([], () => r(require('./Baz.vue')), 'group-foo')