入门

黑马四小时入门视频笔记:

基本/v-on

从一个基本程序讲起,它的功能是:点击按钮增加数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--引入Vue-->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="app">
<!--插值表达式-->
{{ message + '*' + num}}
<button @click="addNum">增加number值</button>
</div>
<script>
// 创建Vue实例对象
let app = new Vue({
el: "#app", // 设置挂载点,可以用多种CSS选择器,推荐ID选择器
data: {
message: "Hello World", // 设置data,它的属性可以是对象或数组等
num: 0
},
methods: { // 绑定的方法定义在methods属性中
addNum: function () {
this.num++;
}
}
})
</script>
</body>
</html>

备注:

  • 除了插值表达式(双大括号),还可以设置标签的v-textv-html,如v-text=”message”,这样做会将标签内的内容覆盖
  • 除了@,还可以用v-on来绑定方法,如v-on:click="addNum"
  • 方法内部通过this关键字来访问定义在data中的数据

v-show/v-if/v-bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<style>
img {
width: 300px;
}
.hot{
outline: 5px solid red;
}
</style>
</head>

<body>
<div id="app">
<button @click="toggleShow">切换图片显示状态</button>
<button @click="tempUp">升高温度</button>
<button @click="tempDown">降低温度</button>
<br>
<p>{{'当前是'+ temp + '度'}}</p>
<br><br>
<img :src="imgSrc1" :title="title1" :class="{hot:temp>30}" v-show="isShow">
<br><br>
<img :src="imgSrc2" :title="title2" v-show="isShow">
<!--v-bind设置属性,:src相当于v-bind:src,其余同理>
<!--v-show和v-if设置图片是否可见,值为true时可见-->
</div>
<script>
let app = new Vue({
el: "#app",
data: {
imgSrc1: "./img/1.jpg",
imgSrc2: "./img/2.jpg",
title1: "神乃木",
title2: "御冥",
isShow: true,
temp: 25
},
methods: {
toggleShow: function () {
this.isShow = !this.isShow;
},
tempUp: function () {
this.temp++;
},
tempDown: function () {
this.temp--;
}
}
})
</script>
</body>

</html>
  • v-showv-if区别:前者是设置display:none,而后者是直接修改dom树,由于前者的性能更优,在切换频繁时应使用前者
  • v-bind:src可以简写成:src(其余同理);设置类名时,可以用三目运算符,如::class="isActive?active"可以写成:class="{active:isActive}"

v-for/v-on/v-model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
li {
list-style: none;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>

<body>
<ul id="app">
<p>输入角色名:</p>
<!--
v-model可以便捷地设置和获取表单元素的值,
input的value和newName动态双向绑定,即一方的值变化时另一方也会立即改变
-->
<input type="text" v-model="newName" @keyup.enter="insert">
<!--@keyup.enter表示按下回车键,这里是对v-on的拓展-->
<li v-for="(item, index) in nameArr">
<!--v-for用于生成列表,in可以是数组...-->
{{index + 1}}. 逆转裁判的角色:{{item}}
</li>
<h4 v-for="item in person">
<!--数组内的元素是对象的情况-->
神乃木前辈的爱好:{{item.hobby}}
</h4>
</ul>
<script>
let app = new Vue({
el: "#app",
data: {
nameArr: ["成步堂龙一", "御剑怜恃", "王泥喜法介", "夕月心音", "神乃木庄龙"],
person: [{ name: "神乃木庄龙", job: "律师、检察官", hobby: "喝咖啡" }],
newName: ""
},
methods: {
insert: function () {
this.nameArr.push(this.newName); // 不要忘记this
}
},

})
</script>
</body>

</html>

基于数据的开发方式:无需关心dom,只要数据发生改变,页面内容就能响应式地发生改变

vue和axios

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>

<body>
<div id="app">
<input type="number" placeholder="请输入要获取的笑话条数" oninput="if(value>100)value=100" v-model="jokeNum" @keyup.enter="getJoke">
<button @click="getJoke">获取笑话</button>
<ul>
<li v-for="item in jokes">
{{ item }}
</li>
</ul>
</div>
<script>
let app = new Vue({
el: "#app",
data: {
jokeNum: 1,
jokes: []
},
methods: {
getJoke: function () {
// 发送axios的方式
axios.get(`https://autumnfish.cn/api/joke/list?num=${this.jokeNum}`).then(
// 获取笑话的API
(res) => {
this.jokes = res.data.jokes;
// 老师说axios中的this和外部的this不一样,故要先that = this,此处用that
// 但是直接在axios内部用this也是可行的
},
(err) => {
console.error(err);
}
)
}
}
})
</script>
</body>

</html>

完整版

尚硅谷教程笔记:

Vue核心

初识Vue

创建一个Vue的实例对象,整个应用只需要一个Vue:

  • el(element)用于指定当前Vue实例为哪个容器服务
  • data存储数据,为root容器提供数据,值为一个对象

除了el,第二种挂载root的方法(后期挂载的方式,不常用):

1
2
3
4
5
6
7
const vm = new Vue({
data: {
msg: '尚硅谷',
address: '宏福科技园综合楼'
}
})
vm.$mount('#root');

data的函数式写法:data是一个函数,返回数据对象(组件化编码必须使用函数式data)

1
2
3
4
5
6
data: function(){
return {
msg: '尚硅谷',
address: '宏福科技园综合楼'
}
}

特别注意:

  1. 若使用函数式data,Vue会帮我们调用data函数,Vue就会得到返回的数据对象,从而使用,此时this是Vue的实例对象
  2. data不要写成箭头函数,否则this的指向为window

重要原则:由Vue所管理的函数,写成普通函数(watch中的函数,computed中的函数);不由Vue管理的函数,写成箭头函数(ajax、定时器)

模板语法

  1. 插值语法:
    • 功能:解析标签体内容
    • 写法:,xxx会作为表达式解析,且可以自动读取到data中的属性
  2. 指令语法:
    • 功能:解析标签(包括:标签属性、标签内容、绑定事件…)
    • 举例:v-bind: …

数据绑定

  • 单向数据绑定(v-bind):data中的值传给input,但input的输入不影响data
1
<input type="text" :value="msg">
  • 双向数据绑定(v-model):data中的值传给input,input的值传给data
1
<input type="text" v-model="msg">

MVVM模型

模型

  • M:模型(Model):对应data中的数据
  • V:视图(View):模板代码
  • VM:视图模型(ViewModel):Vue实例对象

数据代理

  1. 什么是数据代理?

    (1)数据代理:配置对象data中的数据,会被收集到vm._data中,然后通过Object.defineProperty,让vm拥有data所有的值

    (2)当访问vm的某值时,返回的是_data中同名属性的值

    (3)当修改vm的某值时,修改的是_data中同名属性的值

  2. 数据代理的原理:

1
2
3
4
5
6
7
8
9
10
11
12
let _data = {msg:"尚硅谷"};
let vm = {};
Object.defineProperty(vm, 'msg', {
set(value){
// 设置vm.msg的同时也设置_data.msg
_data.msg = value;
},
get(){
// 读取vm.msg时返回_data.msg
return _data.msg;
}
})
  1. 为什么要数据代理?

    为了更方便地读取和修改data中的数据,不这样的话,就要使用vm._data.xxx

  2. 为什么要先收集到_data中,再代理出去?

    为了监视数据更高效(Vue不知道程序员在data中有多少属性,数据代理时可以只用监视_data,而不用监视整个vm)

事件处理

参数

  • 触发事件的函数可以带括号,也可以不带
  • 指定参数会覆盖event,如果需要event,传入$event一般作为第一个参数
1
2
3
@click="show1"

@click="show2($event, 666)"

事件修饰符

  • .prevent:阻止默认行为,比如防止点击带href的a标签会跳转至其他页面
  • .stop:阻止事件冒泡,比如防止点击子元素的事件,冒泡到点击父元素的事件
  • .once:事件仅可触发一次
  • keyup.enter:输入回车键,与之类似的还有.esc等,也可以.按键编码(未来版本可能废弃.编码)
  • .native:该事件为原生DOM事件,不需要配置emit即可触发(见自定义事件)

表达式操作

如取反,不需要在methods中定义函数:

1
2
3
<button @click="isHot = !isHot">
切换天气
</button>

计算属性

  • 介绍:计算属性是由data中属性计算得来的新属性,vm里有计算属性,但_data中没有

  • 执行时机:

    1. 初始显示会执行一次,得到初始值去显示
    2. 当依赖的数据发生改变时,会被再次调用
  • 优势:与methods实现相比,内部有缓存机制,效率更高

  • 备注:计算属性是用于直接读取使用的,不要加()

1
2
3
4
5
6
7
8
9
data: {
firstName: '神乃木',
lastName: '庄龙'
},
computed: {
fullName(){
return this.firstName + ' ' + this.lastName
}
}

监视

  • 介绍:监测数据改变的手段,数据发生改变后执行一些操作
  • 执行时机:data中被监视的值改变时调用
  • 两个参数:分别是新值和原值
  • 备注:函数名要和被监视的变量名一致
1
2
3
4
5
6
7
8
9
// 实现和上方computed相同的功能
watch: {
firstName(newValue, oldValue){
this.fullName = newValue + ' ' + this.lastName
},
lastName(newValue, oldValue){
this.fullName = this.firstName + ' ' + this.newValue
},
}

computed和watch的区别:

  1. 只要是computed能完成的功能,watch都可以完成
  2. watch可以进行异步操作(比如定时器1s后修改fullName),但computed不可以

监视的完整版:(了解)

1
2
3
4
5
6
7
8
9
10
11
12
watch: {
// 监测姓和名,此处只需要将其一改为完整写法:
firstName:{
immediate: true, // 该值为true则handler在初始化时就会调用一次,以后的调用看firstName的改变
handler(newValue, oldValue){ // 该函数必须叫handler
this.fullName = newValue + ' ' + this.lastName
}
},
lastName(newValue, oldValue){
this.fullName = this.firstName + ' ' + this.newValue
},
}

监视的第二种绑定方法(不常用):

1
2
3
4
5
6
vm.$watch('firstName', {
immediate: true, // 该值为true则handler在初始化时就会调用一次,以后的调用看firstName的改变
handler(newValue, oldValue){ // 该函数必须叫handler
this.fullName = newValue + ' ' + this.lastName
}
})

class与style绑定

class的写法(数组写法不常用,未列出):

1
2
3
4
5
6
7
8
<!--class的字符串写法,适用于:类名不确定,要动态获取-->
<h2 :class="myStyle"></h2>

<!--class的对象写法,适用于:类名确定,但不确定用不用-->
<h2 :class="{classA:hasA, classB:hasB}"></h2>

<!--class的三元表达式写法,使用情况同上-->
<h2 :class="hasA ? 'classA' : ''"></h2>

绑定style:

1
2
<!--fontSize在data中-->
<h2 :style="{fontSize:size + 'px'}"></h2>

条件渲染

v-show和v-if在入门处已经说得详细,下述总结:

  • v-if(还有v-else-if和v-else):
    • 适用于:切换频率很低的场景
    • 特点:不展示的DOM结点直接被删除(触发回流重绘)
  • v-show:
    • 适用于:切换频率很高的场景
    • 特点:不展示的DOM结点没有被删除,仅仅用样式隐藏
  • 备注:使用v-if时,DOM结点可能无法获取到,而使用v-show一定可以获取到DOM结点

列表渲染

标准写法需要加上key属性,key属性要求每一个元素都是不同的值

遍历数组

这里假设数组内每一个元素都是一个对象,每一个对象的id都不同

1
<li v-for="(item, index) in arr" :key="item.id">

遍历对象

括号内的key表示键名,由于对象不存在重复键名,故可以赋值给key属性

1
<li v-for="(value, key) in obj" :key="key">

遍历字符串

data表示字符,index是该字符在字符串内的序号,由于序号不会重复,故可以赋值给key属性

1
<li v-for="(data, index) in str" :key="index">

收集表单数据

各种情况:

  • 若是<input type="text">,则v-model收集的是value值
  • 若是<input type="radio">,则v-model收集的是value值
  • 若是<input type="checkbox">
    • 没有配置value属性:收集的是checked(布尔值)
    • 配置value属性:
      • v-model的初始值是非数组:收集的是checked
      • 是数组:收集的是由value组成的数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<div id="root">
<form @submit.prevent="submit">
<!--一般表单都不会写action,并去除提交刷新的默认事件,使用ajax提交-->
账号: <input type="text" v-model="userInfo.account">
<br><br>
密码: <input type="password" v-model="userInfo.password">
<br><br>
性别: 男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
<input type="radio" name="sex" v-model="userInfo.sex" value="female">
<br><br>
爱好:抽烟<input type="checkbox" v-model="userInfo.hobby" value="smoke">
喝酒<input type="checkbox" v-model="userInfo.hobby" value="drink">
开车<input type="checkbox" v-model="userInfo.hobby" value="drive">
<br><br>
所属校区:<select v-model="city">
<option value="">请选择校区</option>
<option value="beijing"></option>
<option value="beijing"></option>
<option value="beijing"></option>
<option value="beijing"></option>
</select>
<br><br>
其他信息:<textarea cols="30" rows="10" v-model="other"></textarea>
<br><br>
<input type="checkbox">阅读并接受<a href="http://www.atguigu.com">《用户协议》</a>
<button>
提交
</button>
</form>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
new Vue({
el: "root",
data: {
userInfo{
account: '',
password: '',
sex: '',
hobby: [],
city: '',
other: '',
agree: false
}
},
methods: {
submit(){
console.log(this.userInfo);
}
}
})

生命周期

生命周期分为:

  • 挂载流程

  • 更新&销毁流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title>分析生命周期</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>

<body>
<!-- 准备好一个容器-->
<div id="root">
<h2 id="h2">当前页面求和为:{{sum}}</h2>
<button @click="add" id="btn">点我+1</button>
<button @click="death">销毁</button>
</div>

<script type="text/javascript">
//修改Vue的全局配置
Vue.config.productionTip = false //关闭生产提示

const vm = new Vue({
el: '#root',
data: {
sum: 0
},
methods: {
add() {
console.log('你点了+按钮');
this.sum += 1;
},
death() {
this.$destroy();
}
},
//vue实例数据代理前
beforeCreate() {
console.log('--beforeCreate--');
console.log(this.sum); //undefined
console.log(this.add); //undefined
},
//vue实例数据代理完毕
created() {
console.log('--created--');
console.log(this.sum); // 0
console.log(this.add); // function
console.log(this);
// debugger;
},
//vue实例更新真实DOM之前(挂载前)
beforeMount() {
console.log('--beforeMount--');
const btn = document.getElementById('btn');
btn.innerText = 'Hello'; // 最终对DOM的操作无效
const h2 = document.getElementById('h2');
console.log(h2);
// debugger;
},
//vue实例更新完真实DOM了(挂载完毕)
mounted() {
console.log('--mounted--');
const h2 = document.getElementById('h2');
const btn = document.getElementById('btn');
btn.innerText = 'Hello'; // 最终对DOM的操作有效
console.log(h2)
console.log(btn);
},
//vue实例将要更新页面
beforeUpdate() {
console.log('--beforeUpdate--');
console.log(this.sum); // sum为1,页面上为0
// debugger;
},
//vue实例完成更新页面
updated() {
console.log('--updated--');
console.log(this.sum) // sum为1,页面上也为1
// debugger;
},
//vue实例销毁前
beforeDestroy() {
console.log('--beforeDestroy--');
console.log(this.sum); //可以获取
console.log(this.add); //可以获取
},
//vue实例销毁完毕
destroyed() {
console.log('--destroyed--');
console.log('移除完毕了所有数据的监视,不会再更新页面了');
},
})
</script>
</body>

</html>

过渡和动画

过渡

步骤:

  1. 在目标元素外包裹<transition name="xxx">
  2. 编写样式:
    • 进入:
      • 进入起始点:xxx-enter
      • 进入过程中:xxx-enter-active
      • 进入结束点:xxx-enter-to
    • 离开:
      • 离开起始点:xxx-leave
      • 离开过程中:xxx-leave-active
      • 离开结束点:xxx-leave-to
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<style>
.picture{
width: 100px;
}
/*离开的起点,即进入的终点*/
.demo-leave-to, .demo-enter{
opacity: 1;
transform: scale(1) rotate(0deg);
}
/*离开的终点,即进入的起点*/
.demo-leave-to, .demo-enter{
opacity: 0;
transform: scale(0.2) rotate(180deg);
}
.demo-leave-active, .demo-enter-active{
transition: 1s all;
/*如果离开时和进入时的transition时间不一样,就得分开写*/
}
</style>

<body>
<transition name="demo">
<img v-show="isShow" class="picture" src="...">
</transition>
</body>

如果是多个元素的过渡,使用<transition-group>,key值必写:

1
2
3
4
<transition-group>
<h1 v-show="!isShow" key="...">元素1</h1>
<h1 v-show="isShow" key="...">元素2</h1>
</transition-group>

动画

和过渡类似:

  1. 基本编码
    • 在目标元素外包裹<transition name="xxx">
    • 编写:进入动画、离开动画的样式
  2. 类名规范:
    • 进入动画样式:xxx-enter-active
    • 离开动画样式:xxx-leave-active
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<style>
.picture{
width: 100px;
}

/*reverse表示动画反向播放*/
.demo-enter-active{
animation: test 1s;
}
.demo-leave-active{
animation: test 1s reverse;
}

@keyframes test{
...
}
</style>
<body>
<transition name="demo">
<img v-show="isShow" class="picture" src="...">
</transition>
</body>

过滤器

Vue2只有全局过滤器,它可以影响多个Vue实例

现有需求:将时间戳转化成格式化的时间,使用第三方库moment.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script src="../js/moment.min.js"></script>
<body>
<div id="root">
<h2>显示格式化后的时间</h2>
<!--过滤器的固定语法 ↓ -->
<h3>{{time | dateFormater}}</h3>
<h3>{{time | dateFormater('YYYY-MM-DD HH:mm:ss')}}</h3>
<!--'YYYY-MM-DD HH:mm:ss'是moment.js要求传入的参数-->
</div>
</body>
<script>
// 当有任何日期想要格式化时,就可以调用dateFormater
Vue.filter('dateFormater', function(value, str='YYYY-MM-DD'){
// value是要进行格式化的时间戳,str是dateFormater的参数,如果不传参,str为默认值
return moment(value).format(str); // moment由moment.js提供
})
new Vue({
el: '#root',
data: {
time: Date.now()
}
})
</script>

自定义指令

分为:全局指令和局部指令,全局指令可以在多个vm中使用

1
2
3
4
5
<div id="root">
<h2 v-upper-text="name">
{{name}}
</h2>
</div>

全局指令:

1
2
3
4
5
6
7
8
9
10
11
Vue.directive('upper-text', function(el, binding){ // 不需写 v-
// 该指令的作用是:将字符变为大写
el.innerText = binding.value.toUpperCase();
});
new Vue({
el: "#root",
data:{
name: danmosama
}
})

局部指令:

1
2
3
4
5
6
7
8
9
10
11
new Vue({
el: "#root",
data:{
name: danmosama
},
directives:{
'upper-text'(el, binding){ // 此处为简写
el.innerText = binding.value.toUpperCase();
}
}
})

自定义插件

感性认识:插件是一个添加指令、属性…的集合

先创建一个plugin.js文件,作为插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 插件的定义:是一个包含install方法的对象
let plugin = {};

plugin.install = function(Vue, options){

// 添加一个全局指令
Vue.directive('upper-text', function(el, binding){
el.innerText = binding.value.toUpperCase();
});

// 给Vue自身添加属性和方法
Vue.proName = '管理系统';
Vue.version = 'V1.0.1';
Vue.showInfo = function(){
console.log('Some info');
}

// 给Vue原型上添加数据,供vm使用
Vue.prototype.$method = function(){
console.log('This method is prepared for vm');
}
}

再调用该插件:

1
2
3
4
5
6
<script src="plugin.js"></script>
// Vue会帮我们调用插件里的install方法
<script>
Vue.use('plugin');
// 其他操作(包括创建vm),可以console.log插件添加的属性,使用插件的方法等
</script>

组件和脚手架

非单文件

在真正开发中,不使用非单文件,它仅用于教学和理解原理,单文件的笔记结合了脚手架

流程

组件:包含页面局部功能的代码和资源的集合,规范要求组件的首字母大写

组件使用的基本流程:

  • 定义组件
  • 注册组件
    • 全局注册:Vue.component(‘组件名, 组件’)
    • 局部注册
  • 写组件标签

定义组件:

  1. 定义方式:使用Vue.extend(options)创建

  2. School的本质是一个构造函数,以后写<School/>,Vue帮我们去new School

  3. options参数是配置对象,它几乎和new Vue时的options一样,区别如下:

    • 不能写el指定容器:

      原因:所有组件实例最终要被一个vm所管理,vm中会指定好el
      
    • data必须写成函数

      原因:确保多个组件中的数据互不干扰(如果写成对象,考虑引用数据类型的特性,修改一个vm的data会影响另一个)
      
    • 组件的模板结构要配置在template属性中:

      • 值为html字符串,推荐模板字符串
      • 模板的结构必须只有一个根标签(在外边套div)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<div id="root1">
<!--可以写开始标签和结束标签,也可以写自结束标签-->
<!--此处并非标准写法,一般最外边是app标签-->
<School/>
</div>

<div id="root2">
<School/>
</div>

<script>
// 定义组件
const School = Vue.extend({
data(){
return{
name: '尚硅谷',
address: '北京'
}
},
template:`
<h2>学校名:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
`
})

// 全局注册
Vue.component('School', School);

// 定义vm,去管理所有的组件
new Vue({
el: "#root1"
});

new Vue({
el: "#root2"
})
</script>
1
2
3
4
5
6
7
8
// 演示局部注册

new Vue({
el: "#root",
components:{
School // 触发简写形式,School:School可以直接写School
}
})

VueComponent的原理

VueComponent继承了Vue,所以Vue.prototype上的属性和方法,vc都能看见

接上节例,我们说School是一个构造函数,如果console.log(new School()),输出在控制台的不是 School (),而是 VueComponent(),由此分析出对应的原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
const Vue = {
extend(){
function VueComponent(){
...
}
return VueComponent;
}

const School = Vue.extend({...});

const s = new School();
console.log(s); // VueComponent()
}

组件的data函数、以及methods中配置的函数中的this都是vc

创建脚手架

运行:

1
2
3
4
5
npm install -g @vue/cli

vue create 项目名(英文)

npm run serve

得到的项目结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.git(自动执行了init、add、commit操作)
node_modules
public
favicon.co
index.html
src
assets(可在此处配置静态资源)
components
HelloWorld.vue
App.vue
main.js
.gitignore
bable.config.js
package.json
README.md
yarn.lock
index.html

分析脚手架

title

1
<title><%= htmlWebpackPlugin.options.title %></title>

此处的title和package.json文件挂钩

render与h函数

模板解析器:解析template

import Vue from 'vue'时,由于没有指定路径,根据package.json,默认引入的是vue.runtime.common.js,该文件的优点是体积很小,但是不含模板解析器,此时默认情况下只能解析.vue文件中的模板,不能解析main.js中的模板

于是官方提供了render函数,它可以调用模板解析器,使得main.js中的模板可以被解析,并且有了render后,我们无需再在index.html中的div中写<app/>

单文件案例

Hello.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* 配置组件模板结构 */
<template>
<div>
<h2 class="title">学校名:{{name}}</h2>
<h2 class="info">学校地址:{{address}}</h2>
</div>
</template>

/* 配置组件数据、交互等 */
<script>
// 暴露组件配置,并没有创建组件(因为没有调用Vue.extend)
export default {
// data中存放组件所需数据
data(){
return {
name:'DanmoSAMA',
address:'湖北省-武汉市-洪山区-华中科技大学'
}
}
}
</script>

/* 配置组件样式 */
<style>
.title{
color: orange;
}
.info{
color: blue;
}
</style>

App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* 配置组件的结构 */
<template>
<div>
<h2 class="like">我最喜欢的角色是:{{name}}</h2>
<Hello/>
</div>
</template>

/* 配置组件数据、交互、事件等等*/
<script>
// 引入Hello组件
import Hello from './components/Hello'

export default {
data(){
return {
name: 'Soryuu Kaminogi'
}
},
components:{Hello}, // 注册组件
}
</script>

<style>
.like{
color: green;
}
</style>

main.js:

1
2
3
4
5
6
7
8
9
10
import Vue from 'vue' // 通过ES6模块化语法引入vue
import App from './App'

Vue.config.productionTip = false

new Vue({
el: "#app",
render: h => h(App)
})

index.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="app"></div>
</body>
</html>

mixin

minxin(混入):

  • 功能:把多个组件共用的配置提取成一个混入对象

  • 使用:如下

  • 原则:如果混合中的属性和方法与组件中data和methods有冲突,组件优先;如果是生命周期钩子有冲突,则同时应用

定义混合:

mixin.js:

1
2
3
4
5
6
7
8
9
10
export const mixin{
data:{
public:'public data'
}
methods:{
showName(){
alert(this.name)
}
}
}

引入混合:

  • 局部引入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import {mixin} from '../mixin'

    export default{
    data(){
    return{
    name: '淡漠',
    age: 19
    }
    },
    mixins:[mixin]
    }
  • 全局引入:

    1
    2
    3
    4
    import {mixin} from '../mixin'

    // 应用到所有vm和vc上
    Vue.mixin(mixin);

scope

scope写在style标签里,作用:让样式在局部生效,防止冲突

但是,App组件里定义的样式一般用于所有的组件,因此App组件的style标签不写scope

书写less

如果要在style中写less:

  • lang=”less”
  • 下载less-loader,不能下载最新版本,因为新版本用webpack5:
    • npm view less-loader versions查看版本
    • npm i less-loader@7下载7.3版本的less-loader

ref

ref被用来给元素或子组件注册引用(reference)信息。引用信息将会注册在父组件的$refs对象上。如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果用在子组件上,引用就指向组件实例。

ref的使用:

  • 标签中直接编写 ref="xxx"
  • 通过this.$refs.xxx获取
  • 备注:
    • 若给html内置标签打ref,则获取到的是真实DOM结点
    • 若给组件标签打ref,则获取到的是组件实例对象vc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div>
<button ref="btn" @click="showData">
点我获取焦点
</button>

<input type="text" ref="keyWord">

<School ref="xuexiao"/>
</div>
</template>

<script>
import School from './components/School'

export default{
components:{School},
methods:{
showData(){
console.log(this.$refs.btn.innerText);
this.$refs.keyWord.focus(); // 获取input的焦点
console.log(this.$refs.xuexiao); // vc
}
}
}
</script>

props

props是properties的简称,它可以限制类型、控制必要性和指定默认值等,一般用于父组件向子组件传递数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<h2 class="name">学校名:{{name}}</h2>
<h2 class="address">学校地址:{{address}}</h2>
<h4>接收到的名字是{{username}}</h4>
</div>
</template>
<script>
export default{
data(){
return{
name: '尚硅谷',
address: '北京'
}
},
props:{
userName:{ // 此处不能和data中的属性名重复,因为它也会成为vc的一个属性
type: String, // 类型
required: true, // 必要性
default: 'DanmoSAMA' // 默认值
}
}
}
</script>

如果在School.vue里这样写了,则它的外壳App.vue必须绑定userName属性,否则会在控制台警告,并将默认值填入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
<!--此处必须绑定userName属性-->
<School :userName="userName"/>
</div>
</template>

<script>
import School form './components/School'

export default {
data(){
return{
userName: '淡漠'
}
},
components:{School}
}
</script>

以上为完整写法,精简写法(更常用)如下:

1
2
3
4
5
6
7
// 次完整写法
props:{
userName:String
}

// 最精简写法
props:['userName']

组件自定义事件

  1. 组件自定义事件是一种组件间通信的方式,适用于子组件=>父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件,事件的回调在A中(回调在父组件中

  3. 绑定自定义事件:

    1. 方法一:在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test">

    2. 方法二:在父组件中:

      1
      2
      3
      4
      5
      <Demo ref="demo"/>
      ...
      mounted(){
      this.$refs.xxx.$on('atguigu', this.test);
      }
    3. 若想让自定义事件只能触发一次,可以用once修饰符或$once方法

  4. 触发自定义事件:this.$emit('atguigu', 数据)

  5. 解绑自定义事件:this.$off('atguigu')

  6. 组件上绑定原生DOM事件:使用native修饰符

  7. 注意:通过this.$refs.xxx.$on('atguigu', 回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题

全局事件总线

可以实现任意组件间的通信。

在main.js中安装全局事件总线:

1
2
3
4
5
6
7
8
//创建vm
new Vue({
el:'#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线
},
})

(此处原理暂不深究,先记住这种方法)

$bus是一个东西,它不属于任何组件,A、B是兄弟组件,当A想收到数据时,在A组件里给$bus绑定一个自定义事件demo,此时demo的回调就留在A组件里。B想给A传数据时,触发$bus上的demo自定义事件,并带一些数据过去,则$bus上的demo事件被触发后,触发A中的回调,就把数据带过去了。

消息订阅与发布

pubsub-js是一个库,在vue中更推荐使用全局事件总线方式

  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

    2. 引入:import pubsub from 'pubsub-js'

    3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身

      1
      2
      3
      4
      5
      6
      7
      methods(){
      demo(data){...}
      }
      ...
      mounted(){
      this.pid = pubsub.subscribe('xxx', this.demo); // 订阅消息
      }
    4. 提供数据:pubsub.publish('xxx',数据)

    5. 应在beforeDestory钩子中,用pubsub.unsubsribe(pid)去取消订阅

配置代理

场景:前端的端口号为8080,服务器的端口号为5000,前端向服务器发送Ajax请求时存在跨域问题,导致前端拿不到服务器返回的数据,故需要配置代理,前端请求代理,代理请求服务器(代理服务器的端口和前端相同,由于同源策略是Ajax的,服务器之间不存在跨域问题,便可以从8080端口的服务器拿到数据)

方法一

在vue.config.js中添加如下配置:

1
2
3
devServer:{
proxy: "http://localhost:5000"
}

说明:

  1. 优点:配置简单,请求资源时直接发给前端(8080)即可
  2. 缺点:不能配置多个代理,不能灵活控制请求是否走代理
  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,才会把该请求转发给服务器(优先匹配前端资源)

方法二(推荐)

编写vue.config.js配置具体代理规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module.exports = {
pages: {
index: {
//入口
entry: 'src/main.js',
},
},
lintOnSave:false, //关闭语法检查
//开启代理服务器(方式二)
devServer: {
proxy: {
'/atguigu': {
target: 'http://localhost:5000',
// 前端向代理请求时要加/atguigu,代理向服务器请求时会去掉/atguigu
pathRewrite:{'^/atguigu':''},
// ws: true, //用于支持websocket
// changeOrigin: true //用于控制请求头中的host值
},
'/demo': {
target: 'http://localhost:5001',
pathRewrite:{'^/demo':''},
// ws: true,
// changeOrigin: true
}
}
}
}

插槽

作用:让父组件可以向子组件指定位置插入HTML结构,也是一种组件间通信的方式,适用于父组件=>子组件

默认插槽

1
2
3
4
5
6
7
8
9
10
11
12
父组件:
<Category>
<div>html结构1</div>
</Category>

子组件:
<template>
<div>
<!--定义插槽-->
<slot>插槽默认内容</slot>
</div>
</template>

具名插槽

template标签的好处是不会被解析到页面上,slotv-slot属性都只能写在template标签上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
父组件:
<Category>
<template slot="center">
<div>html结构1</div>
</template>

<template v-slot:footer>
<div>html结构2</div>
</template>
</Category>

子组件:
<template>
<div>
<!--定义插槽-->
<slot name="center">插槽默认内容</slot>
<slot name="footer">插槽默认内容</slot>
</div>
</template>

作用域插槽

理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
父组件:
<Category>
<template scope="scopeData">
<!--生成的是ul列表-->
<ul>
<li v-for="game in scopeData.games" :key="g">{{game}}</li>
</ul>
</template>
</Category>

<Category>
<template slot-scope="scopeData">
<!--生成的是h4标题-->
<ul>
<h4 v-for="game in scopeData.games" :key="g">{{game}}</h4>
</ul>
</template>
</Category>

子组件:
<template>
<div>
<!--定义插槽-->
<slot :games="games">插槽默认内容</slot>
</div>
</template>

<script>
export default{
name:'Category',
data(){
return{
games: ['game1', 'game2', 'game3']
}
}
}
</script>

vuex

简介

  1. 概念:

    在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进入集中式管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信

  2. 何时使用:多个组件需要共享数据时

工作原理

vuex由ActionsMutationsState组成,这三者被Store管理着

  • vc向Actions传动作和数据,Actions把它们交给Mutations,由它执行并更改State,渲染后影响vc(页面)
  • 如果不进行发ajax等操作,vc可以直接调用commit,跳过Actions
  • 类比:vc是客人,Actions是服务员,Mutations是后厨,State是做好的菜;dispatch即客人告诉服务员需求,commit即服务员把需求告诉后厨,mutate即后厨制作,render即把菜传给客人

搭建环境

npm i vuex

  1. 创建文件:src/store/index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 引入Vue核心库
    import Vue from 'vue'
    // 引入Vuex
    import Vuex from 'vuex'
    // 应用Vuex插件
    Vue.use(Vuex);

    // 准备actions对象,响应组件中用户的动作
    const actions = {};
    // 准备mutations对象,修改state中的数据
    const mutations = {};
    // 准备state对象,保存具体的数据
    const state = {};

    // 创建并暴露store
    export default new Vuex.Store({
    actions,
    mutations,
    state
    })
    1. 在main.js中创建vm时传入store配置项
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ......
    // 引入store
    import store from './store'
    ......

    // 创建vm
    new Vue({
    el: "#app",
    render: h => h(App),
    store
    })

基本使用

  1. 初始化数据,配置actionsmutations,操作文件index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)

    const actions = {
    // 响应组件中加的动作
    add(context, value){
    context.commit('ADD', value);
    }
    }

    const mutations = {
    // 执行加
    ADD(state, value){
    state.sum += value;
    }
    }

    // 初始化数据
    const state = {
    sum: 0
    }

    // 创建并暴露store
    export default new Vuex.Store({
    actions,
    mutations,
    statesj
    })
  2. 组件中读取vuex中的数据:$store.state.sum

  3. 组件中修改vuex中的数据:$store.dispatch('actions中的方法名', 数据)$store.commit('mutations中的方法名', 数据)

若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

getters

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工

  2. store.js中追加getters配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ......
    const getters = {
    bigSum(state){
    return state.sum * 10
    }
    }

    // 创建并暴露store
    export default new Vuex.Store({
    ......
    getters
    })

map方法

map方法不是必需的,它们可以对代码作出一些优化

  1. mapState方法:用于帮助我们映射state中的数据为计算属性

    1
    2
    3
    4
    5
    6
    computed:{
    // 借助mapState生成计算属性:sum、school、subject(对象写法)
    ...mapState({sum:'sum', school:'school', subject:'subject'}),
    // 借助mapState生成计算属性:sum、school、subject(数组写法)
    ...mapState(['sum', 'school', 'subject'])
    }
  2. mapGetters方法:用于帮助我们映射getters中的数据为计算属性

    1
    2
    3
    4
    5
    6
    computed:{
    // 借助mapGetters生成计算属性:bigSum(对象写法)
    ...mapState({bigSum: 'bigSum'}),
    // 借助mapGetters生成计算属性:bigSum(数组写法)
    ...mapState(['bigSum'])
    }
  3. mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    1
    2
    3
    4
    5
    6
    methods:{
    // 靠mapActions生成、increamentOdd、increamentWait(对象形式)
    ...mapActions({increamentOdd:'jiaOdd', increamentWait:'jiaWait'}),
    // 靠mapActions生成、increamentOdd、increamentWait(数组形式)
    ...mapActions(['jiaOdd','jiaWait'])
    }
  4. mapMutatios方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

    1
    2
    3
    4
    5
    6
    methods:{
    // 靠mapActions生成、increamentOdd、increamentWait(对象形式)
    ...mapMutations({increamentOdd:'jiaOdd', increamentWait:'jiaWait'}),
    // 靠mapActions生成、increamentOdd、increamentWait(数组形式)
    ...mapMutations(['jiaOdd','jiaWait'])
    }

vue-router

  1. 定义:是vue的一个插件库,专门实现SPA应用(单页Web应用)
  2. 路由:
    • 一个路由就是一组映射关系(key - value),key为路径,value为function或component
    • 路由分为前端路由(浏览器路径改变时改变显示的组件)和后端路由

基本使用

  1. 安装:npm i vue-router

  2. 应用插件:Vue.use(VueRouter)

  3. 编写router配置项:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import VueRouter from 'vue-router'

    import About from '../components/About'

    const router = new VueRouter({
    routes:[
    {
    path: 'about',
    components: About
    }
    ]
    })

    export default router;
  4. 实现切换(active-class可配置高亮样式)

    1
    <router-link active-class="active" to="/about">About</router-link>
  5. 指定展示位置

    1
    <router-view></router-view>

几个注意点

  1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹
  2. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
  3. 每个组件都有自己的$route属性,里面存储自己的路由信息
  4. 整个应用只有一个router,可以通过组件的$router属性获取到

多级路由

  1. 配置路由规则:使用children配置项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    routes:[
    {
    path: '/home',
    component: Home,
    children:[
    {
    path: 'news', // 此处不要写'/'
    component: News
    },
    {
    path: 'message',
    component: Message
    }
    ]
    }
    ]
  2. 跳转(写完整路径)

    1
    <router-link to="/home/news">News</router-link>

query参数

  1. 传递参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--跳转并携带query参数,to的字符串写法-->
    <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>

    <!--跳转并携带query参数,to的对象写法-->
    <router-link
    :to="{
    path: '/home/message/detail',
    query:{
    id: 666,
    title: '你好'
    }
    }"
    >跳转</router-link>
  2. 接收参数:

    1
    2
    $route.query.id
    $route.query.title

命名路由

  1. 作用:可以简化路由的跳转

  2. 使用:

    1. 给路由命名:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      {
      path: 'demo',
      component: Demo,
      children:[
      name: 'hello', // 命名
      path: 'welcome',
      component: Hello
      ]
      }
    2. 简化跳转:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <!--简化前-->
      <router-link to="/demo/test/welcome">跳转</router-link>
      <!--简化后-->
      <router-link :to="{name:'hello'}">跳转</router-link>
      <!--简化配合传递参数-->
      <router-link
      :to="{
      name: 'hello',
      query:{
      id: 666,
      title: '你好'
      }
      }"
      >跳转</router-link>

params参数

params参数不带a=b&c=d的形式,用/分隔

  1. 配置路由,声明接收params参数

    1
    2
    3
    4
    {
    ...
    path: 'detail/:id/:title' // 使用占位符声明接收params参数
    }
  2. 传递参数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <router-link :to="/home/message/detail/666/你好">跳转</router-link>
    <!--简化配合传递参数-->
    <router-link
    :to="{
    name: 'hello',
    params:{
    id: 666,
    title: '你好'
    }
    }"
    >跳转</router-link>
  3. 接收参数:

    1
    2
    $route.params.id
    $route.params.title

props配置

作用:让路由组件更方便地收到参数

1
2
3
4
5
6
7
8
9
10
11
12
13
{
// props值为对象几乎不用
...
// 写法一:props值为布尔值,为true时把路由收到的所有params参数通过props传给Detail组件
props: true
// 写法二:props值为函数,该函数返回的对象中,每一组key-value都会通过props传给Detail组件
props(route){
return{
id: route.query.id,
title: route.query.title
}
}
}

replace属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:pushreplace,前者追加历史记录,后者替换当前记录,默认为push
  3. 开启replace模式::<router-link replace ...></router-link>

编程式路由导航

  1. 作用:不借助<router-link>实现路由跳转,让路由跳转更加灵活

  2. 代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // $router的两个API
    this.$router.push({
    name: 'demo',
    params:{
    id: xxx,
    title: xxx
    }
    })

    this.$router.replace({
    name: 'demo',
    params:{
    id: xxx,
    title: xxx
    }
    })

    this.$router.forward() // 前进
    this.$router.back() // 后退
    this.$router.go() // 可前进也可后退,传入一个正负数作为参数

缓存路由组件

  1. 作用:让不展示的路由组件保持挂载,不被销毁

  2. 代码:

    1
    2
    3
    4
    5
    6
    7
    <keep-alive include="News">
    <router-view></router-view>
    </keep-alive>

    <keep-alive :include="['News', 'Message']">
    <router-view></router-view>
    </keep-alive>

路由生命周期钩子

捕获路由组件的激活状态:

  1. activated:路由组件被激活时触发
  2. deactived:路由组件失活时触发

路由守卫

作用:对路由进行权限控制

meta中可以存一些程序员想要保存的数据

全局守卫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to,from,next)=>{
console.log('前置路由守卫',to,from)
if(to.meta.isAuth){ //判断是否需要鉴权
if(localStorage.getItem('school')==='atguigu'){
next()
}else{
alert('学校名错误,无权限查看!')
}
}else{
next()
}
})

//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
console.log('后置路由守卫',to,from)
document.title = to.meta.title || '硅谷系统'
})

独享守卫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
name:'news',
path:'news',
component:News,
meta:{isAuth:true,title:'新闻'},
beforeEnter: (to, from, next) => {
console.log('独享路由守卫',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要鉴权
if(localStorage.getItem('school')==='atguigu'){
next()
}else{
alert('学校名不对,无权限查看!')
}
}else{
next()
}
}
}

组件内守卫

注意事项:

  • 不要把离开守卫理解成后置守卫
  • “通过路由规则”:如果一上来组件就展示,不会触发
1
2
3
4
5
6
7
8
// 进入守卫,通过路由规则,进入该组件时被调用
beforeRouterEnter(to, from, next){

},
// 离开守卫,通过路由规则,离开时该组件时被调用
beforeRouteLeave(to, from, next){

}

两种工作模式

  1. url的hash:#及其后面的内容
  2. hash值不会包含在HTTP请求中,即:hash值不会带给服务器
  3. hash模式:
    1. 地址中永远带着#号,不美观
    2. 若以后将地址通过第三方手机App分享,若App校验严格,则地址会被标记为不合法
    3. 兼容性较好
  4. history模式:
    1. 地址美观
    2. 兼容性略差
    3. 应用部署上线时需要后端支持,解决刷新页面服务端404的问题

其他

关闭生产提示

1
Vue.config.productionTip = false;

webStorage

浏览器F12->Application可以查看本地存储

  1. 存储内容大小一般支持5MB左右

  2. 浏览器端通过 Window.sessionStorageWindow.localStorage属性实现本地存储

  3. API:

    1. xxxStorage.setItem('key', 'value')

      将键值对添加到存储中,如果键名存在,则更新其对应值

      如果value不是字符串则浏览器会使用toString()转换,如果传对象,这样会破坏其内部结构

      使用JSON.stringify(),将其转换为字符串同时不破坏内部结构

    2. xxxStorage.getItem('key')

      返回键名对应的值,不存在则返回null

      获取对象时,使用JSON.parse()

    3. xxxStorage.getItem('key')

      删除该键名和键值

    4. xxxStorage.getItem('key')

      清空存储中的所有数据

  4. 备注:

    1. SessionStorage存储的内容随着浏览器窗口关闭而消失
    2. LocalStorage存储的内容需要手动清除(调API或清空缓存)
    3. JSON.parse(null)的结果是null