代码下载
路由
路由是一个比较广义和抽象的概念,路由的本质就是一种对应关系。比如说在url地址中输入我们要访问的url地址之后,浏览器要去请求这个url地址对应的资源。
那么url地址和真实的资源之间就有一种对应的关系,就是路由。
路由分为前端路由和后端路由:
- 后端路由根据不同的用户 URL 请求,返回不同的内容,本质是 URL 请求地址与服务器资源之间的对应关系
- 前端路由根据不同的用户事件,显示不同的页面内容,本质是用户事件与事件处理函数之间的对应关系,是依靠hash值(锚链接)的变化进行实现
后端路由性能相对前端路由来说较低,所以,我们接下来主要学习的是前端路由
前端路由的基本概念:根据不同的事件来显示不同的页面内容,即事件与事件处理函数之间的对应关系
前端路由主要做的事情就是监听事件并分发执行事件处理函数
SPA(Single Page Application)
- 后端渲染(存在性能问题)
- Ajax前端渲染(前端渲染提高性能,但是不支持浏览器的前进后退操作)
- SPA(Single Page Application)单页面应用程序:整个网站只有一个页面,内容的变化通过Ajax局部更新实现、同时支持浏览器地址栏的前进和后退操作
- SPA实现原理之一:基于URL地址的hash(hash的变化会导致浏览器记录访问历史的变化、但是hash的变化不会触发新的URL请求)
- 在实现SPA过程中,最核心的技术点就是前端路由
实现简易前端路由
- 基于URL中的hash实现(点击菜单的时候改变 URL 的 hash,根据 hash 的变化控制组件的切换)
- component 标签根据 is 属性指定的组件名称,把对应的组件渲染到 component 标签所在的位置,可以把 component 标签当做是【组件的占位符】
- 监听 window 的 onhashchange 事件,根据获取到的最新的 hash 值(通过 location.hash 获取),切换要显示的组件的名称
<body>
<div class="main" id="app">
<h2>实现简易前端路由</h2>
<a href="#/zhuye">主页</a>
<a href="#/keji">科技</a>
<a href="#/caijing">财经</a>
<a href="#/yule">娱乐</a>
<component :is="cname"></component>
</div>
<script src="../js/vue.js"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
cname: 'zhuye'
},
components: {
'zhuye': {
template: '<h3>主页内容</h3>'
},
'keji': {
template: '<h3>科技内容</h3>'
},
'caijing': {
template: '<h3>财经内容</h3>'
},
'yule': {
template: '<h3>娱乐内容</h3>'
}
}
});
window.onhashchange = function() {
console.log('hash: ', location.hash.slice(1));
switch (location.hash.slice(1)) {
case '/zhuye':
vm.cname = 'zhuye';
break;
case '/keji':
vm.cname = 'keji';
break;
case '/caijing':
vm.cname = 'caijing';
break;
case '/yule':
vm.cname = 'yule';
break;
}
};
</script>
</body>
Vue Router
Vue Router(官网)是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,可以非常方便的用于SPA应用程序的开发。Vue Router依赖于Vue,所以需要先引入Vue,再引入Vue Router。Vue Router的特性:
- 支持H5历史模式或者hash模式
- 支持嵌套路由
- 支持路由参数
- 支持编程式路由
- 支持命名路由
- 支持路由导航守卫
- 支持路由过渡动画特效
- 支持路由懒加载
- 支持路由滚动行为
vue-router的基本使用
1、引入相关的库文件:
<!-- 导入相关js库文件 -->
<script src="../js/vue.js"></script>
<script src="../js/vue-router_v3.6.5.js"></script>
2、添加路由链接,是路由中提供的标签,默认会被渲染为a标签,to属性默认被渲染为href属性,to属性的值会被渲染为#开头的hash地址:
<router-link to="/user">User</router-link>
<router-link to="/register">Register</router-link>
3、添加路由填充位,(也叫做路由占位符)将来通过路由规则匹配到的组件,会被渲染到 router-view 所在的位置:
<router-view></router-view>
4、定义路由组件:
const user = {
template: '<h3>User 组件</h3>'
};
const register = {
template: '<h3>Register 组件</h3>'
}
5、配置路由规则并创建路由实例:
// 创建路由实例对象
let router = new VueRouter({
// routes 是路由规则数组
routes: [
// 每个路由规则都是一个配置对象,其中至少包含 path 和 component 两个属性:
// path 表示当前路由规则匹配的 hash 地址
// component 表示当前路由规则对应要展示的组件
{path: '/user', component: user},
{path: '/register', component: register}
]
});
6、把路由挂载到 Vue 根实例中:
let vm = new Vue({
el: '#app',
data: {},
router
});
vue-router路由重定向
路由重定向指的是用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面;通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向:
let router = new VueRouter({
routes: [
{path: '/user', component: user},
{path: '/register', component: register},
// 其中,path 表示需要被重定向的原地址,redirect 表示将要被重定向到的新地址
{path: '/', redirect: '/user'}
]
});
vue-router嵌套路由
点击父级路由链接显示模板内容,模板内容中又有子级路由链接,点击子级路由链接显示子级模板内容,使用方法如下:
1、在父路由组件模板中添加子级路由链接和子级路由填充位:
const register = {
template: `
<div>
<h3>Register 组件</h3>
<!-- 子路由连接 -->
<router-link to='/register/tab1'>Tab1</router-link>
<router-link to='/register/tab2'>Tab2</router-link>
<!-- 子路由的占位符 -->
<router-view></router-view>
</div>
`
};
2、构建子路由组件:
const tab1 = {
template: `<h4>Tab1 子组件</h4>`
}
const tab2 = {
template: `<h4>Tab2 子组件</h4>`
}
3、嵌套路由配置,父级路由通过children属性配置子级路由:
const router = new VueRouter({
routes: [
{ path: '/user', component: user },
{ path: '/register', component: register,
children: [
{ path: '/register/tab1', component: tab1 },
{ path: '/register/tab2', component: tab2 }
]
}
]
});
vue-router动态路由
通过动态路由参数的模式进行路由匹配:
- 动态路径参数以冒号开头
- 路由组件中通过$route.params获取路由参数
const user = {
template: `
<div>id: {{$route.params.id}}</div>
`
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: user }
]
});
vue-router路由组件传递参数
$route与对应路由形成高度耦合,不够灵活,所以可以使用props将组件和路由解耦。
1、props的值为布尔类型:props 被设置为 true,route.params 将会被设置为组件属性,使用 props 接收路由参数
const user = {
// 使用 props 接收路由参数
props: ['id'],
template: `<h3>User 组件内容 id: {{id}}</h3>`
};
const router = new VueRouter({
routes: [
// 如果 props 被设置为 true,route.params 将会被设置为组件属性
{ path: '/user/:id', component: user, props: true }
]
});
2、props的值为对象类型:props 是一个对象,它会被按原样设置为组件属性
const user = {
// 使用 props 接收路由参数
props: ['id', 'name', 'age'],
template: `<h3>User 组件内容 id: {{id}}, name: {{name}}, age: {{age}}</h3>`
};
const router = new VueRouter({
routes: [
// 如果 props 是一个对象,它会被按原样设置为组件属性
{ path: '/user/:id', component: user, props: { id: 2, name: '张三', age: 18 } }
]
});
3、props的值为函数类型:如果需要传递参数值则需将 props 设置为一个函数,这个函数接收 route 对象为自己的形参
const user = {
// 使用 props 接收路由参数
props: ['id', 'name', 'age'],
template: `<h3>User 组件内容 id: {{id}}, name: {{name}}, age: {{age}}</h3>`
};
const router = new VueRouter({
routes: [
// 如果 props 是一个函数,则这个函数接收 route 对象为自己的形参
{ path: '/user/:id', component: user, props: (route) => ({ id: route.params.id, name: '张三', age: 18 }) }
]
});
vue-router命名路由
为了更加方便的表示路由的路径,可以给路由规则起一个别名,即为“命名路由”:
const user = {
props: ['id', 'name', 'age'],
template: `<h3>User 组件内容 id: {{id}}, name: {{name}}, age: {{age}}</h3>`
};
const router = new VueRouter({
// 命名路由
routes: [ { name: 'User', path: '/user/:id', component: user, props: (route) => ({ id: route.params.id, name: route.params.name, age: route.params.age }) } ]
});
使用命名路由:
<router-link :to="{ name: 'User', params: { id: 4, name: '李四', age: 18 }}">User 4</router-link>
<router-view></router-view>
或编程式导航:
router.push({ name: 'User', params: { id: 4, name: '李四', age: 18}})
vue-router编程式导航
页面导航的两种方式:
- 声明式导航:通过点击链接实现导航的方式,叫做声明式导航,例如:普通网页中的 链接 或 vue 中的
- 编程式导航:通过调用JavaScript形式的API实现导航的方式,叫做编程式导航,例如:普通网页中的 location.href
常用的编程式导航 API 如下:
- this.$router.push(‘hash地址或对象’)
- this.$router.go(n),其中 n 为数字,正整数代表前进,负整数代表回退,参考history.go
router.push() 方法的参数规则:
- 字符串(路径名称)——
this.$router.push('/user/1');
- 对象——
this.$router.push({ path: '/user/2' });
- 命名的路由(传递参数)——
this.$router.push({ name: 'User', params: { id: 3, name: '李四', age: 19 }});
- 带查询参数,变成 /user?id=4&name=lisi&age=20,在路由规则中可以通过
route.params
获取查询参数——this.$router.push({ path: '/user', query: { id: 4, name: 'lisi', age: 20 }});
<body>
<div class="main" id="app">
<h2>vue-router编程式导航</h2>
<h3>声明式导航</h3>
<router-link :to="{ name: 'User', params: { id: 1, name: '张三', age: 18 }}">User 1</router-link>
<h3>编程式导航</h3>
<button @click="strHandle">字符串</button>
<button @click="objHandle">对象</button>
<button @click="nameHandle">命名路由</button>
<button @click="queryHandle">查询路由</button>
<router-view></router-view>
</div>
<script src="../js/vue.js"></script>
<script src="../js/vue-router_v3.6.5.js"></script>
<script>
let user = {
props: [ 'id', 'name', 'age' ],
template: `
<div>
<h3>User 组件内容 id: {{id}} name: {{name}} age: {{age}}</h3>
<button @click='registerHandel'>去注册</button>
</div>
`,
methods: {
registerHandel: function() {
this.$router.push('/register')
}
}
};
let register = {
template: `
<div>
<h3>Register 组件内容</h3>
<button @click='backHandel'>返回</button>
</div>
`,
methods: {
backHandel: function() {
// 回退
this.$router.go('-1');
}
}
}
let router = new VueRouter({
routes: [
{ path: '/', component: user, props: {id: 1, name: '张三', age: 18} },
// 命名路由 带 params 参数
{ name: 'User', path: '/user/:id', component: user, props: (route) => ({ id: route.params.id, name: route.params.name, age: route.params.age }) },
// 带 query 查询参数
{ path: '/user', component: user, props: (route) => ({ id: route.query.id, name: route.query.name, age: route.query.age }) },
{ path: '/register', component: register }
]
});
let vm = new Vue({
el: '#app',
data: {},
router,
methods: {
strHandle: function() {
console.log('hash: ', location.hash);
if (location.hash !== '#/user/1') {
// 字符串(路径)
this.$router.push('/user/1');
}
},
objHandle: function() {
console.log('hash: ', location.hash);
if (location.hash !== '#/user/2') {
// 对象
this.$router.push({ path: '/user/2' });
}
},
nameHandle: function() {
console.log('hash: ', location.hash);
if (location.hash !== '#/user/3') {
// 命名的路由(传递参数)
this.$router.push({ name: 'User', params: { id: 3, name: '李四', age: 19 }});
}
},
queryHandle: function() {
console.log('hash: ', location.hash);
let name = '王五';
let ecName = encodeURI(name);
if (location.hash !== `#/user?id=4&name=${ecName}&age=19`) {
// // 带查询参数
this.$router.push({ path: '/user', query: { id: 4, name: name, age: 20 }});
}
}
}
});
</script>
</body>
案例
点击左侧的"用户管理",“权限管理”,“商品管理”,“订单管理”,"系统设置"都会出现对应的组件并展示内容;其中"用户管理"组件展示的效果如上图所示,在用户管理区域中的详情链接也是可以点击的,点击之后将会显示用户详情信息,并在详情页实现后退功能。
1、基于组件构建基本结构,右侧的主体内容使用路由填充位,根据路由导航渲染具体组件
let header = {
props: ['title'],
template: `<h2 class="header" v-text="title"></h2>`
};
let leftContent = {
template: `<div class="left content">
<ul>
<li><router-link to="/users">用户管理</router-link></li>
<li><router-link to="/rights">权限管理</router-link></li>
<li><router-link to="/goods">商品管理</router-link></li>
<li><router-link to="/orders">订单管理</router-link></li>
<li><router-link to="/settings">系统设置</router-link></li>
</ul>
</div>`
};
let rightContent = {
template: `<div class="right content">
<router-view></router-view>
</div>`
};
let footer = {
props: ['info'],
template: `<h4 class="footer" v-text="info"></h4>`
};
let app = {
props: ['title', 'crInfo'],
template: `
<div class="main">
<header-com :title="title"></header-com>
<left-com></left-com>
<right-com></right-com>
<footer-com :info="crInfo"></footer-com>
</div>
`,
components: {
'header-com': header,
'left-com': leftContent,
'right-com': rightContent,
'footer-com': footer
}
};
2、定义右侧主体内容的各个路由组件
let users = {
data: function() {
return {
userList: [
{ id: 1, name: '张三', age: 18},
{ id: 2, name: '李四', age: 19},
{ id: 3, name: '王五', age: 20},
{ id: 4, name: '赵六', age: 21}
]
}
},
template: `
<div class="users">
<h3>用户管理</h3>
<table>
<thead>
<tr><th>编号</th><th>姓名</th><th>年龄</th><th>操作</th></tr>
</thead>
<tbody>
<tr v-for="(item) in userList" :key="item.id">
<td v-text="item.id"></td>
<td v-text="item.name"></td>
<td v-text="item.age"></td>
<td><a href="javascript:;" @click="goDetail(item.id)">详情</a></td>
</tr>
</tbody>
</table>
</div>
`,
methods: {
goDetail: function(id) {
console.log('-----', id, '-----');
this.$router.push('/userInfo/' + id);
}
}
}
let rights = {
template: `
<div>
<h3>权限管理</h3>
</div>
`
}
let goods = {
template: `
<div>
<h3>商品管理</h3>
</div>
`
}
let orders = {
template: `
<div>
<h3>订单管理</h3>
</div>
`
}
let settings = {
template: `
<div>
<h3>系统设置</h3>
</div>
`
}
let userInfo = {
props: ['id'],
template: `
<div class="detail">
<h3>用户详情-----{{id}}</h3>
<button @click="goBack">返回</button>
</div>
`,
methods: {
goBack: function() {
this.$router.go(-1);
}
}
}
3、配置路由并挂载到 Vue 根实例中,由一个根路由显示主体结构,子路由显示右侧具体内容
let router = new VueRouter({
routes: [
{
path: '/',
component: app,
redirect: '/users',
children: [
{ path: '/users', component: users },
{ path: '/userInfo/:id', component: userInfo, props: true },
{ path: '/rights', component: rights },
{ path: '/goods', component: goods },
{ path: '/orders', component: orders },
{ path: '/settings', component: settings }
],
props: { title: '后台管理案例', crInfo: '版权信息' }
}
]
});
let vm = new Vue({
el: '#app',
router
});
4、添加路由填充位,渲染根组件
<div id="app">
<router-view></router-view>
</div>