路由是一个比较广义和抽象的概念,路由的本质就是对应关系。
在开发中,路由分为:
后端路由
SPA(Single Page Application)
前端路由
基于URL中的hash实现(点击菜单的时候改变URL的hash,根据hash的变化控制组件的切换)
案例代码实现如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<!-- 导入 vue 文件 -->
<script src="./lib/vue_2.5.22.js"></script>
</head>
<body>
<!-- 被 vue 实例控制的 div 区域 -->
<div id="app">
<!-- 切换组件的超链接 -->
<a href="#/zhuye">主页</a>
<a href="#/keji">科技</a>
<a href="#/caijing">财经</a>
<a href="#/yule">娱乐</a>
<!-- 根据 :is 属性指定的组件名称,把对应的组件渲染到 component 标签所在的位置 -->
<!-- 可以把 component 标签当做是【组件的占位符】 -->
<component :is="comName"></component>
</div>
<script>
// #region 定义需要被切换的 4 个组件
// 主页组件
const zhuye = {
template: '<h1>主页信息</h1>'
}
// 科技组件
const keji = {
template: '<h1>科技信息</h1>'
}
// 财经组件
const caijing = {
template: '<h1>财经信息</h1>'
}
// 娱乐组件
const yule = {
template: '<h1>娱乐信息</h1>'
}
// #endregion
// #region vue 实例对象
const vm = new Vue({
el: '#app',
data: {
comName: 'zhuye'
},
// 注册私有组件
components: {
zhuye,
keji,
caijing,
yule
}
})
// #endregion
// 监听 window 的 onhashchange 事件,根据获取到的最新的 hash 值,切换要显示的组件的名称
window.onhashchange = function() {
// 通过 location.hash 获取到最新的 hash 值
console.log(location.hash);
switch(location.hash.slice(1)){
case '/zhuye':
vm.comName = 'zhuye'
break
case '/keji':
vm.comName = 'keji'
break
case '/caijing':
vm.comName = 'caijing'
break
case '/yule':
vm.comName = 'yule'
break
}
}
</script>
</body>
</html>
vue-router
的基本使用Vue Router
(官网:https://router.vuejs.org/zh/
)是 Vue.js
官方的路由管理器。 它和 Vue.js
的核心深度集成,可以非常方便的用于SPA
应用程序的开发。
基本使用的步骤:
下面看一下具体的实施过程
<!-- 导入 vue 文件,为全局 window 对象挂载 Vue 构造函数 -->
<script src="./lib/vue_2.5.22.js"></script>
<!-- 导入 vue-router 文件,为全局 window 对象挂载 VueRouter 构造函数 -->
<script src="./lib/vue-router_3.0.2.js"></script>
<!-- router-link 是 vue 中提供的标签,默认会被渲染为 a 标签 -->
<!-- to 属性默认会被渲染为 href 属性 -->
<!-- to 属性的值默认会被渲染为 # 开头的 hash 地址 -->
<router-link to="/user">User</router-link>
<router-link to="/register">Register</router-link>
<!-- 路由填充位(也叫做路由占位符) -->
<!-- 将来通过路由规则匹配到的组件,将会被渲染到 router-view 所在的位置 -->
<router-view></router-view>
var User = {
template: '<div>User</div>'
}
var Register = {
template: '<div>Register</div>'
}
// 创建路由实例对象
var router = new VueRouter({
// routes 是路由规则数组
routes: [
// 每个路由规则都是一个配置对象,其中至少包含 path 和 component 两个属性:
// path 表示当前路由规则匹配的 hash 地址
// component 表示当前路由规则对应要展示的组件
{path:'/user',component: User},
{path:'/register',component: Register}
]
})
new Vue({
el: '#app',
// 为了能够让路由规则生效,必须把路由对象挂载到 vue 实例对象上
router
});
完整代码实现如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<!-- 导入 vue 文件 -->
<script src="./lib/vue_2.5.22.js"></script>
<script src="./lib/vue-router_3.0.2.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<router-link to="/user">User</router-link>
<router-link to="/register">Register</router-link>
<!-- 路由占位符 -->
<router-view></router-view>
</div>
<script>
const User = {
template: '<h1>User 组件</h1>'
}
const Register = {
template: '<h1>Register 组件</h1>'
}
// 创建路由实例对象
const router = new VueRouter({
// 所有的路由规则
routes: [
{ path: '/user', component: User },
{ path: '/register', component: Register }
]
})
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el: '#app',
data: {},
// 挂载路由实例对象
// router: router
router
})
</script>
</body>
</html>
var router = new VueRouter({
routes: [
// 其中,path 表示需要被重定向的原地址,redirect 表示将要被重定向到的新地址
//当用户在地址栏中输入`/`,会自动的跳转到`/user`,而`/user`对应的组件为User
{path:'/', redirect: '/user'},
{path:'/user',component: User},
{path:'/register',component: Register}
]
})
具体实现的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<!-- 导入 vue 文件 -->
<script src="./lib/vue_2.5.22.js"></script>
<script src="./lib/vue-router_3.0.2.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<router-link to="/user">User</router-link>
<router-link to="/register">Register</router-link>
<!-- 路由占位符 -->
<router-view></router-view>
</div>
<script>
const User = {
template: '<h1>User 组件</h1>'
}
const Register = {
template: '<h1>Register 组件</h1>'
}
// 创建路由实例对象
const router = new VueRouter({
// 所有的路由规则
routes: [
//路由重定向
{ path: '/', redirect: '/user'},
{ path: '/user', component: User },
{ path: '/register', component: Register }
]
})
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el: '#app',
data: {},
// 挂载路由实例对象
// router: router
router
})
</script>
</body>
</html>
嵌套路由功能分析
点击父级路由链接显示模板内容
模板内容中又有子级路由链接
点击子级路由链接显示子级模板内容
下面看一下实现的步骤
父路由组件模板
<p>
<router-link to="/user">User</router-link>
<router-link to="/register">Register</router-link>
</p>
<div>
<!-- 控制组件的显示位置 -->
<router-view></router-view>
</div>
以上的内容,在前面的课程中已经实现。
子级路由模板
const Register = {
template: `<div>
<h1>Register 组件</h1>
<hr/>
<router-link to="/register/tab1">Tab1</router-link>
<router-link to="/register/tab2">Tab2</router-link>
<!-- 子路由填充位置 -->
<router-view/>
</div>`
}
嵌套路由配置
父级路由通过children
属性配置子级路由
const router = new VueRouter({
routes: [
{ path: '/user', component: User },
{ path: '/register', component: Register,
// 通过 children 属性,为 /register 添加子路由规则
children: [
{ path: '/register/tab1', component: Tab1 },
{ path: '/register/tab2', component: Tab2 }
]
}
]
})
具体代码实现如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<!-- 导入 vue 文件 -->
<script src="./lib/vue_2.5.22.js"></script>
<script src="./lib/vue-router_3.0.2.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<router-link to="/user">User</router-link>
<router-link to="/register">Register</router-link>
<!-- 路由占位符 -->
<router-view></router-view>
</div>
<script>
const User = {
template: '<h1>User 组件</h1>'
}
//修改Register组件
const Register = {
template: `<div>
<h1>Register 组件</h1>
<hr/>
<!-- 子路由链接 -->
<router-link to="/register/tab1">tab1</router-link>
<router-link to="/register/tab2">tab2</router-link>
<!-- 子路由的占位符 -->
<router-view />
<div>`
}
const Tab1 = {
template: '<h3>tab1 子组件</h3>'
}
const Tab2 = {
template: '<h3>tab2 子组件</h3>'
}
// 创建路由实例对象
const router = new VueRouter({
// 所有的路由规则
routes: [
{ path: '/', redirect: '/user'},
{ path: '/user', component: User },
// children 数组表示子路由规则
{ path: '/register', component: Register, children: [
{ path: '/register/tab1', component: Tab1 },
{ path: '/register/tab2', component: Tab2 }
] }
]
})
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el: '#app',
data: {},
// 挂载路由实例对象
// router: router
router
})
</script>
</body>
</html>
思考:
<!– 有如下 3 个路由链接 -->
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>
// 定义如下三个对应的路由规则,是否可行???
{ path: '/user/1', component: User }
{ path: '/user/2', component: User }
{ path: '/user/3', component: User }
虽然以上规则可以匹配成功,但是这样写比较麻烦。如果有100个规则,那么写起来就会非常的麻烦。
通过观察,可以发现整个路由规则中只有后续的数字是在变化的。所以这里可以通过动态路由参数的模式进行路由匹配。
var router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})
const User = {
// 路由组件中通过$route.params获取路由参数
template: '<div>User {{ $route.params.id }}</div>'
}
具体代码实现如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<!-- 导入 vue 文件 -->
<script src="./lib/vue_2.5.22.js"></script>
<script src="./lib/vue-router_3.0.2.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>
<router-link to="/register">Register</router-link>
<!-- 路由占位符 -->
<router-view></router-view>
</div>
<script>
const User = {
template: '<h1>User 组件 -- 用户id为: {{$route.params.id}}</h1>'
}
const Register = {
template: '<h1>Register 组件</h1>'
}
// 创建路由实例对象
const router = new VueRouter({
// 所有的路由规则
routes: [
{ path: '/', redirect: '/user'},
{ path: '/user/:id', component: User },
{ path: '/register', component: Register }
]
})
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el: '#app',
data: {},
// 挂载路由实例对象
// router: router
router
})
</script>
</body>
</html>
$route
与对应路由形成高度耦合,不够灵活,所以可以使用props
将组件和路由解耦
第一种情况:
props的值为布尔类型
const router = new VueRouter({
routes: [
// 如果 props 被设置为 true,route.params 将会被设置为组件属性
{ path: '/user/:id', component: User, props: true }
]
})
const User = {
props: ['id'], // 使用 props 接收路由参数
template: '<div>用户ID:{{ id }}</div>' // 使用路由参数
}
在定义路由规则的时候,为其添加了props
属性,并将其值设置为true
.那么在组件中就可以通过props:['id']
的形式来获取对应的参数值。
具体代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<!-- 导入 vue 文件 -->
<script src="./lib/vue_2.5.22.js"></script>
<script src="./lib/vue-router_3.0.2.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>
<router-link to="/register">Register</router-link>
<!-- 路由占位符 -->
<router-view></router-view>
</div>
<script>
const User = {
props: ['id'], //获取id的值。
template: '<h1>User 组件 -- 用户id为: {{id}}</h1>'
}
const Register = {
template: '<h1>Register 组件</h1>'
}
// 创建路由实例对象
const router = new VueRouter({
// 所有的路由规则
routes: [
{ path: '/', redirect: '/user'},
//将props设置为true.
{ path: '/user/:id', component: User, props: true },
{ path: '/register', component: Register }
]
})
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el: '#app',
data: {},
// 挂载路由实例对象
// router: router
router
})
</script>
</body>
</html>
第二种情况: props
的值为对象类型
const router = new VueRouter({
routes: [
// 如果 props 是一个对象,它会被按原样设置为组件属性
//这里相当于给组件User,通过路由的形式传递了一个对象,而这时候id在User组件中就无法获取到了。
{ path: '/user/:id', component: User, props: { uname: 'lisi', age: 12 }}
]
})
const User = {
props: ['uname', 'age'],
template: ‘<div>用户信息:{{ uname + '---' + age}}</div>'
}
具体代码实现如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<!-- 导入 vue 文件 -->
<script src="./lib/vue_2.5.22.js"></script>
<script src="./lib/vue-router_3.0.2.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>
<router-link to="/register">Register</router-link>
<!-- 路由占位符 -->
<router-view></router-view>
</div>
<script>
const User = {
props: ['id', 'uname', 'age'],
template: '<h1>User 组件 -- 用户id为: {{id}} -- 姓名为:{{uname}} -- 年龄为:{{age}}</h1>'
}
const Register = {
template: '<h1>Register 组件</h1>'
}
// 创建路由实例对象
const router = new VueRouter({
// 所有的路由规则
routes: [
{ path: '/', redirect: '/user'},
{ path: '/user/:id', component: User, props: { uname: 'lisi', age: 20 } },
{ path: '/register', component: Register }
]
})
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el: '#app',
data: {},
// 挂载路由实例对象
// router: router
router
})
</script>
</body>
</html>
在上面的代码中,在路由规则中,通过props
向用户组件中传递了一个对象,那么在User
用户组件中可以接收到传递过来的对象。但是参数id
无法接收到。
如果要解决这个问题,可以使用props
的值为函数类型。也就是给props
传递一个函数。
第三种情况:props
的值为函数类型
const router = new VueRouter({
routes: [
// 如果 props 是一个函数,则这个函数接收 route 对象为自己的形参
//route就是参数对象。
{ path: '/user/:id',
component: User,
props: route => ({ uname: 'zs', age: 20, id: route.params.id })}
]
})
const User = {
props: ['uname', 'age', 'id'],
template: ‘<div>用户信息:{{ uname + '---' + age + '---' + id}}</div>'
}
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<!-- 导入 vue 文件 -->
<script src="./lib/vue_2.5.22.js"></script>
<script src="./lib/vue-router_3.0.2.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link to="/user/3">User3</router-link>
<router-link to="/register">Register</router-link>
<!-- 路由占位符 -->
<router-view></router-view>
</div>
<script>
const User = {
props: ['id', 'uname', 'age'],
template: '<h1>User 组件 -- 用户id为: {{id}} -- 姓名为:{{uname}} -- 年龄为:{{age}}</h1>'
}
const Register = {
template: '<h1>Register 组件</h1>'
}
// 创建路由实例对象
const router = new VueRouter({
// 所有的路由规则
routes: [
{ path: '/', redirect: '/user' },
{
path: '/user/:id',
component: User,
props: route => ({ uname: 'zs', age: 20, id: route.params.id })
},
{ path: '/register', component: Register }
]
})
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el: '#app',
data: {},
// 挂载路由实例对象
// router: router
router
})
</script>
</body>
</html>
为了更加方便的表示路由的路径,可以给路由规则起一个别名,即为“命名路由”。
const router = new VueRouter({
routes: [
{
path: '/user/:id',
name: 'user',
component: User
}
]
})
<!--单击链接,可以跳转到名称为`user`的这个路由规则,并且通过params进行参数的传递-->
<router-link :to="{ name: 'user', params: { id: 123 }}">User</router-link>
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<!-- 导入 vue 文件 -->
<script src="./lib/vue_2.5.22.js"></script>
<script src="./lib/vue-router_3.0.2.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<!--单击链接,可以跳转到名称为`user`的这个路由规则,并且通过params进行参数的传递,id一定要和路由规则中定义的参数保持一致-->
<router-link :to="{ name: 'user', params: {id: 3} }">User3</router-link>
<router-link to="/register">Register</router-link>
<!-- 路由占位符 -->
<router-view></router-view>
</div>
<script>
const User = {
props: ['id', 'uname', 'age'],
template: '<h1>User 组件 -- 用户id为: {{id}} -- 姓名为:{{uname}} -- 年龄为:{{age}}</h1>'
}
const Register = {
template: '<h1>Register 组件</h1>'
}
// 创建路由实例对象
const router = new VueRouter({
// 所有的路由规则
routes: [
{ path: '/', redirect: '/user' },
{
// 命名路由
name: 'user',
path: '/user/:id',
component: User,
props: route => ({ uname: 'zs', age: 20, id: route.params.id })
},
{ path: '/register', component: Register }
]
})
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el: '#app',
data: {},
// 挂载路由实例对象
// router: router
router
})
</script>
</body>
</html>
页面导航的两种方式
声明式导航:通过点击链接实现导航的方式,叫做声明式导航
例如:普通网页中的 <a></a>
链接 或 vue 中的 <router-link></router-link>
编程式导航:通过调用JavaScript
形式的API
实现导航的方式,叫做编程式导航
例如:普通网页中的 location.href
编程式导航基本用法
常用的编程式导航 API 如下:
this.$router.go(n)
const User = {
template: '<div><button @click="goRegister">跳转到注册页面</button></div>',
methods: {
goRegister: function(){
// 用编程的方式控制路由跳转
this.$router.push('/register');
}
}
}
具体吗实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<!-- 导入 vue 文件 -->
<script src="./lib/vue_2.5.22.js"></script>
<script src="./lib/vue-router_3.0.2.js"></script>
</head>
<body>
<!-- 被 vm 实例所控制的区域 -->
<div id="app">
<router-link to="/user/1">User1</router-link>
<router-link to="/user/2">User2</router-link>
<router-link :to="{ name: 'user', params: {id: 3} }">User3</router-link>
<router-link to="/register">Register</router-link>
<!-- 路由占位符 -->
<router-view></router-view>
</div>
<script>
const User = {
props: ['id', 'uname', 'age'],
template: `<div>
<h1>User 组件 -- 用户id为: {{id}} -- 姓名为:{{uname}} -- 年龄为:{{age}}</h1>
<button @click="goRegister">跳转到注册页面</button>
</div>`,
methods: {
goRegister() {
this.$router.push('/register')//编程式导航
}
},
}
const Register = {
template: `<div>
<h1>Register 组件</h1>
<button @click="goBack">后退</button>
</div>`,
methods: {
goBack() {
this.$router.go(-1)
}
}
}
// 创建路由实例对象
const router = new VueRouter({
// 所有的路由规则
routes: [
{ path: '/', redirect: '/user' },
{
// 命名路由
name: 'user',
path: '/user/:id',
component: User,
props: route => ({ uname: 'zs', age: 20, id: route.params.id })
},
{ path: '/register', component: Register }
]
})
// 创建 vm 实例对象
const vm = new Vue({
// 指定控制的区域
el: '#app',
data: {},
// 挂载路由实例对象
// router: router
router
})
</script>
</body>
</html>
router.push() 方法的参数规则
// 字符串(路径名称)
router.push('/home')
// 对象
router.push({ path: '/home' })
// 命名的路由(传递参数)
router.push({ name: '/user', params: { userId: 123 }})
// 带查询参数,变成 /register?uname=lisi
router.push({ path: '/register', query: { uname: 'lisi' }})
App
根组件。将素材中的代码修改成如下的形式:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>基于vue-router的案例</title>
<script src="./lib/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<style type="text/css">
html,
body,
#app {
margin: 0;
padding: 0px;
height: 100%;
}
.header {
height: 50px;
background-color: #5c;
line-height: 50px;
text-align: center;
font-size: 24px;
color: #fff;
}
.footer {
height: 40px;
line-height: 40px;
background-color: #888;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
}
.main {
display: flex;
position: absolute;
top: 50px;
bottom: 40px;
width: 100%;
}
.content {
flex: 1;
text-align: center;
height: 100%;
}
.left {
flex: 0 0 20%;
background-color: #5c;
}
.left a {
color: white;
text-decoration: none;
}
.right {
margin: 5px;
}
.btns {
width: 100%;
height: 35px;
line-height: 35px;
background-color: #f5f5f5;
text-align: left;
padding-left: 10px;
box-sizing: border-box;
}
button {
height: 30px;
background-color: #ecf5ff;
border: 1px solid lightskyblue;
font-size: 12px;
padding: 0 20px;
}
.main-content {
margin-top: 10px;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
ul li {
height: 45px;
line-height: 45px;
background-color: #a0a0a0;
color: #fff;
cursor: pointer;
border-bottom: 1px solid #fff;
}
table {
width: 100%;
border-collapse: collapse;
}
td,
th {
border: 1px solid #eee;
line-height: 35px;
font-size: 12px;
}
th {
background-color: #ddd;
}
</style>
</head>
<body>
<div id="app"><router-view></router-view></div>
<script>
const App = {
template: `<div>
<!-- 头部区域 -->
<header class="header">传智后台管理系统</header>
<!-- 中间主体区域 -->
<div class="main">
<!-- 左侧菜单栏 -->
<div class="content left">
<ul>
<li>用户管理</li>
<li>权限管理</li>
<li>商品管理</li>
<li>订单管理</li>
<li>系统设置</li>
</ul>
</div>
<!-- 右侧内容区域 -->
<div class="content right"><div class="main-content">添加用户表单</div></div>
</div>
<!-- 尾部区域 -->
<footer class="footer">版权信息</footer>
</div>`,
};
//创建路由对象
const router = new VueRouter({
routes: [{ path: "/", component: App }],
});
const vm = new Vue({
el: "#app",
router,
});
</script>
</body>
</html>
在上面的代码中,我们导入了Vue
与Vue-Router
的文件。
然后将核心内容定义到App
这个组件中,同时创建了路由对象,并且指定了路由的规则。接下来将路由对象挂载到了Vue
的实例中。
将模板中的菜单修改成路由连接的形式,如下所示:
<!-- 左侧菜单栏 -->
<div class="content left">
<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>
基本组件创建如下:
const Users = {
template: `<div>
<h3>用户管理区域</h3>
</div>`,
};
const Rights = {
template: `<div>
<h3>权限管理区域</h3>
</div>`,
};
const Goods = {
template: `<div>
<h3>商品管理区域</h3>
</div>`,
};
const Orders = {
template: `<div>
<h3>订单管理区域</h3>
</div>`,
};
const Settings = {
template: `<div>
<h3>系统设置区域</h3>
</div>`,
};
我们知道,当单击左侧的菜单时,上面定义的组件将会在右侧进行展示。
所以需要在右侧,添加一个router-view
的占位符。
<!-- 右侧内容区域 -->
<div class="content right"><div class="main-content"> <router-view /></div></div>
在上一小节中,我们已经将组件都定义好了,下面需要定义其对应的路由规则。
怎样添加对应的路由规则呢?
我们知道整个页面是App
根组件渲染出来的,而前面定义的组件,都是在App
根组件中进行渲染的,也就是作为了App
组件的子组件。
所以,为上一小节中创建的组件添加路由规则,应该是作为App
的子路由来进行添加,这样对应的组件才会在App
组件中进行渲染。
// 创建路由对象
const router = new VueRouter({
routes: [
{
path: "/",
component: App,
redirect: "/users",
children: [
{ path: "/users", component: Users },
{ path: "/rights", component: Rights },
{ path: "/goods", component: Goods },
{ path: "/orders", component: Orders },
{ path: "/settings", component: Settings },
],
},
],
});
当点击左侧的菜单时,对应的组件会在右侧进行展示。
这里将用户组件的内容修改成如下形式:
const Users = {
data() {
return {
userlist: [
{ id: 1, name: "张三", age: 10 },
{ id: 2, name: "李四", age: 20 },
{ id: 3, name: "王五", age: 30 },
{ id: 4, name: "赵六", age: 40 },
],
};
},
template: `<div>
<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>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.age}}</td>
<td>
<a href="javascript:;">详情</a>
</td>
</tr>
</tbody>
</table>
</div>`,
};
在Users
组件中定义用户数据,并且在模板中通过循环的方式将数据渲染出来。
当单击"详情"链接时,跳转到对应的详情页面。这里需要用到编程式导航的内容。
首先定义用户详情页组件
//用户详情组件
const UserInfo = {
props: ["id"],
template: `<div>
<h5>用户详情页 --- 用户Id为:{{id}}</h5>
<button @click="goback()">后退</button>
</div>`,
methods: {
goback() {
// 实现后退功能
this.$router.go(-1);
},
},
};
在该组件中通过props
方式接收传递过来的的用户编号,并且将其打印出来。
同时在该组件中添加一个后退的按钮,通过编程式导航的方式实现后退。
对应的路由规则如下:
// 创建路由对象
const 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 },
],
},
],
});
UserInfo
这个组件也是App
组件的子组件,对应的也会在App
组件的右侧进行展示。
同时,在Users
组件中,给“详情”链接添加对应的单击事件,
const Users = {
data() {
return {
userlist: [
{ id: 1, name: "张三", age: 10 },
{ id: 2, name: "李四", age: 20 },
{ id: 3, name: "王五", age: 30 },
{ id: 4, name: "赵六", age: 40 },
],
};
},
methods: {
goDetail(id) {
console.log(id);
this.$router.push("/userinfo/" + id);
},
},
template: `<div>
<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>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.age}}</td>
<td>
<a href="javascript:;" @click="goDetail(item.id)">详情</a>
</td>
</tr>
</tbody>
</table>
</div>`,
};
对应goDetail
方法中,通过编程式导航跳转到用户详情页面。
完整代码案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>基于vue-router的案例</title>
<script src="./lib/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<style type="text/css">
html,
body,
#app {
margin: 0;
padding: 0px;
height: 100%;
}
.header {
height: 50px;
background-color: #5c;
line-height: 50px;
text-align: center;
font-size: 24px;
color: #fff;
}
.footer {
height: 40px;
line-height: 40px;
background-color: #888;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
}
.main {
display: flex;
position: absolute;
top: 50px;
bottom: 40px;
width: 100%;
}
.content {
flex: 1;
text-align: center;
height: 100%;
}
.left {
flex: 0 0 20%;
background-color: #5c;
}
.left a {
color: white;
text-decoration: none;
}
.right {
margin: 5px;
}
.btns {
width: 100%;
height: 35px;
line-height: 35px;
background-color: #f5f5f5;
text-align: left;
padding-left: 10px;
box-sizing: border-box;
}
button {
height: 30px;
background-color: #ecf5ff;
border: 1px solid lightskyblue;
font-size: 12px;
padding: 0 20px;
}
.main-content {
margin-top: 10px;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
ul li {
height: 45px;
line-height: 45px;
background-color: #a0a0a0;
color: #fff;
cursor: pointer;
border-bottom: 1px solid #fff;
}
table {
width: 100%;
border-collapse: collapse;
}
td,
th {
border: 1px solid #eee;
line-height: 35px;
font-size: 12px;
}
th {
background-color: #ddd;
}
</style>
</head>
<body>
<div id="app"><router-view></router-view></div>
<script>
const App = {
template: `<div>
<!-- 头部区域 -->
<header class="header">传智后台管理系统</header>
<!-- 中间主体区域 -->
<div class="main">
<!-- 左侧菜单栏 -->
<div class="content left">
<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>
<!-- 右侧内容区域 -->
<div class="content right"><div class="main-content"> <router-view /></div></div>
</div>
<!-- 尾部区域 -->
<footer class="footer">版权信息</footer>
</div>`,
};
const Users = {
data() {
return {
userlist: [
{ id: 1, name: "张三", age: 10 },
{ id: 2, name: "李四", age: 20 },
{ id: 3, name: "王五", age: 30 },
{ id: 4, name: "赵六", age: 40 },
],
};
},
methods: {
goDetail(id) {
console.log(id);
this.$router.push("/userinfo/" + id);
},
},
template: `<div>
<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>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.age}}</td>
<td>
<a href="javascript:;" @click="goDetail(item.id)">详情</a>
</td>
</tr>
</tbody>
</table>
</div>`,
};
//用户详情组件
const UserInfo = {
props: ["id"],
template: `<div>
<h5>用户详情页 --- 用户Id为:{{id}}</h5>
<button @click="goback()">后退</button>
</div>`,
methods: {
goback() {
// 实现后退功能
this.$router.go(-1);
},
},
};
const Rights = {
template: `<div>
<h3>权限管理区域</h3>
</div>`,
};
const Goods = {
template: `<div>
<h3>商品管理区域</h3>
</div>`,
};
const Orders = {
template: `<div>
<h3>订单管理区域</h3>
</div>`,
};
const Settings = {
template: `<div>
<h3>系统设置区域</h3>
</div>`,
};
// 创建路由对象
const 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 },
],
},
],
});
const vm = new Vue({
el: "#app",
router,
});
</script>
</body>
</html>
Vue-router
中的路由守卫,主要是对其内容进行保护,如果没有对应的权限,则不允许访问。
我们首先来看一下全局守卫,也就是所有的路由都会经过全局守卫来进行检测。
//实现全局守卫
router.beforeEach((to, from, next) => {
//to:去哪个页面,from来自哪个页面,next继续执行.
//判断哪个路由需要进行守卫,这里可以通过元数据方式
if (to.meta.auth) {
if (window.isLogin) {
next();
} else {
next("/login?redirect=" + to.fullPath);
}
} else {
next();
}
});
如果没有元数据,则继续访问用户要访问的页面。
// 创建路由对象
const router = new VueRouter({
routes: [
{ path: "/login", component: Login },
{
path: "/",
component: App,
redirect: "/users",
children: [
{
path: "/users",
component: Users,
meta: {
auth: true,
},
},
{ path: "/userinfo/:id", component: UserInfo, props: true },
{ path: "/rights", component: Rights },
{ path: "/goods", component: Goods },
{ path: "/orders", component: Orders },
{ path: "/settings", component: Settings },
],
},
],
});
在上面的代码中,给/users
路由添加了元数据。
登录组件创建如下:
const Login = {
data() {
return {
isLogin: window.isLogin,
};
},
template: `<div>
<button @click="login" v-if="!isLogin">登录</button>
<button @click="logout" v-else>注销</button>
</div>`,
methods: {
login() {
window.isLogin = true;
this.$router.push(this.$route.query.redirect);
},
logout() {
this.isLogin = window.isLogin = false;
},
},
};
当单击登录按钮后,进行将window.isLogin
设置为true
, 并且进行跳转。
全部代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>基于vue-router的案例</title>
<script src="./lib/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<style type="text/css">
html,
body,
#app {
margin: 0;
padding: 0px;
height: 100%;
}
.header {
height: 50px;
background-color: #5c;
line-height: 50px;
text-align: center;
font-size: 24px;
color: #fff;
}
.footer {
height: 40px;
line-height: 40px;
background-color: #888;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
}
.main {
display: flex;
position: absolute;
top: 50px;
bottom: 40px;
width: 100%;
}
.content {
flex: 1;
text-align: center;
height: 100%;
}
.left {
flex: 0 0 20%;
background-color: #5c;
}
.left a {
color: white;
text-decoration: none;
}
.right {
margin: 5px;
}
.btns {
width: 100%;
height: 35px;
line-height: 35px;
background-color: #f5f5f5;
text-align: left;
padding-left: 10px;
box-sizing: border-box;
}
button {
height: 30px;
background-color: #ecf5ff;
border: 1px solid lightskyblue;
font-size: 12px;
padding: 0 20px;
}
.main-content {
margin-top: 10px;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
ul li {
height: 45px;
line-height: 45px;
background-color: #a0a0a0;
color: #fff;
cursor: pointer;
border-bottom: 1px solid #fff;
}
table {
width: 100%;
border-collapse: collapse;
}
td,
th {
border: 1px solid #eee;
line-height: 35px;
font-size: 12px;
}
th {
background-color: #ddd;
}
</style>
</head>
<body>
<div id="app"><router-view></router-view></div>
<script>
const App = {
template: `<div>
<!-- 头部区域 -->
<header class="header">传智后台管理系统</header>
<!-- 中间主体区域 -->
<div class="main">
<!-- 左侧菜单栏 -->
<div class="content left">
<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>
<!-- 右侧内容区域 -->
<div class="content right"><div class="main-content"> <router-view /></div></div>
</div>
<!-- 尾部区域 -->
<footer class="footer">版权信息</footer>
</div>`,
};
const Users = {
data() {
return {
userlist: [
{ id: 1, name: "张三", age: 10 },
{ id: 2, name: "李四", age: 20 },
{ id: 3, name: "王五", age: 30 },
{ id: 4, name: "赵六", age: 40 },
],
};
},
methods: {
goDetail(id) {
console.log(id);
this.$router.push("/userinfo/" + id);
},
},
template: `<div>
<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>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.age}}</td>
<td>
<a href="javascript:;" @click="goDetail(item.id)">详情</a>
</td>
</tr>
</tbody>
</table>
</div>`,
};
//用户详情组件
const UserInfo = {
props: ["id"],
template: `<div>
<h5>用户详情页 --- 用户Id为:{{id}}</h5>
<button @click="goback()">后退</button>
</div>`,
methods: {
goback() {
// 实现后退功能
this.$router.go(-1);
},
},
};
const Rights = {
template: `<div>
<h3>权限管理区域</h3>
</div>`,
};
const Goods = {
template: `<div>
<h3>商品管理区域</h3>
</div>`,
};
const Orders = {
template: `<div>
<h3>订单管理区域</h3>
</div>`,
};
const Settings = {
template: `<div>
<h3>系统设置区域</h3>
</div>`,
};
const Login = {
data() {
return {
isLogin: window.isLogin,
};
},
template: `<div>
<button @click="login" v-if="!isLogin">登录</button>
<button @click="logout" v-else>注销</button>
</div>`,
methods: {
login() {
window.isLogin = true;
this.$router.push(this.$route.query.redirect);
},
logout() {
this.isLogin = window.isLogin = false;
},
},
};
// 创建路由对象
const router = new VueRouter({
routes: [
{ path: "/login", component: Login },
{
path: "/",
component: App,
redirect: "/users",
children: [
{
path: "/users",
component: Users,
meta: {
auth: true,
},
},
{ path: "/userinfo/:id", component: UserInfo, props: true },
{ path: "/rights", component: Rights },
{ path: "/goods", component: Goods },
{ path: "/orders", component: Orders },
{ path: "/settings", component: Settings },
],
},
],
});
//实现全局守卫
router.beforeEach((to, from, next) => {
//to:去哪个页面,from来自哪个页面,next继续执行.
//判断哪个路由需要进行守卫,这里可以通过元数据方式
if (to.meta.auth) {
if (window.isLogin) {
next();
} else {
next("/login?redirect=" + to.fullPath);
}
} else {
next();
}
});
const vm = new Vue({
el: "#app",
router,
});
</script>
</body>
</html>
以上是全局守卫,对所有的路由都起作用。
但是,如果项目比较简单,路由规则定义的比较少,可以将守卫定位到某个路由规则内。这就是路由独享守卫
// 创建路由对象
const router = new VueRouter({
routes: [
{ path: "/login", component: Login },
{
path: "/",
component: App,
redirect: "/users",
children: [
{
path: "/users",
component: Users,
meta: {
auth: true,
},
beforeEnter(to, from, next) {
if (window.isLogin) {
next();
} else {
next("/login?redirect=" + to.fullPath);
}
},
},
{ path: "/userinfo/:id", component: UserInfo, props: true },
{ path: "/rights", component: Rights },
{ path: "/goods", component: Goods },
{ path: "/orders", component: Orders },
{ path: "/settings", component: Settings },
],
},
],
});
在上面的代码中,给/users
这个路由守卫,注意这里的方法名为beforeEnter
.同时,这里将守卫定义在/users
路由规则内,所以不需要对元数据进行判断,只需要判断用户是否登录就可以了。(注意:在进行以上测试时,需要将全局守卫的代码注释掉)
组件内守卫
可以在路由组件内直接定义以下路由导航守卫。
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
将如下的代码直接添加到组件内。
const Users = {
data() {
return {
userlist: [
{ id: 1, name: "张三", age: 10 },
{ id: 2, name: "李四", age: 20 },
{ id: 3, name: "王五", age: 30 },
{ id: 4, name: "赵六", age: 40 },
],
};
},
methods: {
goDetail(id) {
console.log(id);
this.$router.push("/userinfo/" + id);
},
},
template: `<div>
<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>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.age}}</td>
<td>
<a href="javascript:;" @click="goDetail(item.id)">详情</a>
</td>
</tr>
</tbody>
</table>
</div>`,
beforeRouteEnter(to, from, next) {
if (window.isLogin) {
next();
} else {
next("/login?redirect=" + to.fullPath);
}
},
};
在上面的代码中,直接将路由守卫对应的方法添加到了组件中。
注意:在测试之前将路由规则中定义的路由守卫的代码注释掉。
在前面的案例中,我们都是将路由定义好,然后通过路由守卫来判断,某个用户是否登录,从而决定能否访问某个路由规则对应的组件内容。
但是,如果某些路由规则只能用户登录以后才能够访问,那么我们也可以不用提前定义好,而是在登录后,通过addRoutes
方法为其动态的添加。
首先这里需要,还需要全局的路由守卫来进行校验判断,只不过这里全局路由守卫的逻辑发生了变化。
router.beforeEach((to, from, next) => {
//to:去哪个页面,from来自哪个页面,next继续执行.
if (window.isLogin) {
//用户已经登录
if (to.path === "/login") {
// 用户已经登录了,但是又访问登录页面,这里直接跳转到用户列表页面
next("/");
} else {
//用户已经登录,并且访问其它页面,则运行访问
next();
}
} else {
//用户没有登录,并且访问的就是登录页,则运行访问登录页
if (to.path === "/login") {
next();
} else {
//用户没有登录,访问其它页面,则跳转到登录页面。
next("/login?redirect=" + to.fullPath);
}
}
});
下面对登录组件进行修改
const Login = {
data() {
return {
isLogin: window.isLogin,
};
},
template: `<div>
<button @click="login" v-if="!isLogin">登录</button>
<button @click="logout" v-else>注销</button>
</div>`,
methods: {
login() {
window.isLogin = true;
if (this.$route.query.redirect) {
//动态添加路由:
this.$router.addRoutes([
{
path: "/",
component: App,
redirect: "/users",
children: [
{
path: "/users",
component: Users,
meta: {
auth: true,
},
// beforeEnter(to, from, next) {
// if (window.isLogin) {
// next();
// } else {
// next("/login?redirect=" + to.fullPath);
// }
// },
},
{ path: "/userinfo/:id", component: UserInfo, props: true },
{ path: "/rights", component: Rights },
{ path: "/goods", component: Goods },
{ path: "/orders", component: Orders },
{ path: "/settings", component: Settings },
],
},
]);
this.$router.push(this.$route.query.redirect);
} else {
this.$router.push("/");
}
},
logout() {
this.isLogin = window.isLogin = false;
},
},
};
在登录成功后,通过addRoutes
方法动态的添加路由规则,也就是所添加的路由规则只能是在登录以后才能够访问,所以全局守卫的判断条件发生了变化,不在判断是否有元数据,而只是判断是否登录。如果登录了,想访问上面的路由规则,则运行访问,如果没有登录则不允许访问。
注意:对应的原有的路由规则应该注释掉。
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>基于vue-router的案例</title>
<script src="./lib/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<style type="text/css">
html,
body,
#app {
margin: 0;
padding: 0px;
height: 100%;
}
.header {
height: 50px;
background-color: #5c;
line-height: 50px;
text-align: center;
font-size: 24px;
color: #fff;
}
.footer {
height: 40px;
line-height: 40px;
background-color: #888;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
}
.main {
display: flex;
position: absolute;
top: 50px;
bottom: 40px;
width: 100%;
}
.content {
flex: 1;
text-align: center;
height: 100%;
}
.left {
flex: 0 0 20%;
background-color: #5c;
}
.left a {
color: white;
text-decoration: none;
}
.right {
margin: 5px;
}
.btns {
width: 100%;
height: 35px;
line-height: 35px;
background-color: #f5f5f5;
text-align: left;
padding-left: 10px;
box-sizing: border-box;
}
button {
height: 30px;
background-color: #ecf5ff;
border: 1px solid lightskyblue;
font-size: 12px;
padding: 0 20px;
}
.main-content {
margin-top: 10px;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
ul li {
height: 45px;
line-height: 45px;
background-color: #a0a0a0;
color: #fff;
cursor: pointer;
border-bottom: 1px solid #fff;
}
table {
width: 100%;
border-collapse: collapse;
}
td,
th {
border: 1px solid #eee;
line-height: 35px;
font-size: 12px;
}
th {
background-color: #ddd;
}
</style>
</head>
<body>
<div id="app"><router-view></router-view></div>
<script>
const App = {
template: `<div>
<!-- 头部区域 -->
<header class="header">传智后台管理系统</header>
<!-- 中间主体区域 -->
<div class="main">
<!-- 左侧菜单栏 -->
<div class="content left">
<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>
<!-- 右侧内容区域 -->
<div class="content right"><div class="main-content"> <router-view /></div></div>
</div>
<!-- 尾部区域 -->
<footer class="footer">版权信息</footer>
</div>`,
};
const Users = {
data() {
return {
userlist: [
{ id: 1, name: "张三", age: 10 },
{ id: 2, name: "李四", age: 20 },
{ id: 3, name: "王五", age: 30 },
{ id: 4, name: "赵六", age: 40 },
],
};
},
methods: {
goDetail(id) {
console.log(id);
this.$router.push("/userinfo/" + id);
},
},
template: `<div>
<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>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.age}}</td>
<td>
<a href="javascript:;" @click="goDetail(item.id)">详情</a>
</td>
</tr>
</tbody>
</table>
</div>`,
// beforeRouteEnter(to, from, next) {
// if (window.isLogin) {
// next();
// } else {
// next("/login?redirect=" + to.fullPath);
// }
// },
};
//用户详情组件
const UserInfo = {
props: ["id"],
template: `<div>
<h5>用户详情页 --- 用户Id为:{{id}}</h5>
<button @click="goback()">后退</button>
</div>`,
methods: {
goback() {
// 实现后退功能
this.$router.go(-1);
},
},
};
const Rights = {
template: `<div>
<h3>权限管理区域</h3>
</div>`,
};
const Goods = {
template: `<div>
<h3>商品管理区域</h3>
</div>`,
};
const Orders = {
template: `<div>
<h3>订单管理区域</h3>
</div>`,
};
const Settings = {
template: `<div>
<h3>系统设置区域</h3>
</div>`,
};
const Login = {
data() {
return {
isLogin: window.isLogin,
};
},
template: `<div>
<button @click="login" v-if="!isLogin">登录</button>
<button @click="logout" v-else>注销</button>
</div>`,
methods: {
login() {
window.isLogin = true;
if (this.$route.query.redirect) {
//动态添加路由:
this.$router.addRoutes([
{
path: "/",
component: App,
redirect: "/users",
children: [
{
path: "/users",
component: Users,
meta: {
auth: true,
},
// beforeEnter(to, from, next) {
// if (window.isLogin) {
// next();
// } else {
// next("/login?redirect=" + to.fullPath);
// }
// },
},
{ path: "/userinfo/:id", component: UserInfo, props: true },
{ path: "/rights", component: Rights },
{ path: "/goods", component: Goods },
{ path: "/orders", component: Orders },
{ path: "/settings", component: Settings },
],
},
]);
this.$router.push(this.$route.query.redirect);
} else {
this.$router.push("/");
}
},
logout() {
this.isLogin = window.isLogin = false;
},
},
};
// 创建路由对象
const router = new VueRouter({
routes: [
{ path: "/login", component: Login },
// {
// path: "/",
// component: App,
// redirect: "/users",
// children: [
// {
// path: "/users",
// component: Users,
// meta: {
// auth: true,
// },
// // beforeEnter(to, from, next) {
// // if (window.isLogin) {
// // next();
// // } else {
// // next("/login?redirect=" + to.fullPath);
// // }
// // },
// },
// { path: "/userinfo/:id", component: UserInfo, props: true },
// { path: "/rights", component: Rights },
// { path: "/goods", component: Goods },
// { path: "/orders", component: Orders },
// { path: "/settings", component: Settings },
// ],
// },
],
});
//实现全局守卫
// router.beforeEach((to, from, next) => {
// //to:去哪个页面,from来自哪个页面,next继续执行.
// //判断哪个路由需要进行守卫,这里可以通过元数据方式
// if (to.meta.auth) {
// if (window.isLogin) {
// next();
// } else {
// next("/login?redirect=" + to.fullPath);
// }
// } else {
// next();
// }
// });
router.beforeEach((to, from, next) => {
//to:去哪个页面,from来自哪个页面,next继续执行.
if (window.isLogin) {
//用户已经登录
if (to.path === "/login") {
// 用户已经登录了,但是又访问登录页面,这里直接跳转到用户列表页面
next("/");
} else {
//用户已经登录,并且访问其它页面,则运行访问
next();
}
} else {
//用户没有登录,并且访问的就是登录页,则运行访问登录页
if (to.path === "/login") {
next();
} else {
//用户没有登录,访问其它页面,则跳转到登录页面。
next("/login?redirect=" + to.fullPath);
}
}
});
const vm = new Vue({
el: "#app",
router,
});
</script>
</body>
</html>
利用keepalive
做组件缓存,保留组件状态,提高执行效率。
<keep-alive include="home">
<router-view></router-view>
</keep-alive>
使用include
或者exclude
时要给组件设置name
(这个是组件的名称,组件的名称通过给组件添加name
属性来进行设置)
当我们进行路由切换的时候,对应的组件会被重新创建,同时数据也会不断的重新加载。
如果数据没有变化,就没有必要每次都重新发送异步请求加载数据
现在,在App
组件中添加keep-alive
因为切换的组件都是在该router-view
中进行展示。
<!-- 右侧内容区域 -->
<div class="content right"><div class="main-content">
<keep-alive>
<router-view />
</keep-alive>
</div></div>
</div>
下面可以进行验证。
const Rights = {
template: `<div>
<h3>权限管理区域</h3>
</div>`,
created() {
console.log(new Date());
},
};
在Rights
组件中,添加了created
方法,该方法中输出日期时间,但是我们不断的切换,发现并不是每次都打印日期时间内容。
当然,以上keep-alive
的使用方式,是将所有的组件都缓存了,如果只想缓存某个组件,可以采用如下的方式
<!-- 右侧内容区域 -->
<div class="content right"><div class="main-content">
<keep-alive include='goods'>
<router-view />
</keep-alive>
</div></div>
</div>
在上面的代码中,通过include
添加了需要缓存的组件的名称,如果有多个在include
中可以继续添加,每个组件名称之间用逗号分隔。
以上的含义就是只有goods
组件需要被缓存(goods
是组件的name
值)
const Goods = {
name: "goods",
template: `<div>
<h3>商品管理区域</h3>
</div>`,
created() {
console.log(new Date());
},
};
exclude
表示的就是除了指定的组件以外(也是组件的name
),其它组件都进行缓存。
应用场景
如果未使用keep-alive组件,则在页面回退时仍然会重新渲染页面,触发created钩子,使用体验不好。 在以下场景中使用keep-alive组件会显著提高用户体验,菜单存在多级关系,多见于列表页+详情页的场景如:
生命周期:
activated
和deactivated
会在keep-alive
内所有嵌套的组件中触发
如:B页面是缓存页面
当A页面跳到B页面时,B页面的生命周期:activated(可在此时更新数据)
B页面跳出时,触发deactivated
B页面自身刷新时,会触发created-mouted-activated
前端路由中,不管是什么实现模式,都是客户端的一种实现方式,也就是当路径发生变化的时候,是不会向服务器发送请求的。
如果需要向服务器发送请求,需要用到ajax
方式。
两种模式的区别
首先是表现形式的区别
Hash
模式
https://www.baidu.com/#/showlist?id=22256
当然这种模式相对来说比较丑,路径中带有与数据无关的符号,例如#
与?
History
模式
https://www.baidu.com/showlist/22256
History
模式是一个正常的路径的模式,如果要想实现这种模式,还需要服务端的相应支持。
下面再来看一下两者原理上的区别。
Hash
模式是基于锚点,以及onhashchange
事件。
History
模式是基于HTML5
中的History API
也就是如下两个方法
history.pushState( )
IE10
以后才支持
history.replaceState( )
History
模式的使用History
模式需要服务器的支持,为什么呢?
所以说,在服务端应该除了静态资源外都返回单页应用的index.html
下面我们开始history
模式来演示一下对应的问题。
首先添加一个针对404组件的处理
首先在菜单栏中添加一个链接:
<!-- 左侧菜单栏 -->
<div class="content left">
<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>
<li><router-link to="/about"> 关于</router-link></li>
</ul>
</div>
这里我们添加了一个“关于”的链接,但是我们没有为其定义相应的组件,所以这里需要处理404的情况。
const NotFound = {
template: `<div>
你访问的页面不存在!!
</div>`,
};
在程序中添加了一个针对404的组件。
const router = new VueRouter({
mode: "history",
const router = new VueRouter({
mode: "history",
routes: [
{ path: "/login", component: Login },
{ path: "*", component: NotFound },
{
path: "/",
component: App,
redirect: "/users",
children: [
{
path: "/users",
component: Users,
meta: {
auth: true,
},
// beforeEnter(to, from, next) {
// if (window.isLogin) {
// next();
// } else {
// next("/login?redirect=" + to.fullPath);
// }
// },
},
{ path: "/userinfo/:id", component: UserInfo, props: true },
{ path: "/rights", component: Rights },
{ path: "/goods", component: Goods },
{ path: "/orders", component: Orders },
{ path: "/settings", component: Settings },
],
},
],
});
在上面的代码中,指定了处理404的路由规则,同时将路由的模式修改成了history
模式。同时,启用这里启用了其它的组件的路由规则配置,也就是不在login
方法中使用addRoutes
方法来动态添加路由规则了。
login
方法修改成如下形式:
login() {
// window.isLogin = true;
window.sessionStorage.setItem("isLogin", true);
if (this.$route.query.redirect) {
// //动态添加路由:
// this.$router.addRoutes([
// {
// path: "/",
// component: App,
// redirect: "/users",
// children: [
// {
// path: "/users",
// component: Users,
// meta: {
// auth: true,
// },
// // beforeEnter(to, from, next) {
// // if (window.isLogin) {
// // next();
// // } else {
// // next("/login?redirect=" + to.fullPath);
// // }
// // },
// },
// { path: "/userinfo/:id", component: UserInfo, props: true },
// { path: "/rights", component: Rights },
// { path: "/goods", component: Goods },
// { path: "/orders", component: Orders },
// { path: "/settings", component: Settings },
// ],
// },
// ]);
this.$router.push(this.$route.query.redirect);
} else {
this.$router.push("/");
}
}
现在已经将前端vue
中的代码修改完毕了,下面我们要将页面的内容部署到node.js
服务器中。
而且上面的代码中,我们使用了sessionStorage
来保存登录用户的信息,不在使用window
下的isLogin
对应的data
内容下的代码也要修改:
const Login = {
data() {
return {
isLogin: window.sessionStorage.getItem("isLogin"),
};
},
路由守卫中的代码进行如下修改:
jsrouter.beforeEach((to, from, next) => {
//to:去哪个页面,from来自哪个页面,next继续执行.
if (window.sessionStorage.getItem("isLogin")) {
//用户已经登录
if (to.path === "/login") {
// 用户已经登录了,但是又访问登录页面,这里直接跳转到用户列表页面
next("/");
} else {
//用户已经登录,并且访问其它页面,则运行访问
next();
}
} else {
//用户没有登录,并且访问的就是登录页,则运行访问登录页
if (to.path === "/login") {
next();
} else {
//用户没有登录,访问其它页面,则跳转到登录页面。
next("/login?redirect=" + to.fullPath);
}
}
});
在上面的代码中,我们也是通过sessionStorage
来获取登录信息。
index.html
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>基于vue-router的案例</title>
<script src="./lib/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app"><router-view></router-view></div>
<script>
const App = {
template: `<div>
<!-- 头部区域 -->
<header class="header">传智后台管理系统</header>
<!-- 中间主体区域 -->
<div class="main">
<!-- 左侧菜单栏 -->
<div class="content left">
<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>
<li><router-link to="/about"> 关于</router-link></li>
</ul>
</div>
<!-- 右侧内容区域 -->
<div class="content right"><div class="main-content">
<keep-alive include='goods'>
<router-view />
</keep-alive>
</div></div>
</div>
<!-- 尾部区域 -->
<footer class="footer">版权信息</footer>
</div>`,
};
const Users = {
data() {
return {
userlist: [
{ id: 1, name: "张三", age: 10 },
{ id: 2, name: "李四", age: 20 },
{ id: 3, name: "王五", age: 30 },
{ id: 4, name: "赵六", age: 40 },
],
};
},
methods: {
goDetail(id) {
console.log(id);
this.$router.push("/userinfo/" + id);
},
},
template: `<div>
<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>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.age}}</td>
<td>
<a href="javascript:;" @click="goDetail(item.id)">详情</a>
</td>
</tr>
</tbody>
</table>
</div>`,
// beforeRouteEnter(to, from, next) {
// if (window.isLogin) {
// next();
// } else {
// next("/login?redirect=" + to.fullPath);
// }
// },
};
//用户详情组件
const UserInfo = {
props: ["id"],
template: `<div>
<h5>用户详情页 --- 用户Id为:{{id}}</h5>
<button @click="goback()">后退</button>
</div>`,
methods: {
goback() {
// 实现后退功能
this.$router.go(-1);
},
},
};
const Rights = {
template: `<div>
<h3>权限管理区域</h3>
</div>`,
};
const Goods = {
name: "goods",
template: `<div>
<h3>商品管理区域</h3>
</div>`,
created() {
console.log(new Date());
},
};
const Orders = {
template: `<div>
<h3>订单管理区域</h3>
</div>`,
};
const Settings = {
template: `<div>
<h3>系统设置区域</h3>
</div>`,
};
const NotFound = {
template: `<div>
你访问的页面不存在!!
</div>`,
};
const Login = {
data() {
return {
isLogin: window.sessionStorage.getItem("isLogin"),
};
},
template: `<div>
<button @click="login" v-if="!isLogin">登录</button>
<button @click="logout" v-else>注销</button>
</div>`,
methods: {
login() {
// window.isLogin = true;
window.sessionStorage.setItem("isLogin", true);
if (this.$route.query.redirect) {
// //动态添加路由:
// this.$router.addRoutes([
// {
// path: "/",
// component: App,
// redirect: "/users",
// children: [
// {
// path: "/users",
// component: Users,
// meta: {
// auth: true,
// },
// // beforeEnter(to, from, next) {
// // if (window.isLogin) {
// // next();
// // } else {
// // next("/login?redirect=" + to.fullPath);
// // }
// // },
// },
// { path: "/userinfo/:id", component: UserInfo, props: true },
// { path: "/rights", component: Rights },
// { path: "/goods", component: Goods },
// { path: "/orders", component: Orders },
// { path: "/settings", component: Settings },
// ],
// },
// ]);
this.$router.push(this.$route.query.redirect);
} else {
this.$router.push("/");
}
},
logout() {
this.isLogin = window.isLogin = false;
},
},
};
// 创建路由对象
const router = new VueRouter({
mode: "history",
routes: [
{ path: "/login", component: Login },
{ path: "*", component: NotFound },
{
path: "/",
component: App,
redirect: "/users",
children: [
{
path: "/users",
component: Users,
meta: {
auth: true,
},
// beforeEnter(to, from, next) {
// if (window.isLogin) {
// next();
// } else {
// next("/login?redirect=" + to.fullPath);
// }
// },
},
{ path: "/userinfo/:id", component: UserInfo, props: true },
{ path: "/rights", component: Rights },
{ path: "/goods", component: Goods },
{ path: "/orders", component: Orders },
{ path: "/settings", component: Settings },
],
},
],
});
//实现全局守卫
// router.beforeEach((to, from, next) => {
// //to:去哪个页面,from来自哪个页面,next继续执行.
// //判断哪个路由需要进行守卫,这里可以通过元数据方式
// if (to.meta.auth) {
// if (window.isLogin) {
// next();
// } else {
// next("/login?redirect=" + to.fullPath);
// }
// } else {
// next();
// }
// });
router.beforeEach((to, from, next) => {
//to:去哪个页面,from来自哪个页面,next继续执行.
if (window.sessionStorage.getItem("isLogin")) {
//用户已经登录
if (to.path === "/login") {
// 用户已经登录了,但是又访问登录页面,这里直接跳转到用户列表页面
next("/");
} else {
//用户已经登录,并且访问其它页面,则运行访问
next();
}
} else {
//用户没有登录,并且访问的就是登录页,则运行访问登录页
if (to.path === "/login") {
next();
} else {
//用户没有登录,访问其它页面,则跳转到登录页面。
next("/login?redirect=" + to.fullPath);
}
}
});
const vm = new Vue({
el: "#app",
router,
});
</script>
</body>
</html>
下面看一下具体的node
代码的实现。
app.js文件中的代码如下:
const path = require("path");
//导入处理history模式的模块
const history = require("connect-history-api-fallback");
const express = require("express");
const app = express();
//注册处理history模式的中间件
// app.use(history())
//处理静态资源的中间件,处理web目录下的index.html
app.use(express.static(path.join(__dirname, "../web")));
app.listen(3000, () => {
console.log("服务器开启");
});
connect-history-api-fallback
模块的安装如下(注意在上面的代码中还没有使用该模块)
npm install --save connect-history-api-fallback
下面还需要安装express
npm install express
启动服务
node app.js
并且当我们去单击左侧的菜单的时候,可以实现页面的切换,同时单击“关于”的时候,会出现NotFound
组件中的内容。
经过测试发现好像没有什么问题,那这是什么原因呢?你想一下当我们单击左侧菜单的时候,路由是怎样工作的呢?
所以说现在整个操作都是在客户端完成的。
但是,当我刷新了浏览器以后,会出现怎样的情况呢?
以上就是如果vue-router
开启了history
模式后,出现的问题。
下面解决这个问题,在服务端启用connect-history-api-fallback
模块就可以了,如下代码所示:
const path = require("path");
//导入处理history模式的模块
const history = require("connect-history-api-fallback");
const express = require("express");
const app = express();
//注册处理history模式的中间件
app.use(history());
//处理静态资源的中间件
app.use(express.static(path.join(__dirname, "../web")));
app.listen(3000, () => {
console.log("服务器开启");
});
服务端的代码做了修改以后,一定要服务端重新启动node app.js
然后经过测试以后发现没有问题了。
那么现在你考虑一下,具体的工作方式是什么?
当我们在服务端开启对history
模式的支持以后,我们刷新浏览器,会想服务器发送请求,例如:http://localhost:3000/orders
History
模式代理服务器
代理服务器:一般是指局域网内部的机器通过代理服务器发送请求到互联网上的服务器,代理服务器一般作用在客户端。应用比如:GoAgent,神器.
反向代理服务器
**反向代理服务器:**在服务器端接受客户端的请求,然后把请求分发给具体的服务器进行处理,然后再将服务器的响应结果反馈给客户端。Nginx就是其中的一种反向代理服务器软件。
Nginx简介
Nginx (“engine x”) ,Nginx (“engine x”) 是俄罗斯人Igor Sysoev(塞索耶夫)编写的一款高性能的 HTTP 和反向代理服务器。也是一个IMAP/POP3/SMTP代理服务器;也就是说,Nginx本身就可以托管网站,进行HTTP服务处理,也可以作为反向代理服务器使用。
Nginx的应用现状
淘宝、新浪博客、新浪播客、网易新闻、六间房、56.com、Discuz!、水木社区、豆瓣、YUPOO、海内、迅雷在线 等多家网站使用 Nginx 作为Web服务器或反向代理服务器。
Nginx的特点
**跨平台:**Nginx 可以在大多数 Unix like OS编译运行,而且也有Windows的移植版本。
**配置异常简单:**非常容易上手。配置风格跟程序开发一样,神一般的配置
**非阻塞、高并发连接:**数据复制时,磁盘I/O的第一阶段是非阻塞的。官方测试能够支撑5万并发连接,在实际生产环境中跑到2~3万并发连接数.
高并发:其实就是使用技术手段使得系统可以并行处理很多的请求!衡量指标常用的有响应时间,吞吐量,每秒查询率QPS,并发用户数。响应时间:系统对请求做出响应的时间。你简单理解为一个http请求返回所用的时间。
吞吐量:单位时间内处理的请求数量。
QPS:每秒可以处理的请求数
并发用户数:同时承载正常使用系统功能的用户数量。也就是多少个人同时使用这个系统,这个系统还能正常运行。这个用户数量就是并发用户数了
**内存消耗小:**处理大并发的请求内存消耗非常小。在3万并发连接下,开启的10个Nginx 进程才消耗150M内存(15M*10=150M)。
**成本低廉:**Nginx为开源软件,可以免费使用。而购买F5 BIG-IP、NetScaler等硬件负载均衡交换机则需要十多万至几十万人民币 。
**内置的健康检查功能:**如果 Nginx Proxy 后端的某台 Web 服务器宕机了,不会影响前端访问。
**节省带宽:**支持 GZIP 压缩,可以添加浏览器本地缓存的 Header 头。
**稳定性高:**用于反向代理,宕机的概率微乎其微
Nginx启动
修改配置文件
启动服务:
•直接运行nginx.exe
下面我们要做的就是将我们的程序部署到Nginx
中。
http://localhost:8081/
就可以看到对应的页面了,然后单击菜单,发现可以进行留有的切换。当单击刷新按钮后,发现出现了404的错误。
原因,点击刷新按钮就会向服务器发送请求,而在服务端没有对应的文件,所以会出现404的错误。
然后进行如下的配置:
location / {
root html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
现在,我们又加了try_files
配置,表示尝试访问文件。
下面可以进行测试。
测试之前需要重新启动服务器。
nginx -s reload
现在我们已经掌握了Vue Router
的基本使用,下面我们来模拟Vue Router
的实现,通过模拟实现,来了解其内部的实现原理。
我们这里模拟的是History
模式。Hash
模式基本实现上是一样的。
这里先来复习一下Hash
模式的工作原理。
URL
中#
后面的内容作为路径地址,当地址改变的时候不会向服务器发送请求,但是会触发hashchange
事件。hashchange
事件,在该事件中记录当前的路由地址,然后根据路由地址找到对应组件。下面再来复习一下History
模式
history.pushState()
方法改变地址栏,并且将当前地址记录到浏览器的历史记录中。当前浏览器不会向服务器发送请求popstate
事件,可以发现浏览器历史操作的变化,记录改变后的地址,单击前进或者是后退按钮的时候触发该事件在模拟Vue Router
之前,
首先来看一下Vue Router
的核心代码,做一个简单的分析
//注册插件
Vue.use(VueRouter)
//创建路由对象
const router=new VueRouter({
routes:[
{name:'home',path:'/',component:homeComponent}
]
})
// 创建Vue实例,注册router对象
new Vue({
router,
render:h=>h(App)
}).$mount('#apps')
我们知道Vue Router
是Vue
的插件,所以在上面的代码中,我们首先调用use
方法注册该插件。
use
方法需要的参数可以是一个函数或者是对象,如果传递的是函数,use
内部会直接调用该函数,
如果传递的是一个对象,那么在use
内部会调用该对象的install
方法。
而我们这里传递的是对象,所以后面在模拟VUe Router
的时候,要实现一个install
方法。
下面我们创建了VueRouter
实例,所以VueRouter
可以是构造方法或者是类,那么我们在模拟的时候,将其定义为类。并且该类中有一个静态的install
方法,因为我们将VueRouter
传递给了use
方法。
在VueRouter
类的构造方法中,需要有一个参数,该参数是一个对象,该对象中定义了路由的规则。
最后创建了Vue
的实例,并且将创建好的Vue Router
对象传递到该实例中。
下面我们在看一下Vue Router
的类图
在该类图中,上半部分是VueRouter
的属性,而下半部分是VueRouter
的方法。
options
作用是记录构造函数中传入的对象, 我们在创建Vue Router
的实例的时候,传递了一个对象,而该对象中定义了路由规则。而options
就是记录传入的这个对象的。
install
是一个静态方法,用来实现Vue
的插件机制。
Constructor
是一个构造方法,该该构造方法中会初始化options
,data
,routeMap
这几个属性。
inti
方法主要是用来调用下面的三个方法,也就把不同的代码分隔到不同的方法中去实现。
initEvent
方法,用来注册popstate
事件,
initComponents
方法,主要作用是用来创建router-link
和router-view
这两个组件的。
现在我们已经对Vue Router
做了一个分析。
下面开始创建自己的Vue Router
.
install
方法需要的参数是Vue
的构造方法。
let _Vue = null;
export default class VueRouter {
//调用install方法的时候,会传递Vue的构造函数
static install(Vue) {
//首先判断插件是否已经被安装,如果已经被安装,就不需要重复安装。
//1、判断当前插件是否已经被安装:
if (VueRouter.install.installed) {
//条件成立,表明插件已经被安装,什么都不要做。
return;
}
VueRouter.install.installed = true;
//2、把Vue构造函数记录到全局变量中。
_Vue = Vue;
//3、把创建Vue实例时候传入的router对象注入到Vue实例上。
_Vue.mixin({
beforeCreate() {
//在创建Vue实例的时候
// 也就是new Vue()的时候,才会有$options这个属性,
//组件中是没有$options这个属性的。
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router;
}
},
});
}
}
在介绍VueRouter
的类图时,我们说过
Constructor
是一个构造方法,该该构造方法中会初始化options
,data
,routeMap
这几个属性。
constructor(options) {
this.options = options;
this.routeMap = {};
this.data = _Vue.observable({
current: "/",
});
}
createRouteMap() {
this.options.routes.forEach((route) => {
this.routeMap[route.path] = route.component;
});
}
initComponents
方法,主要作用是用来创建router-link
和router-view
这两个组件的。
下面先在这个方法中创建router-link
这个组件。
先来看一下router-link
这个组件的基本使用
<router-link to="/users"> 用户管理</router-link>
我们知道,router-link
这个组件最终会被渲染成a
标签,同时to
作为一个属性,其值会作为a
标签中的href
属性的值。同时还要获取<router-link>
这个组件中的文本,作为最终超链接的文本。
initComponents(Vue) {
Vue.component("router-link", {
props: {
to: String,
},
template: '<a :href="to"><slot></slot></a>',
});
}
在上面的代码中,我们通过Vue.component
来创建router-link
这个组件,同时通过props
接收to
属性传递过来的值,并且对应的类型为字符串。
最终渲染的模板是一个a
标签,href
属性绑定了to
属性的值,同时使用<slot>
插槽作为占位符,用具体的文字内容填充该占位符。
现在已经将router-link
这个组件创建好了。
下面我们需要对我们写的这些代码进行测试。
要进行测试应该先将createRouteMap
方法与initComponents
方法都调用一次,那么问题是
在什么时候调用这两个方法呢?
我们可以在VueRoute对象创建成功后,并且将VueRouter
对象注册到Vue
的实例上的时候,调用这两个方法。
也就是在beforeCreate
这个钩子函数中。
当然为了调用这两个方便,在这里我们又定义了init
方法,来做了一次封装处理。
init() {
this.createRouteMap();
this.initComponents(_Vue);
}
对init
方法的调用如下:
beforeCreate() {
//在创建Vue实例的时候
// 也就是new Vue()的时候,才会有$options这个属性,
//组件中是没有$options这个属性的。
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router;
//调用init
this.$options.router.init();
}
},
this.$options.router.init();
这句代码的含义:this
表示的就是Vue实例,$options
表示的就是在创建Vue
的实例的时候传递的选项,如下所示:
const vm = new Vue({
el: "#app",
router,
});
通过上面的代码,我们可以看到,传递过来的选项中是有router
.
而这个router
是什么呢?
const router = new VueRouter({})
就是VueRouter
这个类的实例。而我们当前自己所模拟的路由,所创建的类就叫做VueRouter
(也就是以后在创建路由实例的时候,使用我们自己创建的VueRouter
这个类来完成).
而init
方法就是VueRouter
这个类的实例方法。所以可以通过this.$options.router.init()
的方式来调用。
下面我们来测试一下。
如下代码所示:
import Vue from "vue";
// import Router from "vue-router";
import Router from "./vuerouter";//注意:这里导入的是自己定义的路由规则
import Login from "./components/Login.vue";
import Home from "./components/Home.vue";
Vue.use(Router);
export default new Router({
model: "history",
routes: [
{ path: "/", component: Home },
{ path: "/login", component: Login },
],
});
Home.vue
的代码如下:
<template>
<router-link to="/login">登录</router-link>
</template>
<script>
export default {};
</script>
<style></style>
Login.vue
的代码如下:
<template>
<div>
登录页面
</div>
</template>
<script>
export default {};
</script>
App.vue
组件的内容如下:
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link>
<router-link to="/login">Login</router-link>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {};
</script>
<style></style>
在main.js
中完成路由的注册。
import Vue from "vue";
import App from "./App.vue";
//导入router.js
import router from "./router";
Vue.config.productionTip = false;
new Vue({
router,
render: (h) => h(App),
}).$mount("#app");
运行上面的代码会出现如下的错误:
第二个错误是我们还没有创建router-view
这个组件,所以才会出现该错误。这里暂时可以先不用考虑。
主要是第一个错误,该错误的含义是,目前我们使用的是运行时版本的Vue
, 模板编译器不可用。
你可以使用预编译把模板编译成render
函数,或者是使用包含编译版本的Vue
.
以上错误说明了Vue
的构建版本有两个,分别是“运行时版”和"完整版".
运行时版:不支持template
模板,需要打包的时候提前编译。
完整版:包含运行时和编译器,体积比运行时版大10k
左右,程序运行的时候把模板转换成render
函数。性能低于运行时版本。
使用vue-cli
创建的项目默认为运行时版本
,而我们创建的VueRouter
类中有template
模板,所以才会出现第一个错误。
官方文档:https://cn.vuejs.org/v2/guide/installation.html
下面我们看一下解决方案:
在前面我们已经提到过,使用vue-cli
创建的项目是运行时项目,所以没有编译器,如果我们将其修改成完整版,就有编译器,对模板进行编译。
版本(完整版)。设置为true
后你就可以在Vue
组件中使用template
选项了,但是这会让你的应用额外增加10kb
左右。默认该选项的取值为false
.
vue.config.js
文件配置如下
module.exports = {
runtimeCompiler: true,
};
表示使用的是完整版,这时编译器会将template
选项转换成render
函数。
注意:要想以上配置内容起作用,必须重新启动服务器。
npm run serve
render
函数虽然使用完整版Vue
可以解决上面的问题,但是由于带有编译器,体积比运行时版本大10k
左右,所以性能比运行时版要低。
那么这一小节我们使用运行时版本来解决这个问题。
我们知道,完整版中的编译器的作用就是将template
模板转成render
函数,所以在运行时版本中我们可以自己编写render
函数。
但是在这你肯定也有一个问题,就是在单文件组件中,我们一直都是在写<template></template>
,并且没有写render
函数,
但是为什么能够正常的工作呢?这时因为在打包的时候,将<template>
编译成了render
函数,这就是预编译。
最终代码如下:
//该方法需要一个参数为Vue的构造函数。
//当然也可以使用全局的_Vue.
initComponents(Vue) {
Vue.component("router-link", {
props: {
to: String,
},
// template: '<a :href="to"><slot></slot></a>',
render(h) {
return h(
"a",
{
attrs: {
href: this.to,
},
},
[this.$slots.default]
);
},
});
}
router-view
组件router-view
组件就是一个占位符。当根据路由规则找到组件后,会渲染到router-view
的位置。
在initComponents
方法中创建router-view
组件
//该方法需要一个参数为Vue的构造函数。
//当然也可以使用全局的_Vue.
initComponents(Vue) {
Vue.component("router-link", {
props: {
to: String,
},
// template: '<a :href="to"><slot></slot></a>',
render(h) {
return h(
"a",
{
attrs: {
href: this.to,
},
},
[this.$slots.default]
);
},
});
const self = this;//修改this的指向
Vue.component("router-view", {
render(h) {
//根据当前的路径从routeMap中查找对应的组件.
const component = self.routeMap[self.data.current];
//将组件转换成虚拟dom
return h(component);
},
});
}
下面,我们可以测试一下效果。
当我们单击链接的时候,发现了浏览器进行了刷新操作。表明向服务器发送了请求,而我们单页面应用中是不希望向服务器发送请求。
修改后的initComponents
方法如下:
//该方法需要一个参数为Vue的构造函数。
//当然也可以使用全局的_Vue.
initComponents(Vue) {
Vue.component("router-link", {
props: {
to: String,
},
// template: '<a :href="to"><slot></slot></a>',
render(h) {
return h(
"a",
{
attrs: {
href: this.to,
},
on: {
click: this.clickHandler,
},
},
[this.$slots.default]
);
},
methods: {
clickHandler(e) {
history.pushState({}, "", this.to);
this.$router.data.current = this.to;
//阻止向服务器发送器。
e.preventDefault();
},
},
});
const self = this;
Vue.component("router-view", {
render(h) {
//根据当前的路径从routeMap中查找对应的组件.
const component = self.routeMap[self.data.current];
//将组件转换成虚拟dom
return h(component);
},
});
}
给a
标签添加了单击事件。
initEvent
方法实现这时候要解决这个问题, 就需要用到popstate
事件
initEvent() {
window.addEventListener("popstate", () => {
this.data.current = window.location.pathname;
});
}
针对initEvent
方法的调用如下:
init() {
this.createRouteMap();
this.initComponents(_Vue);
this.initEvent();
}
在第二章中,我们已经对VueRouter
做了一个基本的实现,通过这个基本的实现,已经对VueRouter
的原理有了一个基本的理解。
但是,我们并没有实现路由嵌套的形式,这次我们重点来实现这一点。
Vue.use( )
源码源码位置:vue/src/core/global-api/use.js
export function initUse (Vue: GlobalAPI) {
//use方法的参数接收的是一个插件,该插件的类型可以是一个函数,也可以是一个对象
Vue.use = function (plugin: Function | Object) {
//_installedPlugins数组中存放已经安装的插件。
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
//判断一下传递过来的插件是否在installedPlugins中存在,如果存在,则直接返回
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
//将arguments转换成数组,并且将数组中的第一项去除。
const args = toArray(arguments, 1)
//把this(也就是Vue,这里是通过Vue.use来调用的)插入到数组中的第一个元素的位置。
args.unshift(this)
//这时plugin是一个对象,看一下是否有install这个函数。
if (typeof plugin.install === 'function') {
//如果有install这个函数,直接调用
//这里通过apply将args数组中的每一项展开传递给install这个函数。
// plugin.install(args[0],args[1])
//而args[0],就是上面我们所插入的Vue.这一点与我们前面在模拟install方法的时候是一样的。
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
//如果plugin是一个函数,则直接通过apply去调用
plugin.apply(null, args)
}
//将插件存储到installedPlugins数组中。
installedPlugins.push(plugin)
return this
}
}
我们先来核心的文件。
link.js
文件创建RouterLink
组件
view.js
文件创建RouterView
组件。
index.js
文件是用来创建VueRouter
install.js
文件是关于install
方法
下面先来在index.js
文件中实现基本代码。
export default class VueRouter {
//在创建VueRouter对象的时候,会传递选项
constructor(options) {
//获取routes选项,该选项中定义路由规则
this._options = options.routes || [];
}
// 注册路由变化的事件。该方法的参数是一个Vue实例,后期完善
init(Vue) {}
}
下面实现install.js
基本代码(通过查看源代码来实现)
export let _Vue = null; //将其导出,在其它文件中也可以使用Vue实例,而不需要单独的引入Vue的js文件
export default function install(Vue) {
//获取Vue构造函数
_Vue = Vue;
_Vue.mixin({
//通过混入以后,所有的Vue实例中都会有beforeCreate方法
beforeCreate() {
//判断是否为Vue的实例,如果条件成立为Vue的实例,否则为其它对应的组件(因为在创建Vue实例的时候会传递选项)
if (this.$options.router) {
//通过查看源码发现,Vue的实例会挂在到当前的私有属性_routerRoot属性上
this._routerRoot = this;
this._router = this.$options.router;
//调用index.js文件中定义的init方法
this._router.init(this);
} else {
this._routerRoot = this.$parent && this.$parent._routerRoot;
}
},
});
}
下面需要将install
方法挂载到VueRouter
上。
import install from "./install";
export default class VueRouter {
//在创建VueRouter对象的时候,会传递选项
constructor(options) {
//获取routes选项,该选项中定义路由规则
this._routes = options.routes || [];
}
// 注册路由变化的事件。
init(Vue) {}
}
//将install方法挂载到VueRouter上
VueRouter.install = install;
下面,我们可以简单实现一下Router-link
组件与Router-view
组件,来做一个简单的测试。(接下来讲解如下内容)
export default {
render(h) {
return h("div", "router-view");
},
};
以上是Router-View
组件的基本功能,后面在继续完善。
link.js
文件的实现如下:
export default {
props: {
to: {
type: String,
required: true,
},
},
render(h) {
//通过插槽获取`a`标签内的文本。
return h("a", { domProps: { href: "#" + this.to } }, [this.$slots.default]);
},
};
在install.js
文件中,导入上面的组件进行测试。
import View from "./components/view";
import Link from "./components/link";
export let _Vue = null; //将其导出,在其它文件中也可以使用Vue实例,而不需要单独的引入Vue的js文件
export default function install(Vue) {
//获取Vue构造函数
_Vue = Vue;
_Vue.mixin({
//通过混入以后,所有的Vue实例中都会有beforeCreate方法
beforeCreate() {
//判断是否为Vue的实例,如果条件成立为Vue的实例,否则为其它对应的组件(因为在创建Vue实例的时候会传递选项)
if (this.$options.router) {
//通过查看源码发现,Vue的实例会挂在到当前的私有属性_routerRoot属性上
this._routerRoot = this;
this._router = this.$options.router;
//调用index.js文件中定义的init方法
this._router.init(this);
} else {
this._routerRoot = this.$parent && this.$parent._routerRoot;
}
},
});
//完成组件的注册
Vue.component("RouterView", View);
Vue.component("RouterLink", Link);
}
在上面的代码中,导入组件,并且完成组件的注册。
下面,我们测试一下。
import Router from "./my-vue-router";
在源码的index.js
文件中,创建了VueRouter
类,对应的构造方法中,有如下代码:
this.matcher = createMatcher(options.routes || [], this)
createMatcher
方法是在create-matcher.js
文件中创建的。
该方法返回的matcher
就是一个匹配器,其中有两个成员,match
,另外一个是addRoutes
addRoutes
动态添加路由
首先在我们自己的index.js
文件中添加如下的代码:
import install from "./install";
import createMatcher from "./create-matcher";
export default class VueRouter {
//在创建VueRouter对象的时候,会传递选项
constructor(options) {
//获取routes选项,该选项中定义路由规则
this._routes = options.routes || [];
this.matcher = createMatcher(this._routes);
}
// 注册路由变化的事件。
init() {}
//init(Vue){}
}
//将install方法挂载到VueRouter上
VueRouter.install = install;
在上面的代码中,导入了createMatcher
方法。
并且在调用该方法的时候传递了路由规则。
create-matcher.js
文件的代码如下:
import createRouteMap from "./create-route-map";
export default function createMatcher(routes) {
const { pathList, pathMap } = createRouteMap(routes);
function match() {}
function addRoutes(routes) {
createRouteMap(routes, pathList, pathMap);
}
return {
match,
addRoutes,
};
}
下面,我们需要在create-route-map.js
文件中实现createRouteMap
这个方法。
export default function createRouteMap(routes, oldPathList, oldPathMap) {
const pathList = oldPathList || [];
const pathMap = oldPathMap || {};
//遍历所有的路由规则,进行解析。同时还要考虑children的形式,
//所以这里需要使用递归的方式。
routes.forEach((route) => {
addRouteRecord(route, pathList, pathMap);
});
return {
pathList,
pathMap,
};
}
function addRouteRecord(route, pathList, pathMap, parentRecord) {
//从路由规则中获取path。
const path = parentRecord ? `${parentRecord.path}/${route.path}` : route.path;
//构建记录
const record = {
path,
component: route.component,
parent: parentRecord, //如果是子路由的话,记录子路由对应的父record对象(该对象中有path,component),相当于记录了父子关系
};
//如果已经有了path,相同的path直接跳过
if (!pathMap[path]) {
pathList.push(path);
pathMap[path] = record;
}
//判断route中是否有子路由
if (route.children) {
//遍历子路由,把子路由添加到pathList与pathMap中。
route.children.forEach((childRoute) => {
addRouteRecord(childRoute, pathList, pathMap, record);
});
}
}
下面测试一下上面的代码。
import createRouteMap from "./create-route-map";
export default function createMatcher(routes) {
const { pathList, pathMap } = createRouteMap(routes);
console.log("pathList==", pathList);
console.log("pathMap==", pathMap);
function match() {}
function addRoutes(routes) {
createRouteMap(routes, pathList, pathMap);
}
return {
match,
addRoutes,
};
}
在上面的代码中,我们打印了pathList
与pathMap
.
当然,现在在我们所定义的路由规则中,还没有添加children
,构建相应的子路由。下面重新修改一下。
import Vue from "vue";
// import Router from "vue-router";
// import Router from "./vuerouter";
import Router from "./my-vue-router";
import Login from "./components/Login.vue";
import Home from "./components/Home.vue";
import About from "./components/About.vue";
import Users from "./components/Users";
Vue.use(Router);
export default new Router({
// model: "history",
routes: [
{ path: "/", component: Home },
{ path: "/login", component: Login },
{
path: "/about",
component: About,
children: [{ path: "users", component: Users }],
},
],
});
这时候可以查看对应的输出结果。
match
函数实现在create-matcher.js
文件中,我们实现了createRouteMap
方法,同时还需要实现match
方法。
function match(path) {
const record = pathMap[path];
if (record) {
//根据路由地址,创建route路由规则对象
return createRoute(record, path);
}
return createRoute(null, path);
}
create-matcher.js
文件完整代码如下:
import createRouteMap from "./create-route-map";
import createRoute from "./util/route";
export default function createMatcher(routes) {
const { pathList, pathMap } = createRouteMap(routes);
console.log("pathList==", pathList);
console.log("pathMap==", pathMap);
//实现match方法
function match(path) {
const record = pathMap[path];
if (record) {
//根据路由地址,创建route路由规则对象
return createRoute(record, path);
}
return createRoute(null, path);
}
function addRoutes(routes) {
createRouteMap(routes, pathList, pathMap);
}
return {
match,
addRoutes,
};
}
export default function createRoute(record, path) {
const matched = [];
while (record) {
matched.unshift(record);
record = record.parent;
}
return {
path,
matched,
};
}
总结:match
这个方法的作用就是根据路径,创建出路由规则对象,而所谓的路由规则对象其实就是包含了路径以及对应的路由记录的信息(这里有可能包含了父路由以及子路由记录,这块内容存储到一个数组中)。
以后,我们就可以根据路径直接获取到包含了整个路由记录的这个数组,从而可以将对应的组件全部创建出来。
关于路由有三种模式:hash
模式,html5
模式,abstract
模式(该模式与服务端渲染有关)
在这里我们实现hash
模式的历史记录管理,不管是哪种模式,都有相同的内容,这里我们相同的内容定义到
父类中。
在该父类中主要有如下内容:
router
属性:路由对象(ViewRouter
)
current
属性,记录当前路径对应的路由规则对象{path:'/',matched:[]}
,关于该对象,我们在前面已经处理完了。也就是在createRoute
方法中返回的内容。
transitionTo()
跳转到指定的路径,根据当前路径获取匹配的路由规则对象route
,然后更新视图。
import createRoute from "../util/route";
export default class History {
// router路由对象ViewRouter
constructor(router) {
this.router = router;
this.current = createRoute(null, "/");
}
transitionTo(path, onComplete) {
this.current = this.router.matcher.match(path);
//该回调函数在调用transitionTo方法的时候,会传递过来。
onComplete && onComplete();
}
}
父类已经实现了,下面实现对应的子类。也就是HashHistory
import History from "./base";
export default class HashHistory extends History {
constructor(router) {
//将路由对象传递给父类的构造函数
super(router);
//确保 首次 访问地址加上 #/ (//由于没有添加this,为普通方法)
ensureSlash();
}
// 获取当前的路由地址 (# 后面的部分)所以这里需要去除#
getCurrentLocation() {
return window.location.hash.slice(1);
}
// 监听hashchange事件
//也就是监听路由地址的变化
setUpListener() {
window.addEventListener("hashchange", () => {
//当路由地址发生变化后,跳转到新的路由地址。
this.transitionTo(this.getCurrentLocation());
});
}
}
function ensureSlash() {
//判断当前是否有hash
// 如果单击的是链接,肯定会有hash
if (window.location.hash) {
return;
}
window.location.hash = "/";
}
Init
方法实现我们知道当创建VueRouter
的时候,需要可以传递mode
,来指定路由的形式,例如是hash
模式还是html5
模式等。
import install from "./install";
import createMatcher from "./create-matcher";
import HashHistory from "./history/hash";
import HTML5History from "./history/html5";
export default class VueRouter {
//在创建VueRouter对象的时候,会传递选项
constructor(options) {
//获取routes选项,该选项中定义路由规则
this._routes = options.routes || [];
this.matcher = createMatcher(this._routes);
//获取传递过来的选项中的mode,mode中决定了用户设置的路由的形式。
//这里给VueRouter添加了mode属性
const mode = (this.mode = options.mode || "hash");
switch (mode) {
case "hash":
this.history = new HashHistory(this);
break;
case "history":
this.history = new HTML5History(this);
break;
default:
throw new Error("mode error");
}
}
// 注册路由变化的事件。
init() {}
//init(Vue){}
}
//将install方法挂载到VueRouter上
VueRouter.install = install;
首先导入HashHistory
与HTML5History
.
import HashHistory from "./history/hash";
import HTML5History from "./history/html5";
下面获取选项中的mode
,如果在创建VueRouter
对象的时候,没有指定mode
,那么默认的值为hash
.
下面就对获取到的mode
进行判断,根据mode
的不同的值,创建不同的history
的实例。
//获取传递过来的选项中的mode,mode中决定了用户设置的路由的形式。
//这里给VueRouter添加了mode属性
const mode = (this.mode = options.mode || "hash");
switch (mode) {
case "hash":
this.history = new HashHistory(this);
break;
case "history":
this.history = new HTML5History(this);
break;
default:
throw new Error("mode error");
}
同时html5.js
文件,添加了基本的代码
import History from "./base";
export default class HTML5History extends History {}
关于Html5
的形式这里不在实现了。
下面完善一下init
方法
// 注册路由变化的事件。
init() {}
具体的实现代码如下:
// 注册路由变化的事件(初始化事件,监听路由地址的变化)。
init() {
const history = this.history;
const setUpListener = () => {
history.setUpListener();
};
history.transitionTo(
history.getCurrentLocation(),
//如果直接history.setUpListener
// 这样的话setUpListener里面的this会有问题。
setUpListener
);
}
同时完成了hashchange
事件的绑定(路由变化的事件)。
下面可以进行测试一下,在base.js
文件中的transitionTo
方法中,打印出current
属性的值。
transitionTo(path, onComplete) {
this.current = this.router.matcher.match(path);
console.log("current===", this.current);
//该回调函数在调用transitionTo方法的时候,会传递过来。
onComplete && onComplete();
}
http://localhost:8080/#/about/users
后期就可以获取具体的组件来进行渲染。
下面我们要做的就是渲染组件。
在install.js
文件中添加如下的代码:
Vue.util.defineReactive(this, "_route", this._router.history.current);
以上完成了响应式属性的创建,但是要注意的是defineReactive
方法为Vue
的内部方法,不建议平时通过该方法来创建响应式对象。
beforeCreate() {
//判断是否为Vue的实例,如果条件成立为Vue的实例,否则为其它对应的组件(因为在创建Vue实例的时候会传递选项)
if (this.$options.router) {
//通过查看源码发现,Vue的实例会挂在到当前的私有属性_routerRoot属性上
this._routerRoot = this;
this._router = this.$options.router;
//调用index.js文件中定义的init方法
this._router.init(this);
//在Vue的实例上创建一个响应式的属性`_route`.
Vue.util.defineReactive(this, "_route", this._router.history.current);
}
在哪完成_route
属性值的修改呢?
base.js
文件修改后的代码如下:
import createRoute from "../util/route";
export default class History {
// router路由对象ViewRouter
constructor(router) {
this.router = router;
this.current = createRoute(null, "/");
//这个回调函数是在hashhistory中赋值,作用是更改vue实例上的_route,_route的值发生变化,视图会进行刷新操作
this.cb = null;
}
//给cb赋值
listen(cb) {
this.cb = cb;
}
transitionTo(path, onComplete) {
this.current = this.router.matcher.match(path);
// 调用cb
this.cb && this.cb(this.current);
// console.log("current===", this.current);
//该回调函数在调用transitionTo方法的时候,会传递过来。
onComplete && onComplete();
}
}
在History
中的构造方法中初始化cb
函数。
this.cb = null;
定义listen
方法给cb
函数赋值。
//给cb赋值
listen(cb) {
this.cb = cb;
}
在transitionTo
方法中调用cb
函数,同时传递获取到的当前的路由规则对象也就是路由记录信息。
this.cb && this.cb(this.current);
在什么地方调用listen
方法呢?
在index.js
文件中的init
方法中完成listen
方法的调用。
// 注册路由变化的事件(初始化事件,监听路由地址的变化)。
init(app) {
const history = this.history;
const setUpListener = () => {
history.setUpListener();
};
history.transitionTo(
history.getCurrentLocation(),
//如果直接history.setUpListener
// 这样的话setUpListener里面的this会有问题。
setUpListener
);
//调用父类的中的listen方法
history.listen((route) => {
app._route = route;
});
}
在上面的代码中调用了父类中的listen
方法,然后将箭头函数传递到了listen
中。
这时候,在transitionTo
方法中调用cb
,也就是调用箭头函数,这时传递过来的参数route
,为当前更改后的路由规则信息,交给了app
中的_route
属性。
app
这个参数其实就是Vue
的实例,因为在install.js
文件中调用了init
方法,并且传递的就是Vue
的实例。
这样就完成了对Vue
实例上的响应式属性_route
值的修改,从而会更新组件。
$route/$router
创建创建$route
与$router
的目的是能够在所有的Vue
实例(组件)中,可以获取到。
$route
是路由规则对象,包含了path
,component
等内容
$router
为路由对象(ViewRouter
对象)。
通过查看源码(install.js
)可以发现,其实就是将$router
与$route
挂载到了Vue
的原型上。
所以可以直接将源码内容复制过来就可以了。
Object.defineProperty(Vue.prototype, "$router", {
get() {
return this._routerRoot._router;
},
});
Object.defineProperty(Vue.prototype, "$route", {
get() {
return this._routerRoot._route;
},
});
通过上面的代码,可以看到$route
与$router
都是只读的,因为对应的值,在前面已经设置完毕,这里只是获取。
$router
是通过_routerRoot
来获取。
$route
是通过_routerRoot._route
来获取。
Vue.util.defineReactive(this, "_route", this._router.history.current);
在Vue
对象上创建了_route
属性,该属性的值为路由规则内容
Router-View
创建router-view
就是一个占位符,会用具体的组件来替换该占位符。
router-view
的创建过程如下:
$route
路由规则对象matched
匹配的record
(里面有component)/about
,matched
匹配到一个record
,直接渲染对应的组件/about/users
,matched
匹配到两个record
(第一个是父组件,第二个是子组件)export default {
render(h) {
//获取当前匹配的路由规则对象
const route = this.$route;
//获取路由记录对象.只有一个内容,所以获取的是`matched`中的第一项。
const record = route.matched[0];
if (!record) {
return h();
}
//获取记录中对应的组件
const component = record.component;
return h(component);
},
};
以上的代码处理的是没有子路由的情况。
下面,看一下子路由情况的处理。
当然在编写子路由的处理代码之前,我们先把案例中的路由完善一下。
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link>
<router-link to="/login">Login</router-link>
<router-link to="/about">About</router-link>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {};
</script>
对应在About
这个组件中,完成子路由应用
<template>
<div>
关于组件
<router-link to="/about/users">用户</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {};
</script>
<style>
</style>
下面完善一下对子路由的处理。
export default {
render(h) {
//获取当前匹配的路由规则对象
const route = this.$route;
let depth = 0;
//记录当前组件为RouterView
this.routerView = true;
let parent = this.$parent;
while (parent) {
if (parent.routerView) {
depth++;
}
parent = parent.$parent;
}
//获取路由记录对象.
// 如果是子路由,例如:子路由/about/users
//子路由是有两部分内容,matched[0]:是父组件内容,matched[1]是子组件内容
const record = route.matched[depth];
if (!record) {
return h();
}
//获取记录中对应的组件
const component = record.component;
return h(component);
},
};
是没有父组件,那么depth
属性的值为0,这时候获取的第一个组件然后进行渲染。
的属性_route
.
Vue.util.defineReactive(this, “_route”, this._router.history.current);
}
下面要考虑的就是当路由地址发生了变化后,需要修改`_route`属性的值。
在哪完成`_route`属性值的修改呢?
在`base.js`文件中,因为在该文件中定义了`transitionTo`方法,而该方法就是用来完成地址的跳转,同时完成组件的渲染。
`base.js`文件修改后的代码如下:
```js
import createRoute from "../util/route";
export default class History {
// router路由对象ViewRouter
constructor(router) {
this.router = router;
this.current = createRoute(null, "/");
//这个回调函数是在hashhistory中赋值,作用是更改vue实例上的_route,_route的值发生变化,视图会进行刷新操作
this.cb = null;
}
//给cb赋值
listen(cb) {
this.cb = cb;
}
transitionTo(path, onComplete) {
this.current = this.router.matcher.match(path);
// 调用cb
this.cb && this.cb(this.current);
// console.log("current===", this.current);
//该回调函数在调用transitionTo方法的时候,会传递过来。
onComplete && onComplete();
}
}
在History
中的构造方法中初始化cb
函数。
this.cb = null;
定义listen
方法给cb
函数赋值。
//给cb赋值
listen(cb) {
this.cb = cb;
}
在transitionTo
方法中调用cb
函数,同时传递获取到的当前的路由规则对象也就是路由记录信息。
this.cb && this.cb(this.current);
在什么地方调用listen
方法呢?
在index.js
文件中的init
方法中完成listen
方法的调用。
// 注册路由变化的事件(初始化事件,监听路由地址的变化)。
init(app) {
const history = this.history;
const setUpListener = () => {
history.setUpListener();
};
history.transitionTo(
history.getCurrentLocation(),
//如果直接history.setUpListener
// 这样的话setUpListener里面的this会有问题。
setUpListener
);
//调用父类的中的listen方法
history.listen((route) => {
app._route = route;
});
}
在上面的代码中调用了父类中的listen
方法,然后将箭头函数传递到了listen
中。
这时候,在transitionTo
方法中调用cb
,也就是调用箭头函数,这时传递过来的参数route
,为当前更改后的路由规则信息,交给了app
中的_route
属性。
app
这个参数其实就是Vue
的实例,因为在install.js
文件中调用了init
方法,并且传递的就是Vue
的实例。
这样就完成了对Vue
实例上的响应式属性_route
值的修改,从而会更新组件。
$route/$router
创建创建$route
与$router
的目的是能够在所有的Vue
实例(组件)中,可以获取到。
$route
是路由规则对象,包含了path
,component
等内容
$router
为路由对象(ViewRouter
对象)。
通过查看源码(install.js
)可以发现,其实就是将$router
与$route
挂载到了Vue
的原型上。
所以可以直接将源码内容复制过来就可以了。
Object.defineProperty(Vue.prototype, "$router", {
get() {
return this._routerRoot._router;
},
});
Object.defineProperty(Vue.prototype, "$route", {
get() {
return this._routerRoot._route;
},
});
通过上面的代码,可以看到$route
与$router
都是只读的,因为对应的值,在前面已经设置完毕,这里只是获取。
$router
是通过_routerRoot
来获取。
$route
是通过_routerRoot._route
来获取。
Vue.util.defineReactive(this, "_route", this._router.history.current);
在Vue
对象上创建了_route
属性,该属性的值为路由规则内容
Router-View
创建router-view
就是一个占位符,会用具体的组件来替换该占位符。
router-view
的创建过程如下:
$route
路由规则对象matched
匹配的record
(里面有component)/about
,matched
匹配到一个record
,直接渲染对应的组件/about/users
,matched
匹配到两个record
(第一个是父组件,第二个是子组件)export default {
render(h) {
//获取当前匹配的路由规则对象
const route = this.$route;
//获取路由记录对象.只有一个内容,所以获取的是`matched`中的第一项。
const record = route.matched[0];
if (!record) {
return h();
}
//获取记录中对应的组件
const component = record.component;
return h(component);
},
};
以上的代码处理的是没有子路由的情况。
下面,看一下子路由情况的处理。
当然在编写子路由的处理代码之前,我们先把案例中的路由完善一下。
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link>
<router-link to="/login">Login</router-link>
<router-link to="/about">About</router-link>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {};
</script>
对应在About
这个组件中,完成子路由应用
<template>
<div>
关于组件
<router-link to="/about/users">用户</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {};
</script>
<style>
</style>
下面完善一下对子路由的处理。
export default {
render(h) {
//获取当前匹配的路由规则对象
const route = this.$route;
let depth = 0;
//记录当前组件为RouterView
this.routerView = true;
let parent = this.$parent;
while (parent) {
if (parent.routerView) {
depth++;
}
parent = parent.$parent;
}
//获取路由记录对象.
// 如果是子路由,例如:子路由/about/users
//子路由是有两部分内容,matched[0]:是父组件内容,matched[1]是子组件内容
const record = route.matched[depth];
if (!record) {
return h();
}
//获取记录中对应的组件
const component = record.component;
return h(component);
},
};
是没有父组件,那么depth
属性的值为0,这时候获取的第一个组件然后进行渲染。
在循环的时候,做了一个判断,判断的条件就是当前的父组件必须为:RouterView
组件(子组件中router-view
与父组件中的router-view
构成了父子关系),才让depth
加1,然后取出子组件进行渲染。
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- bangwoyixia.com 版权所有 湘ICP备2023022004号-2
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务