【Vue】Vue

Vue 简介

Vue.js 是一套用于构建用户界面的渐进式框架(基于 JavaScript,本质上是一个 JS 框架)。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。Vue 是视图层框架,遵守 Soc(关注度分离原则)

Vue 采用 MVVM 思想:

  • M:model 包括数据和一些基本操作
  • V:view 视图,页面渲染结果
  • VM:View-model,模型与视图间的双向操作(无需开发人员干涉)

视图 View 和数据 Model 通过 VM 绑定起来,Model 里有变化会自动地通过 Directives 填写到视图 View 中,视图表单中添加了内容也会自动地通过 DOM Listeners 保存到模型中。Vue 使用虚拟 DOM 技术更新 View 中的数据,而不是更新真实 DOM。

即:视图中的数据变化会自动同步到模型中的变量上;模型中变量值的改变也会自动同步到视图上

image-20220205173020413

View 视图写完后就不需要再修改了,只需要发出请求访问后端获取数据修改 Model 中的数据即可动态更新视图,实现与 View 与 Model 的解耦合。

Vue 本身只关注于视图层,不包含异步通信、页面跳转等功能,这些功能都需要使用其他框架或组件来实现,例如借助于 Axios 实现异步通信,借助于 Vue Router 实现页面跳转。

Vue.js 就是一个 MVVM 的实现者,他的核心就是实现了 DOM 监听数据绑定

Element

Element 是饿了么开发的基于 Vue 的桌面端组件库(提供了许多 Vue 组件),其提供了许多的 Vue 组件,可以直接使用。

Axios

Axios 是一个开源的可以用在浏览器端和 Node.js 的异步通信框架,其主要作用就是实现 Ajax 请求。

由于 Vue.js 是一个视图层框架并且作者(尤玉溪)严格遵守 Soc(关注度分离原则),所以 Vue.js 并不包含 Ajax 的通信功能,为了解决通信问题,推荐使用 Axios 框架,少用 jQuery,因为它需要频繁操控 Dom。

基础命令

导入 vue.js

1
<script src="../node_modules/vue/dist/vue.js"></script>

每一个 html 组件元素都可以绑定一个 Vue 对象,实现双向绑定。例如下面例子中 vm 对象绑定元素 div id="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
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>
</head>

<body>
<div id="app">
<input type="text" v-model="num">
v-model实现双向绑定
<button v-on:click="num++">点赞</button>
v-on:click绑定事件,实现自增
<button v-on:click="cancel">取消</button>
回到自定义的方法
<h1> {{name}} ,非常帅,有{{num}}个人为他点赞{{hello()}}</h1>
</div>

<!-- 导入依赖 -->
<script src="./node_modules/vue/dist/vue.js"></script>

<script>
// 1、vue声明式渲染
let vm = new Vue({ // 生成vue对象
el: "#app", // 绑定元素 div id="app"
data: { // 封装数据
name: "张三", // 也可以使用{} //表单中可以取出
num: 1
},
methods:{ // 封装方法
cancel(){
this.num -- ;
},
hello(){
return "1"
}
}
});
// 还可以在html控制台vm.name

// 2、双向绑定,模型变化,视图变化。反之亦然。
// 3、事件处理
// 创建vue实例,关联页面的模板,将自己的数据(data)渲染到关联的模板,响应式的指令来简化对dom的一些操作。
// 声明方法来做更复杂的操作。methods里面可以封装方法。
</script>
</body>
</html>

v-text、v-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
<!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>
</head>
<body>
<div id="app">
{{msg}} {{1+1}} {{hello()}}<br/>
用v-html取内容
<span v-html="msg"></span>
<br/>
原样显示
<span v-text="msg"></span>
</div>

<script src="../node_modules/vue/dist/vue.js"></script>

<script>
new Vue({
el:"#app",
data:{
msg:"<h1>Hello</h1>",
link:"http://www.baidu.com"
},
methods:{
hello(){
return "World"
}
}
})
</script>
</body>
</html>

单向绑定 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
<!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>
</head>
<body>
<!-- 给html标签的属性绑定 -->
<div id="app">
<a v-bind:href="link">gogogo</a>
<!-- class,style {class名:加上?}-->
<span v-bind:class="{active:isActive,'text-danger':hasError}"
:style="{color: color1,fontSize: size}">你好</span>
</div>

<script src="../node_modules/vue/dist/vue.js"></script>

<script>
let vm = new Vue({
el:"#app",
data:{
link: "http://www.baidu.com",
isActive:true,
hasError:true,
color1:'red',
size:'36px'
}
})
</script>
</body>
</html>

双向绑定 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
<!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>
</head>
<body>
<!-- 表单项,自定义组件 -->
<div id="app">
精通的语言:
<input type="checkbox" v-model="language" value="Java"> java<br/>
<input type="checkbox" v-model="language" value="PHP"> PHP<br/>
<input type="checkbox" v-model="language" value="Python"> Python<br/>
选中了 {{language.join(",")}}
</div>

<script src="../node_modules/vue/dist/vue.js"></script>

<script>
let vm = new Vue({
el:"#app",
data:{
language: []
}
})
</script>
</body>
</html>

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
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 name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<!--事件中直接写js片段-->
<button v-on:click="num++">点赞</button>
<!--事件指定一个回调函数,必须是Vue实例中定义的函数-->
<button @click="cancel">取消</button>
<!-- -->
<h1>有{{num}}个赞</h1>
<!-- 事件修饰符 -->
<div style="border: 1px solid red;padding: 20px;" v-on:click.once="hello">
大div
<div style="border: 1px solid blue;padding: 20px;" @click.stop="hello">
小div <br />
<a href="http://www.baidu.com" @click.prevent.stop="hello">去百度</a>
</div>
</div>

<!-- 按键修饰符: -->
<input type="text" v-model="num" v-on:keyup.up="num+=2" @keyup.down="num-=2" @click.ctrl="num=10"><br />
提示:
</div>

<script src="../node_modules/vue/dist/vue.js"></script>

<script>
new Vue({
el:"#app",
data:{
num: 1
},
methods:{
cancel(){
this.num--;
},
hello(){
alert("点击了")
}
}
})
</script>
</body>
</html>

v-for

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
<!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>
</head>
<body>
<div id="app">
<ul>
<li v-for="(user,index) in users" :key="user.name" v-if="user.gender == '女'">
<!-- 1、显示user信息:v-for="item in items" -->
当前索引:{{index}} ==> {{user.name}} ==> {{user.gender}} ==>{{user.age}} <br>
<!-- 2、获取数组下标:v-for="(item,index) in items" -->
<!-- 3、遍历对象:
v-for="value in object"
v-for="(value,key) in object"
v-for="(value,key,index) in object"
-->
对象信息:
<span v-for="(v,k,i) in user">{{k}}=={{v}}=={{i}};</span>
<!-- 4、遍历的时候都加上:key来区分不同数据,提高vue渲染效率 -->
</li>
</ul>
<ul>
<li v-for="(num,index) in nums" :key="index"></li>
</ul>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>
<script>
let app = new Vue({
el: "#app",
data: {
users: [{ name: '柳岩', gender: '女', age: 21 },
{ name: '张三', gender: '男', age: 18 },
{ name: '范冰冰', gender: '女', age: 24 },
{ name: '刘亦菲', gender: '女', age: 18 },
{ name: '古力娜扎', gender: '女', age: 25 }],
nums: [1,2,3,4,4]
},
})
</script>
</body>
</html>

v-if 和 v-show

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
<!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>
</head>

<body>
<!--
v-if,顾名思义,条件判断。当得到结果为true时,所在的元素才会被渲染。
v-show,当得到结果为true时,所在的元素才会被显示。
-->
<div id="app">
<button v-on:click="show = !show">点我呀</button>
<!-- 1、使用v-if显示 -->
<h1 v-if="show">if=看到我....</h1>
<!-- 2、使用v-show显示 -->
<h1 v-show="show">show=看到我</h1>
</div>

<script src="../node_modules/vue/dist/vue.js"></script>

<script>
let app = new Vue({
el: "#app",
data: {
show: true
}
})
</script>
</body>
</html>

v-else 和 v-else-if

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
<!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>
</head>

<body>
<div id="app">
<button v-on:click="random=Math.random()">点我呀</button>
<span>{{random}}</span>

<h1 v-if="random>=0.75">
看到我啦?! &gt;= 0.75
</h1>

<h1 v-else-if="random>=0.5">
看到我啦?! &gt;= 0.5
</h1>

<h1 v-else-if="random>=0.2">
看到我啦?! &gt;= 0.2
</h1>

<h1 v-else>
看到我啦?! &lt; 0.2
</h1>
</div>

<script src="../node_modules/vue/dist/vue.js"></script>

<script>
let app = new Vue({
el: "#app",
data: { random: 1 }
})
</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
49
50
51
52
<!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>
</head>

<body>
<div id="app">
<!-- 某些结果是基于之前数据实时计算出来的,我们可以利用计算属性来完成 -->
<ul>
<li>西游记; 价格:{{xyjPrice}},数量:<input type="number" v-model="xyjNum"> </li>
<li>水浒传; 价格:{{shzPrice}},数量:<input type="number" v-model="shzNum"> </li>
<li>总价:{{totalPrice}}</li>
{{msg}}
</ul>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>

<script>
// watch可以让我们监控一个值的变化。从而做出相应的反应。
new Vue({
el: "#app",
data: {
xyjPrice: 99.98,
shzPrice: 98.00,
xyjNum: 1,
shzNum: 1,
msg: ""
},
computed: {
totalPrice(){
return this.xyjPrice*this.xyjNum + this.shzPrice*this.shzNum
}
},
watch: {
xyjNum(newVal,oldVal){
if(newVal>=3){
this.msg = "库存超出限制";
this.xyjNum = 3
}else{
this.msg = "";
}
}
},
})
</script>
</body>
</html>

过滤器

过滤器常用来处理文本格式化的操作。过滤器可以用在两个地方:双花括号插值和 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
<!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>
</head>

<body>
<!-- 过滤器常用来处理文本格式化的操作。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 -->
<div id="app">
<ul>
<li v-for="user in userList">
{{user.id}} ==> {{user.name}} ==> {{user.gender == 1?"男":"女"}} ==>
{{user.gender | genderFilter}} ==> {{user.gender | gFilter}}
</li>
</ul>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>

<script>
Vue.filter("gFilter", function (val) {
if (val == 1) {
return "男~~~";
} else {
return "女~~~";
}
})

let vm = new Vue({
el: "#app",
data: {
userList: [
{ id: 1, name: 'jacky', gender: 1 },
{ id: 2, name: 'peter', gender: 0 }
]
},
filters: {
filters 定义局部过滤器,只可以在当前vue实例中使用
genderFilter(val) {
if (val == 1) {
return "男";
} else {
return "女";
})
</script>
</body>
</html>

组件化

在大型应用开发的时候,页面可以划分成很多部分。往往不同的页面,也会有相同的部分。例如可能会有相同的头部导航。但是如果每个页面都自开发,这无疑增加了我们开发的成本。所以我们会把页面的不同分拆分成立的组件,然后在不同页面就可以共享这些组件,避免重复开发。

在 Vue 里,所有的 Vue 实例都是组件。组件其实也是一个 Vue 实例,因此它在定义时也会接收:data、methods、生命周期函等。

组件是可复用的 Vue 实例,说白了就是一组可重复使用的模板

和之前介绍的写法不同的是:组件不会与页面的元素绑定,否则就无法复用了,因此没有 el 属性。但是组件渲染需要 HTML 模板,所以增加了template属性,值就是 HTML 模板。全局组件定义完毕,任何 Vue实例都可以直接在 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
<!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>
</head>

<body>
<div id="app">
<button v-on:click="count++">我被点击了 {{count}} 次</button>
<!-- 使用自定义的组件 counter -->
<counter></counter>
<counter></counter>
<counter></counter>
<counter></counter>
<counter></counter>
<button-counter></button-counter>
</div>
<script src="../node_modules/vue/dist/vue.js"></script>

<script>
// 1、全局声明注册一个组件 counter
Vue.component("counter", {
template: `<button v-on:click="count++">我被点击了 {{count}}</button>`,
data() {
return {
count: 1
}
}
});

// 2、局部声明一个组件
const buttonCounter = {
template: `<button v-on:click="count++">我被点击了 {{count}} 次~~~</button>`,
data() {
return {
count: 1
}
}
};

// 非组件化对象,绑定一个html元素 #app,指定使用上面你定义的组件 buttonCounter
new Vue({
el: "#app",
data: {
count: 1
},
components: {
'button-counter': buttonCounter
}
})
</script>
</body>
</html>

实际项目中,通常将每个 Vue 组件单独提取成一个 xxx.vue 文件,其就代表一个 Vue 组件,里面三部分:

  • <template>:组件的 HTML 模板。显示在 HTML 页面上的内容,每个 DOM 都可以使用上文介绍的基础命令(例如 v-model)绑定 Vue 对象中的某个 data 数据
  • <script>:定义 Vue 组件,包括 components(可以导入其他 Vue 组件,例如将上传文件的提示框抽取出一个 Vue 组件,这样就可以在多个页面复用该组件)、data(与 DOM 双向绑定的数据)、methods(方法集合)、computed(计算属性)、watch(监听器)与生命周期函数。同时使用 export 导出当前组件给其他组件使用
  • <style>:样式,修改当前 Vue 组件的样式

示例一:

image-20220203203549811

示例二:

image-20220203204557599

其中的 <el-form> 标签是 Element 提供的现成 Vue 组件,可以直接使用,本质上也是 Vue 组件

生命周期钩子函数

  • beforeCreate:Vue 实例初始化之前调用,此时还没创建出 Vue 对象,无法访问当前 this 实例
  • created:Vue 实例初始化之后调用,此时 Vue 实例初始化完成,可以访问当前 this 实例。data 中数据赋初值,但是还没绑定 DOM,页面无法渲染出真实数据(例如只展示模板 {{message}})。常用于在该函数里添加一些初始化逻辑,例如在这里发出请求访问数据库获取商品三级分类数据并保存到 data 数据中。
  • beforeMount:挂载到 DOM 树之前调用。此时页面还没完成渲染,看到的只是模板
  • mounted:挂载到 DOM 树之后调用,此时才可以访问 DOM 元素,此时 HTML 页面才渲染完成(之前看到的还不是要展示的数据,只是模板,例如展示 {{message}}。在挂载完成后才会替换成真实数据)
  • beforeUpdate:数据更新之前调用,Vue 里的模型数据更新了,但是页面还没及时更新
  • updated:数据更新之后调用,Vue 里的模型数据更新了,页面也更新了
  • beforeDestroy: Vue 实例销毁之前调用
  • destroyed:Vue 实例销毁之后调用

示意图:

1019981-20200602161556262-1528029320

简略版:

img

代码:

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
<!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>
</head>
<body>
<div id="app">
<span id="num">{{num}}</span>
<button @click="num++">赞!</button>
<h2>{{name}},有{{num}}个人点赞</h2>
</div>

<script src="../node_modules/vue/dist/vue.js"></script>

<script>
let app = new Vue({
el: "#app",
data: {
name: "张三",
num: 100
},
methods: {
show() {
return this.name;
},
add() {
this.num++;
}
},
// 生命周期 - 创建之前
beforeCreate() {
console.log("=========beforeCreate=============");
console.log("数据模型未加载:" + this.name, this.num);
console.log("方法未加载:" + this.show());
console.log("html模板未加载:" + document.getElementById("num"));
},
// 生命周期 - 创建完成(可以访问当前this实例)
created: function () {
// 创建组件就向后端发送数据,获取三级菜单
this.getMenus();
console.log("=========created=============");
console.log("数据模型已加载:" + this.name, this.num);
console.log("方法已加载:" + this.show());
console.log("html模板已加载:" + document.getElementById("num"));
console.log("html模板未渲染:" + document.getElementById("num").innerText);
},
// 生命周期 - 挂载之前
beforeMount() {
console.log("=========beforeMount=============");
console.log("html模板未渲染:" + document.getElementById("num").innerText);
},
// 挂载完成(可以访问DOM元素)
mounted() {
console.log("=========mounted=============");
console.log("html模板已渲染:" + document.getElementById("num").innerText);
},
// 生命周期 - 更新之前
beforeUpdate() {
console.log("=========beforeUpdate=============");
console.log("数据模型已更新:" + this.num);
console.log("html模板未更新:" + document.getElementById("num").innerText);
},
// 生命周期 - 更新之后
updated() {
console.log("=========updated=============");
console.log("数据模型已更新:" + this.num);
console.log("html模板已更新:" + document.getElementById("num").innerText);
},
beforeDestroy() {}, // 生命周期 - 销毁之前
destroyed() {}, // 生命周期 - 销毁完成
activated() {} // 如果页面有 keep-alive 缓存功能,这个函数会触发
});
</script>
</body>
</html>

Vue Router

Vue 本身只关注于视图层,不包含页面跳转功能。Vue Router 是 Vue.js 官方的路由管理器。作用:实现前端页面的路径跳转,不需要借助于后端的 Controller 也能实现路径跳转。

路由配置示例:

image-20220205185951187

  • <route-link> 指定路由到哪个组件
  • <route-view>:组件展示在哪个页面什么位置

案例

具体介绍见文章 【Project】云商城

Vue 工程目录结构(npm 管理):

image-20220101132840890