路由(vue-router)
中文文档:https://router.vuejs.org/zh/
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。路由实际上就是可以理解为指向,就是我在页面上点击一个按钮需要跳转到对应的页面,这就是路由跳转;
名词解释:
route:首先它是个单数,译为路由,即我们可以理解为单个路由或者某一个路由;
routes:它是个复数,表示多个的集合才能为复数;即我们可以理解为多个路由的集合,JS中表示多种不同状态的集合的形式只有数组和对象两种,事实上官方定义routes是一个数组;所以我们记住了,routes表示多个数组的集合;
router:译为路由器,上面都是路由,这个是路由器,我们可以理解为一个容器包含上述两个或者说它是一个管理者,负责管理上述两个;举个常见的场景的例子:当用户在页面上点击按钮的时候,这个时候router就会去routes中去查找route,就是说路由器会去路由集合中找对应的路由.
1.路由分类
后端路由:对于普通的网站,所有的链接都是url地址,所有的url地址都对应服务器上的资源。
前端路由:对于单页面应用程序来说,主要通过URL中的hash(#号)来实现不同页面之间的切换,同时,hash有一个特点,http请求中不会包含hash的内容,所以单页面程序中的页面跳转主要是hash来实现的。
2.路由的使用
- 导入vue-router
- 创建一个路由对象,为构造函数传递一个对象
- 通过vue实例中的属性与上面的路由对象绑定关系
- 在页面上设定一个容器 router-view ,表示占位符,路由规则匹配到的组件,就会放在这里面
- 注意:要想用a标签去跳转,一定要在href中的url前面加上#,表示hash
Demo案例
src目录下新建3个.vue文件和1个.js文件:
- Page1.vue、Page2.vue、Page3.vue,仅改变上述代码的对应名字即可。
<template>
<div>
<h1>Page1</h1>
<p>{{msg}}</p>
</div>
</template>
<script>
export default {
data () {
return {
msg: '我是Page1组件'
}
}
}
</script>
- router2.js
// 引入Vue
import Vue from 'vue'
// 引入Vue-router
import VueRouter from 'vue-router'
// 引用路由页面
import Page1 from '@/Page1'
import Page2 from '@/Page2'
import Page3 from '@/Page3'
// 第三方库,需要use一下才能使用
Vue.use(VueRouter)
// 定义routes路由的集合,数组类型
const routes = [
// 单个路由均为对象类型,path代表路径,component代表组件
{
path: '/', // 默认路由
component: Page2
},
{
path: '/page1',
component: Page1
},
{
path: '/page2',
component: Page2
},
{
path: '/page3',
component: Page3
}
]
// 实例化VueRouter并将routes添加进去
const router = new VueRouter({
// ES6简写,等于routes:routes
routes
})
// 抛出这个这个实例对象方便外部读取以及访问
export default router
// 以上写法和/router/index.js中
// export default new VueRouter({
// routes: [{}, {}, ...]
// })
// 一样
- main.js修改
由于main.js默认使用的是/router/index.js,我们需要改成router2.js
- 修改App.vue
<template>
<div id="app">
<div>
<!-- router-link定义页面中点击触发部分 -->
<router-link to="/page1">Page1</router-link>
<router-link to="/page2">Page2</router-link>
<router-link to="/page3">Page3</router-link>
</div>
<!-- router-view定义页面中显示部分 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
控制台输入npm run dev
,项目构建完成,点击访问地址,可以看到路由跳转成功实现。
3.动态路由匹配
举一个例子,一些论坛网站在我们登录之后,通常会有一个“欢迎回来,XXX”之类的提示语,这个我们就可以通过动态路由来实现这个效果,代码如下:
- 在src目录下新建user.vue
<template>
<div>
<h1>Cyberverse</h1>
<!-- 这里可以通过$route.params.name来获取路由的参数 -->
<p>欢迎回来,{{$route.params.name}}</p>
</div>
</template>
<script>
</script>
- 修改App.vue
//添加两个router-link标签
<div>
<router-link to="/user/cyber">动态路由Cyber</router-link>
<router-link to="/user/verse">动态路由Verse</router-link>
</div>
- 修改router2.js
// 引入Vue
import Vue from 'vue'
// 引入Vue-router
import VueRouter from 'vue-router'
// 引用路由页面
import Page1 from '@/Page1'
import Page2 from '@/Page2'
import Page3 from '@/Page3'
import user from '@/user'
// 第三方库,需要use一下才能使用
Vue.use(VueRouter)
// 定义routes路由的集合,数组类型
const routes = [
// 单个路由均为对象类型,path代表路径,component代表组件
{
path: '/',
component: Page2
},
{
path: '/page1',
component: Page1
},
{
path: '/page2',
component: Page2
},
{
path: '/page3',
component: Page3
},
// 使用冒号标记,当匹配到的时候,参数值会被设置到this.$route.params中
{
path: '/user/:name',
component: user
}
]
// 实例化VueRouter并将routes添加进去
const router = new VueRouter({
// ES6简写,等于routes:routes
routes
})
// 抛出这个这个实例对象方便外部读取以及访问
export default router
// 以上写法和router/index.js中
// export default new VueRouter({
// routes: [{}, {}, ...]
// })
// 一样
动态路由使得我们通过配置一个路由来实现页面局部修改的效果,一个页面局部变化却有种多个页面切换的感觉。
但是,动态路由同样有一些问题,因为使用路由参数时,从/user/cyber导航到/user/verse,原来的组件实例会被复用,两个路由都渲染同个组件,比起销毁再创建,显示复用显得效率更高,带来的直观问题就是生命周期钩子函数不会再被调用,也就是不会再被触发,这可能导致页面数据渲染异常甚至报错;但是我们可以通过监听$route对象来实现。
- 修改user.vue的代码
<template>
<div>
<h1>Cyberverse</h1>
<!-- 这里可以通过$route.params.name来获取路由的参数 -->
<p>欢迎回来,{{msg}}</p>
</div>
</template>
<script>
export default {
data () {
return {
msg: ''
}
},
watch: {
// to表示即将要进入的那个组件,from表示从哪个组件过来的
$route (to, from) {
this.msg = to.params.name
console.log(this.msg)
}
}
}
</script>
4.嵌套路由
官方文档中给我们提供了一个children属性,这个属性是一个数组类型,里面实际放着一组路由;这个时候父子关系结构就出来了,所以children属性里面的是路由相对来说是children属性外部路由的子路由;
- 在/src/下新建文件cpu.vue、gpu.vue
<template>
<div>
<p>{{msg}}</p>
</div>
</template>
<script>
export default {
data () {
return {
msg: '我是CPU组件'
}
}
}
</script>
<template>
<div>
<p>{{msg}}</p>
</div>
</template>
<script>
export default {
data () {
return {
msg: '我是GPU组件'
}
}
}
</script>
- 在router2.js添加代码
- 在父文件中添加代码Page1.vue
<template>
<div>
<h1>Page1</h1>
<p>{{msg}}</p>
<router-link to="/page1/cpu">CPU</router-link>
<router-link to="/page1/gpu">GPU</router-link>
<!--给子路由的占位符-->
<router-view></router-view>
</div>
</template>
<script>
export default {
data () {
return {
msg: '我是Page1组件'
}
}
}
</script>
- 启动
监听属性及计算属性
监听属性 watch
Demo:
- src目录下新增WatchTest.vue
<template>
<div>
<h1>{{msg}}</h1>
<div>
<button v-on:click="clickFun()">数字加1</button>
<div>
旧值:{{oldNum}}
新值:{{newNum}}
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
msg: '监听属性 watch',
number: 0,
oldNum: '',
newNum: ''
}
},
watch: {
// 最简单的监听例子
// 监听变量的变化,newValue是变量number最新的值,oldValue是之前的旧值
// number (newValue, oldValue) {
// this.oldNum = oldValue
// this.newNum = newValue
// console.log('oldValue = ' + oldValue + ', newValue = ' + newValue)
// },
// handler、immediate。immediate:true代表即在最初绑定data的时候就执行监听
number: {
handler (newValue, oldValue) {
this.oldNum = oldValue
this.newNum = newValue
console.log('oldValue = ' + oldValue + ', newValue = ' + newValue)
},
immediate: true
}
},
methods: {
clickFun () {
this.number++
console.log(this.number)
}
}
}
</script>
- 修改router2.js
import watchtest from '@/WatchTest'
// ....
,{
path: '/watch',
component: watchtest
}
- 注释App.vue多余代码
<template>
<div id="app">
<div>
<!-- router-link定义页面中点击触发部分 -->
<!-- <router-link to="/page1">Page1</router-link>
<router-link to="/page2">Page2</router-link>
<router-link to="/page3">Page3</router-link>-->
</div>
<br />
<!-- 添加两个router-link标签 -->
<div>
<!-- <router-link to="/user/cyber">动态路由Cyber</router-link>
<router-link to="/user/verse">动态路由Verse</router-link>-->
</div>
<!-- router-view定义页面中显示部分 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
监听变量的变化
// newValue是变量number最新的值,oldValue是之前的旧值
number (newValue, oldValue) {
this.oldNum = oldValue
this.newNum = newValue
console.log('oldValue = ' + oldValue + ', newValue = ' + newValue)
}
访问地址:http://localhost:8080/#/watch
handler、immediate
immediate:true代表即在最初绑定data的时候就执行监听
watch: {
number: {
handler (newValue, oldValue) {
this.oldNum = oldValue
this.newNum = newValue
console.log('oldValue = ' + oldValue + ', newValue = ' + newValue)
},
immediate: true
}
在点击之前,已经对最初绑定number时已经监听了,未点击之前新值已经渲染了
deep
deep的意思就是深入观察,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器,但是这样性能开销就会非常大了,任何修改obj里面任何一个属性都会触发这个监听器里的 handler
<template>
<div>
<h1>{{msg}}</h1>
<div>
<!-- <button v-on:click="clickFun()">数字加1</button>
<div>
旧值:{{oldNum}}
新值:{{newNum}}
</div>-->
<h2>deep测试</h2>
<button v-on:click="changeName()">修改名字</button>
<button v-on:click="changeAge()">修改年龄</button>
<div>
名字:{{obj.name}}
年龄:{{obj.age}}
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
msg: '监听属性 watch',
number: 0,
oldNum: '',
newNum: '',
obj: { 'name': 'chengsw', 'age': 20 }
}
},
watch: {
// 最简单的监听例子
// 监听变量的变化,newValue是变量number最新的值,oldValue是之前的旧值
// number (newValue, oldValue) {
// this.oldNum = oldValue
// this.newNum = newValue
// console.log('oldValue = ' + oldValue + ', newValue = ' + newValue)
// },
// handler、immediate。immediate:true代表即在最初绑定data的时候就执行监听
number: {
handler (newValue, oldValue) {
this.oldNum = oldValue
this.newNum = newValue
console.log('oldValue = ' + oldValue + ', newValue = ' + newValue)
},
immediate: true
},
// deep的意思就是深入观察,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器,但是这样性能开销就会非常大了,任何修改obj里面任何一个属性都会触发这个监听器里的 handler
obj: {
handler (newValue, oldValue) {
console.log(this.obj.name)
console.log(this.obj.age)
},
immediate: true,
deep: true
}
},
methods: {
// clickFun () {
// this.number++
// console.log(this.number)
// },
changeName () {
this.obj.name += '6'
},
changeAge () {
this.obj.age++
}
}
}
</script>
针对开销大的问题,我们可以根据实际使用场景,仅监听需要的对象属性
<template>
<div>
<h1>{{msg}}</h1>
<div>
<!-- <button v-on:click="clickFun()">数字加1</button>
<div>
旧值:{{oldNum}}
新值:{{newNum}}
</div>-->
<h2>deep测试</h2>
<button v-on:click="changeName()">修改名字</button>
<button v-on:click="changeAge()">修改年龄</button>
<div>
名字:{{obj.name}}
年龄:{{obj.age}}
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
msg: '监听属性 watch',
number: 0,
oldNum: '',
newNum: '',
obj: { 'name': 'chengsw', 'age': 20 }
}
},
watch: {
// 最简单的监听例子
// 监听变量的变化,newValue是变量number最新的值,oldValue是之前的旧值
// number (newValue, oldValue) {
// this.oldNum = oldValue
// this.newNum = newValue
// console.log('oldValue = ' + oldValue + ', newValue = ' + newValue)
// },
// handler、immediate。immediate:true代表即在最初绑定data的时候就执行监听
number: {
handler (newValue, oldValue) {
this.oldNum = oldValue
this.newNum = newValue
console.log('oldValue = ' + oldValue + ', newValue = ' + newValue)
},
immediate: true
},
// deep的意思就是深入观察,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器,但是这样性能开销就会非常大了,任何修改obj里面任何一个属性都会触发这个监听器里的 handler
// obj: {
// handler (newValue, oldValue) {
// console.log(this.obj.name)
// console.log(this.obj.age)
// },
// immediate: true,
// deep: true
// },
// 优化
'obj.name': {
handler (newValue, oldValue) {
console.log(this.obj.name)
console.log(this.obj.age)
},
immediate: true
}
},
methods: {
// clickFun () {
// this.number++
// console.log(this.number)
// },
changeName () {
this.obj.name += '6'
},
changeAge () {
this.obj.age++
}
}
}
</script>
如图:代码仅监听了obj.name变量,点击修改年龄控制台并不打印信息,只有变更名字才会触发watch监听
注销watch
为什么要注销 watch?因为我们的组件是经常要被销毁的,比如我们跳一个路由,从一个页面跳到另外一个页面,那么原来的页面的 watch 其实就没用了,这时候我们应该注销掉原来页面的 watch 的,不然的话可能会导致内置溢出。好在我们平时 watch 都是写在组件的选项中的,他会随着组件的销毁而销毁。
const app = new Vue({
template: '<div id="root">{{text}}</div>',
data: {
text: 0
},
watch: {
text(newVal, oldVal){
console.log(`${newVal} : ${oldVal}`);
}
}
});
但是,如果我们使用下面这样的方式写 watch,那么就要手动注销了,这种注销其实也很简单
const unWatch = app.$watch('text', (newVal, oldVal) => {
console.log(`${newVal} : ${oldVal}`);
})
unWatch(); // 手动注销watch
app.$watch调用后会返回一个值,就是unWatch方法,你要注销 watch 只要调用unWatch方法就可以了。
watch监听路由
代码恢复到路由那一部分
Page1.vue添加代码:
<template>
<div>
<h1>Page1</h1>
<p>{{msg}}</p>
<router-link to="/page1/cpu">CPU</router-link>
<router-link to="/page1/gpu">GPU</router-link>
<!--给子路由的占位符-->
<router-view></router-view>
</div>
</template>
<script>
export default {
data () {
return {
msg: '我是Page1组件'
}
},
watch: {
// '$route.path' (to, from) {
// '$route'表示监听整个路由对象
// '$route.path'表示监听路由对象的path属性
'$route' (to, from) {
console.log(to) // to表示去往的界面
console.log(from) // from表示来自于哪个界面
if (to.path === '/page1/cpu') {
console.log('监听路由成功to.path == /page1/cpu')
}
if (to.path === '/page1/gpu') {
console.log('监听路由成功from.path == /page1/gpu')
}
}
}
}
</script>
计算属性 computed
computed可定义一些函数,这些函数叫做【计算属性】。只要data里面的数据发生变化computed会同步改变,引用【计算属性】时不要加 (),当做普通属性使用,例:console.log(this.computedName)
computed用的最多是在template模板中。如果模板中放入太多声明式逻辑会让模板臃肿,尤其在页面大量使用复杂的逻辑表达式处理数据,对页面的可维护性造成很大的影响,而 computed 的设计初衷也正是用于解决此类问题。
例:< p> {{‘名称:’ + this.name + ‘,年龄:’ + this.age + ‘,性别:’ + (this.info.sex ? ‘男’ : ‘女’)}}</ p>
1. getter
一般都只用 getter,以下是 getter 的默认模式
- 修改Page2.vue
<template>
<div>
<h1>Page2</h1>
<p>{{msg}}</p>
<div>
<button @click="testFunc()">computed测试</button>
<!--在模板中多出声明式逻辑导致模板臃肿-->
<p>声明式: {{'名称:' + this.name + ',年龄:' + this.age + ',性别:' + (this.sex ? '男' : '女')}}</p>
<!--computed显得简洁-->
<p>computed: {{computedName}}</p>
</div>
</div>
</template>
<script>
export default {
data () {
return {
msg: '我是Page2组件',
name: 'abc',
age: 18,
sex: true
}
},
methods: {
testFunc () {
// 引用计算属性时不要加(),当做普通属性使用
console.log(this.computedName)
}
},
computed: { // 实时计算
computedName () { // 计算属性
return '名称:' + this.name + ',年龄:' + this.age + ',性别:' + (this.sex ? '男' : '女')
}
}
}
</script>
2. setter
每一个计算属性都包含一个 getter 函数和 setter 函数;
计算属性会默认使用 getter 函数;
你也可以提供 setter 函数,当修改计算属性的值时,就会触发 setter 函数,执行一些自定义的操作。
- 修改Page2.vue
<template>
<div>
<h1>Page2</h1>
<p>{{msg}}</p>
<div>
<!-- <button @click="testFunc()">computed测试</button> -->
<!--在模板中多出声明式逻辑导致模板臃肿-->
<!-- <p>声明式: {{'名称:' + this.name + ',年龄:' + this.age + ',性别:' + (this.sex ? '男' : '女')}}</p> -->
<!--computed显得简洁-->
<!-- <p>computed: {{computedName}}</p> -->
<button @click="getTest()">获取</button>
<button @click="setTest()">设置</button>
<p>computed: {{computedInfo}}</p>
<p>computed新值: {{this.computedinfo}}</p>
</div>
</div>
</template>
<script>
export default {
data () {
return {
msg: '我是Page2组件',
info: { name: 'abc', age: 18, sex: true },
computedinfo: ''
}
},
methods: {
// testFunc () {
// 引用计算属性时不要加(),当做普通属性使用
// console.log(this.computedName)
// }
getTest () {
console.log('computed调用: ', this.computedInfo)
},
setTest () {
console.log('computed设置值')
this.computedInfo = 1
}
},
computed: { // 实时计算
// computedName () { // 计算属性
// return '名称:' + this.name + ',年龄:' + this.age + ',性别:' + (this.sex ? '男' : '女')
// }
computedInfo: {
get: function () {
return '名称:' + this.info.name + ',年龄:' + this.info.age + ',性别:' + (this.info.sex ? '男' : '女')
},
set: function (val) {
console.log('computedInfo被设置为: ' + val)
this.computedinfo = val
}
}
}
}
</script>
Computed 与 Watch 对比:
这两个都可以实现相同效果但过程有点不一样可以根据实际情况来用。
-
Computed特点:
需要主动调用,具有缓存能力。只有数据再次改变才会重新渲染,否则就会直接拿取缓存中的数据。 -
Watch特点:
无论在哪只要被绑定数据发生变化Watch就会响应,这个特点很适合在异步操作时用上。
Webpack的使用
在网页中会引用哪些常见的静态资源?
js文件、css文件、images文件、字体文件、模板文件等。当网页中引入的静态资源多了以后,会引发很多问题:
- 网页加载慢,因为需要发送二次请求
- 要处理错综复杂的依赖关系
一般常见的解决方法是:
- 合并,压缩,精灵图,图片的base64编码等解决第一个问题
- 使用webpack解决各个包之间的复杂的依赖关系
什么是精灵图:
css精灵(CSS sprites)是一种网页图片应用处理技术。主要是指将网页中需要的零星的小图片集成到一个大的图片中。
应用的原因:
1.减少对浏览器的请求次数,避免网页的延迟
2.方便小图标的统一管理
什么是webpack?
Webpack是前端的一个项目构建工具,它是基于node.js开发出来的一个前端工具。借助于webpack这个前端自动化构建工具,可以完美实现资源的合并、打包、压缩、混淆(前端代码加密等)等诸多功能。