- 注册路由器插件
在 ./router/index.js 中,引入 VueRouter 插件并用 Vue.use() 注册。 - 创建路由对象
定义路由规则数组,并传入创建的 VueRouter 实例对象,导出对象。
import Vue from 'vue'
import VueRouter from 'vue-router'
import Index from '../views/Index.vue'
// 1. 注册路由插件
// Vue.use 是用来注册插件,他会调用传入对象的 install 方法
Vue.use(VueRouter)
// 路由规则
const routes = [
{
path: '/',
name: 'Index',
component: Index
},
{
path: '/blog',
name: 'Blog',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/Blog.vue')
},
{
path: '/photo',
name: 'Photo',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "photo" */ '../views/Photo.vue')
}
]
// 2.创建路由对象
const router = new VueRouter({
routes
})
export default router
- 注册 router 对象
在 main.js 中创建 vue 实例并把 router对象传进去。
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
- 创建路由组件的占位
App.vue 中引入<router-view/>
标签。页面嵌套就由这个组件实现。 - 创建链接
App.vue 中引入<router-link>
标签。这个并不是必要的,只是按需引入一些组件。
<template>
<div id="app">
<div>
<img src="@/assets/logo.png">
</div>
<div id="nav">
<!-- 5. 创建链接 -->
<router-link to="/">Index</router-link> |
<router-link to="/blog">Blog</router-link> |
<router-link to="/photo">Photo</router-link>|
</div>
<!-- 4. 创建路由组件的展占位 -->
<router-view/>
</div>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style>
动态路由
例如 Detail 组件,对于不同 ID 的详情,都要使用这个组件来渲染。在路由路径中使用“动态路径参数”就能达到这种效果。
const routes = [
{
path: '/',
name: 'Index',
component: Index
},
{
// 动态路由
path: '/detail/:id',
name: 'Detail',
// 开启 props,会把 URL 中的参数传递给组件
// 在组件中通过 props 来接收 URL 参数
props: true,
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "detail" */ '../views/Detail.vue')
}
]
组件接收参数时有两种方式:
- 通过当前路由规则获取,但是这种方式强依赖于路由。
通过在路由规则数组里开启 Detail 的 props 属性来获取。 ```vue
通过当前路由规则获取:{{ $route.params.id }}
通过开启 props 获取:{{ id }}
```javascript
const routes = [
{
name: 'login',
path: '/login',
component: Login
},
// 嵌套路由
{
path: '/',
component: Layout,
children: [
{
name: 'index',
path: '',
component: Index
},
{
name: 'detail',
path: 'detail/:id',
props: true,
component: () => import('@/views/Detail.vue')
}
]
}
]
编程式导航
除了使用 <router-link>
创建 a 标签来定义导航链接以外,还可以借助 router 的实例方法来实现组件加载。
常用的方法有 $router.push()
,$router.replace()
,$router.go()
等,事实上当点击<router-link>
时,内部调用的就是 $router.push()
方法。另外,<router-link to= "..." replace>
相当于 $router.replace()
。
<template>
<div class="home">
<div id="nav">
<router-link to="/">Index</router-link>
</div>
<button @click="replace"> replace </button>
<button @click="goDetail"> Detail </button>
</div>
</template>
<script>
export default {
name: 'Index',
methods: {
replace () {
this.$router.replace('/login')
},
goDetail () {
this.$router.push({ name: 'Detail', params: { id: 1 } })
}
}
}
</script>
$router.push
和$router.replace
都可以传入字符串或对象。如果提供了path
,那么 params
不会生效。name
和path
都能使用query
传参,但是会变成 url 参数,使用动态路由的话,不能用query
。所以动态路由可以使用name
+params
或者使用path
+ 字符串拼接或者直接使用字符串拼接。
const userId = '123'
// 动态路由可用的模式
router.push(`/user/${userId}`) // 直接传字符串 -> /user/123
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
Hash 模式和 History 模式
- URL 的区别:Hash 模式的 URL 带了一个 # 号,可以携带问号和 url 参数,History 模式不带 # 号。
const hashStyle = 'https://music.163.com/#/playlist?id=310321893'
const historyStyle = 'https://music.163.com/playlist/310321893'
- Hash 模式是基于锚点以及 onhashchange 实现的,以锚点的值作为路由地址,当锚点变化,触发 onhashchage 事件,根据当前的路由地址找到对应组件重新渲染,如果改变 # 号后面的内容,浏览器不会向服务器发出请求,但是会把它加入访问历史;History 模式基于 Html5 的 API实现,
history.pushState()
和history.replaceState()
。调用history.pushState()
时,浏览器不会向服务器发出请求,只会替换地址栏的 url,并且添加历史记录。通过监听 popstate 事件,可以获取到浏览器历史操作的变化,在 popstate 事件的处理函数中,可以获取改变后的地址,根据当前路由地址找到对应组件重新渲染。需要注意的是 popstate 只有在点击浏览器的前进和后退,以及调用history.back()
和histoty.forward()
时才会出发。
History 模式
History 模式需要服务器支持。因为 Vue 应用是单页应用,如果服务器没有配置,访问一个不存在的地址或点击浏览器的刷新按钮会返回 404 或“找不到该页面”,比如要访问 http://yoursite.com/user/id
输入错误变成 http://yoursite.com/userid
,或者在http://yoursite.com/user/id
刷新页面。因此,服务器端应该配置匹配不到任何静态资源就返回index.html
页面。
Vue 项目默认开启 Hash 模式,在实例化 router 对象的时候,传入 mode 参数,可以控制要使用的模式。
const router = new VueRouter({
mode: 'history',
routes: [...]
})
启用了 History 模式后,服务器就不再返回 404 页面,不能匹配的请求都会返回 index.html
。为了避免这种情况,应该在路由规则里添加一条覆盖所有路由情况的规则,返回自己实现的 404 页面。
const routes = [
{
path: '*',
name: '404',
component: () => import(/* webpackChunkName: "404" */ '../views/404.vue')
}
]
const router = new VueRouter({
mode: 'history',
routes
})
Node.JS 环境
模拟在 Node.JS 环境下,运行打包后生成的 web应用。connect-history-api-fallback
就是用来处理 history 模式的模块。
const path = require('path')
// 导入处理 history 模式的模块
const history = require('connect-history-api-fallback')
// 导入 express
const express = require('express')
const app = express()
// 注册处理 history 模式的中间件
app.use(history())
// 处理静态资源的中间件,网站根目录 ../web
app.use(express.static(path.join(__dirname, '../web')))
// 开启服务器,端口是 3000
app.listen(3000, () => {
console.log('服务器开启,端口:3000')
})
Nginx 服务器
Nginx 默认没有支持 History 模式,所以刷新页面或者访问不存在的页面会返回 404,需要修改 nginx 配置。
需要修改的文件是/Nginx根目录/conf/nginx.conf
,找到项目对应的 server,配置try_files
查找文件的顺序。首先查找是否有这个文件,没有的话查找是否有这个文件夹,再没有直接返回 index,.html。
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}