{{ post.title }}
{{ post.body }}
简单的路由原理,基于组件
const NotFound = { template: 'Page not found
' }
const Home = { template: 'home page
' }
const About = { template: 'about page
' }
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学习
npm install vue-router
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
Hello App!
Go to Foo
Go to Bar
// 0. 如果使用模块化机制编程, 要调用 Vue.use(VueRouter)
// 1. 定义(路由)组件。
// 可以从其他文件 import 进来
const Foo = { template: 'foo' }
const Bar = { template: 'bar' }
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点在讨论嵌套路由。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
routes // (缩写)相当于 routes: routes
})
// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
router
}).$mount('#app')
// 现在,应用已经启动了!
动态路由监听
$route.params.
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 对路由变化作出响应...
}
}
}
/user/foo
/user/foo/profile
/user/foo/posts
const User = {
template: `
User {{ $route.params.id }}
`
}
const UserHome = { template: 'Home' }
const UserProfile = { template: 'Profile' }
const UserPosts = { template: 'Posts' }
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
// UserHome will be rendered inside User's
// when /user/:id is matched
{ path: '', component: UserHome },
// UserProfile will be rendered inside User's
// when /user/:id/profile is matched
{ path: 'profile', component: UserProfile },
// UserPosts will be rendered inside User's
// when /user/:id/posts is matched
{ path: 'posts', component: UserPosts }
]
}
]
})
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})
// 带查询参数,变成 /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)
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const Home = { template: 'This is Home' }
const Foo = { template: 'This is Foo' }
const Bar = { template: 'This is Bar {{ $route.params.id }}' }
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: `
Named Routes
Current route name: {{ $route.name }}
home
foo
bar
`
}).$mount('#app')
(同级)展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar
(侧导航) 和main
(主内容) 两个视图,这个时候命名视图就派上用场了
Named Views
/
/other
const Foo = { template: 'foo' }
const Bar = { template: 'bar' }
const Baz = { template: 'baz' }
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/',
// a single route can define multiple named components
// which will be rendered into 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'
})
重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
重定向的目标也可以是一个命名的路由:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
甚至是一个方法,动态返回重定向目标:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
})
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const Home = { template: 'Home
' }
const Foo = { template: 'foo' }
const Bar = { template: 'bar' }
const Baz = { template: 'baz' }
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: `
Route Alias
/foo (renders /home/foo)
/home/bar-alias (renders /home/bar)
/baz (renders /home/baz)
/home/baz-alias (renders /home/baz)
`
}).$mount('#app')
vue-router
提供的导航钩子主要用来拦截导航,让它完成跳转或取消。有多种方式可以在路由导航发生时执行钩子:全局的, 单个路由独享的, 或者组件级的。
(1)全局钩子
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
每个钩子方法接收三个参数:
**to: Route
**: 即将要进入的目标 路由对象
**from: Route
**: 当前导航正要离开的路由
**next: Function
**: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next
方法的调用参数。
**next()
**: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 **confirmed**
(确认的)。
**next(false)
**: 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),
那么 URL 地址会重置到 from 路由对应的地址。
**next('/')
或者 next({ path: '/' })
**: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
(2)某个路由独享的钩子
你可以在路由配置上直接定义 beforeEnter 钩子:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
(3)组件内的钩子
最后,你可以使用 beforeRouteEnter 和 beforeRouteLeave,在路由组件内直接定义路由导航钩子,
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) => {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当钩子执行前,组件实例还没被创建
},
beforeRouteLeave (to, from, next) => {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
beforeRouteEnter 钩子 不能 访问 this,因为钩子在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter (to, from, next) => {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
可以 在 beforeRouteLeave 中直接访问 this。这个 leave 钩子通常用来禁止用户在还未保存修改前突然离开。可以通过 next(false) 来取消导航。
定义路由的时候可以配置 meta 字段:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: { requiresAuth: true }
}
]
}
]
})
下面例子展示在全局导航钩子中检查 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() // 确保一定要调用 next()
}
})
组件给它添加一些过渡效果:
单个路由的过渡
const Foo = {
template: `
...
`
}
const Bar = {
template: `
`
}
动态路由
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const Home = {
template: `
Home
hello
`
}
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: `
Parent
`
}
const Default = { template: 'default' }
const Foo = { template: 'foo' }
const Bar = { template: ' ' }
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: `
Transitions
/
/parent
/parent/foo
/parent/bar
`
}).$mount('#app')
假设我们有一个 Post 组件,需要基于 $route.params.id 获取文章数据:
导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示『加载中』之类的指示。
导航完成之前获取:导航完成前,在路由的 enter
钩子中获取数据,在数据获取成功后执行导航。
Loading...
{{ error }}
{{ post.title }}
{{ post.body }}
export default {
data () {
return {
loading: false,
post: null,
error: null
}
},
created () {
// 组件创建完后获取数据,
// 此时 data 已经被 observed 了
this.fetchData()
},
watch: {
// 如果路由有变化,会再次执行该方法
'$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
}
})
}
}
}
在导航完成前获取数据
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
})
}
})
},
// 路由改变前,组件就已经渲染完了
// 逻辑稍稍不同
watch: {
$route () {
this.post = null
getPost(this.$route.params.id, (err, post) => {
if (err) {
this.error = err.toString()
} else {
this.post = post
}
})
}
}
}
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const Home = { template: 'home' }
const Foo = { template: 'foo' }
const Bar = {
template: `
bar
Anchor
`
}
// 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: `
Scroll Behavior
/
/foo
/bar
/bar#anchor
`
}).$mount('#app')
结合 Vue 的 异步组件 和 Webpack 的 code splitting feature, 轻松实现路由组件的懒加载。
我们要做的就是把路由对应的组件定义成异步组件:
const Foo = resolve => {
// require.ensure 是 Webpack 的特殊语法,用来设置 code-split point
// (代码分块)
require.ensure(['./Foo.vue'], () => {
resolve(require('./Foo.vue'))
})
}
这里还有另一种代码分块的语法,使用 AMD 风格的 require,于是就更简单了:
const Foo = resolve => require(['./Foo.vue'], resolve)
不需要改变任何路由配置,跟之前一样使用 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')