banner
Hi my new friend!

vue2内容

Scroll down

vue 简介


Vue 是一套用于构建用户界面的 渐进式框架 。

与其它大型框架不同的是,Vue 采用自底向上增量开发的设计。

Vue 的核心库只关注视图层,并且非常容易学习,非常容易与其它库或已有项目整合。另一方面,Vue 完全有能力驱动采用单文件组件和Vue生态系统支持的库开发的复杂单页应用。

Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

最初它不过是个人项目,时至今日,已成为全世界三大前端框架之一,github 上拥有 19.8万 Star。 领先于 React 和 Angular,在国内更是首选。
alt

自从Vue 2.0 发布之后,Vue 就成了前端领域的热门话题。

2019.02.05,Vue 发布了 2.6.0 ,这是一个承前启后的版本,在它之后,推出了 3.0.0。

2019.12.05,在万众期待中,尤雨溪公布了 Vue 3 源代码,此时的 Vue 3仍 处于 Alpha 版本。

2020年09月18日,Vue.js 3.0 正式发布。


Vue 周边生态


vue-cli:vue 脚手架
vue-resource(axios):ajax 请求
vue-router:路由
vuex:状态管理(它是 vue 的插件但是没有用 vue-xxx 的命名规则)
vue-lazyload:图片懒加载
vue-scroller:页面滑动相关
mint-ui:基于 vue 的 UI 组件库(移动端)
element-ui:基于 vue 的 UI 组件库(PC 端)

原生计数器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. 获取DOM原生
const titleEl = document.querySelector(".title");
const btnInEl = document.querySelector('.increment');
const btnDeEl = document.querySelector('.decrement');
// 2. 默认设置的是Hello World
let counter = 0
// 3. 设置titleEl的内容
titleEl.innerHTML = counter;
// 4. 监听按钮的点击
btnInEl.addEventListener('click',(=>{
counter += 1;
titleEl.innerHTML =counter;
})
btnDeEl.addEventListener('click',(=>{
counter -=1;
titleEl.innerHTML = counter;
})

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!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>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>

<body>
<div id="app">
<!-- vue指令: v-on:事件类型 -->
<button v-on:click="increment">+</button>
<!-- 简写 比如: @click @keyup @keydown -->
<button @click="increment">+</button>
{{ count }}
<button v-on:click="decrement">-</button>
<button @click="decrement">-</button>
</div>
<script>
// 关闭生产提示
Vue.config.productionTip = false

var vm = new Vue({
el: '#app',
// 数据
data: {
count: 0
},
// 方法
methods: {
increment(){
// this => vue实例
this.count++
},
decrement(){
this.count--
}
}
});
</script>
</body>

</html>

指令语法

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
# 属性绑定 v-bind:
# 功能:指定变化的属性值
# 完整写法
v-bind:xxx='yyy' // yyy会作为表达式解析执行
# 简洁写法
:xxx='yyy'
# 属性绑定应用
<div id="app">
<!-- 数组 -->
<h1 :class="['red', 'thin']">{{msg ? '尘埃等闲':'大千世界'}}</h1>
<hr>
<!-- 数组嵌套三元表达式 -->
<h1 :class="['red', 'thin', isactive?'active': '']">{{msg ? '尘埃等闲':'大千世界'}}</h1>
<hr>
<!-- 数组嵌套对象 -->
<h1 :class="['red', 'thin', {active: isactive}]">{{msg ? '尘埃等闲':'大千世界'}}</h1>
<hr>
<!-- 直接使用对象 -->
<h1 :class="{red:true}">{{msg ? '尘埃等闲':'大千世界'}}</h1>
<hr>
<h1 :class="styles">{{msg ? '尘埃等闲':'大千世界'}}</h1>
<hr>
<button @click='click'>点击</button>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: true,
info: '明天就中秋了,你们的中秋节目准备的咋样了?',
isactive: true,
index: 1,
styles: {
red: true,
thin: true,
active: true
}
},
methods: {
click() {
console.log(this);
}
},
})
</script>
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
# 单向数据绑定
# 语法:
v-bind:href ="xxx" 或简写为 :href ="xxx"
# 特点:数据只能从 data 流向页面
# 双向数据绑定 指令 v-model
# 语法:
v-mode:value="xxx" 或简写为 v-model="xxx"
# 特点:数据不仅能从 data 流向页面,还能从页面流向 data
v-model应用
v-model:用来辅助开发者在不操作 DOM 的前提下,快速获取表单的数据
<p>选择的省份是: {{province}}</p>
<select v-model='province'>
<option value="0">--请选择--</option>
<option value="1">上海</option>
<option value="2">深圳</option>
<option value="3">广州</option>
<option value="4">北京</option>
<option value="5">杭州</option>
</select>

<p>您选中的是:{{sex}}</p>
<input type="radio" name="sex" v-model='sex' value="man">man
<input type="radio" name="sex" v-model='sex' value="woman">woman

<p>您的爱好是: {{loves}}</p>
<input type="checkbox" name="game" v-model=loves value="玩游戏">玩游戏
<input type="checkbox" name="music" v-model=loves value="听音乐">听音乐
<input type="checkbox" name="basketball" v-model=loves value="打篮球">打篮球
<input type="checkbox" name="eat" v-model=loves value="吃东西">吃东西
<input type="checkbox" name="treenp" v-model=loves value="吹牛皮">吹牛皮

.number: 将输入值转化为数字类型
想要严格限制输入框只允许输入数字,请使用属性type="number"
<input type="text" v-model.number='first' @keyup='sum'>+
<input type="text" v-model.number='last' @keyup='sum'> =
<span>{{result}}</span>

.lazy: 当添加了.lazy修饰符后,双向绑定的数据就不同步了,相当于在input输入框失去焦点后触发的change事件中同步
<div>
<input v-model.lazy="msg" @change="show">
<span>{{msg}}</span>
</div>
<script>
export default {
data () {
return {
msg: 老许
}
},
methods: {
show () {
console.log(this.msg)
}
}
}
</script>

.trim 自动去除输入框前后(首尾)空格
如下: 如果直接在开头输入空格,或者是在末尾输入空格,是不会显示有输入内容的
<input type="text" v-model.trim="msg">

v-on指令

1
2
3
4
5
6
7
8
v-on:click='xxx'
v-on:keyup='xxx(参数)'
v-on:keyup.enter='xxx'
功能:绑定指定事件名的回调函数
简洁写法
@click='xxx'
@keyup='xxx'
@keyup.enter='xxx'

事件修饰符
prevent:阻止默认事件(常用);

1
2
<form v-on:submit.prevent="onSubmit"></form>
<a href="https://www.baidu.com" @click.prevent="showInfo">点我</a>

stop:阻止事件冒泡(常用);

1
2
3
4
5
<div class="demo1" @click="showInfo">
<button @click.stop="showInfo">点我</button>
<!-- 修饰符可以连续写 先写的先起作用: 这样写就是先阻止默认行为后阻止冒泡-->
<!-- <a href="https://www.baidu.com" @click.prevent.stop="showInfo">点我</a> -->
</div>

once:事件只触发一次(常用)

1
2
3
<!-- 事件只触发一次(常用) -->
<button @click.once="showInfo">点我</button>

指令语法:条件渲染指令

v-if是控制元素是否加载到页面上(有性能开销)

适用于:切换频率较低的场景。

特点:不展示的DOM元素直接被移除。当条件不成立时, v-if 的所有子节点不会解析

注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被 ‘打断’。

v-show是控制元素的显示与隐藏 (初始创建时加载一次)

切换频率较高的场景。
不展示的DOM元素未被移除,仅仅是使用样式隐藏掉

1
2
3
4
5
6
7
8
9
<p v-if='flag'>海底月是天上月</p>
<p v-else>眼前人是心上人</p>

<p v-if="type === 'A'">优秀</p>
<p v-else-if="type === 'B'">良好</p>
<p v-else-if="type === 'C'">一般</p>
<p v-else="type === 'D'">差</p>

<p v-show='flag'></p>

列表渲染指令

v-for指令:

1、用于展示列表数据

2、语法:v-for=“(item, index) in xxx” :key=“yyy”

3、可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)

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
<!-- 遍历数组 -->
<ul>
<span>key是唯一的标识 其数据类型只能是字符串或者数字</span>
<!-- item代表数组中的每一项 -->
<!-- 如果需要取下标 (item, index) -->
<li v-for="(item,index) of persons" :key="index">
{{item.name}}-{{item.age}}
</li>
</ul>

<!-- 遍历对象 -->
<ul>
<li v-for="(value,key) of car" :key="key">
{{key}}-{{value}}
</li>
</ul>

<!-- 遍历字符串 -->
<ul>
<li v-for="(char,index) of str" :key="index">
{{char}}-{{index}}
</li>
</ul>

<!-- 遍历指定次数 -->
<ul>
<li v-for="(number,index) of 5" :key="index">
{{index}}-{{number}}
</li>
</ul>

key使用注意事项

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
<!-- 
面试题:react、vue中的key有什么作用?(key的内部原理)

1. 虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

2.对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
(2).旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。

3. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
4. 开发中如何选择key?
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。
-->

<div id="app">
<input type="text" v-model='carname'>
<button @click='add'>添加一辆车</button>
<ul v-for='(item, index) in list' :key='item.id'>
<li>{{ item.name }}<input></input></li>
</ul>
</div >
<script>
var vm = new Vue({
el: '#app',
data: {
carname: '',
nextId: 4,
list: [
{id: 001, name: '法拉利' },
{id: 002, name: '兰博基尼' },
{id: 003, name: '布加迪' }]
},
methods: {
add() {
this.list.unshift({
id: this.nextId,
name: this.carname
})
this.nextId += 1
}
}
});
</script>

过滤器

​ 定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。

​ 语法:

​ 1.注册过滤器:Vue.filter (name,callback) 或 new Vue { filters:{} }

​ 2.使用过滤器:

1
'{{ xxx | 过滤器名 }}' 或 v-bind:属性 = "xxx | 过滤器名"

​ 备注:

​ 1.过滤器也可以接收额外参数、多个过滤器也可以串联

​ 2.并没有改变原本的数据, 是产生新的对应的数据

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
< !--准备好一个容器-->
<div id="root">
<h2>显示格式化后的时间</h2>
<!-- 计算属性实现 -->
<h3>现在是:{{ fmtTime }}</h3>
<!-- methods实现 -->
<h3>现在是:{{ getFmtTime() }}</h3>
<!-- 过滤器实现 -->
<h3>现在是:{{ time | timeFormater}}</h3>
<!-- 过滤器实现(传参) -->
<h3>现在是:{{ time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
<h3 :x="msg | mySlice">哈哈哈</h3>
</div >

<div id="root2">
<h2>{{ msg | mySlice}}</h2>
</div>
</body >

<script type="text/javascript">
Vue.config.productionTip = false
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})

new Vue({
el:'#root',
data:{
time:1621561377603, //时间戳
msg:'你好,vue'
},
computed: {
fmtTime(){
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
}
},
methods: {
getFmtTime(){
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
}
},
//局部过滤器
filters:{
timeFormater(value, str = 'YYYY年MM月DD日 HH:mm:ss'){
// console.log('@',value)
return dayjs(value).format(str)
}
}
})

new Vue({
el:'#root2',
data:{
msg:'hello,vue!'
}
})

computed和method

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
<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>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>

<body>
<div id="app">
<input type="text" v-model='firstName'>
<input type="text" v-model='lastName'>
<!-- 插值表达式的实现 简单的js逻辑表达式 -->
<!-- <span>{{firstName +'-'+ lastName}}</span> -->
<!-- <span>{{firstName}} - {{lastName}} </span> -->

<!-- 更复杂的通过方法的实现
methods中的方法调用一次就会执行一次 如果数据不需要更新
会造成性能的浪费-->
<!-- <span>{{fullName()}} </span>
<span>{{fullName()}} </span>
<span>{{fullName()}} </span>
<span>{{fullName()}} </span>
<span>{{fullName()}} </span> -->

<!-- <span>{{fullName}}</span>
<span>{{fullName}}</span>
<span>{{fullName}}</span>
<span>{{fullName}}</span> -->
<span>{{fullName}}</span>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
// 定义到data中的数据默认会被响应式监听
firstName: '张',
lastName: '三'
},
methods: {
// fullName() {
// console.log(111);
// return this.firstName + '-' + this.lastName
// }
},
// 计算属性computed: 计算属性的值本身不存在 是需要通过计算得到的
// 底层也是通过object.defineProperty来实现的
// computed 与 methods的区别
// 1、是否存在缓存: methods调用一次执行一次
// computed第一次调用时会将结果缓存 (有缓存)、后续调用在所依赖的数据不发生变化的前提下 直接返回缓存的结果 => 复用率高 提升性能
// 2、调用方式不同: methods使用时,一般情况需要加括号,而computed则不需要
// 3、绑定方式不同: methods是单向数据绑定 只有getter, computed是双向数据绑定, 有setter和getter

// 使用场景
// 某个属性的值需要来自多个属性的简单计算或者复杂逻辑计算得出的值时, 推荐使用computed属性, 比如购物车的总价计算

computed: {
// 完整写法
fullName: {
// get作用: 当使用到fullName的时候 默认会调用get方法
// get特点:
// 1、初次使用计算属性的时候会执行
// 2、当依赖的响应式数据发生变化的时候会执行
get() {
console.log(111);
return this.firstName + '-' + this.lastName
},
// 如果需要修改计算属性 需要提供一个set方法
// 并且这个set方法中要能够引起依赖的数据的变化
set(val) {
let arr = val.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
},
// 简写
// fullName(){
// return this.firstName + '-' + this.lastName
// }
}
});
</script>
</body>

</html>

watch

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
96
97
98
99
100
<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>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.21.4/dist/axios.min.js"></script>
</head>

<body>
<div id="app">
<input type="text" v-model='name'>
<span>{{message}}</span>
<input type="text" v-model='student.loves.playGame.tencent.name'>
</div>
<script>
// watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作
var vm = new Vue({
el: '#app',
data: {
// _data会收集data中的数据 处理成响应式
// 再将其挂载到vm实例对象下方便开发者使用 => 数据代理
// 响应式: 简单的说就是数据发生变化 页面会重新渲染
// 页面发生变化 数据也会随之变化

name: 'admin',
message: '',
student: {
loves: {
playGame: {
tencent: {
name: '王者荣耀',
price: 99
},
wangyi: {
name: '和平精英',
}
}
}
}
},
methods: {},
watch: {
// 侦听器本质是一个函数 需要侦听谁 就将侦听的属性当作函数名放到watch中即可
// 当被监视的属性发生变化时,回调函数自动调用,执行函数中的代码
// name() {
// if (this.name == '') return
// const { data } = await axios.get('https://www.escook.cn/api/finduser/' + this.name)
// this.message = data.message
// }

// 有配置项的情况 将函数改造成对象的形式
// name: {
// async handler(newVal, oldVal) {
// console.log(111);
// console.log('新值'+ newVal, '老值'+ oldVal);
// if (this.name == '') return
// const { data } = await axios.get('https://www.escook.cn/api/finduser/' + this.name)
// this.message = data.message
// },
// 表示页面初次渲染好之后,就立即触发当前的 watch 侦听器
// 执行handler函数中的内容
// immediate: true,
// },
// 如果层次不深 可以直接通过这种方式侦听
// 'student.name':{}

// 'student.loves.playGame.tencent.name':{
// handler(newVal){
// console.log(11111);
// console.log(newVal);
// }
// }
// vue中的watch默认不监视对象内部的值改变
student: {
handler(newVal) {
console.log(11111);
console.log(newVal);
},
// 开启深度监听
//
deep: true
}
}
});
vm.$watch('student', {
handler(newVal) {
console.log(11111);
console.log(newVal);
},
// 开启深度监听
deep: true
}
})
</script>
</body>

</html>

computed和watch

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

<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>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>

<body>
<div id="app">
<div id="demo">{{ fullName }}</div>
</div>
<script>
var vm = new Vue({
el: '#app',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
// 侦听器的方式
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
},
// 计算属性的方式
computed: {
fullName() {
this.fullName = this.firstName + ' ' + this.lastName
}
}
// computed和watch的区别
// 1、computed是有缓存的 watch没有
// 2、computed一般执行同步操作 异步操作在watch中实现
// 3、computed监听的那个数据的数据发生变化时,不会重新计算,只有依赖的数据发生变化时才会重新调用getter来计算
// watch监听的数据发生变化时 立马执行相应的回调函数重新计算
});
</script>
</body>

</html>

数据代理

什么是数据代理?

通过一个对象代理对另一个对象中属性的操作(读/写)

1
2
3
4
5
6
7
8
9
10
11
12
let obj1 = { x: 100 }
let obj2 = { y: 200 }

Object.defineProperty(obj2, 'x', {
get() {
return obj1.x
},
set(value) {
obj1.x = value
}
})
此时操作obj2实际上在操作obj1

vue中的数据代理

vue里面data的数据代理 通过_data收集data中的数据

利用Object.defineProperty中的get和set将data中的每个数据进行数据代理

再将代理好的数据挂载到vm实例上

好处: 方便开发者操作data中的数据

Vue.set()

注意:受现代 JavaScript 的限制 (以及废弃 Object.observe),Vue 不能检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的

如果在实例创建之后添加新的属性到实例上,它不会触发视图更新

如果想要数据是响应式的并且能触发视图更新 使用Vue.set()

(比如 this.myObject.newProperty = ‘hi’)

自定义指令

除了核心功能默认内置的指令 (v-model 和 v-show),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
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
96
97
98
99
100
自定义指令总结:
一、定义语法:
(1).局部指令:
new Vue({ new Vue({
directives:{指令名:配置对象} 或 directives{指令名:回调函数}
}) })
(2).全局指令:
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)

二、配置对象中常用的3个回调:
(1).bind:指令与元素成功绑定时调用。
(2).inserted:指令所在元素被插入页面时调用。
(3).update:指令所在模板结构被重新解析时调用。

三、备注:
1.指令定义时不加v-,但使用时要加v-;
2.指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。


<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>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>

<body>
<div id="app">
<!-- <input type="text" v-focus> -->

<p>{{ number }}</p>
自定义指令: <p v-two-double.flag="number"></p>
<button @click="number += 1">++</button>
<hr>
<p>{{ age }}</p>
<button @click="age += 1">++</button>

<hr>
自动聚焦: <input type="text" v-focus v-if="flag"><br>
<button @click="del">移除元素</button>
</div>
<script>
// ctrl+shift+k 删除当前行
Vue.config.productionTip = false

// 1、自定义指令什么时候会被执行 ?
// - 指令与页面元素绑定成功时(页面一打开)
// - 自定义指令所在的vue模板被重新解析时

// 函数式写法
Vue.directive('two-double', (el, binding) => {
el.innerText = binding.value * 2
})

// Vue.directive('focus', (el, binding) => {
// // 聚焦失败 原因是此时元素并没有渲染到页面上
// el.focus()
// })

// 对象式写法
Vue.directive('focus', {
// 指令与元素成功绑定时调用
bind(el,binding){
el.style.backgroundColor = 'skyblue'
},
// 指令所在元素被插入页面时调用
inserted(el,binding){
el.focus()
},
// 指令所在模板结构被重新解析时调用
update(el,binding) {
console.log('update')
},
unbind(){
console.log('unbind')
}
})

var vm = new Vue({
el: '#app',
data: {
number: 1,
age: 1,
flag: true
},
methods: {
del(){
this.flag = false
}
},
// 局部自定义指令
// directives: {}
});
</script>
</body>

</html>

vue组件

组件化和模块化的区别

模块化

站在代码逻辑的角度来划分 解决js依赖等问题 方便分层开发 保证每个模块职能单一

组件化

站在UI界面的角度来划分 方便将来UI组件的复用

全局组件

第一种注册方式

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
<!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>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>

<body>
<div id="app">
<!-- 在页面中使用组件 -->
<my-com></my-com>
<my-com></my-com>
<my-com></my-com>
</div>
<script>
// 关闭生产环境下的vue提示
Vue.config.productionTip = false

// 使用组件三步骤
// 1、通过Vue.extend(options)定义组件
// 2、通过Vue.component('组件名', 定义好的组件)注册全局组件
// 3、在页面结构中通过<组件名></组件名>使用组件

// 注意事项
// 1、template有且只能有一个根元素
// 2、不要写el属性 定义的组件都要交给vm管理 由vm中的el来决定服务于哪个容器
// 3、如果自定义组件名使用了驼峰命名法(myCom) 在页面中使用的时候要用-分割组件名并且转为小写(my-com)
// 4、组件名不要使用已有的元素名称,比如:div、span都不行。
// 5、组件中的data必须定义为一个函数 函数中必须返回一个对象 避免组件在复用时 数据直接相互引用导致冲突问题
// 6、不用使用脚手架时,单标签会导致复用组件后续无法渲染的问题
// 7、组件中的this指向的是当前组件的实例对象
// 8、使用name配置项指定组件在开发者工具中呈现的名字

// 第一种注册方式 Vue.extend + Vue.component 结合
// 定义组件
let mycom = Vue.extend({
// el: '#app', 违背设计组件的初心
// template: 将来展示在页面上的结构
// name: 'qwer', 使用name配置项指定组件在开发者工具中呈现的名字
template: `<div>
<h1 @click='show'>{{msg}}</h1>
<h3>{{msg}}</h3>
</div>`,
data() {
return {
msg: 'hello component'
}
},
methods: {
show(){
// this指向的是当前组件的实例对象
console.log(this);
}
},
})
// 注册全局组件
Vue.component('my-com', mycom)

var vm = new Vue({
el: '#app',
data() {
return {}
},
methods: {}
});
</script>
</body>

</html>

第二种注册方式

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
<!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>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>

<body>
<div id="app">
<mycom1></mycom1>
</div>

<!-- .vue文件中也是通过这种方式定义页面结构 -->
<!-- template模板 只会将template中的内容渲染到页面上 不会将template标签渲染到页面上 -->
<template id="mycom1">
<div>
<h1>第三种注册方式</h1>
</div>
</template>

<script>
// 第二种注册方式 => template没有提示
// Vue.component('mycom1', Vue.extend({
// template: '<h1>第二种注册方式</h1>',
// }))

// 简写 Vue.component('组件名', 配置对象) 内部会帮我们调用Vue.extend
// Vue.component('mycom1', {
// template: '<h1>第二种注册方式</h1>',
// })

// 第三种注册方式
Vue.component('mycom1', {
template: '#mycom1'
})

var vm = new Vue({
el: '#app',
data: {},
methods: {}
});
</script>
</body>

</html>

组件data详解

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 name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>

<body>
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script>
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data() {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

// 为什么data必须是一个函数且返回一个对象
// 如果是对象的话多个页面可能会复用这个组件 如果其中一个页面对组件data进行修改的话 其它页面的data也会发生变化 造成数据冲突 (引用问题)
// 而定义成一个函数 每次返回一个新的对象 保证了对象的唯一性 避免了组件数据修改影响其他组件数据
var vm = new Vue({
el: '#app',
data() {
return {}
},
components: {

},
methods: {}
});

// var obj = {x:100, y:100}
// var obj2 = obj
// obj2.x = 1000
// console.log(obj); // {x:1000, y:100}

// ===================================
// function getObj(){
// return {x: 100, y:100}
// }
// var obj = getObj()
// var obj2 = getObj()
// obj2.x = 1000
// console.log(obj); // {x:100, y:100}

</script>
</body>

</html>
局部组件
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
 <!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>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>

<body>
<!-- 容器 -->
<div id="app">
<mycom></mycom>
<mycom2></mycom2>
</div>

<!-- 模板 -->
<template id="tmpl">
<h1>{{msg}}</h1>
</template>

<script>
let mycom = Vue.extend({
template: '#tmpl',
data(){
return {
msg: '局部组件注册'
}
}
})
let mycom2 = Vue.extend({
template: '#tmpl',
data(){
return {
msg: '局部组件注册222'
}
}
})

var vm = new Vue({
el: '#app',
data: {},
methods: {},
components: {
mycom,
mycom2
}
});
</script>
</body>

</html>
嵌套组件
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
96
97
98
99
100
101
 <!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>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>

<body>
<div id="app">
<!-- <app></app> -->
</div>

<template id="bwm">
<h1 @click='show'>{{msg}}</h1>
</template>

<template id="car">
<div>
<h1 @click='show'>{{msg}}</h1>
<bwm></bwm>
</div>
</template>

<template id="app2">
<div>
<h1 @click='show'>{{msg}}</h1>
<car></car>
</div>
</template>

<script>
Vue.config.productionTip = false

// 宝马组件
let bwm = Vue.extend({
template: '#bwm',
data() {
return {
msg: '宝马最新款x7只需要100w'
}
},
methods: {
show() {
console.log('仅仅100w 心动不如行动');
}
},
})

// 车组件
let car = Vue.extend({
template: '#car',
data() {
return {
msg: '男人必须有一辆爱车~'
}
},
methods: {
show() {
console.log('比如说劳斯莱斯');
}
},
components: {
bwm
}
})

// App组件
let App = Vue.extend({
template: '#app2',
data() {
return {
msg: 'app2'
}
},
methods: {
show() {
console.log('app2');
}
},
components: {
car
}
})

var vm = new Vue({
template: '<App></App>',
components: {
App
},
data: {},
methods: {}
}).$mount('#app')

</script>
</body>

</html>

vue 生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
beforeCreate(创建前): 在实例初始化之后,数据代理和事件配置之前被调用,此时组件的选项对象还未创建,el 和 data 并未初始化,因此无法访问methods, data等上的方法和数据,这个钩子一般不会做太多功能,会初始化自己的生命周期,事件方法如:$on $emit

created(创建完成): 此时已经完成了数据响应式监听和数据代理,可以获取数据和调用方法 但是DOM未渲染 在这个周期里面,可以去发送请求, 因为请求是异步的,不会阻碍实例加载,除非是那些同步操走才会导致页面空白。站在这个角度说来,在这个周期里面进行请求,渲染速度反而会更快。

beforeMount(挂载前): 检测有没有template属性 有的话会把template渲染成一个render函数,开始解析模板,生成虚拟dom,没有则将外部的html作为模板进行解析。此时数据虽然初始化完成,DOM未完成挂载,页面还不能显示解析好的内容,数据的双向绑定还是显示{{}}

上下两者中间还有一个创建vm.$el保存真实dom的过程,将来虚拟dom对比发现可复用的元素,就从该属性身上取出来渲染 同学们切记 !!!

mounted(挂载完成): 数据和真实DOM都完成挂载,这个周期适合执行初始化需要操作DOM的方法。至此初始化操作完成,一般在此进行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等初始化操作

beforeUpdate(更新前): 只要是页面数据改变了都会触发,数据更新之前,页面数据还是原来的数据,页面和数据没有同步,当你请求赋值一个数据的时候会执行这个周期,如果没有数据改变不执行。

中间还有个虚拟DOM对比的过程,根据新数据,生成新的虚拟DOM,随后与旧的虚拟DOM进行比较,最终完成页面更新,如果真实DOM可以复用 则从vm.$el中取出对应的真实DOM直接渲染 即:完成了Model => View的更新,同学们切记 !!!

updated(更新完成): 只要是页面数据改变了都会触发,数据更新完毕,页面的数据是更新完成的。beforeUpdate和updated要谨慎使用,因为页面更新数据的时候都会触发,在这里操作数据很影响性能和容易死循环。此时页面和数据保持同步

beforeDestroy(销毁前): 这个周期是在组件销毁之前执行,此时: vm中所有的: data、methods、指令等等,都处于可用状态,马上要执行销毁过程,一般在此阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作

destroyed(销毁完成): 在实例销毁之后调用,调用后,所以的事件监听器会被移出,所有的子实例也会被销毁,自定义事件会被移除

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
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
<!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>
<style>
/* v-enter 【这是一个时间点】 是进入之前,元素的起始状态,此时还没有开始进入 */
/* v-leave-to 【这是一个时间点】 是动画离开之后,离开的终止状态,此时,元素 动画已经结束了 */
.v-enter,
.v-leave-to {
opacity: 0;
transform: translateX(150px);
}

/* v-enter-active 【入场动画的时间段】 */
/* v-leave-active 【离场动画的时间段】 */
.v-enter-active,
.v-leave-active {
transition: all .5s ease
}

.my-enter,
.my-leave-to {
opacity: 0;
transform: translateY(150px);
}

.my-enter-active,
.my-leave-active {
transition: all .5s cubic-bezier(0, 1.95, .89, .38)
}
</style>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>

<body>
<div id="app">
<button @click='flag = !flag'>Toggle</button>
<!-- 使用 transition 元素,把 需要被动画控制的元素,包裹起来 -->
<transition>
<h3 v-if='flag'>有些同学的心已经到家了</h3>
</transition>

<hr>
<!-- 对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>,则 v- 是这些类名的默认前缀。
如果需要自定义类名 可以通过name属性: name='my' .my-enter -->
<transition name='my'>
<h3 v-if='flag'>有些同学的心已经到家了</h3>
</transition>
</div>
<script>
var vm = new Vue({
el: '#app',
data() {
return {
flag: false
}
},
methods: {}
});
</script>
</body>

</html>

vue2响应式原理

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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// vue2.x 双向数据绑定原理是通过Objcet.defineProperty来实现的
// 这种方式有缺点: 数组的长度 数组的内容发生变化检测不到

let obj = {
name: 'zs',
age: 18,
phone: {
name: 'iphone'
}
}
// let obj = [1, 2, 3, 4, 5]
// 视图更新的方法
function render() {
console.log('视图更新了 ~');
}

let methods = ['push', 'pop', 'shift', 'unshift', 'sort', 'reverse', 'splice']
// 将数组的原型保存一份
let arrProto = Array.prototype
// 创建原型 将数组原型拷贝一份
let proto = Object.create(arrProto)
// 重写数组的方法
methods.forEach(method => {
proto[method] = function () { // AOP 面向切面编程
// 改变了数组的数据
arrProto[method].call(this, ...arguments)
// 重新渲染视图
render()
}
})

// 观察者模式
function observe(obj) {
// 判断一个对象是不是数组
// if(Object.prototype.toString.call(obj) === '[object Array]'){
if (Array.isArray(obj)) {
// 让观测的对象的原型和我们自己重写的原型建立关系
obj.__proto__ = proto
return;
}
// 如果观察的是一个对象的话 对其属性进行响应式监听(set、get)
if (Object.prototype.toString.call(obj) === '[object Object]') {
// 取出对象中的每一个键和值
for (let key in obj) {
// 调用响应式处理函数
defineReactive(obj, key, obj[key])
}
}
}
// 观察obj对象
observe(obj)

// 响应式处理
// Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
function defineReactive(obj, key, val) {
// 检测对象的属性值是否还是对象
observe(val)
Object.defineProperty(obj, key, {
// 获取
get() {
return val
},
// 设置
set(newVal) {
// 检测设置的值是不是对象
observe(newVal)
// 新值和旧值不相等的时候
if (newVal !== val) {
// 覆盖掉原来的val值
val = newVal
// 通知render函数重新渲染视图
render()
}
}
})
}

// obj.name = 'lisi'
// obj.age = 20
// console.log(obj.name, obj.age);
// 至此 基本可以实现数据发生变化 视图更新的效果
// 但是 如果数据有多层呢 也就是属性对应的值也是对象?
// obj.phone.name = 'huawei'
// console.log(obj.phone.name);

// 修改的值和原来的值一样 不需要重新渲染视图
// obj.phone.name = 'iphone'

// 如果重新为对象obj的phone赋值一个新的对象呢? 视图要重新渲染
// obj.phone = {name: 'huawei'}

// 并且当修改新的对象的属性值时 视图也要重新渲染
// obj.phone.name = 'zs'

// 为对象新增属性值呢? 也是没有办法监测到的
// obj.sex = 'man'
// $set来解决这个
function $myset(obj, key, val){
if(Array.isArray(obj)){
return obj.splice(key, val)
}
defineReactive(obj, key, val)
}
// $myset(obj, 'sex', 'man')
// 修改新增的属性值 视图也能更新
// obj.sex = 'woman'

// obj.phone = Object.assign(obj.phone, {price: '666'})
// obj.phone.price = 888

// obj.phone = {...obj.phone, ...{price: '666'}}
// obj.phone.price = '888'
// 以后vue涉及到给data中的对象新增属性时 有三种方式
// 1、this.$set() || Vue.set()
// 2、obj = Object.assign(原对象, 新对象(新增的属性:值))
// 2、obj = {...原对象, ...新对象(新增的属性:值)}

// =================== 数组 ======================
// 数组的响应式数据处理依赖的并不是Object.defineProperty 而是对数组的能够引起数据变化的方法进行重写
// obj.push(6)
// obj.length ++
// obj[0] = 88
// $myset(obj, '0', 66)

props (父传子)

父组件要正向地向子组件传递数据或参数,子组件收到后,根据传递过来的数据不同,渲染不同的页面内容,或者执行操作。
这个正向传递数据的过程是通过props来实现的。子组件使用props来声明需要从父组件接受的数据。

注意:
1、所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
2、每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

两种常见的试图变更一个 prop 的情形
1、这个 prop 用来传递一个初始值 这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值
2、这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性

nextTick

作用: 在下次 DOM 更新循环结束之后执行其指定的回调。
应用场景:
1、在修改数据之后,要基于更新后的新dom进行某些操作时,可以立即使用这个方法,获取更新后的 DOM。
2、在created中需要操作dom,此时dom并没有加载完毕

Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。
Vue 是异步执行 DOM 更新的,简单来说,Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。

mixin

mixin
Vue.mixin给我们提供了一种混入Vue实例的方法,创建了混入对象之后,我们自定义的方法或者变量可以很轻松的挂载在Vue实例上,给我们带来方便

Vue.mixin为我们提供了两种混入方式:局部混入和全局混入
局部混入:
顾名思义就是部分混入,也就是只有引入了mixin的混入对象才可以使用,并且只有在引入了mixin混入对象的组件中才生效

全局混入:
全局混入我们只需要把mixin.js引入到main.js中,然后将mixin放入到Vue.mixin()方法中即可;
全局混入更为便捷,我们将不用在子组件声明,全局混入将会影响每一个组件的实例,使用的时候需要小心谨慎;这样全局混入之后,我们可以直接在组件中通过this.变量/方法来调用mixin混入对象的变量/方法;

mixin可以定义公用的变量或方法,但是mixin中的数据是不共享的,也就是每个组件中的mixin实例都是不一样的,都是单独存在的个体,不存在相互影响的;

mixin合并策略

1、data
mixins中的data会合并到data中,有冲突的话,data中数据覆盖mixins中的数据。

2、methods、components 和 directives
methods、components 和 directives会执行,但有冲突时,组件中的methods、components 和 directives会覆盖 mixins 中的methods、components 和 directives。

3、特殊的钩子函数(生命周期)

组件和mixin的生命周期都要执行,但是mixin的生命周期优先于组件生命周期执行
mixin beforeCreate -> component beforeCreate -> mixin created -> component created

动态组件

什么是动态组件?

动态组件指的是动态切换组件的显示与隐藏

如何实现?

vue提供了一个内置的组件,专门用来实现动态组件的渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<button @click="comName='MyLeft'">显示Left组件</button>
<button @click="comName='MyRight'">显示Right组件</button>
<component :is="comName"></component>

<script>
import MyLeft from '@/components/Left.vue'
import MyRight from '@/components/Right.vue'
export default {
// 组件的交互
name: 'App',
data() {
return {
comName: 'MyLeft',
}
},
components: {
MyLeft,
MyRight,
},
}
</script>

keep-alive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
export default {
name: 'left',
data() {
return {
count: 0,
};
},
created() {
console.log("left组件被创建了");
},
destroyed() {
console.log("left组件被销毁了");
},
deactivated() {
console.log("组件被缓存了");
},
activated() {
console.log("组件被激活了");
},
};
</script>

devServer代理跨域

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
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
const path = require("path");
function resolve(dir) {
return path.join(__dirname, dir);
}
module.exports = {
chainWebpack: config => {
config.resolve.alias
.set("@", resolve("src"))
.set("assets", resolve("src/assets"))
.set("components", resolve("src/components"))
.set("base", resolve("baseConfig"))
.set("public", resolve("public"));
},
pages: {
index: {
// page 的入口
entry: 'src/index.js',
}
},
// 关闭eslint语法校验
lintOnSave: false,
devServer: {
// 代理的方式配置跨域
// 缺点:
// 1、会先从本地public下查找有没有对应接口的文件 如果有 先返回本地的文件数据 没有再去发起请求访问服务器的数据 (优先匹配前端资源)
// 2、没法配置多个代理
// proxy: 'http://localhost:3000/',
proxy: {
// 正则匹配所有以'/api'开头的请求路径
'/api': {
// http://localhost:8080/api/teachers
// http://localhost:3000/teachers

// target: 代理目标的基础路径
// http://localhost:8080/teachers
target: 'http://localhost:3000/',
// 路径重写
pathRewrite: {
'^/api': ''
},
// ws:websocket
// ws: true,
// 改变源, 也就是控制host
// changeOrigin: true
},
'/v1': {
// target: 代理目标的基础路径
target: 'http://localhost:3001',
// 路径重写
pathRewrite: {
'^/v1': ''
}
},
}
}
}

路由

vue-router

vue 的一个插件库,是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目 中组件的切换。

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
基本使用
安装vue-router,命令:npm i vue-router@3
引入插件: import VueRouter form 'vue-router'
应用插件:Vue.use(VueRouter)
编写router配置项:
//引入VueRouter
import VueRouter from 'vue-router'
//引入Vue
import Vue from 'vue'
//使用vueRouter
Vue.use(VueRouter)
//引入路由组件
import About from '../components/About'
import Home from '../components/Home'

//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})

//暴露router
export default router
//main.js中引入挂载router

实现切换(active-class可配置高亮样式)

1
<router-link active-class="active" to="/about">About</router-link>

指定展示位置

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

路由注意点

路由组件通常存放在pages或者view文件夹,一般组件(可复用)通常存放在components文件夹。

通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载(keep-alive)。

每个组件都有自己的$route属性,里面存储着自己的路由信息。

整个应用只有一个router,可以通过组件的$router属性获取到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
router : 是路由操作对象,只写对象
route : 路由信息对象,只读对象

// 操作 路由跳转
this.$router.push({
name:'hello',
params:{
name:'word',
age:'11'
}
})

// 读取 路由参数接收
this.name = this.route.params.name;
this.age = this.route.params.age;

嵌套路由(多级路由)

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
routes:[
{
path:'/about',
component:About,
},
{
path:'/home',
component:Home,
children:[ //通过children配置子级路由
{
path:'news', //此处一定不要写:/news
component:News
},
{
path:'message',//此处一定不要写:/message
component:Message
},
{
path: 'user',
// 路由懒加载写法
component:()=> import(/*webpackChunkName*:'user'/, User)
}
]
}
]

路由懒加载

懒加载简单来说就是延迟加载或按需加载,就是用到的时候再进行加载。

作用: 首屏组件加载速度更快一些,解决白屏问题;更好的客户体验;也是性能优化的一种方式;

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
const routes = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'home',
// 首屏没有做路由懒加载
component: HomeView
},
{
path: '/center',
name: 'center',
// 路由懒加载: 懒加载简单来说就是延迟加载或按需加载,就是用到的时候再进行加载。
// 作用: 首屏组件加载速度更快一些,解决白屏问题;更好的客户体验;
// 也是性能优化的一种方式;
component: () => import(/* webpackChunkName: "center" */ '../views/Center.vue')
},
{
path: '/search',
component: ()=> import('../views/Search.vue')
}
]
const router = new VueRouter({
routes
})

路由的query参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 跳转并携带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>
接收参数

$route.query.id
$route.query.title

路由的params参数

动态路由指的是:把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。 在 vue-router 中使用英文的冒号(:)来定义路由的参数项

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
component:Message,
children:[
{
name:'xiangqing',
path:'detail/:id/:title', //使用占位符声明接收params参数
component:Detail
}
]
}
]
}

传递参数

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

<!-- 跳转并携带params参数,to的对象写法 -->
<router-link
:to="{
name:'xiangqing',
params:{
id:666,
title:'你好'
}
}"
>跳转</router-link>

接收参数

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

路由守卫

用:对路由进行权限控制

分类:全局守卫、独享守卫、组件内守卫

全局守卫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 //全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
console.log('beforeEach',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('username') === 'admin'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next() //放行
}
})

//全局后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
console.log('afterEach',to,from)
if(to.meta.title){
document.title = to.meta.title //修改网页的title
}else{
document.title = 'vue_test'
}
})

独享守卫:

1
2
3
4
5
6
7
8
9
10
11
12
13
 beforeEnter(to,from,next){
console.log('beforeEnter',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('username') === 'admin'){
next()
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next()
}
}

组件内守卫:

1
2
3
4
5
6
 //进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}

路由器的两种工作模式

对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
hash模式:
地址中永远带着#号,不美观 。
若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
兼容性较好。
history模式:
地址干净,美观 。
兼容性和hash模式相比略差。底层是h5 api history对象
应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。可以安装connect-history-api-fallback插件解决

vuex

什么是vuex?
vue中实现集中式状态(数据)管理的vue插件(vue.use(vuex)),对vue应用中多个组件的共享状态进行集中式管理(读/写),是一种任意组件之间通信的方式,适用于较大型项目

使用场景?
1、多个视图依赖于同一状态

2、来自不同视图的行为需要变更同一状态
alt

Vuex的工作流程

一、安装vuex npm i vuex@3

二、引入Vue,引入VueX,Vue.use(VueX)

三、在项目创建store文件夹,及index.js
定义相关的vuex的state,getters,actions,mutations,

并创建vuex实例:const sotre = new Vue.Store({state,getters,actions,mutations})

四、在入口文件main.js中引入store,并在根实例注册挂载

五、在任意组件中,通过this.$store.state就可以访问到共享数据

六、当通过组件要修改store中的数据时,

1
2
3
1. 如果该操作是同步的并且不需要共用,可以用this.$store.commit()来触发mutations, 只有mutations才可以直接更改state共享数据
2. mutations中只能有同步操作,不能有异步操作
3. 如果修改数据的操作是异步的,通过this.$store.dispatch()触发actions, actions中可以发起异步请求,获取数据后,再调用 commit触发mutations,通过mutations修改共享数据

辅助函数

mapState
当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键

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
<template>
<div>
<p>
商品名称: {{goodName}}
</p>
<p>
商品价格: {{goodPrice}}
</p>
<p>
商品描述: {{goodDesc}}
</p>
<p>
商品最新描述: {{goodNewDesc}}
</p>
</div>
</template>

<script>
// 引入辅助函数
// mapState: 映射状态
import { mapState, mapGetters } from 'vuex'
export default {
name: 'counter',
data() {
return {
n: ''
}
},
methods: {},
computed: {
// 封装思路
// function mapState(obj){
// 需要取出对象的键值对
// for(let key in obj){
// return function [key](){
// return this.$store.state[obj[key]]
// }
// }
// }
// mapState({name: 'goodName', price: 'goodPrice'})

// 手写计算属性 + state
// name() {
// return this.$store.state.goodName
// },
// price() {
// return this.$store.state.goodPrice
// },
// desc() {
// return this.$store.state.goodDesc
// }

// 生成计算属性 => 对象的写法
// 传入的对象键为: 生成的计算属性名字
// 传入的对象值为: 需要访问state中的哪个属性
// ...mapState({ name: 'goodName', price: 'goodPrice', desc: 'goodDesc' }),

// goodName() {
// return this.$store.state.goodName
// },
// goodPrice() {
// return this.$store.state.goodPrice
// },
// goodDesc() {
// return this.$store.state.goodDesc
// },
// 当计算属性的名字和需要访问state的属性名字一样时 可以使用数组的写法
...mapState(['goodName', 'goodPrice', 'goodDesc'])
},
mounted() {
// 返回值是一个对象 {name:function}
let res = mapState({ name: 'goodName' })
console.log(res)
}
}
</script>

mapGetters

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
<template>
<div>
<p>
商品名称: {{goodName}}
</p>
<p>
商品价格: {{goodPrice}}
</p>
<p>
商品描述: {{goodDesc}}
</p>
<p>
商品最新描述: {{goodNewDesc}}
</p>
</div>
</template>

<script>
// 引入辅助函数
// mapState: 映射状态
import { mapState, mapGetters } from 'vuex'
export default {
name: 'counter',
data() {
return {
n: ''
}
},
methods: {},
computed: {
// 手写的计算属性 + getter
// newDesc(){
// return this.$store.getters.goodNewDesc
// },
// newDesc(){
// return this.$store.getters.goodNewDesc
// },
// newDesc(){
// return this.$store.getters.goodNewDesc
// },

// 利用辅助函数生成
// 对象写法
// ...mapGetters({newDesc: 'goodNewDesc'})
// 数组写法
...mapGetters(['goodNewDesc'])
},
mounted() {
// 返回值是一个对象 {name:function}
let res = mapGetters({ newDesc: 'goodNewDesc' })
console.log(res)
}
}
</script>

mapMutations

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
<template>
<div>
<p>
商品名称: {{goodName}}
</p>
<p>
商品价格: {{goodPrice}}
</p>
<p>
商品描述: {{goodDesc}}
</p>
<p>
商品最新描述: {{goodNewDesc}}
</p>
<div>
<input type="text" placeholder="请输入要涨价的金额" v-model.number="money">
<button @click='increase(money)'>涨价</button>
<button @click='increaseDouble(money)'>翻倍的涨价</button>
</div>
</div>
</template>

<script>
// 引入辅助函数
// mapState: 映射状态
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
name: 'counter',
data() {
return {
money: ''
}
},
methods: {
// increase(){
// this.$store.commit('INCREASE', this.money)
// },
// 没有手动传参会将$event事件参数对象当作第二个参数传过去
// increase(e){
// this.$store.commit('INCREASE', e)
// },
// 手动传参就使用我们的参数当作第二个参数传过去
// increase(money){
// this.$store.commit('INCREASE', money)
// },
// 对象的写法
// ...mapMutations({ increase: 'INCREASE' })
// 数组的写法
...mapMutations(['increase']),
},
computed: {
...mapState(['goodName', 'goodPrice', 'goodDesc']),
...mapGetters(['goodNewDesc'])
},
mounted() {
// 返回值是一个对象 {name:function}
let res = mapState({ name: 'goodName' })
console.log(res)
}
}
</script>

mapActions

1
2
3
4
5
6
// increaseDouble(){
// // 逻辑稍微复杂且需要共用 dispatch
// this.$store.dispatch('increaseDouble', this.money)
// }
// ...mapActions({increaseDouble: 'increaseDouble'})
...mapActions(['increaseDouble'])

持久化

在VUE项目中,由于是单页应用,vuex中的数据在页面刷新时就会被清除,所以我们要考虑怎样让vuex中的数据持久保存在浏览器中,至少不能每次刷新时都丢失登录状态,这篇文章介绍VUE项目中常用到的两种vuex持久化的方法,底层实现原理一直,方法不太一样,可以在项目中根据实际来区分
方法一、浏览器监听+本地存储
我们可以监听浏览器的刷新,在页面刷新时将vuex内的数据保存在本地存储中(根据项目需要可以考虑保存在sessionStorage或者localStorage中)
在App.vue中,created生命周期写我们的监听方法

1
2
3
window.addEventListener("beforeunload", () => {
sessionStorage.setItem("store", JSON.stringify(this.$store.state))
})

然后在进入到created生命周期时,去到sessionStorage/localStorage中的数据,将数据替换

到vuex中

1
2
3
if(sessionStorage.getItem("store")) {
this.$store.replaceState(Object.assign({}, this.$store.state, JSON.parse(sessionStorage.getItem('store'))))
}

方法二、状态持久化插件
推荐使用 vuex-persistedstate 插件
使用方法如下
1、在项目中安装该插件 cnpm i –save vuex-persistedstate
2、在 src/store/index.js中引入该插件 import createPersistedState from ‘vuex-persistedstate’
在vuex中使用 plugins: [createPersistedState()]
整体代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)

export default new Vuex.Store({
plugins: [createPersistedState({
storage: window.sessionStorage, // 默认位置是 localStorage
reducer: (state) => {
return {
// 默认是全部缓存,在这里可以设置需要缓存的状态
...state
}
}
})],
state: {
token: ''
},
mutations: {
},
}

觉得写的不错 就可怜可怜博主吧.

其他文章
cover
其他知识
  • 21/09/28
  • 12:23
  • 5.5k
  • 26