Vue全套教程

 

1、初识Vuejs
1.1、为什么学习Vuejs?
可能你的公司正要用Vue将原项目重构
可能你的公司新项目决定使用Vue技术栈
可能你正在找工作,会发现十个前端八个对Vue有或多或少的要求
当然,最重要的是Vue非常火,很流行
1.2、简单认识Vuejs
Vue(读音/vju:/ ,类似于view)
Vue是一个渐进式框架
渐进式意味着你可以将Vue作为你应用的一部分嵌入其中,带来更丰富的交互体验
或者如果你希望更多的业务逻辑使用Vue实现,那么Vue的核心库及其生态系统比如Core + Vue-router + Vuex,也可以满足你各种各样的需求;
Vue有很多特点和Web开发中常见的高级功能
解耦视图和数据
可服用的组件
前端路由技术
状态管理
虚拟DOM
1.3、Vuejs的安装
CDN引入
开发环境(包好了有帮助的命令行警告)

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
1
生产环境,优化了尺寸和速度

<script src="https://cdn.jsdelivr.net/npm/vue"></script>
1
下载引入
开发环境:https://vuejs.org/js/vue.js
生产环境:https://vuejs.org/js/vue.min.js

npm安装
npm install vue
1
1.4、Vuejs初体验
之前范式:命令式编程
如今范式:声明式编程

Hello Vuejs
<body>
<div id="app">
{{message}}
</div>
</body>

<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
message: 'hello, vue'
}
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


Vue列表展示
<body>
<div id="app">
<ul>
<li v-for="item in movies">{{item}}</li>
</ul>
</div>
</body>

<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
movies: ['星际穿越', '大话西游', '少年派', '盗梦空间']
}
});
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


案例:计数器(v-on事件监听)
<body>
<div id="app">
<h2>当前计数:{{counter}}</h2>
<!--@可替代v-on:--><!--方法若无参数,()可省略,若存在参数,vue会默认第一个为event事件对象, 以$event形式传入event-->
<button v-on:click="increament()">+</button>
<button v-on:click="subtraction($event)">-</button>
</div>
</body>

<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
counter: 0
},
methods: {
increament: function (){
this.counter++;
},
subtraction: function (){
this.counter--;
}
}
});
</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


2、MVVM
2.1、MVVM
View层
视图层
在前端开发中,通常指DOM
主要用来给用户展示信息
Model层
数据层
可能是固定的死数据,更多是来自服务器、网络上的数据
ViewModel
视图模型层
View与Model层沟通的桥梁
实现了Data Binding(数据绑定),将Model的改变实时反映到View中
实现了DOM Listener(DOM监听),当监听到DOM发生某种事件时,改变对应的Data
2.2、Vue中的MVVM


2.3、Vue的生命周期

 

3、插值操作
3.1、Mustache语法:{{}}
<div id="app">
<!--Mustache语法中不仅可以显示变量,还能写一些表达式-->
<h2>{{message}}, {{firstName + ' ' + lastName}}</h2>
<h2>{{counter* 2}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
message: 'Hello',
firstName: 'kobe',
lastName: 'bryant',
counter: 100
}
});
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


3.2、v-once
被v-once修饰的变量,一旦赋值无法更改

<div id="app">
<h2 v-once>{{counter}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
counter: 100
}
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12


3.3、v-html
将字符串形式的html代码添加到dom中

<div id="app">
<h2 v-html="url"></h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
url: '<a href="http://www.baidu.com">百度</a>'
}
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12


3.4、v-pre
页面会原封不动的展示标签中的内容

<div id="app">
<h2 v-pre>{{url}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
url: '<a href="http://www.baidu.com">百度</a>'
}
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12


3.5、v-cloak
当你的js解析html时产生了停顿,就将显示出未渲染的半成品页面,v-cloak属性在渲染之后会自动删除,可以与display:none结合使用解决这个问题

<style>
[v-cloak]{
display: none;
}
</style>

<div id="app" >
<h2 v-cloak>{{message}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
setTimeout(function (){
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
message: '你好'
}
});
}, 2000)
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

 

3.6、v-if / v-else-if / v-else
<div id="app">
<h1 v-if="isShow == true">true</h1>
<h1 v-else>false</h1>
</div>
1
2
3
4

 


3.7、v-show
控制元素是否显示
v-show 与 v-if 的区别

v-show为false时,只是加了一个display : none的样式
v-if为false时,元素不会被渲染到页面,直接从dom清除
简单来说前者操作样式,而后者操作dom树
当切换频率很高时,使用v-show
频率较低,v-if更佳
3.8、v-for
<div id="app">
<!--遍历数组 格式: value |(value, index)-->
<ul>
<li v-for="(movie, i) in movies">{{movie}}----{{i}}</li>
</ul>
<!--遍历对象 格式:value |(value, key)|(value, key, index)-->
<ul>
<li v-for="(value, key) in person">{{value}}----{{key}}</li>
</ul>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
movies: ['海贼王', '火影忍者', '名侦探柯南'],
person: {
id: 8,
name: '柯南',
profession: 'detective'
}
}
});
</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


4、v-bind(:)
4.1、动态绑定属性
<div id="app">
<img v-bind:src="imageURL" alt="">
</div>

<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
imageURL: 'https://img11.360buyimg.com/seckillcms/s280x280_jfs/t1/197347/6/13295/199552/61696bf5E162a9a54/23d76b487b81fe05.jpg.webp'
}
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13


4.2、动态绑定样式
<style>
.red{
color: red;
}
.green{
color: green;
}
</style>

<div id="app">
<!--class可与:class共存,解析时会被放到一起-->
<h1 class="title":class="getClasses()">你好啊</h1>
<button @click = "red">红色</button>
<button @click = "green">绿色</button>
</div>

<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
isGreen: false,
isRed: false
},
methods: {
red: function (){
this.isRed = !this.isRed;
},
green: function (){
this.isGreen = !this.isGreen;
},
getClasses: function (){
return {red: this.isRed, green: this.isGreen};
}
}
});
</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


4.3、作业
需求
鼠标移到上边字体变红,移出恢复

<style>
.red {
color: red;
}
</style>

<div id="app">
<ul>
<li v-for="(m, index) in movies" @mouseover="i = index" @mouseleave="i = -1" :class="getClasses(index)">{{m}}</li>
</ul>
</div>

<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
movies: ['海贼王', '火影忍者', '名侦探柯南', '蜡笔小新'],
i: -1
},
methods: {
getClasses: function (i) {
if(i == this.i){
return {red: true};
}
}
}
});
</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
4.4、对象语法和数组语法
<ul>
<!--对象语法-->
<li :style="{fontSize: '50px', color: 'red'}">{{message}}</li>
<!--数组语法-->
<li :style="[{fontSize: '60px'},{color: 'red'}]">{{message}}</li>
</ul>
1
2
3
4
5
6
4.5、计算属性(computed)
computed: {
fullname: { //计算方法一般不设置set方法,只读属性
set: function (newValue) {},
get: function () {
return this.firstName + ' ' + this.lastName;
}
},
full: function () { //简写
return this.firstName + ' ' + this.lastName;
}
}
1
2
3
4
5
6
7
8
9
10
11
methods与computed区别:

计算属性存在缓存,return值未发生改变时只会调用一次
5、修饰符
5.1、stop
阻止其他跟随事件, 也不会跟随其他事件

<div id="app">
<div @click = 'divClick' style="background-color: red">
我是盒子
<button @click.stop = 'btnClick'>我是按钮</button>
</div>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
methods: {
btnClick(){
console.log('按钮被点了!!!!!!!!')
},
divClick(){
console.log('盒子被点了');
}
}
});
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5.2、prevent
推迟事件的发生

<div id="app">
<form action="http://www.baidu.com">
<input type="submit" value="提交" @click.prevent="submitClick">
</form>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
methods: {
submitClick(){
console.log('提交被推迟');
}
}
});
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
5.3、enter
监听enter键

<div id="app">
<for m action="http://www.baidu.com">
<!--监听键盘抬起的动作-->
<input type="text" @keyup="enter">

<!--监听回车-->
<input type="text" @keyup.enter="enter">
</form>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
methods: {
enter(){
console.log('回车');
}
}
});
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5.4、once
被修饰元素只有第一次会被渲染

5.5、native
用于本地组件生效

5.6、数组中的响应式
并不是所有改变数组的方式都能做到响应式
只有返回新数组才会发生响应,而仅仅是通过下标改变原数组的值是不能做到响应式的

5.7、JavaScript中的高级函数
filter
fileter是数组的一个高阶函数,参数是一个回调函数;也就是说,他会根据数组的长度来决定回调的次数,返回值是bool值;

当为true时,会将当前元素放入一个新数组,全部执行完成会将新数组返回;
当为false,不做任何操作
const nums = [11,1,111,1111];
let newArry = nums.filter(function (n){
return n < 100; // 得到所有小于100的元素
});
console.log(newArry); // 打印:[11, 1]
1
2
3
4
5
map
参数也是回调函数,迭代每个元素,执行某种操作后返回

const nums = [11, 1, 111, 1111];
let newArry = nums.map(function (n) {
return n * 2; // 将数组中元素*2
});
console.log(newArry); //打印:[22, 2, 222, 2222]
1
2
3
4
5
reduce
对数组中的元素进行汇总

reduce函数参数有两个
一为回调函数,其中参数(preValue,n)
preValue:上一次回调函数的返回值
n:当前下标的元素
二为preValue的初始值
回调次数为数组长度,当循环完毕返回回调函数的最终值
所有元素相加之和:
const nums = [11, 1, 111, 1111];
let sum = nums.reduce(function (preValue, n) {
return preValue + n; // 计算总和
}, 0);
console.log(sum); // 打印:1234
1
2
3
4
5
三者结合
常规做法

const nums = [11, 1, 111, 1111];
let sum = nums.filter(function (n) {
return n < 100; // 计算总和
}).map(function (n){
return n * 2;
}).reduce(function (preValue, n) {
return preValue + n;
}, 0);
console.log(sum); //打印:24
1
2
3
4
5
6
7
8
9
简洁做法

const nums = [11, 1, 111, 1111];
let sum = nums.filter(n => n < 100)
.map(n => n * 2)
.reduce((pre, n) => pre + n);
console.log(sum); // 打印:24
1
2
3
4
5
6、v-model
表单绑定
表单控件在实际开发中很常见,特别是对于用户信息的提交,需要大量表单
Vue中使用v-model来实现表单元素和数据的双向绑定
6.1、input和data的双向绑定
<div id="app">
<input type="text" v-model="message"/>
</div>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
message: '哈哈哈'
}
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12

v-model其实是个语法糖,他的背后本质包含两个操作:

v-bind绑定属性value
v-on执行给当前元素绑定input事件
6.2、radio和data的双向绑定
<div id="app">
<input type="radio" value="男" name="sex" v-model="sex"/> 男
<input type="radio" value="女" name="sex" v-model="sex"/> 女
<h1>您选择的性别是:{{sex}}</h1>
</div>

<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
sex: '男'
}
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


6.3、checkbox和data的双向绑定
单个单选框

<div id="app">
<input type="checkbox" v-model="isAgree"/> 同意协议
<button :disabled="!isAgree">下一步</button>
</div>

<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
isAgree: false
}
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

关于多选框true和false的问题
多个多选框

<div id="app">
<!--多选框-->
<input type="checkbox" value="唱" v-model="hobbies"/> 唱
<input type="checkbox" value="跳" v-model="hobbies"/> 跳
<input type="checkbox" value="rap" v-model="hobbies"/> rap
<input type="checkbox" value="篮球" v-model="hobbies"/> 篮球
<h1>您的爱好是:{{hobbies}}</h1>

</div>

<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
hobbies: []
}
});
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19


6.4、select和data的双向绑定
选择单个

<div id="app">
<select v-model="option">
<option type="checkbox" value="唱"> 唱</option>
<option type="checkbox" value="跳"> 跳</option>
<option type="checkbox" value="rap"> rap</option>
<option type="checkbox" value="篮球"> 篮球</option>

</select>
<h1>您的爱好是:{{option}}</h1>

</div>

<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
option: 'rap'
}
});
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

选择多个

<div id="app">
<select v-model="option" multiple>
<option type="checkbox" value="唱"> 唱</option>
<option type="checkbox" value="跳"> 跳</option>
<option type="checkbox" value="rap"> rap</option>
<option type="checkbox" value="篮球"> 篮球</option>

</select>
<h1>您的爱好是:{{option}}</h1>

</div>

<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
option: []
}
});
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21


6.5、修饰符
lazy
有时我们更改输入框的数据不想让它实时更新,这时我们就可以使用lazy修饰符,它只有在元素回车失去焦点才会更新


number
<div id="app">
<input v-model.number="message" type="text">
{{message}}
{{typeof(message)}}
</div>

<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
message: 0
}
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
若输入为number类型,会自动转换


若开始为字符串,无论后面是什么类型,都为字符串


若开始为number,后面为字符串,会自动忽略后面的所有类型


trim
去除首尾空格


7、组件化
人面对复杂问题的处理方式:
任何一个人处理信息的逻辑能力都是有限的
所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆的内容。
但是,我们人有一种天生的能力,就是将问题进行拆解。
如果将一个复杂的问题,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现大的问题也会迎刃而解。
组件化也是类似的思想:
如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
但如果,我们将一个页面拆分成一个个小的功能块
每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
7.1、组件的使用(全局与局部)
创建
调用Vue.extend()方法创建组件构造器
注册
调用Vue.component()方法注册组件
使用
在Vue作用范围内使用组件
<div id="app">
<smile></smile>
</div>
<script src="../js/vue.js"></script>
<script>
//创建组件构造器对象
const myComp = Vue.extend({
template: "<div>\n" +
" <h1>哈</h1>\n" +
" <h2>呵呵</h2>\n" +
" <h3>嘿嘿嘿</h3>\n" +
" </div>"
});
//注册组件(全局)
//smile为组件的标签名
Vue.component('smile', myComp);

const app = new Vue({
el: "#app",
components: {
// 局部
cpn: smile //cpn为组件的标签名
}
});
</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

Vue.extend()

调用Vue.extend()创建的是一个组件构造器。
通常在创建组件构造器时,传入template代表我们自定义组件的模板。
该模板就是在使用到组件的地方,要显示的HTML代码。
事实上,这种写法在Vue2g的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。
Vue.component

调用Vue.component0是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
所以需要传递两个参数:
注册组件的标签名
组件构造器
7.2、组件的使用(语法糖)
==Vue提供了一个语法糖,省去了手动调用extend的步骤,直接在注册时传入对象,component方法会在底层帮你调用。

<div id="app">
<uncst></uncst>
<cst></cst>
</div>
<script src="../js/vue.js"></script>
<script>
//全局
Vue.component('cst', {
template: `<div>
<h1>哈</h1>
<h2>全局</h2>
<h3>嘿嘿嘿</h3>
</div>`
});

const app = new Vue({
el: "#app", //用于挂载要管理的元素
components: {
'uncst': {
template: `<div>
<h1>哈</h1>
<h2>全局</h2>
<h3>嘿嘿嘿</h3>
</div>`
}
}
});
</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
7.3、父子组件
const myCompSon = Vue.extend({
template: `<div>
<h1>哈</h1>
<h2>全局</h2>
<h3>嘿嘿嘿</h3>
</div>`
});

const myCompFather = Vue.extend({
template: `<div>
<h1>哈</h1>
<h2>全局</h2>
<h3>嘿嘿嘿</h3>
</div>`,
components: {
cpn2: myCompSon
}
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
7.4、模板html分离
第一种:template标签

<template id="cpn1">
<h1>第一种模板</h1>
</template>
<script>
Vue.component('cpn1', {
template: '#cpn1'
})
</script>
1
2
3
4
5
6
7
8
第二种:script标签

<script type="text/x-template" id="cpn2">
<div>
<h1>第二种模板</h1>
</div>
</script>
<script>
Vue.component('cpn2', {
template: '#cpn2'
})
</script>
1
2
3
4
5
6
7
8
9
10
7.5、组件中的数据
组件不能直接访问实例中的数据
可以在组件中添加自己的data属性
必须定义为一个函数,且返回一个对象
对象中保存着数据
Vue.component('cpn', {
data: function () {
return {
title: '我是标题'
}
},
template: `
<div>
<h1>{{title}}</h1>
</div>
`
});
1
2
3
4
5
6
7
8
9
10
11
12
为什么Vue要将data设计成一个函数并return呢?
因为当页面中使用了多个相同组件时,如果不是函数,data数据就会共享,产生不必要的bug,而使用函数,每次调用都能返回一个新的对象,能够很好的隔离各个组件,因此被设计成函数。
7.6、父子间的通信
父传子:props属性
子传父:emit事件


父传子
方式一:字符串数组,数组中的字符串就是传递时的名称
方式二:对象,对象可以设置传递时的类型,也可以设置默认值等
注:若想在props中使用驼峰命名法,子标签的v-bind绑定的属性必须用’-‘分割
比如:

props:cMessage
v:bind:c-message = ‘message’
v-bind作用是使父组件变量名生效,当然你也可以选择不用,直接将值放进去,虽效率不高,然易于理解
<div id="app">
<!--父传子-->
<cpn :cmovies="movies" :cmessage="messages"></cpn>
</div>
<script src="../js/vue.js"></script>
<!--创建模板-->
<script type="text/x-template" id="cpn_tpe">
<div>
<h1>{{cmessage}}</h1>
<h1>{{cmovies}}</h1>
</div>
</script>
<script>
// 创建组件
const cpn = {
template: '#cpn_tpe', //引用模板
// props: ['cmovies', 'cmessage'], //父 传 子 (数组法)
props: { //父 传 子 (对象法)
// cmovies: Array, //类型限制
// cmessage: String
cmovies: { //类型限制和默认值
type: Array,
//2.5.3以上会报错,当值为对象或数组,必须传入函数
// default: [],
default(){
return []
},
required: true //required值为true,使用组件必须传值,否则报错
},
cmessage: {
type: String,
default: '默认值'
}
}
}

const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
messages: '雷猴啊',
movies: ['海王', '海贼王', '海尔兄弟', '海绵宝宝']
},
components: {
cpn //注册组件,es6的增强写法
}
});
</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
子传父
<div id="app">
<!--3. 检测到item_click被调用,调用父类方法cpn_click-->
<son @item_click="cpn_click"></son> <!--v-on监听事件若无参数,浏览器默认传递event; 但这里是我们自定义的事件,默认传递我们传递的参数item-->
</div>
<script src="../js/vue.js"></script>
<script type="text/x-template" id="sontemplate">
<div>
<button type="button" v-for="(item) in catagories" @click="btn_click(item)"> <!--1. 检测到点击,调用btn_click-->
{{item.name}}
</button>
</div>
</script>

<script>
const son = {
template: '#sontemplate',
data(){
return {
catagories: [
{id: 'a', name: '热门推荐'},
{id: 'b', name: '手机数码'},
{id: 'c', name: '家用家电'},
{id: 'd', name: '电脑办公'}
]
}
},
methods:{
btn_click(item){
// 自定义事件item_click
this.$emit('item_click', item); // 2. 调用item_click
}
}
}

const app = new Vue({
el: "#app", //用于挂载要管理的元素
components: {
son
},
methods: {
cpn_click(item){
console.log(item.name);
}
}
});
</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
父子互传案例
<div id="app">
<cpn :cpn_num1="num1" :cpn_num2="num2" @change_num1="changeNum1" @change_num2="changeNum2"></cpn>
</div>


<template id="cpn">
<div>
<h1> props1: {{cpn_num1}}</h1>
<h1> data1: {{dnum1}}</h1>
<input :value="dnum1" @input="inputNum1"/>
<h1> props2: {{cpn_num2}}</h1>
<h1> data2: {{dnum2}}</h1>
<input :value="dnum2" @input="inputNum2"/>
</div>
</template>

<script>
const cpn = {
template: '#cpn',
data(){
return {
dnum1: this.cpn_num1,
dnum2: this.cpn_num2
}
},
props: {
cpn_num1: Number,
cpn_num2: Number
},
methods: {
inputNum1(event){
this.dnum1 = event.target.value;
this.$emit('change_num1', this.dnum1);
this.dnum2 = this.dnum1 * 100;
this.$emit('change_num2', this.dnum2);
},
inputNum2(event){
this.dnum2 = event.target.value;
this.$emit('change_num2', this.dnum2);
this.dnum1 = this.dnum2 * 1/100
this.$emit('change_num1', this.dnum1);
}
}
}
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
num1: 1,
num2: 0
},
components: {
cpn
},
methods: {
changeNum1(value){
this.num1 = value*1;
},
changeNum2(value){
this.num2 = parseInt(value);
}

}
});
</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
61
62
63
64


父子访问
父组件访问子组件的成员,有两种方式

通过this.$children属性取到由子组件Component的构成的数组集合;
ref在子组件标签起名字:<cpn ref='xxx'></cpn>,通过this.$ref.xxx取到单个Component;(常用)
注意:访问不是传递!!!
子组件访问父组件的成员:
通过this.$parent属性得到父组件Component,若父组件为Vue实例,则返回Vue

子组件访问root组件
通过this.$parent属性得到根组件,一般为Vue实例

8、插槽Slot
8.1、什么是插槽?
组件里的插槽,类似于Java抽象类中的抽象方法;
因为具有不确定性,因此能够很好的复用;
导航栏为一种使用场景
8.1、为什么使用Slot?
Slot翻译为插槽:

在生活中很多地方都有插槽,电脑的USB插槽,插板当中的电源插槽。
插槽的目的是让我们原来的设备具备更多的扩展性。
比如电脑的USB我们可以插入U盘、硬盘、手机、音响、键盘、鼠标等等。组件的插槽:
组件的插槽也是为了让我们封装的组件更加具有扩展性。
让使用者可以决定组件内部的一些内容到底展示什么。
8.3、案例:单插槽
<div id="app">
<cpn></cpn>
<cpn>
<font>slot</font>
</cpn>
<cpn>!</cpn>
</div>

<template id="cpn">
<slot>
<button>hello,</button><!--若为空,则默认-->
</slot>
</template>
<script src="../js/vue.js"></script>
<script>

const cpn = {
template: '#cpn'
}
const app = new Vue({
el: "#app", //用于挂载要管理的元素
components: {
cpn
}
});
</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


8.4、案例:具名(多)插槽
<div id="app">
<cpn>
<!--替换name为middle的slot-->
<!--给middle加入多个元素-->
<h1 slot="middle">嘿嘿嘿</h1>
<h2 slot="middle">吼吼吼</h2>
</cpn>
<cpn>
<!--若不指定名字,会把没有名字的全部替换-->
<h1>哈哈哈</h1>
</cpn>
</div>

<template id="cpn">
<div>
<slot name="left">左</slot>
<slot name="middle">中</slot>
<slot name="right">右</slot>
<slot>右右</slot>
<slot>右右右</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const cpn = {
template: '#cpn'
}
const app = new Vue({
el: "#app", //用于挂载要管理的元素
components: {
cpn
}
});
</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


8.5、作用域插槽
父组件替换插槽的标签,但数据是由子组件提供
比如有些子组件内容要水平展示,或者竖直展示,它的结构是变化的,因此就有了作用域插槽
<div id="app">
<cpn></cpn>
<cpn>
<template slot-scope="aaa">
<!-- <span v-for="item in slot.data">{{item}} </span>-->
<span>{{aaa.xxx.join(' - ')}}</span>
</template>
</cpn>
</div>

<template id="cpn">
<div>
<slot :xxx="pLanguage">
<li v-for="item in pLanguage">{{item}}</li>
</slot>
</div>
</template>
<script>
const cpn = {
template: '#cpn',
data() {
return {
pLanguage: ['Java', 'Python', 'JavaScript', 'MySQL', 'C#']
}
}
}
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
},
components: {
cpn
}
});
</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


9、模块化
9.1、为什么要有模块化?
命名冲突

比如小明写了a.js,定义变量a,为1;
小红写了b.js文件,也定义了a,为2;
如果将a与b同时引入,大概率出现严重问题!
如果将代码改成这样

;(function(){
/*代码写在这*/
})()
1
2
3
闭包可以解决这种冲突,但会产生新的问题,js代码无法被引用,复用性太差。
可以将可能被访问的数据返回到一个变量用来保存

var moduleA = (function(){
var result = {}
/*代码写在这*/
return result
})()
1
2
3
4
5
9.2、ES6的模块化
aaa.js

let a = 1
function sum(num1, num2) {
return num1 + num2
}
//导出方式一:
export {
a, sum
}
//导出方式二:
export var a = 1000
export function sum(num1, num2) {
return num1 + num2
}
// 导出方式三:只能有一个default,导入时可以起任意名
export default var address = 1
export default function(){
console.log("匿名函数");
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bbb.js

// 导入即可使用
import {a, sum, addr} from './aaa.js'
console.log('a = ' + a)
console.log(sum(1, 2))
// 若导入变量过多或者命名冲突,还可以这样
import * as aaa from './aaa.js'
console.log(aaa.变量名);
1
2
3
4
5
6
7
ccc.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ES6模块化</title>
<!--type为module,去除了命名冲突-->
<script src="../js/bbb.js" type="module"/>
</head>
</html>
1
2
3
4
5
6
7
8
9
10、webpack
10.1、认识webpack
从本质来讲,webpack是一个现代的JS应用的静态模块打包工具。
有些文件,如.sass,ES6语法,要转成ES5大部分浏览器才能支持。所以要通过一些工具做一些打包转化之类的工作。
webpack不仅用来打包,还能进行模块化,帮我们处理模块之间的依赖关系
打包就是将各种资源合并成一或多个包(Bundle)
10.2、webpack的起步
webpack依赖node环境
node必须包含各种依赖的包才能正常执行代码,
所以安装node时会自动安装帮助我们管理各种包的工具npm(node package manager)
10.3、webpack的配置
安装支持环境nodejs,会自动安装npm

全局安装webpack

npm install webpack@3.6.0 -g
1
局部安装webpack(后需用到)

cd 对应目录
npm install webpack@3.6.0 --save-dev
1
2
10.4、案例
编写mainUtils.js

// CommonJS语法
function sum(num1, num2){
return num1 + num2
}

function mul(num1, num2) {
return num1 * num2
}

module.exports = {
sum, mul
}
// ES6语法
export const id = 1
export const name = 'Candy'
export const age = 18

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
编写info.js

// ES6语法
export const id = 1
export const name = 'Candy'
export const age = 18
1
2
3
4
编写main.js

// CommonJS语法
const {sum, mul} = require('./mainUtil.js')

console.log(sum(1, 2));;
console.log(mul(1, 4));;

// ES6语法
import {id, name, age} from './mainUtil'

console.log(id);
console.log(name);
console.log(age);
1
2
3
4
5
6
7
8
9
10
11
12
终端打包

PS E:\VueProjects\Day-01> webpack ./js/main.js ./dist/bundle.js
Hash: 0237d7838c5fb13a1c57
Version: webpack 3.6.0
Time: 59ms
Asset Size Chunks Chunk Names
bundle.js 2.76 kB 0 [emitted] main
[0] ./js/main.js 69 bytes {0} [built]
[1] ./js/mainUtil.js 142 bytes {0} [built]
PS E:\VueProjects\Day-01>
1
2
3
4
5
6
7
8
9
会将main.js及所有依赖js打包成一个bundle.js文件

编写main.html引入bundle.js文件

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../dist/bundle.js"></script>
</head>
</html>
1
2
3
4
5
6
7
8
结果展示:


10.5、配置文件设置出入口
终端输入npm init
package name起个名字,之后可以一路回车
生成package.json文件

如果使用了node依赖的包,需要这个文件

项目根目录编写webpack.config.js

const path = require('path')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(_dirname, 'dist'),
//path是一个绝对路径
1
2
3
4
5
6
//__dirname是node上下文的东西,是一个全局变量,保存了当前文件所在目录
//resolve函数可以拼接参数
filename: 'bundle.js'
}
}
1
2
3
4
5

1
打包:
终端输入webpack
报错:
ERROR in Entry module not found: Error: Can't resolve 'src/main.js' in 'E:\VueProjects\Day-01
检查entry路径
果然是路径错了
继续打包,成功!

10.6、打包需要注意的问题:
有时候使用了比如`4.0版本的语法
全局的打包工具是3.6的,就会出问题
这时候需要配置本地webpack
在项目目录下输入npm install webpack@4.0.0 --save-dev来下载本地webpack
生成本地文件

终端使用webpack都会使用全局的webpack,不可选
package.json的script关键字中设置build,值为webpack,终端执行npm run build,会优先使用本地webpack
10.7、loader
Webpack 本身只能处理 JS 模块,若要处理其他类型的文件,就需要扩展 loader
.css 的处理
如果需要使用 css 文件,就需要使用到 css-loader 和 style-loader,
css-loader 解析CSS文件,使用import加载并返回CSS代码
style-loader 将模块的导出作为样式添加到DOM
使用步骤:
安装
通过npm安装需要使用的loader,全局安装需要参数 -g

npm install css-loader@2.0.2 style-loader@0.23.1 --save-dev
1
执行以上命令会在当前目录生成 node_modules 目录,它是 css-loader 和 style-loader 的安装目录。


配置
在webpack.config.js中的modules关键字下进行配置大部分loader

module: {
loaders: [{
test: /\.css$/,
loader: "style-loader!css-loader"
}]
}
1
2
3
4
5
6
编写style.css文件,在入口文件引用

require("./css/style.css");
1
打包运行

成功!

.less 的处理
编写my.less

@fontSize: 50px;
@fontColor: black;
body{
font-size: @fontSize;
color: @fontColor;
}
1
2
3
4
5
6
在入口文件中引用

require("./css/my.less");
1
本地安装less的loader

npm install less-loader@4.1.0 less@3.9.0 --save-dev
# less-loader只负责加载less文件
# less是用来解析less文件的工具,不是一个loader
1
2
3
安装成功

在webpack.config.js中使用loader

{
test: /\.less$/,
use: [
// loader是按照从后往前的顺序加载的,顺序不能错
{
loader: "style-loader"
},
{
loader: "css-loader"
},
{
loader: "less-loader"
}
]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
打包运行

npm run build
1


静态文件 的处理
安装依赖loader

npm install url-loader@1.1.2 --save-dev
1
在webpack.config.js中使用loader

{
test: /\.png|gif|jpg|jpeg$/,
use: [
{
loader: "url-loader"
options: {
limit: 8192
}
}
]
}
1
2
3
4
5
6
7
8
9
10
11
file-loader
当加载的图片,小于limit时,会将图片编译成base64字符串形式.

npm install file-loader@3.0.1 --save-dev
1
使用file-loader需要注意的第一个问题

ta会将超出大小限制的资源打包到输出目录下,并用hashcode重新命名防止重复

所以我们直接访问肯定会报错的

在webpack.config.js的output配置publicPath属性为dist/

这样ta访问url时都会自动拼接上dist/

publicPath: "dist/"
1
使用
打包,运行,成功!
使用file-loader需要注意的第二个问题

file-loader打包后会生成32位hash值替换原文件名,但开发中会起一个有规律的名字,最好是 name + hash.ext,其中hash不需要这么长的话可以截取,而且生成文件位置可以自定。

在webpack.config.js中url-loader的option中添加配置

name: img/[name].[hash:8].[ext]
1

成功!

ES6 的处理
如果你仔细阅读webpack打包的js文件
发现写的ES6语法并没有转成ES5
那么就意味着可能一些对ES6还不支持的浏览器不能很好的运行我们的代码。
此时我们需要手动进行处理
安装babel-loader

npm install babel-loader@7 babel-core babel-preset-es2015 --save-dev
# babel-preset-es2015用来转换es语法
1
2
配置
webpack.config.js -> module.exports -> module -> rule

{
test: /\.js$/,
exclude: /(node_modules|bower_components)/, //排除
use: [
{
loader: "babel-loader",
options: {
presets: ['es2015']
}
}
]
}
1
2
3
4
5
6
7
8
9
10
11
12
打包

npm run build
1

成功!

10.8、Webpack的Vue
npm安装vue

# 因为项目上线也需要依赖vue,因此不需要-dev
npm install vue@2.5.21 --save
1
2
配置
出口文件中配置

// 导入node_modules包下的export defualt Vue
import Vue from 'vue'

const app = new Vue({
el: '#app',
data: {
message: '信息'
}
})
1
2
3
4
5
6
7
8
9
html页面body中配置

<div id="app">
{{message}}
</div>
1
2
3
运行报错

npm run build
1


原因是vue发布后会生成runtime-only和runtime-compiler两个版本

前者不能包含template,因此找不到‘#app’

而后者因为有compiler可以用于编译template,因此选择ta
在webpack.config.js -> module.exports中配置

resolve: {
// 别名
alias: {
// 当vue被导入,ta就不会按照默认路径
// 会先来这里找一下是否配置了映射路径
// 这个路径里包含了compiler
'vue$': 'vue/dist/vue.esm.js'
}
}
1
2
3
4
5
6
7
8
9
vue实例中的template
如果vue实例中有template属性,会将该属性值进行编译,将编译后的虚拟dom直接替换掉vue实例绑定的元素(即el绑定的那个元素);
template属性中的dom结构只能有一个根元素
如果有多个根元素需要使用v-if、v-else、v-else-if设置成只显示其中一个根元素;
vue -> app.js

// 将模板抽离到一个组件
// 模板用到的data和method一并抽离
export default {
template: `
<div>
<h1>{{name}}</h1>
</div>
`,
data(){
return {
name: 'Candy'
}
},
methods: {
btnClick(){
console.log("我是组件!");
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在出口文件中配置

import Vue from 'vue'
import app from './vue/app'

new Vue({
el: '#app',
// 子组件以前是放在父组件的模板中
// 现在这种写法会直接将父组件模板替换
template: '<app/>',
components: {
app
}
})
1
2
3
4
5
6
7
8
9
10
11
12
运行


升级写法 -> 模板分离

创建Vue组件


将之前模板移入


script移入


样式移入


出口函数中更改配置

// import app from './vue/app'
import app from './vue/app'
1
2
安装loader

npm install vue-loader vue-template-compiler --save-dev
# vue-loader加载vue组件
# vue-template-compiler编译模板
1
2
3
引入子组件
vue -> Cpn.vue

<template>
<div>
<h1>我是子组件</h1>
</div>
</template>

<script>
export default {
name: "Cpn"
}
</script>

<style scoped>

</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在vue -> App.vue导入,components中注册

import Cpn from './Cpn.vue'
1
如果不像每次都写后缀,可以在配置文件的resolve关键字中加入以下代码:

extensions: ['.js', '.css', '.vue']
1
10.9、plugin的使用
plugin是插件的意思,通常用于对现有的架构进行扩展
loader主要用于转换类型,相当于转换器;
plugin是对webpack本身的扩展,是一个扩展程序
版权声明
配置文件中引入webpack

const webpack = require('webpack')
1
module.exports中添加plugins关键字

plugins: [
new webpack.BannerPlugin('最终版权归Monster所有')
]
1
2
3
成功


打包html
安装插件
npm install html-webpack-plugin@3.2.0 --save-dev
1
配置引入
const HtmlWebpackPlugin = require('html-webpack-plugin')
...
// plugins加入
new HtmlWebpackPlugin()
1
2
3
4
打包运行

在dist下生成index.html,帮我们引入了bundle.js

之前配置的拼接路径dist/不再需要

但细细一看,我们id为app的div不见了,这是因为此页面是自动生成的,我们可以在插件配置中添加原页面为模板

重新打包
压缩
安装

npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
1
引入

const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
...
// plugins加入
new UglifyjsWebpackPlugin ()
1
2
3
4
丑化


10.10、搭建本地服务器
webpack提供了一个可选的本地开发服务器
基于node.js搭建
内部使用express框架
可以实现让浏览器自动刷新显示修改后的结果。
每次修改时会将结果放到内存,等我们手动发布后才会放到磁盘
不过他是一个单独的模块,需要先进行安装

npm install webpack-dev-server@2.9.3 --save-dev
1
devserver也是作为webpack中的一个选项,选项本身可以设置如下属性:

contentBase :为哪一个文件夹提供本地服务,默认是根文件夹,我们这里要填写/dist
port :端口号
inline :页面实时监听
historyApiFallback :在SPA页面中,依赖HTML5的history模式
devServer: {
contentBase: './dist',
inline: true
}
1
2
3
4
启动服务器的方式:

相对路径

# 终端启动
.\node_modules\.bin\webpack_dev_server
1
2
脚本启动
在package.json中的scripts关键字中配置

"dev": "webpack-dev-server --open"
// open参数,启动时自动打开网页
1
2
# 终端启动
npm install dev
1
2
10.11、配置分离
有些开发时的配置等项目上线后就没用了
我们可以将他们抽离出来

项目根目录创建build文件夹

在build下创建base.config.js,里面放一些开发上线通用的配置

创建prod.config.js,放生产时的配置

创建dev.config.js,放开发时的配置

为了最终能合并配置文件,需要安装webpack-merge

npm isntall webpack-merge --save-dev
1
在prod.config.js和dev.config.js中引入

const baseConfig = require('./base.config')
const WebpackMerge = require('webpack-merge')

module.exports = WebpackMerge(baseConfig, {
//自己的配置
})
1
2
3
4
5
6
项目启动时会默认启动webpack.config.js,所以我们需要对我们的配置文件进行单独配置

package.json -> scripts

找到之前配置的build和dev属性,更改为:

"build": "webpack --config ./build/prod.config.js",
1
"dev": "webpack-dev-server --open --config ./build/dev.config.js"
1

1
更改打包位置
通过观察可以发现打包位置在build下,是因为我们之前设置的是配置文件当前目录下打包,我们可以更改一下,找到base.config.js,更改output -> path的第二个参数为../dist

项目地址: https://gitee.com/candy-xiaojing/vue-study.git

11、Vue CLI
11.1、简介
如果你只是简单写几个Vue的Demo程序,那么你不需要Vue CL.
如果你在开发大型项目,那么你需要,并且必然需要使用Vue CLI
使用Vuejs开发大型应用时,我们需要考虑代码目录结构、项目结构和部署、热加载、代码单元测试等事情。
如果每个项目都要手动完成这些工作,那无疑效率比较低效,所以通常我们会使用一些脚手架工具来帮助完成这些事情。
CLI是什么意思?
CLI是Command-Line Interface,翻译为命令行界面,但是俗称脚手架.
Vue CLI是一个官方发布vuejs项目脚手架
使用vue-cli可以快速搭建Vue开发环境以及对应的webpack配置.

11.2、安装
安装NodeJs

安装脚手架

cnpm install -g @vue/cli
1
这里安装的是CLI3,Vue CLI2和3使用了相同的vue命令,因此被覆盖了,如果还想使用旧版,可以安装一个桥接工具

cnpm install @vue/cli-init -g
1
11.3、Vue CLI2
初始化项目
vue init webpack my_project
1
初始化参数解读
Project name: 项目名称初始化命令中的名称是最终生成文件夹的名称,这里才是真正的项目名,一般情况下保持一致,直接回车
Project description:描述信息,自定义,回车
Author:作者信息,自定义,回车
Runtime Compiler / Runtime Only:初学者可以选择前者,回车
vue-router:是否安装路由,自定义Yes / No,回车
Use ESLint to lint your code?(Y/n):是否对你的代码开启严格模式,自定义,回车
6.1. Pick an ESLint preset(Standard / Airbnb / none):选择规范模板,回车
Set up unit tests?(Y/n)是否需要单元测试,自定义,回车
Setup e2e tests with NightWatch:end to end 安装NightWatch,是一个利用selenium或webdriver或phantomjs等进行自动化测试的框架,自定义
NPM / Yarn:使用什么工具管理我们的项目,自定义,回车
环境解读
node由C++开发,内嵌了V8引擎,可以直接通过命令运行js代码

node xxx.js
1
11.4、Vue CLI3
一、CLI3介绍
vue-cli 3与2版本有很大区别

vue-cli 3是基于webpack 4打造, vue-cli 2还是webapck 3
vue-cli 3的设计原则是"0配置" ,移除的配置文件根目录下的, build和config等目录
vue-cli 3提供了vue ui命令,提供了可视化配置,更加人性化
移除了static文件夹,新增了public文件夹,并且index.html移动到public中
二、 初始化项目
vue create 项目名
1
三、初始化参数解读
Please piick a preset:
选择一个配置
default(babel,eslint):默认
Manually select feature:手动选择特性(回车,空格选中或取消)
Where do you prefer placing config for Babel, PostCSS, ESLint, etc.?:关于Babel、PostCSS、ESLint等配置你是想放在哪里呢?(In delicated config files)单独配置文件,(In package.json)放到package.json里
Save this as a preset for feature projects(y/N):保存当前配置到以后的第一步
Save preset as:保存的配置名称
四、CLI3介绍
build和config的配置文件并未移除
只是移到了路径/node_modules/@vue/cli_service下
若对默认配置不满,可在项目根目录创建vue.config.js写入你的配置,最终会被vue合并为一个
12、路由
12.1、前端的发展
后端路由阶段
早期的网站开发整个HTML页面是由服务器来渲染的
服务器直接生产渲染好对应的HTML页面,返回给客户端进行展示.
但是,一个网站,这么多页面服务器如何处理呢?
一个页面有自己对应的网址,也就是URL.
URL会发送到服务器,服务器会通过正则对该URL进行匹配,并且最后交给一个Controller进行处理.
Controller进行各种处理,最终生成HTML或者数据,返回给前端.
这就完成了一个1O操作.
上面的这种操作,就是后端路由.
当我们页面中需要请求不同的路径内容时,交给服务器来进行处理,服务器渲染好整个页面,并且将页面返回给客户顿.
这种情况下渲染好的页面,不需要单独加载任何的js和css,可以直接交给浏览器展示,这样也有利于SEO的优化.
后端路由的缺点
一种情况是整个页面的模块由后端人员来编写和维护的.
另一种情况是前端开发人员如果要开发页面,需要通过PHP和Java等语言来编写页面代码.
而且通常情况下HTML代码和数据以及对应的逻辑会混在一起,编写和维护都是非常糟糕的事情.
前端路由阶段
前后端分离阶段:

随着Ajax的出现,有了前后端分离的开发模式.
后端只提供API来返回数据,前端通过Ajax获取数据,并且可以通过JavaScript将数据渲染到页中.
这样做最大的优点就是前后端责任的清晰,后端专注于数据上,前端专注于交互和可视化上.
并且当移动端(iOS/Android)出现后,后端不需要进行任何处理,依然使用之前的一套API即可.口目前很多的网站依然采用这种模式开发
单页面富应用阶段:
其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由.
也就是前端来维护一套路由规则.
URL的hash
锚点(#),本质是该百年window.location的href属性
可以通过直接赋值location.hash来改变href,但是页面不发生刷新

HTML5中的history模式


history模式就相当于一个栈,默认显示栈顶

history方法:

pushState、replaceState:向栈中添加元素,并显示栈顶
二者区别:前者可以在栈中前进后退,后者不能
go(index):通过改变下标来决定显示的内容,比如go(-1)显示上一级
back=go(-1)
forward = go(1)
12.2、认识路由
说起路由你想起了什么?
路由是一个网络工程里面的术语。
路由( routing )就是通过互联的网络把信息从源地址传输到目的地址的活动.-维基百科
额,啥玩意?没听懂
在生活中,我们有没有听说过路由的概念呢?当然了,路由器嘛.
路由器是做什么的?你有想过吗?
路由器提供了两种机制:路由和转送.
路由是决定数据包从来源到目的地的路径.
转送将输入端的数据转移到合适的输出端.
路由中有一个非常重要的概念叫路由表
路由表本质上就是一个映射表,决定了数据包的指向.
认识vue-router
目前前端主流三大框架,都有自己的路由实现

Angular的ngRouter
React的ReactRouter
Vue的vue-router
vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建页面应用
vue-router是基于路由和组件的
路由用于设定访问路径,将路径和组件映射起来
在vue-router的单页应用中,页面的路径的改变就是组件的切换
12.3、基本使用
安装

npm install vue-router --save
1
在模块化工程中使用ta,因为是一个插件,所以可以通过Vue.use()来安装路由功能

src/router/index.js

import VueRouter from 'vue-router'
import Vue from 'vue'
// 导入路由对象,通过use安装插件
Vue.use(VueRouter)

//创建路由实例,并传入路由映射配置
const routes = [这里配置url-component映射关系]
const router = new VueRouter({
// 配置路由和组件之间的应用关系
routes,
// 默认会通过改变hash值来改变路径,长这个样子 https://localhost:8080/#/home
//不好看是吧,所以我们通过history模式来改变路径,两种方式都不会去访问服务器
mode: 'history'
})
// 将router对象传入vue实例
export default router

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
在Vue实例中挂载创建的路由实例

// 若为文件夹,则默认寻找index
import router from './router'
new Vue({
router
})
1
2
3
4
5
创建两个路由

src/components/Home.vue

src/components/About.vue

修改index.js

import Home from '../components/Home'
import About from '../Components/About'
const routes = [
{
path: '/',
// 重定向
redirect: '/home'
},
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
修改App.vue,添加对应按钮用于跳转

<template>
<div id="app">
<div id="nav">
<router-link to="/home">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
// 该标签会根据当前的路径,动态渲染粗不同的组件
<router-view/>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
router-link补充

该标签是vue-router中内置的组件,默认被渲染成a标签
to:决定了要渲染显示的组件
tag:决定了渲染之后的标签
replace:相当于调用replaceState方法,不会产生路径记录
active-class:当前元素被选中,会自动增加class为router-link-active,表示被选中,若觉得名称过长,可设置此属性来更换,但router-link标签太多会造成空间的浪费,我们可以在初始化路由的位置增加linkActiveClass属性,值为你想替换的值
12.5、懒加载
当打包构建应用时,JS包非常大,影响页面加载
如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了
路由懒加载主要作用就是将路由对应的组件打包成一个个js代码块
只有在这个路由被访问到的时候,才加载对应组件
懒加载的使用

component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
1
12.6、嵌套路由
认识嵌套路由
嵌套路由是一个很常见的功能

比如在home页面中,我们希望通过/home/news和/home/message访问一些内容
一个路径映射一个组件,访问这两个路径也会分别渲染两个组件
实现嵌套路由
创建对应子组件News.vue,并且在路由映射中配置对应的子组件

{
path: '/home',
components: Home,
children: [
{
path: 'news',
components: () => import('../components/News')
}
]
}
1
2
3
4
5
6
7
8
9
10
组件内部使用标签

<router-link to='/home/news'>新闻</router-link>
<router-view></router-view>
1
2
12.7、参数传递
传递参数有两种方式:params和query

params类型:

配置路由格式:/router/:id

传递方式:在path后面跟上对应的值

比如:to=’/path/123’

最终路径:/router/123、/router/abc

取值:$route.params.参数名

query类型:

传递方式:对象中使用query的key作为传递方式

比如::to="{path : ‘/path’, query : {name : ‘Candy’, age : 18, height : 1.60}}"

比如:/router?id=123、/router?id=abc

取值:$route.params.参数名

12.8、导航守卫
为什么使用导航守卫?
我们来考虑一个需求:在一个SPA应用中,如何改变网页的标题呢?

网页标题是通过
但是我们可以通过JavaScript来修改
那么在Vue项目中,在哪里修改?什么时候修改比较合适呢?

普通的修改方式:

我们比较容易想到的修改标题的位置是每一个路由对应的组件.vue文件中
通过mounted声明周期函数,执行对应的代码进行修改即可.
但是当页面比较多时,这种方式不容易维护(因为需要在多个页面执行类似的代码).有没有更好的办法呢?使用导航守卫即可.
什么是导航守卫?

vue-router提供的导航守卫主要用来监听监听路由的进入和离开的.
vue-router提供了beforeEach和afterEach的钩子函数,它们会在路由即将改变前和改变后触发.
全局守卫
用法测试:改变标题

路由映射配置

path: '/path',
component: Home,
meta: [
title: '首页'
]
1
2
3
4
5
beforeEach

beforeEach函数在路由跳转之前执行的函数,俗称前置钩子函数,ta有三个参数,to、from和next;

to为跳转地址

from为跳转源头

next用来继续执行跳转

就相当于Java中的Filter

// 前置钩子
router.beforeEach((to, from, next) => {
document.title = to.matched[0].meta.title
next()
})
1
2
3
4
5
afterEach

与前置钩子对应的是后置钩子

是在组件跳转之后调用的

除了没有next,其他的一致

// 后置钩子
router.afterEach((to, from) => {
...
})
1
2
3
4
独享守卫
路由独享的守卫,在配置路由映射的位置配置此路由

const routes = [
{
path: '/home',
component: Home
beforeEnter: (to, from, next) => {
// ...
},
afterEnter: (to, from) => {
// ...
}

}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
组件守卫
组件内部的守卫

export default {
name: 'HelloWorld',
beforeRouteEnter (to, from, next) {
//在渲染该组件的对应路由被 confirm 前调用
//不!能!获职组件实例`this`
//因为当守卫执行前,组件实例还没坡创建
},
beforeRouteUpdate (to, from, next) {
//在当前路由改变,但是该组件被复用时调用
//举例来说,对于一个带有动态参数的路径/foo/:id,在/foo/1和/foo/2之间跳转的时候,
//由于会渲染同样的Foo组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
//可以访问组件实例`this`
},
BeforeRouteLeave (to, from, next) {
//导航离开该組件的对应路由时调用
//可以访问组件实例`this`
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
12.9 、keep-alive
当组件每次被加载时都会重新创建,离开时自然会被销毁,这样无疑会造成无意义的延迟,而且每次重新回到组件,页面修改的内容以及使用痕迹也会消失

这时候可以使用vue内置组件keep-alive,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

<keep-alive>
<router-veiw/>
</keep-alive>
1
2
3
keep-alive有两个属性

exclude:值为组件的name值,表示将此组件不会被缓存,阻止ta的存活,若有多个,以逗号间隔,不要加空格!

include:字符串或正则表达式,只有匹配的组件才会被缓存

13、Promise
ES6中一个非常重要好用的特性就是Promise

Promise是异步编程的一种解决方案

我什么时候会处理异步事件呢?

一种很常见的场景应该就是网络请求了
我们封装一个网络请求的函数,因为不能立即拿到结果,因此不能像简单的3+4=7一样返回结果
所以往往我们会传入另一个函数,在数据请求成功,将数据通过传入的参数会掉出去
如果只是简单的网络请求,那这种方案不会给我们带来大麻烦
但是,当网络请求非常复杂时,就会出现回调地狱

Promise可以用一种优雅的方式解决这个问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zxyVOtnL-1638110741791)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211128164226181.png)]

Promise的三种状态
首先,当我们开发中有异步操作时,就可以给异步操作包装一个Promise

异步操作之后会有三种状态:

pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。
fulfill :满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then
reject :拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch)
若没有进行异步操作,在return时可以简化

return Promise.resolve(a + b);
return a + b; //内部会自动帮我们包装为Promise.resolve(a + b)
return Promise.reject();
throw ''
1
2
3
4
all方法
如果一个操作需要两个异步操作的结果才能执行,可以使用Promise的all方法

Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 2000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 1000)
})
]).then(results => {
// results: [1, 2]
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
14、 Vuex
14.1、介绍
官方解释: Vuex是一个专为Vuejs应用程序开发的状态管理模式。

它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

Vuex也集成到Vue的官方调试工具 devtols extension ,提供了诸如零配置的time-travel调试、状态快照导入导出等高级调试功能。

状态管理到底是什么?

状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。
其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。
然后,将这个对象放在页层的Vue实例中,让其他组件可以使用。
那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?
14.2、使用
安装

npm install vuex --save
1
src下创建store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

// 1. 安装插件
Vue.use(Vuex)
// 2. 创建对象
const sotre = new Vuex.store({
state: {
couter: 1000 // 外界通过$store.state.couter取值
}
mutations: {}
actions: {},
getters: {},
modules:{}
})
// 3. 导出
export default store
// 4. 回到main.js
import store from './store'

new Vue({
store, //挂载时隐式执行了Vue.prototype.$store = store
render: h => h(App)
}).$mount('#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
14.3、Vuex核心概念
State:状态存储
Getters:类似于计算属性
Mutation:状态更新
Action:异步操作
Module
State单一状态树:单一数据源

一个项目里就一个store,方便维护和管理
1
Getters详解

相当于计算属性
1
// 声明:
getters: {
powerCounter(state){
return state.counter * state.counter
}
}
// 调用:
$store.getters.powerCounter

// 传参:getters是不能传参的,可以使用这种方式传入
getters: {
addCounter(state){
return count => state.counter + count
}
}
$store.getters.powerCounter(10)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
14.4、Mutations

修改状态时要通过Mutations,这样Devtools才能够跟踪到修改的内容,以便于调试

// 声明:
mutations: {
increment(state, count){
state.counter+=count
}
}
// 调用:
this.$store.commit('increment', 10);

// 另一种commit方式
mutations: {
increment(state, payload){
state.counter+=payload.count
}
}
this.$store.commit({
type: 'increment',
count
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
响应规则

事先在state中初始化过的数据,会被加入到响应式系统里

而后来加入的则不会

通过Vue的set 和 delete可以做到后来响应式

14.5、Actions
当Mutations中有一些异步操作时,程序就会出问题,虽然修改时页面确实更新了,但devtools不能跟踪到,因此有一个属性Actions是专门处理异步的操作

actions: {
// context上下文
aUpdate(context, message){
setTimeout(() => {
context.commit('update')//修改状态必须使用Mutations,我们只是通过Actions处理异步,并不会绕过Mutations
}, 1000)
}
}
// 调用
this.$store.acions.dispatch('aUpdate', '可以传参')
1
2
3
4
5
6
7
8
9
10
14.6、modules
Vue使用单一状态树,意味着很多状态都会交给Vuex管理
当程序变得非常复杂时,store对象就有可能变得相当臃肿
为了解决这个问题,Vuex允许我们将store分割成模块(Module),而每个模块拥有自己的state、mutation、action、getters等
只是分块存储,便于管理,但本质上还是一个同级,不要有相同的名字
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gbksu0up-1638110741805)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211128211133847.png)]

14.7、项目结构
Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:

应用层级的状态应该集中到单个 store 对象中。
提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
异步逻辑都应该封装到 action 里面。
只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。

对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:

├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15、axios
15.1、什么是axios?
选择什么网络模块?

Vue中发送网络请求有非常多的方式,在开发中,如何选择呢?

传统的Ajax

是基于XMLhttpRequest(XHR)

为什么不选择ta?

配置和调用方式等非常混乱

真实开发很少使用,而是使用jQuery-Ajax

jQuery-Ajax

为什么不选择ta?
首先,Vue整个开发都不需要jQuery
为了一个网络请求特意引入jQuery不合适
jQuery代码1w+行
Vue代码才1w+行
官方在Vue1.x时,推出了Vue-resource

Vue-resource的体积比jQuery小很多
为什么不选择ta?
在Vue2.0推出后,Vue-resource被移除了
意味着以后Vue-resource不再支持新的版本,也不再更新和维护
对以后的项目开发和维护都存在很大隐患
在说明不在继续更新和维护Vue-resource的同时,作者推荐了一个框架:axios

功能特点:
在浏览器中发送XMLHttpRequests请求
在node.js中发送http请求
支持Promise API
拦截情切和响应
转换请求和响应数据
15.2、axios请求方式
支持多种请求方式:

axios(config)
axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])
15.3、axios的使用
安装

npm install axios --save
1
使用

main.js

// 导入
import axios from './axios'

// 使用
axios({
url: '',
method: 'get', //默认get,可以不填
params: { // 参数传递
type: 'pop',
page: 1
}
//返回Promise对象,可以直接调用then
}).then(res => {
console.log(res)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
15.4、axios并发请求
axios.all([axios(...), axios(...)]).then(results => {
console.log(results[0])
console.log(results[1])
})
1
2
3
4
spread方法

axios.all([axios(...), axios(...)]).then(axios.spread((res1, res2) => {
console.log(res1)
console.log(res1)
}))
1
2
3
4
15.5、全局配置
开发中很多参数都是固定的

这时候可以进行抽取,也可以利用axios的全局配置

axios.defaults.参数名 = 参数值 //请求时会自动加上这个参数
1
15.6、axios实例和模块封装
当全局配置不适用时,可以使用实例

const instace1 = axios.create({
axios.defaults.参数名 = 参数值 //请求时会自动加上这个参数
// 使用
instance1({
url: ''
}).then(res => {

})
1
2
3
4
5
6
7
8
我们每次使用网络请求都会写一遍代码,这样看着好像没什么问题,但如果有一天axios要被替换,就会出现尴尬的情况,每一处都需要改,工作量巨大
模块封装
我们可以将网络请求当成一个单独的模块进行封装,每次使用时调用即可,这样即使不用了,也只需要修改一处代码。

src/network/request.js
方式一:

import axios from 'axios'

export function request1(config, success, failure){
// 1. 创建axios的实例
const instance = axios.create({
baseURL: 'http://ip:port',
timeout: 5000
})
// 调用实例
instance(config).then(res => {
...
success(res) // 回调
})
.catch(err = > {
failure(err) // 回调
})
}
// 调用
import {request} from './request'
request({
url: '',
timeout: 5000
}, res => {
console.log(res)
}, err => {
console.log(err)
})

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
方式二:

import axios from 'axios'

export function request2(config){
// 1. 创建axios的实例
const instance = axios.create({
baseURL: 'http://ip:port',
timeout: 5000
})
// 调用实例
instance(baseConfig).then(res => {
...
config.success(res) // 回调
})
.catch(err = > {
config.failure(err) // 回调
})
}
// 调用
import {request} from './request'
request({
baseConfig: {
url: '',
timeout: 5000
},
success: function(){
console.log(res)
},
failure: function(){
console.log(res)
}
})

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
方式三:

import axios from 'axios'

export function request3(config){
return new Promise((resolve, reject) = > {
// 1. 创建axios的实例
const instance = axios.create({
baseURL: 'http://ip:port',
timeout: 5000
})
// 调用实例
instance(config).then(res => {
...
resolve(res) // 回调
})
.catch(err = > {
reject(err) // 回调
})
})
}
// 调用
import {request} from './request'
request({
url: ''
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})

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
方式四:推荐

import axios from 'axios'

export function request3(config){
// 1. 创建axios的实例
const instance = axios.create({
baseURL: 'http://ip:port',
timeout: 5000
})
// 发送真正的网络请求
return instance(config)
}
// 调用
import {request} from './request'
request({
url: ''
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
15.7、拦截器
axios提供了拦截器,用于我们在发送每次请求或得到响应后,进行相应的处理
如何配置拦截器呢?
//请求拦截
instance.interceptors.request.use(config => {
return config
}, err =>{
console.log(err)
});

// 响应拦截
instance.interceptors.response.use(res => {
return res.data
}, err =>{
console.log(err)
});
1
2
3
4
5
6
7
8
9
10
11
12
13
16、初始化环境
16.1、创建项目
打开WebStorm,终端输入vue create 项目名创建项目
连接Git
16.2、划分目录结构
src/assets:静态资源
src/assets/img:图片资源
src/assets/css:样式资源
src/assets/css/normalize.css:第三方样式
src/assets/css/base.css:公共样式
src/components:公共组件
src/components/common:项目共用组件
src/components/content:当前项目公共组件
src/views:大型视图组件
src/router:路由相关组件
src/store:状态管理组件
src/network:网络相关组件
src/common:公共JS文件
src/common/const.js:公用常量
src/common/const.js:公用工具
16.3、配置别名
根目录创建 vue.config.js,打包时会与公共配置进行合并而生效

editorconfig
根目录创建 .editorconfig 文件,用来对文件的格式进行控制

https://blog.csdn.net/weixin_44338156/article/details/121420409
————————————————
版权声明:本文为CSDN博主「也曾心怀暖阳」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44338156/article/details/121420409

 

posted @ 2023-02-21 16:22  imxiangzi  阅读(731)  评论(0编辑  收藏  举报