Vue2-深度掌握
Vue 框架
Vscode-自定义代码片段
-
配置步骤
-
Vscode基本配置过程 vueTemplate
-
搜索
html.json,添加已经给出的具体模板即可【注意: 更改 Vue本地路径至匹配路径】 -
Vscode中键入 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{
"Html5-Vue": {
"prefix": "vue",
"body": [
"<!DOCTYPE html>",
"<html lang=\"zh-CN\">\n",
"<head>",
"\t<meta charset=\"UTF-8\">",
"\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
"\t<meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">",
"\tVue<hanla></hanla>模板文件<hanla></hanla> ",
"\t<script src=\"\"><\/script>", // 更改本地 Vue 文件路径
"</head>\n",
"<body>",
"\t<div id=\"app\">$1</div>\n",
"\t<script>",
"\t\tconst app = new Vue({",
"\t\t\tel: '#app',",
"\t\t\tdata: {message:'Hello Word'},",
"\t\t\tmethods: {}",
"\t\t});",
"\t</script>",
"</body>\n",
"</html>"
],
"description": "快速创建在html5 编写的 vue 模板"
}
} -
Vue-初体验
-
CDN使用 <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<!-- 将 src 地址的内容在浏览器访问并下载到本地自定义名称为 vue.js 文件 -->
<script src="../vue.js"></script>
<body>
<div id="app">
<h1>{{message}}</h1>
</div>
</body>
// 编程范式: 声明式编程
<script>
const app = new Vue({
el: "#app", // 挂载元素
data: { // 定义数据
message: "HelloVue"
}
});
</script>
Vue-指令
-
具体指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<div id="app">
<ul>
<li v-for="movie in movies"> // 电影在电影列表中遍历
{{movie}} // 【渲染/ 输出】电影名称
</li>
</ul>
</div>
<script>
const app = new Vue({
el: "#app",
data: {
message: "HelloVue",
movies: ['VueJS', 'Javascript', 'Java', 'c#']
}
});
</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<div id="app">
<h2>当前计数: {{counter}}</h2>
<button v-on:click="increment"> + </button>
<button v-on:click="subtraction"> - </button>
</div>
<script>
const app = new Vue({
el: "#app",
data: {
counter: 0
},
methods: {
// 加号函数事件
increment: function () {
console.log("正在点击加号");
// counter 需要通过 this.counter 形式
this.counter++;
},
// 减号函数事件
subtraction: function () {
console.log("正在点击减号");
this.counter--;
}
}
});
</script>1
2
3
4<h1 v-once> {{ message }} </h1>
只渲染元素和组件一次。随后的重新渲染,元素 / 组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<h1 v-html="url"> </h1>
<script>
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word',
url: '百度一下 '
},
});
</script>
<h1 v-text="message"> </h1>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<style>
[v-cloak] {
display: none;
}
</style>
// 解决闪动问题
<div id="app" v-cloak></div>
<script>
setTimeout(() => {
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word'
},
methods: {}
});
}, 1000);
</script>-
基础语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19作用: 动态绑定属性
缩写: : 【语法糖写法】
<img v-bind:src="imgUrl" alt=""> // 普通写法
<img :src="imgUrl" alt=""> // 语法糖写法
<a :href="url">百度一下 </a>
<script>
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word',
imgUrl: "https://gitee.com/wang_hong_bin/repo-bin/raw/master/IMG_0021(20210418-000849).PNG",
url: "https://www.baidu.com"
},
methods: {}
});
</script> -
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方式一:
语法: <h1 :class="{active:isActive,line:isLine}"> {{message}} </h1>
data: {
message: 'HelloWord',
isActive: true,
isLine: true
},方式二:
语法: <h1 :class="getClass()"> {{message}} </h1>
data: {
message: 'HelloWord',
isActive: true,
isLine: true
},
methods: {
getClass: function () {
return {
active: this.isActive,
line: this.isLine
}
}
}方法三: 数组语法
data(){
return{
arrClass:['xxA','xxB']
}
}
<p :class="arrClass">Text</p> -
v-bind动态绑定 style(对象语法)1
2
3
4
5
6
7
8
9
10
11//
{{message}}
// 'red' 属性值必须添加单引号,否则会被解析为变量
<h1 :style="{backgroundColor:'red'}"> {{message}} </h1>
// 变量写法
<h1 :style="{backgroundColor: finaColor}"> {{message}} </h1>
data: {
message: 'Hello Word',
finaColor: 'blue'
},-
渲染
动态绑定 
-
-
基础使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<div id="app">
<input type="text" v-model="message">
<h1> {{message}}</h1>
</div>
// 原理
<div id="app">
<input type="text" :value="message" v-on:input="函数名 valueChange">
<h1> {{message}}</h1>
</div>
methods:{
valueChange(event){
this.message = event.target.value;
}
}
// 其他
v-model 结合 radio-
渲染
v-model
-
-
案例练习
v-model1
2
3
4
5
6
7
8
9
10
11
12<form action="#">
<label for="agree">
<input type="checkbox" name="" id="agree" v-model="isAgree"><a href="javascript:;">查看许可协议 </a>
</label>
<h1>协议许可: {{isAgree}}</h1>
<button :disabled="!isAgree">下一步 </button>
</form>
data:{
isAgree: false
} -
多选框
v-model1
2
3
4
5
6
7
8
9<input type="checkbox" v-model="hobbies" value="javascript">javascript
<input type="checkbox" v-model="hobbies" value="java">java
<input type="checkbox" v-model="hobbies" value="c#">c#
<input type="checkbox" v-model="hobbies" value="python">python您选择的兴趣是: {{hobbies}}
data:{
hobbies: []
} -
v-for值绑定 1
2
3
4
5<!-- 值绑定 -->
<label v-for="item in originHobbies" :for="item">
<input type="checkbox" :value="item" v-model="hobbies">{{item}}
</label> -
修饰符
lazy修饰符 - 默认情况下,
v-model默认是在 input事件中同步输入框的数据的, 也就是说, 一旦有数据发生对应的 data中的数据就会自动发生改变 lazy修饰符可以让数据在失去焦点或者回车时才会更新
- 默认情况下,
number修饰符 - 默认情况下,
在输入框中无论我们输入的是字母还是数组, 都会被当作字符串类型进行处理, 但是我们希望处理的数字类型, 那么最好直接将内容当作数字处理 number修饰符可以让输入框中输入的内容自动转换为数字类型
- 默认情况下,
trim修饰符 - 如果输入的内容首尾有很多空格,
通常我们希望将其去除 trim修饰符可以过滤内容左右两边的空格
- 如果输入的内容首尾有很多空格,
-
Vue-MVVM
-
MVVMmvvm
-
基础选项
1
2
3
4
5
6
7
8
9
10el:
类型: string | HTMLElement
作用: 决定之后Vue 实例会管理哪一个 DOM
data:
类型: Object | Function
作用: Vue实例对应的数据对象
methods:
类型: {[key:string]:Function}
作用: 定义属于Vue 的一些方法, 可以再其他地方调用, 也可以在指令中使用
键盘事件
-
Vue中常用键别名 - 回车
=> enter - 删除
=> delete捕获删除和退格按键 - 退出
=> esc - 空格
=> space - 换行
=> tab必须配合 keydown 使用 - 上
=> up - 下
=> down - 左
=> left - 右
=> right
- 回车
-
Vue中为提供别名的案件, 可以使用按键原始的 key值去绑定, 但注意要转为 短横线命名 -
系统修饰键:
ctrl、alt、shift、meta-
配合
keyup使用: 按下修饰键的同时, 在按下其他键,随后释放其他键, 事件才被触发 -
配合
keydown使用: 正常出发事件 -
使用
1
@keyup.ctrl.f (input 框获取聚焦)
-
-
vue.config.keyCodes.自定义键名 = 键码, 可以去定制按键别名
计算属性
-
计算属性使用时不需要添加小括号
- 计算属性
- 定义: 要用的属性不存在,
要通过已有的属性计算得来 - 原理: 底层借助了
Object.defineproperty方法提供的 getter 和 setter get函数执行时机 - 初次读取会执行一次
- 当依赖的数据发生改变时会被再次调用
- 优势: 与
methods实现相比, 内部有缓存机制 (可以实现复用), 效率更高, 调式方便 - 备注
- 计算属性最终会出现在
vm上, 直接读取使用即可 - 如果计算属性要被修改,那必须写
set函数去响应修改, 且 set中药引用计算时依赖的数据发生改变
- 计算属性最终会出现在
- 定义: 要用的属性不存在,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<div id="app">
{{fullName}}
</div>
<script>
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word',
firstName: "Vue",
lastName: ".js"
},
methods: {},
computed: {
// get 时,简写
fullName: function () {
return this.firstName + this.lastName
}
}
});
</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<h1>{{totalPrice}}</h1>
books: [{
id: 1101,
name: 'Unix编程艺术',
price: 100
},
{
id: 1102,
name: '代码大全',
price: 100
},
{
id: 1103,
name: '深入理解计算机原理',
price: 100
},
{
id: 1104,
name: '现代操作系统',
price: 100
}
]
// 计算属性
computed: {
totalPrice: function () {
// 定义变量存储书籍价格
let price = 0;
// 循环遍历对象
for (let book = 0; book < this.books.length; book++) {
// 书籍价格累加
price += this.books[book].price
}
return price;
}
}
// Es6循环高级用法
for(let i in this.books){
}
for(let i of this.books){
console.log(i); // i == this.books[book]
}计算属性与 methods使用区别 
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
<html lang="zh-CN">
<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>计算属性和 methods 的对比 </title>
<script src="../vue.js"></script>
</head>
<body>
<div id="app">
<!-- methods -->
<h1>{{getFullName()}}</h1>
<h1>{{getFullName()}}</h1>
<h1>{{getFullName()}}</h1>
<h1>{{getFullName()}}</h1>
<!-- computed -->
<h2> {{fullName}} </h2>
<h2> {{fullName}} </h2>
<h2> {{fullName}} </h2>
<h2> {{fullName}} </h2>
</div>
<script>
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word',
firstName: "Vue",
lastName: ".js"
},
methods: {
getFullName: function () {
console.log("methods 输出··········");
}
},
computed: {
fullName: function () {
console.log("计算属性输出···········");
}
}
});
</script>
</body>
</html>-
属性分析
监视属性
watch:-
当监视属性变化时,
回调函数自动调用, 进行相关操作 -
监视的属性必须存在,
才能进行监视 -
监视的两种写法
-
.new Vue时传入 watch 配置 -
通过
vm.$watch监视 1
2
3
4
5
6
7
8
9
10const vm = new Vue({
data(){
return{
isHot: true
}
}
});
vm.$watch('isHot',{
同上
})
-
-
-
基础使用
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<template>
<div>
<el-button @click="changeWeatherInfo" type="success">获取天气信息 </el-button>
<h1>今天天气是: {{ info }} </h1>
</div>
</template>
<script>
export default {
name: "Orders",
data() {
return {
input: '',
isHot: true
}
},
methods: {
changeWeatherInfo() {
this.isHot = !this.isHot;
},
},
computed: {
info() {
return this.isHot ? '凉爽' : '炎热';
}
},
// 监视属性
watch: {
isHot: {
immediate: true, // 初始化时调用一下
// export type WatchHandler<T> = string | ((val: T, oldVal: T) => void);
handler(newValue, oldValue) {
console.log('获取的新值: ' + newValue);
console.log('获取的旧值: ' + oldValue);
}
}
}
}
</script>
<style scoped>
</style> -
渲染
watch基础使用 
-
深度监视
-
深度监视:
Vue中的 watch默认不监视对象内部值的改变 ( 一层)- 配置
deep:true可以检测对象内部值改变 ( 多层)
-
备注:
Vue自身可以检测对象内部值的改变, 但 Vue提供的 watch默认不可以 - 使用
watch时根据数据的具体结构, 决定是否采用深度监视
-
-
简写
1
2
3
4
5
6
7检测的属性: isHot
watch:{
// 代替 handler 非深度监视
isHot(newValue,oldValue){
}
}
- 计算属性
V-on 修饰符
-
修饰符
-
基础使用
1
2
3
4
5
6
7
8
9
10<div>
// element-ui 不传参
<el-button @click="getInnerHTML" type="success">event</el-button>
</div>
<div>
// 传递参数
<el-button @click="getValue(66,$event)" type="success">event</el-button>
</div>1
2
3
4
5
6
7
8
9
10
11getInnerHTML(event) {
// 获取按钮的文本信息
console.log(event.target.innerHTML)
}
getInnerHTML(number,event) {
console.log(number);
console.log(event)
} -
渲染
事件参数 
-
stop1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25------------------------------
【@click.stop="函数"】 阻止冒泡
------------------------------
<div id="app" @click="divClick">
<button @click.stop="btnClick">按钮 </button>
</div>
<script>
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word'
},
methods: {
divClick: function () {
console.log('divClick·······');
},
btnClick: function () {
console.log('btnClick··············');
}
}
});
</script> -
prevent阻止默认事件 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<form action="https://www.baidu.com">
<input type="submit" value="提交按钮" @click.prevent="submitClick">
</form>
methods:{
submitClick: function () {
console.log('打印不提交····');
}
}
--------------------------------------------------------
noHref: function () {
console.log('不会发生跳转的超链接··········');
}
<a href="https://www.baidu.com" @click.prevent="noHref">百度一下, 这里不会发生跳转 -
监听键帽的点击
1
2
3
4
5
6
7
8
9<!-- 监听键帽点击 -->
<input type="text" @keyup.enter="keyUp">
methods:{
keyUp: function(){
console.log("enter 键触发监听事件");
}
} -
组件点击监听
1
<cpn @click.native="cpnClick"> </cpn>
-
@click.once1
2// 事件只触发一次
<button @click.once="onceClick">点击
-
条件渲染
-
条件渲染
-
条件渲染
v-if
v-if='表达式'v-else-if='表达式'v-else- 适用于:
切换频率较低的场景 - 特点:
不展示DOM 元素直接被移除
v-show- 写法:
v-show='表达式' - 适用于:
切换频率较高的场景 - 特点:
不展示的DOM 元素未被移除, 仅仅是使用样式隐藏掉
- 写法:
- 使用
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
36
37
38
39
<html lang="zh-CN">
<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>Vue模板文件 </title>
<script src="../../vue.js"></script>
</head>
<body>
<div id="app">
<div v-if="isUser">
<label for="user">
账号登录 <input type="text" id="user">
</label>
</div>
<div v-else>
<label for="email">
邮箱登录 <input type="text">
</label>
</div>
<button @click="isUser=!isUser">切换登录类型 </button>
</div>
<script>
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word',
isUser: true
},
methods: {}
});
</script>
</body>
</html>-
问题描述
- 如果我们在没有输入内容的情况下,
切换了类型, 我么会发现文字依然显示之前输入的内容 - 由于已经发生切换,
我们并没有再另外一个 input元素中输入
- 如果我们在没有输入内容的情况下,
-
问题解答
这是由于
Vue在进行 DOM渲染时,处于性能考虑,会尽可能的复用已经存在的元素,而不是重新创建新的元素 -
解决方案
-
如果我们不希望 Vue
出现类似重复利用的问题, 可以给对应的 input添加key-
保证
key的唯一性
-
保证
-
如果我们不希望 Vue
-
列表渲染
-
列表渲染
-
基础使用
1
2
3
4
5<ul>
<li v-for="user in userInfoList" :key="user.id">
{{ user }} ----- {{ user.id }} + {{ user.userName }}
</li>
</ul>1
2
3
4
5
6
7
8
9
10
11
12
13
14data(){
return {
userInfoList: [{
id: '1001',
userName: 'userA'
}, {
id: '1002',
userName: 'userB'
}, {
id: '1003',
userName: 'userC'
}]
}
} -
渲染
列表渲染 
-
index作为 keyindex作为 key
-
面试题
-
虚拟
DOM中 key的作用 key是虚拟 DOM对象的标识, 当数据发生变化时, Vue会根据 [ 新数据]生成 [ 新的虚拟],随后DOM Vue进行 [ 新虚拟]DOM 与 [ 旧虚拟]DOM 的差异比较, 比较规则如下: -
对比规则
-
旧虚拟
DOM中找到了与新虚拟 DOM相同的 key- 若虚拟
DOM中内容没变, 直接使用之前的真是 DOM - 若虚拟
DOM中内容变了, 则生成新的真实 DOM,随后替换页面中之前的真实DOM
- 若虚拟
-
旧虚拟
DOM中未找到与新虚拟 DOM相同的 key=> 创建新的真实DOM,随后渲染到页面
-
-
用
index作为 key可能会引发的问题 若对数据进行: 逆序添加、逆序删除等破化顺序操作:会产生没有必要的真实
DOM更新 => 界面效果没问题, 但效率低 -
如果结构中还包含输入类的
DOM: 会产生错误DOM更新 => 界面有问题 -
开发中如何选择
key- 最好使用每条数据的唯一标识作为
key,比如id、手机号、身份证号、学号等唯一值 - 如果不存在对数据的逆序添加,
逆序删除等破坏顺序操作, 仅用于渲染列表用于展示,使用 index作为 key是没有问题的
- 最好使用每条数据的唯一标识作为
-
-
过滤器
-
过滤器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<style>
table {
border: 1px solid #e9e9e9;
border-collapse: collapse;
border-spacing: 0;
}
th,
td {
padding: 8px 16px;
border: 1px solid #e9e9e9;
text-align: left;
}
th {
background-color: #f7f7f7;
color: #5c6b77;
font-weight: 600;
}
</style>-
布局
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<table>
<thead>
<tr>
<th></th>
<th>书籍名称 </th>
<th>出版日期 </th>
<th>价格 </th>
<th>购买数量 </th>
<th>操作 </th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in books">
<td> {{item.id }}</td>
<td> {{item.name }}</td>
<td> {{item.date }}</td>
<td> {{item.price|showPrice }}</td>
<td>
<button @click="decrement(index)" :disabled="item.count<=1"> - </button>
{{ item.count }}
<button @click="increment(index)"> + </button>
</td>
<td> <a href="javascript:;" @click="removeH(index)">删除 </a> </td>
</tr>
</tbody>
</table>
-
测试数据
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
26books: [{
id: 1,
name: "<<算法与导论>>",
date: "2006-3",
price: 85.00,
count: 1
}, {
id: 2,
name: "<<算法与导论>>",
date: "2006-3",
price: 85.00,
count: 1
}, {
id: 3,
name: "<<算法与导论>>",
date: "2006-3",
price: 85.00,
count: 1
}, {
id: 4,
name: "<<算法与导论>>",
date: "2006-3",
price: 85.00,
count: 1
}]
},
-
详细使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24filters: {
showPrice(price) {
return '¥' + price.toFixed(2);
}
}事件监听:
// 添加按钮事件
increment(index) {
console.log('添加');
this.books[index].count++;
},
// 减少按钮事件
decrement(index) {
console.log('减少');
this.books[index].count--;
},
// 删除按钮事件
removeH(index){
this.books.splice(index,1);
}
-
详细使用
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
33computed: {
totalPrice() {
--------------------------------------------------------------
// 写法一:
let totalPrice = 0;
for (let i = 0; i < this.books.length; i++) {
totalPrice += this.books[i].price * this.books[i].count;
}
--------------------------------------------------------------
// 写法二:
for (let i in this.books) {
console.log(i);// 索引
totalPrice += this.books[i].price * this.books[i].count;
}
--------------------------------------------------------------
// 写法三:
for (let item of this.books) {
console.log(item); // 获取到书籍
totalPrice += item.price * item.count;
}
--------------------------------------------------------------
// 写法四[高阶函数]:
return this.books.reduce(function(preValue,book){
return preValue + book.price*book.count;
},0)
}
--------------------------------------------------------------
return totalPrice // 普通方法都要返回 totalPrice,
}
-
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<template>
<div>
<el-input style="width: 70%" v-model="keyWord"></el-input>
<ul>
<li v-for="user in filterUserInfo" :key="user.id">{{ user.userName }}</li>
</ul>
</div>
</template>
<script>
import Vue from "vue";
export default {
name: "Orders",
data() {
return {
keyWord: '',
userInfo: [
{id: '1001', userName: '马冬梅'},
{id: '1002', userName: '周冬雨'},
{id: '1003', userName: '周杰伦'},
{id: '1004', userName: '温兆伦'}
],
filterUserInfo: []
}
},
// watch 实现过滤器 不推荐
watch: {
keyWorld: {
immediate: true,
handler(val) {
this.filterUserInfo = this.userInfo.filter((info) => {
return info.userName.indexOf(val) !== -1
})
}
}
}
}
</script>watch-filter
-
计算属性实现过滤器
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<template>
<div>
<el-input style="width: 70%" v-model="keyWord"></el-input>
<ul>
<li v-for="user in filterUserInfo" :key="user.id">{{ user.userName }}</li>
</ul>
</div>
</template>
<script>
import Vue from "vue";
export default {
name: "Orders",
data() {
return {
keyWord: '',
userInfo: [
{id: '1001', userName: '马冬梅'},
{id: '1002', userName: '周冬雨'},
{id: '1003', userName: '周杰伦'},
{id: '1004', userName: '温兆伦'}
]
}
},
computed: {
filterUserInfo() {
return this.userInfo.filter((info) => {
return info.userName.indexOf(this.keyWord) !== -1
})
}
}
}
</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
65
66
67
68
69
70
71
72
73
74<template>
<div>
<div class="flContainer hidden">
<el-input v-model="keyWord"></el-input>
<el-button @click="sortType=2" type="primary">年龄升序 </el-button>
<el-button @click="sortType=1" type="success">年龄降序 </el-button>
<el-button @click="sortType=0" type="danger">原始顺序 </el-button>
</div>
<ul>
<li v-for="user in filterUserInfo" :key="user.id">{{ user.userName }} {{ user.age }}岁 </li>
</ul>
</div>
</template>
<script>
import Vue from "vue";
export default {
name: "Orders",
data() {
return {
// 搜索关键字
keyWord: '',
// 排序标识 0 原始顺序,1降序,2 升序
sortType: 0,
userInfo: [
{id: '1001', userName: '马冬梅', age: 14},
{id: '1002', userName: '周冬雨', age: 23},
{id: '1003', userName: '周杰伦', age: 18},
{id: '1004', userName: '温兆伦', age: 10}
]
}
},
computed: {
filterUserInfo() {
const array = this.userInfo.filter((info) => {
return info.userName.indexOf(this.keyWord) !== -1
})
// 判断一下 sortType 是否需要排序
if (this.sortType) {
array.sort((obj1, obj2) => {
console.log(obj1, obj2)
return this.sortType === 1 ? obj2.age - obj1.age : obj1.age - obj2.age;
})
}
return array
}
}
}
</script>
<style scoped>
.flContainer {
width: 100%;
}
.el-input {
float: left;
width: 50%;
margin-right: 20px;
}
.el-button {
float: left;
margin: auto 5px;
}
.hidden {
overflow: hidden;
}
</style>-
原始顺序分析
原始顺序分析 
-
:key的验证 :key的验证, 破坏顺序, 未发生乱序 
-
-
分析
分析 
-
失败
1
2// 原则上是改变了,
但是 vue 与 dev-tool 不认
this.userInfo[0] = {id: '1001', userName: '李四', age: 28}; -
原理分析
-
Vue会监视 data中所有层次的数据 -
如何检测对象中的数据
-
通过
setter实现监视, 且要在 new Vue时就传入要监测的数据 -
对象中后追加的属性,
Vue默认不做响应式处理 -
如需给后添加的属性做响应式,
请使用如下 APIVue.set(target,propertyName/index,value) | vm.$set(target,propertyName/index,value)
-
-
如何检测数组中的数据
- 通过包裹数组更新元素的方法实现,
本质就是做了两件事 - 调用原生对应的方法对数组进行更新
- 重新解析模板,
进而更新页面
- 通过包裹数组更新元素的方法实现,
-
在
Vue修改数组中的某个元素一定用如下方法 - 使用这个
API:push、pop、shift、unshift、splice、sort、reverse Vue.set() 或 vm.$set
- 使用这个
-
特别:
vue.set() 和 vm.$set()不能给 vm 或 vm的根数据对象添加属性
-
-
数据劫持
1
student: [{ id: 1, name: 'A' }]
拥有 setter和 getter
-
数组-高阶函数
-
filter1
2
3
4
5
6
7
8
9
10
11filter
中回调函数有一个要求: 必须返回一个 boolean 值
true: 当返回 true 时,函数内部会自动将这次回调的【n】加入到新的数组中
false: 当返回 false时, 函数内部会过滤掉这次的 【n】
// 1. filter 高阶函数
const nums = [100, 110, 89, 43, 299];
let filterNums = nums.filter((n) => {
return n > 100
});
console.log(filterNums); -
map1
2
3
4
5// 2. map 高阶函数
let mapNums = nums.map(function (n) {
return n * 2
})
console.log("乘积: ", mapNums); -
reduce1
2
3
4
5
6// reduce 作用对数组中所有内容进行汇总
var apps = [1, 2, 3, 4]
let total = apps.reduce(function (preValue, n) {
return preValue + n
}, 0);
console.log(total); -
高阶函数的汇总使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 定义一个数组过滤掉 小于 100,
对过滤后的数组 *2,再对数组求和
// 函数式编程
let newArrays = [900, 120, 89, 54, 210]
let newA = newArrays.filter((n) => {
return n < 100 // 过滤掉小于 100 的数
}).map((n) => {
return n * 2 // 对新数组内容*2
}).reduce((preValue, n) => {
return preValue + n
}, 0)
console.log(newA);
// 简化操作
{
let newArrays = [900, 120, 89, 54, 210];
let newA = newArrays.filter(n => n < 100).map(n => n * 2).reduce((preValue, n) => preValue + n)
console.log(newA);
}
生命周期
-
分析
生命周期阶段分析 
-
总结
- 创建之前指的是
数据监测、数据代理
- 创建之前指的是
组件化
-
详细使用
-
传统方式
传统方式编写应用 
-
组件化
组件化 
-
定义
组件化的定义 
-
流程
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
<html lang="zh-CN">
<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>初始组件化 </title>
<script src="../../../vue.js"></script>
</head>
<body>
<div id="app">
<!-- 组件化开发 -->
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
<my-cpn></my-cpn>
</div>
<script>
// 1. 创建组件构造器对象
const cpnContructor = Vue.extend({
template: `
<div>
哈哈哈
1111
哈哈哈
1111
</div>
`
});
// 2. 注册组件
// Vue.component('注册组件的标签名','组件构造器');
Vue.component('my-cpn', cpnContructor);
// 3. 使用组件
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word'
},
methods: {}
});
</script>
</body>
</html>
-
渲染数据
1
2
3
4
5
6
7
8
9
10
11哈哈哈 1111 哈哈哈 1111 哈哈哈 1111 哈哈哈 1111 哈哈哈 1111 哈哈哈 1111 哈哈哈 1111 哈哈哈 1111
-
局部组件
-
局部组件
-
疑问
怎么注册的组件才是局部组件?
-
局部组件
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
<html lang="zh-CN">
<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>局部组件 </title>
<script src="../../vue.js"></script>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
<script>
// 创建组件构造器
const cpnC = Vue.extend({
template: `
`组件构造器
});
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word'
},
methods: {},
components: {
// 创建组件标签名: 组件构造器
cpn: cpnC
}
});
</script>
</body>
</html>
-
总结
结论: 在
Vue实例中创建的组件为局部组件
-
父子组件
-
父子组件
注意:
Unknown custom element: <cpn1>错误解决方案: 组件注册要么在全局,只有注册了的组件才可用
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
<html lang="zh-CN">
<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>父子组件 </title>
<script src="../../../vue.js"></script>
</head>
<body>
<div id="app">
<cpn2></cpn2>
<cpn1></cpn1>
</div>
<script>
</script>
</body>
</html>-
关于
VueComponent-
xx组件本质是一个名为 VueComponent的构造函数,且不是编程人员定义的, 是 Vue.extend生成的 组件的本质 
-
我们只需要写
<school/>,Vue解析时会帮我们创建 school组件的实例对象, 即 Vue帮我们执行 new VueComponent(options)验证调用次数 
-
特别注意:
每次调用 Vue.extend,返回的都是一个全新的VueComponent全新 VueComponent
-
关于
this指向 -
组件配置中
data函数、methods 中的函数、 watch中的函数、 computed中的函数, 它们的 this均是 VueComponent实例对象( vc) -
new Vue()配置中: data函数、methods 中的函数、 watch中的函数、 computed中的函数, 它们的 this均是 Vue实例对象 ( vm) -
this指向 this指向 
-
-
VueComponent的实例对象,以后简称 vc,Vue的实例对象简称 vm
-
-
严格来说
vm!=vc-
vm可以有 el,而vc不能指定 -
vm允许 data为函数或对象, vc不行 -
vc有的功能 vm都有 -
内置关系
内置关系 
-
一个重要的内置关系
VueComponent.prototype.__proto__===Vue.prototype -
为什么要有这个关系: 让组件实例
( vc)可以访问到 Vue原型上的属性, 方法
-
-
组件模板抽离
-
组件模板抽离
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// type="text/x-template" 标识: id="cpnT"
<script type="text/x-template" id="cpnT">
<div>
<h1> 模板抽离 </h1>
</div>
</script>
// 创建组件构造器
// 组件化: 语法糖
Vue.component('cpn1', {
template: '#cpnT'
})
// 使用
<cpn1></cpn1>1
2
3
4
5
6
7
8
9
10
11
12<template id="cpn2">
<h2> template 模板</h2>
</template>
Vue.component('cpn2', {
template: '#cpn2'
})
// 使用
<cpn2></cpn2>
组件可以访问 Vue 实例数据吗
-
组件数据
答案: 不可以
- 组件是一个单独功能模块的封装
- 这个模块有属于自己的 HTML 模板,
也应该由于属于自己的数据 data
- 这个模块有属于自己的 HTML 模板,
-
访问测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<div id="app">
<cpn></cpn>
</div>
<template id="cpnT">
<div>
<h1> {{title}} </h1>
</div>
</template>
<script>
//
Vue.component('cpn', {
template: '#cpnT',
data() {
return {
title: "Title"
}
}
});
-
为什么组件中的
data必须是函数 Vue组件中 data值不能为对象,因为对象是引用类型,组件可能会被多个实例同时引用。如果 data值为对象,将导致多个实例共享一个对象,其中一个组件改变 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<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<template id="cpnT">
<div>
<h1>计数器: {{count}}</h1>
<button @click="increment"> + </button>
<button @click="descment"> - </button>
</div>
</template>
<script>
// 注册组件
Vue.component('cpn', {
template: '#cpnT',
data() {
return {
count: 1
}
},
methods: {
increment() {
this.count++
},
descment() {
this.count--
}
}
});
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word'
},
methods: {}
});
</script>
</body>-
data类型使用对比 data为函数时 data为对象时 

-
- 组件是一个单独功能模块的封装
单文件组件
-
命名
- 全小写 | 短杠
my-student.vue - 首字母大写
MyStudent.vue
- 全小写 | 短杠
-
单文件组件
1
2
3
4
5
6
7
8
9
10
11
12
13<template>
<div>
<!-- 组件结构 -->
</div>
</template>
<script>
// 组件交互相关的代码(数据、方法等等)
</script>
<style scoped>
/* 组件的样式 */
</style> -
使用
1
2
3
4
5
6<scrip>
import xxx from 'path'
components:{
xxx
}
</script>
组件通信
-
组件通信
-
原理图
组件通信的两种方式 
-
props的值有两种方式 - 字符串数组,
数组中的字符串就是传递时的名称 - 对象,
对象可以摄制为传递时的类型, 也可以设置为默认值等
注意: 抽离
template时需要一个最外层 div包裹, 否则会产生如下错误: 1
2
3
4
5
6vue.js:634 [Vue warn]: Error compiling template:
Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-
else-if to chain them instead. - 字符串数组,
-
父子组件通信
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
<html lang="zh-CN">
<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>组件通信-父组件向子组件传递数据 </title>
<script src="../../../vue.js"></script>
</head>
<body>
<div id="app">
<h1>父组件 </h1>
<cpn :ccourse="course"> </cpn>
</div>
<template id="cpnT">
<div>
<h1>子组件 </h1>
<h2> {{ccourse}}</h2>
</div>
</template>
<script>
// 建立新的子组件
const cpn = {
template: '#cpnT',
// 父传子: props
props: ['ccourse']
}
// app 作为父组件
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word',
// 父组件数据
course: ['JavaScript', 'Java', 'Python']
},
methods: {},
components: {
cpn
}
});
</script>
</body>
</html>
-
poops当类型是
对象或者数组时,默认值必须是一个函数 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18ccourse:{
type: Array, // 可以指定类型
default: [],
require: true [布尔值: 必填项]
}
// 改为如下写法
ccourse:{
type: Array, // 可以指定类型
default(){
return []
},
require: true [布尔值: 必填项]
}
// 允许自定义验证 【coderwhy p58】1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21props:{
// 类型限制
ccourse: Array
// 提供默认值
ccourse:{
type: String // 可以指定类型
default: '传递默认值', // 可以传递默认值 【没有进行数据绑定时 --> v-bind, 又进行使用时 --> {{course]}},将获取默认值 】
require: true [布尔值: 必填项]
}
}
// 支持的所有数据类型
String
Number
Boolean
Array
Object
Date
Function
Symbol
-
驼峰标识问题
在
v-bind中暂不支持驼峰标识, 需要将驼峰标识转换为: HelloWorld -- 转换为 --> :hello-world 绑定注意: 在子组件需要使用父组件的数据时,
那么应想到 props,子组件使用父组件数据时是在模板配置时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// 子组件的模板
<template id="cpnT">
<div>
<h2> 子组件 </h2>
<h1> 子组件使用: {{cinfo}}</h1>
</div>
</template>
// 绑定 cinfo 子组件使用时定义的变量名称 info: 父组件的变量
// 通过 v-bind
<cpn :cinfo="info"></cpn>
// 分析 cinfo 和 info
const cpn = {
template: "#cpnT",
props: {
// cinfo 子组件定义的变量名
cinfo: {
type: Object,
default(){
return {}
}
}
}
};
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word',
// info 父组件的变量名
info: {
name: "张三",
age: 18,
sex: "男"
}
},
-
子传父
-
数据传递-子组件传向父组件
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<!DOCTYPE html>
<html lang="zh-CN">
<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">
<hanla></hanla>组件通信-子传父<hanla></hanla>(自定义事件)
<script src="../../../vue.js"></script>
</head>
<body>
<div id="app">
父组件
<cpn @itemclick="cpnclick"> </cpn>
</div>
<template id="cpnT">
<div>
<button v-for="item in categories" @click="btnclick(item)">{{item.name}}</button>
</div>
</template>
<script>
// 子组件
const cpn = {
template: "#cpnT", // 绑定模板
// 自定义事件
data() {
return {
categories: [{
id: 'aaa',
name: "热门推荐"
},
{
id: 'bbb',
name: "手机数码"
},
{
id: 'ccc',
name: "家用家电"
},
{
id: 'ddd',
name: "电脑办公"
}
]
}
},
methods: {
btnclick(item) {
// 自定义事件 发射
// this.$emit("事件名称","参数");
this.$emit('itemclick', item);
console.log("子组件: ", item);
}
}
};
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word'
},
methods: {
cpnclick(item) {
console.log("父组件: ", item);
}
},
// 注册组件
components: {
cpn
}
});
</script>
</body>
</html>-
分析
btnclick(子组件事件)<cpn @itemclick=”cpnclick”>
itemclick事件为子组件发射的自定义事件名称 【 this.$emit(“自定义事件名”,“参数”)】cpnclick(父组件所监听的子组件的处理事件名称) -
自定义事件流程
-
在子组件中,
通过 $emit()来触发 -
在父组件中,
通过 v-on来监听子组件事件
-
-
父子组件的访问方式
-
父子组件的访问方式
-
父组件访问子组件: 使用
$children或 $refs -
实现
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
<html lang="zh-CN">
<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>组件通信-ref 的使用 </title>
<script src="../../../vue.js"></script>
</head>
<body>
<div id="app">
<cpn ref="refName"></cpn>
<button @click="btnClick"> 按钮 </button>
</div>
<template id="cpnT">
<div>
<h1> 我是子组件 </h1>
</div>
</template>
<script>
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word'
},
methods: {
btnClick() {
console.log(this.$refs.refName.cpnValue);
}
},
components: {
cpn: {
template: "#cpnT",
data() {
return {
cpnValue: "子组件Value 将被获取"
}
},
methods: {
}
}
}
});
</script>
</body>
</html>
- 子组件访问父组件: 使用
$parent
-
组件化编码流程 (通用)
-
组件化
- 实现静态组件: 抽取组件,
使用组件实现静态页面效果 - 展示动态数据
- 数据的类型,
名称是什么? - 数据保存在那个组件中
- 数据的类型,
- 交互: 从绑定事件监听开始
- 实现静态组件: 抽取组件,
Nano ID
-
是什么
一个小巧、安全、
URL友好、唯一的 JavaScript字符串ID生成器。 -
安装
1
npm install --save nanoid
-
使用
1
import { nanoid } from 'nanoid'
TodoList 案例
-
案例
-
效果渲染
todolist
-
子组件与父组件数据传递
子组件与父组件数据传递 
-
在未使用其他方式之前,
数据可以通过逐层传递实现 在未使用其他方式之前, 数据可以通过逐层传递实现 
-
TodoList案例总结 - 组件化编码流程
- 拆分静态组件: 组件要按照功能点拆分,
命名不要与 html元素冲突 - 实现动态组件: 考虑好数据要存放的位置,
数据是一个组件在用, 还是一些组件再用 - 一个组件在用:
放在组件自身即可 - 一些组件再用: 放在他们共同的父组件上
( 状态提升)
- 一个组件在用:
- 实现交互: 从绑定事件开始
- 拆分静态组件: 组件要按照功能点拆分,
props适用于 - 父组件
==>子组件 通信 - 子组件
===>父组件 通信( 要求父先给子一个函数)
- 父组件
- 使用
v-model时要切记: v-model绑定的值不能是 props传过来的值, 因为 props是不可以修改的 props传过来的若是对象类型的值, 修改对象中的属性时 Vue不会报错, 但不推荐这样做
- 组件化编码流程
-
组件自定义事件
-
组件自定义事件
-
回顾
(子组件向父组件传递数据, 通过函数) 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// App:
<template>
<div>
<h1>App: {{ receiveSchoolName }}</h1>
<school :receiveSchoolSendName="receiveSchoolSendName"></school>
</div>
</template>
// School
<template>
<div class="school">
<h1>School => 学校名称: {{ schoolName }}</h1>
<h1>School => 学校地址: {{ schoolAddress }}</h1>
<el-button type="success" @click="sendSchoolName">上传名称给 App</el-button>
<hr>
<student></student>
</div>
</template>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// App:
data(){
return{
receiveSchoolName: ''
}
},
methods:{
// 给 App 通过函数传递数据
receiveSchoolSendName(name) {
console.log('App收到了学校名: ', name)
this.receiveSchoolName = name
}
}
// School
<script>
import Student from "@/views/Student"
export default {
name: "Test",
props: ['receiveSchoolSendName'],
data() {
return {
schoolName: 'coder-itl',
schoolAddress: 'BJ'
}
},
components: {
Student
},
methods: {
sendSchoolName() {
this.receiveSchoolSendName(this.schoolName)
}
}
}
</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<template>
<div class="student">
<h1>Studnet=> 学生姓名: {{ stuName }}</h1>
<h1>Studnet=> 学生年龄: {{ stuAge }}</h1>
<el-button type="success" @click="sendStudentName">上传学生名称给 App</el-button>
</div>
</template>
<script>
export default {
name: "Student",
data() {
return {
stuName: '里斯',
stuAge: 18
}
},
methods: {
sendStudentName() {
// 触发 Student 组件实例身上的 coder-itl 事件
this.$emit('coderitl', this.stuName)
}
},
mounted() {
}
}
</script>
<style scoped>
.student {
background-color: skyblue;
}
.el-button {
margin: 10px;
}
</style> -
渲染
this.$emit('自定义事件名',参数) 
-
$on与 ref配合 mounted使用 1
2
3
4
5
6
7
8
9
10
11
12
13
14// App
<!-- 自定义事件绑定在哪就去哪里做触发 -->
<student ref="studentRef"></student>
methods:{
getStudentName(name) {
this.receiveSchoolName = name
console.log('coderitl被调用了...', name);
}
},
mounted() {
this.$refs.studentRef.$on('coderitl', this.getStudentName)
}
-
解绑一个
1
this.$off('coderitl')
-
解绑多个自定义事件
1
this.$off(['coderitl','b'])
-
解绑所有
1
this.$off()
-
绑定自定义事件
-
第一种方式,
在父组件中: <Demo @coderitl="test"></Demo> -
第二种方式,
在父组件中 1
2
3
4
5
6
7
8
9
10
11
12
13<Demo ref="DemoRef"/>
...
methods:{
test(){
...
}
}
mounted(){
this.$refs.xxx.$on('coderitl',this.test)
}
-
若想让自定义事件只能触发一次,
可以使用 once修饰符, 或 $once方法
-
-
触发自定义事件:
this.$emit('coderitl',数据) -
解绑自定义事件:
this.$off('coderitl') -
组件上也可以绑定原生
DOM事件, 需要使用 native修饰符 -
注意: 通过
this.$refs.xxx.$on('coderitl',回调) 绑定自定义事件时, 回调 要么配置在,methods 中 要么用箭头函数, 否则 this指向会出问题!
-
SLOT 插槽
-
插槽
-
实现
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
<html lang="zh-CN">
<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>slot</title>
<script src="../../../vue.js"></script>
</head>
<body>
<div id="app">
<!-- 普通插槽的使用 -->
<cpn1>
<h1> <font color="purple">内容替换 </font> </h1>
</cpn1>
<!-- 添加具有默认值的插槽 -->
<cpn2></cpn2>
<!-- 多元素覆盖 -->
<cpn3>
-----------------------
<h1> 标题一 </h1>
<h2> 标题二 </h2>
<p> 文本内容 </p>
-----------------------
</cpn3>
</div>
<template id="cpnT">
<div>
<h1> 插槽使用: </h1>
<slot></slot>
</div>
</template>
<template id="cpnD">
<div>
<h1> 插槽使用: </h1>
<slot> <button> 默认值 </button> </slot>
</div>
</template>
<template id="cpnM">
<div>
<h1> 插槽使用: </h1>
<slot></slot>
</div>
</template>
<script>
const app = new Vue({
el: '#app',
data: {
message: 'Hello Word'
},
methods: {},
components: {
cpn1: {
template: "#cpnT"
},
cpn2: {
template: "#cpnD"
},
cpn3: {
template: "#cpnM"
}
}
});
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 定义 slot
<template id="cpnT">
<div>
<h1> 聚名插槽的使用</h1>
<slot name="left"> 左侧</slot>
<slot name="center">中间 </slot>
<slot name="right"> 右侧</slot>
</div>
</template>
// 使用
<cpn> <span slot="right">我从右边变为其他了 </span></cpn>
cpn虽然是子组件,但却在 app实例中使用,此时的 isShow就是属于对应实例中的 ,isShow:true,所以显示当前内容dataScope
注意: 官方指出
父组件模板的所有东西都会在父级作用域内编译,子组件模板的所有东西都会在子级作用域内编译 解释作用域插槽:
父组件替换插槽的标签,但是内容由子组件来提供 作用域插槽 
-
实现
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<body>
<div id="app">
<!-- 默认展示方式 -->
<cpn></cpn>
<!-- 获取子组件数据 -->
<cpn>
<template slot-scope="slot">
<span>{{slot.data.join(' - ')}}</span>
</template>
</cpn>
</div>
<template id="cpnT">
<div>
<h1> 子组件</h1>
<slot :data="languages">
<!-- 默认展示方式 -->
<ul>
<li v-for="item in languages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script>
const app = new Vue({
el: '#app',
components: {
cpn: {
template: "#cpnT",
data() {
return {
languages: ['JavaScript', 'Java', 'MYSQL', 'Mongodb']
}
}
}
}
});
</script>
</body>
-
Vue-CLI
-
安装
1
npm install -g @vue/cli
-
项目创建
1
vue create proj-name[名称只能是小写]
-
项目创建流程
-
创建流程
创建流程 
-
初始安装选择项
空格选择,, 回车确定,进行下一步 
-
版本与路由模式
版本与路由模式, 回车确定 
-
选择独立文件管理
选择独立文件管理 
-
是否要保存为模板
n | 回车Save this as a preset for future projects? (y/N) y,如果进行了保存, 则输入保存的名称 -
项目运行
项目运行 手机访问测试 

-
手机截图
手机端访问测试结果 
-
-
目录分析
-
public-
index.html1
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
<html lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<!-- 开启移动端的理想视口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<!-- 配置页签图标 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<!-- 配置网页标题 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 当浏览器不支持 js 时 noscript 的元素就会被渲染 -->
<noscript>
<strong>
We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't
work properly without JavaScript enabled. Please enable it to
continue.
</strong>
</noscript>
<!-- 容器 -->
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
-
-
main.js入口文件 1
2
3
4
5
6
7
8
9
10
11
12
13// 引入
Vue
import Vue from 'vue'; // "module": "dist/vue.runtime.esm.js",
// 引入 App 组件,是所有组件的父组件
import App from './App.vue';
// 引入路由
import router from './router';
// 关闭生产提示
Vue.config.productionTip = false;
// vm 实例对象
new Vue({
router,
render: h => h(App),
}).$mount('#app');-
rander1
render?(createElement: CreateElement, hack: RenderContext<Props>): VNode;
-
-
webpack:Vue脚手架隐藏了所有 webpack相关配置, 若想查看具体的 webpack配置, 可以执行如下 1
2
3
4
5
6vue inspect > output.js
// 对文件添加如下就可以避免错误,用来查看 webpack 配置
export default {
...
}
-
-
脚手架配置文件
vue.config.js与 Vue-CLIvue.config.js

-
lintOnSave: false关闭语法检查 -
最新脚手架项目中的配置项
transpileDependencies
-
-
Ref 属性
-
获取
dom1
2
3
4
5
6<template>
<div>
<h1 ref="getInfoRef">coder-itl</h1>
</div>
</template>1
2
3
4
5
6
7
8methods: {
getH1() {
// 获取 HTML 标签
console.log(this.$refs.getInfoRef);
// 获取组件标签 => VueComponent
},
},
浏览器本地存储
-
localStorage-
存储
localStorage| 读取localStorage数据 | 删除 localStorage数据 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
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>localStorage</title>
</head>
<body>
<h1>localStorage 的使用</h1>
<hr />
<h1>读取 localStorage 的数据:</h1>
<p class="getLocalStorage"></p>
<button id="btnSave">点我保存数据 </button>
<button id="btnRead">点我读取数据 </button>
<button id="btnRemove">点我删除数据 </button>
<script>
const btnSave = document.querySelector('#btnSave');
const btnRead = document.querySelector('#btnRead');
const btnRemove = document.querySelector('#btnRemove');
const getLocalStorage = document.querySelector('.getLocalStorage');
btnSave.addEventListener('click', () => {
let userInfo = {
userName: 'coder-itl',
userAge: 18,
};
localStorage.setItem('userInfo', JSON.stringify(userInfo));
});
btnRead.addEventListener('click', () => {
const result = localStorage.getItem('userInfo');
getLocalStorage.innerHTML = result;
console.log(JSON.parse(result));
});
btnRemove.addEventListener('click', () => {
localStorage.removeItem('userInfo');
console.log('数据已经被删除了!');
});
</script>
</body>
</html>-
渲染
存储数据 
-
-
清空所有
localStorage1
localStorage.clear()
-
-
sessionStorage -
webStorage是两者的统称 - 存储内容大小一般支持
5MB左右 (不同浏览器可能不一样) - 浏览器通过
Window.sessionStorage和 Window.localStorage属性来实现本地存储机制 - 备注
sessionStorage存储的内容会随着浏览器窗口关闭而消失 lolcaStorage存储的内容, 需要手动清除才会消失 xxxStorage.getItem(xx)如果 xxx对应的 value获取不到, 那么 getItem的返回值是 nullJSON.parse(null)的结果是 null
- 存储内容大小一般支持
全局事件总线 (GlobalEventBus)
-
一种组件间通信方式,适用于
任意组件间通信 -
安装全局事件总线
1
2
3
4
5
6
7
8
9// vm 实例对象
new Vue({
router,
render: h => h(App),
beforeCreate() {
// 安装全局事件总线 this == vm
Vue.prototype.$bus = this
}
}).$mount('#app'); -
使用事件总线
-
接受数据,:
A组件想接受数据, 则在 A组件中给 $bus绑定自定义事件, 事件的回调留在 A组件自身 1
2
3
4
5
6
7
8
9methods:{
demo(data){
...
}
}
mounted(){
this.$bus.$on('xxx',this.demo)
} -
提供数据:
this.$bus.$emit('xxx',数据)
-
-
最好在
beforeDestroy钩子中, 用 $off,去解绑当前组件所用到的事件 (绑定事件处解绑)
消息订阅与发布 (pubsub)
-
消息订阅与发布:
一种组件间通信方式,适用于任意组件通信 -
使用步骤
-
安装
1
npm install pubsub-js
-
引入
1
import pubsub from 'pubsub-js'
-
接受数据:
A组件想接受数据, 则在 A组件中订阅消息, 订阅的 回调留在 A 组件自身1
2
3
4
5
6
7
8
9
10
11
12
13
14methods:{
getSendStuName(_, data) {
this.getChildrenName = data
console.log('有人订阅了消息 coderitl!')
}
}
mounted() {
// 订阅消息
this.pubId = pubsub.subscribe('消息名', this.getSendStuName)
},
beforeDestroy() {
// 在销毁之前解绑
pubsub.unsubscribe(this.pubId)
} -
提供数据
1
2
3
4
5
6methods: {
sendSchoolName() {
// 提供数据
pubsub.publish('消息名', 数据)
}
} -
最好在
beforeDestroy钩子中, 用 pubsub.unsubscribe(this.pubId)去 取消订阅
-
nextTick
-
语法
1
this.$nextTick(回调函数)
-
作用:
在下一次 DOM更新结束后执行其指定的回调 -
什么时候用: 当改变数据后,
要基于更新后的新 DOM进行某些操作, 要在 nextTick所指定的回调函数中执行
Webpack
-
webpack-
什么是
webpackwebpack是前端醒目工程化的具体解决方案 -
主要功能
他提供了有好的
前端模块化开发支持,以及代码压缩混淆、处理浏览器端 Javascript的兼容性、性能优化等强大的功能 webpack
-
创建列表隔行变色项目
- 新建项目空白目录,并运行
npm init -y命令, 初始化包管理器配置文件 package.json - 新建
src源代码目录 - 新建
src -> index.html首页和 src -> index.js脚本文件 - 初始化首页基本的结构
- 运行
npm install jquery -S命令, 安装 jQuery - 通过
ES6模块化的方式导入 jQuery,实现列表隔行变色效果 - 安装
webpack,解决错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- <script src="./index.js"></script> -->
<script src="../dist/main.js"></script>
</head>
<body>
<ul>
<li>这是第 1 个 li</li>
<li>这是第 2 个 li</li>
<li>这是第 3 个 li</li>
<li>这是第 4 个 li</li>
<li>这是第 5 个 li</li>
<li>这是第 6 个 li</li>
<li>这是第 7 个 li</li>
<li>这是第 8 个 li</li>
<li>这是第 9 个 li</li>
<li>这是第 10 个 li</li>
</ul>
</body>
</html>1
2
3
4
5
6
7
8
9
10// index.js
// 1. 使用 es6 导入语法,导入 jQuery
import $ from 'jquery'
// 2. 定义 jQuery 的入口函数
$(function () {
// 3. 实现隔行换色
$("li:odd").css('background-color', 'pink')
$("li:even").css('background-color', 'skyblue')
}) - 新建项目空白目录,并运行
-
webpack的基础使用 -
安装
1
2# -D: --save-dev 只在开发阶段使用的依赖
npm install webpack@5.42.1 webpack-cli@4.7.2 -D -
在项目根目录中,
创建名为 webpack.config.js的 webpack配置文件, 并初始化如下的基本配置 1
2
3module.exports = {
mode: 'development' // mode 用来指定构建模式,可选值有 development 和 production
} -
在
package.json的 scripts节点下, 新增 dev脚本 1
2
3
4"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack" // 在scripts 节点下的脚本, 可以通过 npm run 执行, 例如 npm run dev
}, -
在终端中执行
npm run dev命令, 启动 webpack进行项目的打包构建 -
在
index.html中引入在 dist/main.js -
注意点
- 当前环境使用的
node不要太高, 16即可
- 当前环境使用的
-
效果
效果 
-
1
2
3
4
5
6
7# 全局安装
npm install webpack -g
# 安装指定版本
npm install webpack@3.6.0 -g
# 检测安装
webpack -v-
目录
1
2dist: distribution (发布)
src: 源码 -
导出
1
2
3
4
5
6
7
8function add(n1, n2) {
return n1 + n2
}
// 模块导出
module.exports = {
add
}; -
导入
1
2
3
4const {add} = require('./src/mathUntils.js');
// 使用
console.log(add(10,20)); -
打包
1
webpack ./src/main.js ./dist/bundle.js
-
配置文件
webpack.config.js-
入口
( entry): 可以是字符串、数组、对象, -
出口
( output): 通常是一个对象,里面至少包含两个重要属性, path和 filename -
配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14// path --> node
const path = require("path");
module.exports = {
entry: "./src/main.js", // 入口文件
// 输出文件
output: {
path: path.resolve(__dirname,'dist' ), // 获取绝对路径
filename: 'bundle.js' // bundle.js 打包
}
};
// 终端
webpack 【不需要再添加路径参数】
-
-
脚本命令映射
build => webpack
-
Error错误原因 解决方案 

解决方案一: 更换 webpack版本 解决方案二: 更换 style-loader、css-loader版本 -
less-loader与 webpack版本问题 -
查看
所需安装的版本1
npm view less | webpack | xxx versions
-
安装指定版本
1
npm install less-loader@
版本号
-
-
配置文件基础形式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 配置文件名称 webpack.config.js
const path = require('path');
module.exports = {
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, 'dist'), // 获取绝对路径
filename: 'bundle.js'
},
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader'] // 需下载对应包
}]
}
};
// current 当前的 -
目录结构
目录结构 
-
less预处理 lessloader-error
-
使用
less-loader的具体思路 - 前往官网获取基本使用方式 【 less-loader】
- 在项目中添加
less文件 - 在
main.js中依赖 less文件 - 在进行安装对应工具
-
安装
less-loader-
安装
1
npm install --save-dev less-loader less
-
配置文件
webpack.config.js1
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
27const path = require('path');
module.exports = {
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, 'dist'), // 获取绝对路径
filename: 'bundle.js'
},
module: {
rules: [{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
// 新增 less 配置
{
test: /\.less$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "less-loader" // compiles Less to CSS
}]
}
]
}
};
// current 当前的 -
webpack-less打包 -
运行
build
-
网页结构
打包 
-
-
-
未安装依赖
img-loader未安装对应 loader就使用时 
-
安装
1
2
3npm install --save-dev url-loader
# limit 大于时使用
npm install file-loader --save-dev -
图片资源大小
96.0 KB -
配置文件新增
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 配置 img
{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'url-loader',
options: {
/*
当加载的图片,小于 limit 时, 会将图片编译成 base64 字符串形式
当加载的图片,大于 limit 时, 需要使用 file-loader 模块进行加载
*/
limit: 8192
}
}]
} -
img使用 添加图片资源 图片资源打包后 

-
图片命名及其他配置
- options
添加选项 img:文件要打包的文件夹name: 获取图片原来的名字,放在该位置 hash:8: 为了防止图片名称冲突,依然使用 hash,只保留 8 位 ext: 使用图片原来的扩展
- options
-
效果图
打包效果 
-
-
Es6处理 -
安装依赖
1
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
-
Es6相关配置 1
2
3
4
5
6
7
8
9{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: { loader: 'babel-loader',
options: {
presets:['es2015']
}
}
}
-
-
安装
1
npm install vue-loader vue-template-compiler --save--dev # 降低 vue-loader 版本 本地测试中
-
添加配置
webpack.config.js1
// vue: webpack 配置
{ test:/\.vue$/, use:['vue-loader']} -
webpack 配置Vue -
main.js1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 依赖 Vue : npm install --save
// 使用 Vue 模块式开发
import Vue from "vue"
import App from './vue/App.vue'
new Vue({
el: '#app',
// 由于 app会被替换, 将 App 作为一个组件
template: '<App/>',
components: {
// 注册组件 App
App
}
}); -
APP.vue1
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<template>
<div>
<h1>{{message}}</h1>
<h1 class="count">计数器: {{count}}</h1>
<button @click="increment"> + </button>
<button @click="descrement"> - </button>
</div>
</template>
<script>
name: "App", // 组件名称
data() {
return {
count: 1
}
},
methods: {
increment() {
// 增加按钮点击事件
this.count++;
},
descrement() {
// 减少按钮点击事件
if (this.count <= 1) {
return this.count = 1
} else {
this.count--
}
}
}
</script>
<style lang="scss" scoped>
// 字体颜色
.count {
color: purple;
}
</style>
-
-
VueRouter(路由)
-
路由
-
vue-router的理解 vue的一个插件库, 专门用来实现 SPA应用 -
对
SPA应用的理解 - 单页
Web应用 ( Single Page Web Application => SPA) - 整个应用只有一个完整的页面
- 点击页面中的导航连接
不会刷新页面,只会做页面的局部刷新 - 数据需要通过
ajax请求获取
- 单页
-
什么是路由
- 一个路由就是一组映射关系
key - value key为路径, value可能是 function或 component
- 一个路由就是一组映射关系
-
路由分类
-
后端路由
- 理解:
value是 function,用于处理客户端提交的请求 - 工作过程:
服务器接收到一个请求时,根据请求路径知道匹配的函数来处理请求, 返回响应数据
- 理解:
-
前端路由
-
理解:
value是 component,用于展示页面内容 -
工作过程: 当浏览器的路径改变时,
对应的组件就会显示 component
-
-
-
安装插件
1
2
3
4# 永远安装最新版本
npm install --save vue-router
# 如果项目为 vue2,则需要安装为 vue-router 的 3
npm install --save vue-router@3
-
使用
vue-router1
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/*
1. 在 src 目录下新建文件夹 router,再在该文件夹下新建 "index.js" 文件
2 在 main.js 中引入 router,注册
3. 注意: routes 名称
*/
index.js(路由基础使用):
-----------------------------------------------------------
import Vue from "vue"
import VueRouter from "vue-router"
// 路由引入: 方式一
import Home from '../components/home/Home'
// 路由引入: 路由懒加载(方式二)
const Home = () => import('../components/home/Home');
// 安装插件
Vue.use(VueRouter)
// 映射
const routes =
// 路由重定向
{
path: '/',
redirect: '/home'
},
// 路由映射
{
path: '/home',
component: Home
}
]
// 创建对象
const router = new VueRouter({
mode: 'history',
routes
})
// 导出
export default router -
注意事项
一般组件与路由组件 
-
几个注意点
- 路由组件通常存放在
pages文件夹, 一般组件通常存放在 components文件夹 - 通过切换,
了的路由组件,隐藏 默认是被 销毁的,需要使用的时候再去 挂载 - 每个组件都有自己的
$route属性, 可以通过组件的 $router属性获取到
- 路由组件通常存放在
-
多级路由
-
配置路由规则,
使用 children配置项 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const routes = [
{
path: '/about',
component: () => import('@/views/About'),
},
{
path: '/home',
component: () => import('@/views/Home'),
// 通过 children 配置子级路由
children: [
{
path: 'news', // 此处是不能在路径上添加 /,即不能是 /news
component: () => import('@/pages/News')
}
]
},
]; -
跳转要写为完整路径
1
<router-link to="/home/news">news</router-link>
-
-
使用渲染效果
路由与嵌套路由使用 
-
路由携带
query参数 -
字符串写法
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<template>
<div class="about">
<h1>About</h1>
<ul>
<li v-for="message in messageList">
<router-link :to="`/about/detail?id=${message.id}&title=${message.title}`"> {{ message.title }}</router-link>
</li>
</ul>
<router-view></router-view>
</div>
</template>
<script>
import {nanoid} from "nanoid";
export default {
name: "About",
data() {
return {
messageList: [
{id: '001', title: '消息001'},
{id: nanoid(), title: '消息002'},
{id: nanoid(), title: '消息003'}
]
}
}
}
</script>
<style scoped>
</style>-
路由实现数据传递
路由实现数据传递 
-
数据渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<template>
<div class="detail">
<h1>Detail</h1>
<ul>
<li>消息编号: {{ $route.query.id }}</li>
<li>消息名称: {{ $route.query.title }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "Detail",
mounted() {
// $route 可以获取路由相关的配置信息
console.log(this.$route);
}
}
</script>
<style scoped>
</style>
-
-
对象写法
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<template>
<div class="about">
<h1>About</h1>
<ul>
<li v-for="message in messageList">
<router-link :to="{
path:'/about/detail',
query:{
id: message.id,
title:message.title
}
}">
{{ message.title }}
</router-link>
</li>
</ul>
<router-view></router-view>
</div>
</template>
<script>
import {nanoid} from "nanoid";
export default {
name: "About",
data() {
return {
messageList: [
{id: '001', title: '消息001'},
{id: nanoid(), title: '消息002'},
{id: nanoid(), title: '消息003'}
]
}
}
}
</script>
<style scoped>
</style> -
数据渲染结果
query对象写法 
-
-
命名路由
-
作用: 可以简化路由跳转
-
使用
-
给路由命名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const routes = [
{
// 一级路由
path: '/about',
component: () => import('@/views/About'),
// 多级路由
children: [
{
path: 'detail',
component: () => import('@/pages/Detail'),
// 多级路由
children: [
name: 'hello'
path: 'Welcome',
component: () => import('@/pages/xxx/Hello'),
]
}
]
}
]; -
简化跳转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!-- 简化之前: 需要写完整的路径 -->
<router-link to="/about/detail/hello">news</router-link>
<!-- 简化之后: 直接通过名称跳转 -->
<router-link :to="{name:'hello'}">news</router-link>
<!-- 简化写法配合传递参数 -->
<router-link :to="{
name:'hello',
query:{
id: xxx,
title: xxx
}
}">
</router-link>
-
-
-
params参数 -
路由配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const routes = [
{
path: '/about',
name: 'about',
component: () => import('@/views/About'),
children: [
{
// param参数配置路由方式
path: 'detail/:id/:title',
name: 'detail',
component: () => import('@/pages/Detail')
}
]
}
} -
参数传递
-
数据来源
param类型参数的数据来源 
-
-
数据渲染
param获取参数数据 
-
-
对象写法时
1
2
3
4
5
6
7
8
9<router-link :to="{
name:'hello', // 只能是 name 而不能是 path
params:{
id: xxx,
title: xxx
}
}">
</router-link> -
路由的
props1
2
3
4
5
6
7
8
9
10
11
12
13
14
15{
name: 'xxx',
path: '/xxx',
component: ()=>import('xxx')
// 第一种写法: props 值为对象,该对象中所有的 key-value 的组合最终都会通过 props 传给需要数据的目标组件 (Eg: Detail)
// 第二种写法: props 值为布尔值,布尔值为 true,则把路由收到的所有 praams 参数通过 props 传给 Detail 组件
// 第三种写法: props 值为函数,该函数返回的对象中每一组 key-value 都会通过 props 传给 Datail 组件
props($route){
return{
id: query.id,
title: query.title
}
}
}-
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const routes = [
{
path: '/about',
name: 'about',
component: () => import('@/views/About'),
children: [
{
path: 'detail',
name: 'detail',
component: () => import('@/pages/Detail'),
// props
props($route) {
return {
id: $route.query.id,
title: $route.query.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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40// 字符串形式无法获取数据
<template>
<div class="about">
<h1>About</h1>
<ul>
<li v-for="message in messageList">
<router-link :to="{
path:'/about/detail',
query:{
id:message.id,
title:message.title
}
}">{{ message.title }}
</router-link>
</li>
</ul>
<router-view></router-view>
</div>
</template>
<script>
import {nanoid} from "nanoid";
export default {
name: "About",
data() {
return {
messageList: [
{id: '001', title: '消息001'},
{id: nanoid(), title: '消息002'},
{id: nanoid(), title: '消息003'}
]
}
}
}
</script>
<style scoped>
</style>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<template>
<div class="detail">
<h1>Detail</h1>
<ul>
<li>消息编号: {{ id }}</li>
<li>消息名称: {{ title }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "Detail",
// props 接受数据
props: ['id', 'title'],
mounted() {
console.log(this.$route);
}
}
</script>
<style scoped>
</style>
-
-
解决路由多次点击报错问题
1
2
3
4const VueRouterPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(to) {
return VueRouterPush.call(this, to).catch(err => err)
}
replace属性 - 作用: 控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史纪录有两种写入方式
push是追加历史记录,默认 pushreplace替换当前记录 - 开启
replace=><route-link replace >xxx</router-link>
-
keep-alice以及其他问题 -
keep-alive是 Vue内置的一个组件, 可以使被包含的组件保留状态, 或避免重新渲染 -
router-view也是一个组件, 如果直接被包在 keep-alive里面, 所有路径匹配的视图组件都会被缓存 -
作用: 让不展示的路由组件保持挂载,
不被销毁 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 视图缓存 使用 keep-alive 包裹要展示路由的占位符 <router-view>
<keep-alive include="组件名">
<router-view></router-view>
</keep-alive>
beforeRouterLeave(to,from,next){
this.path =this.$route.path;
next()
}
keep-alive属性:
include="组件名"
exclude
// 缓存多个
:include="['A','B']"
-
-
include名称 include为组件名 
-
作用: 路由组件所独有的两个钩子,用于捕获组件的激活状态
-
具体名字
-
activated路由组件被激活时触发 -
deactivated路由组件失活时触发 1
2
3
4
5
6activated() {
console.log('news组件激活');
},
deactivated() {
console.log('news组件失活');
}
-
-
全局前置路由守卫
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{
// name 可以简化路径
name:'',
// path 跳转的路径
path:'',
// children 多级路由
children:[],
// meta 元
meta:{
isAuth: true // 对需要开启验证的路由添加 meta
}
}
// 全局前置路由守卫=> 初始化的时侯被调用、每次路由切换之前被调用
router.beforeEach((to, from, next) => {
console.log(to, from)
// 拥有该属性的路由进行鉴权(判断是否需要鉴权)
if (to.meta.isAuth) {
if (localStorage.getItem('coderitl') === 'coderitl') {
// 放行
return next()
} else {
// console.log('权限不足!')
Vue.prototype.$message({
message: '权限不足!',
type: 'warning'
});
}
} else {
// 对不需要鉴权的路由放行
return next()
}
})点击 about导航,输出 to,from
-
权限测试
前置导航守卫 
-
后置路由导航守卫
1
2
3
4router.afterEach((to, from) => {
// 路由跳转时 title 发生变化 对 meta 添加新的配置项 title
document.title = to.meta.title || 'coder-itl'
}) -
独享路由守卫
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20{
path: '/home',
name: 'home',
component: () => import('@/views/Home'),
children: [
{
path: 'news',
name: 'news',
component: () => import('@/pages/News')
}
],
meta: {
isAuth: true,
title: 'coder-itl'
},
// 独享路由守卫
beforeEnter: (to,from,next) => {
}
} -
组件内路由守卫
-
进入某一特定组件
1
2
3
4// 进入守卫 通过路由规则 进入该组件时被调用
beforeRouteEnter(to,from,next){}
// 离开守卫 通过路由规则 离开该组件时被调用
beforeRouteLeave(to,from,next){}
-
-
对于一个
url来说, 什么是 hash值? #及其后面的内容就是hash值 -
hash值不会包含在 HTTP请求中, 即 hash值不会带给服务器 -
hash模式 - 地址中永远带着
#号,不美观 - 若以后将地址通过第三方手机
app分享, 若 app校验严格, 则地址会被标记为不合法 - 兼容性号
- 地址中永远带着
-
history模式 - 地址干净,美观
- 兼容性和
hash模式相比较差 - 应用部署上线时需要后端人员支持,解决刷新页面服务端
404的问题
-
配置
1
2
3
4const router = new VueRouter({
mode:'history',
routes,
}); -
后端插件
node1
2
3
4
5
6
7# 安装 https://www.npmjs.com/package/connect-history-api-fallback
npm install --save connect-history-api-fallback
# 使用
const history = require('connect-history-api-fallback');
app.use(history)
# 开放静态文件
app.use(express.static(__dirname+'/static'))
-
路由抽离1
2
3
4
5
6
7
8
9
10
11
12
13
14// food.js
export const food = {
path: '/food',
name: 'home',
component: () => import('@/components/Home.vue'),
}
export default {
path: '/add',
name: 'add',
component: () => import('@/components/Card.vue'),
}1
2
3
4
5
6
7
8
9
10// index.js
// export const food
import { food } from './food'
// default
import add from './food'
const routes = [food, add]路由渲染 
-
Mixin
-
基础使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<template>
<div class="food">
<ul>
<li v-for="(value, name, index) in authorInfo">
{{ value }} - {{ name }} - {{ index }}
</li>
</ul>
</div>
</template>
<script>
// 引入
import { authorInfo } from '@/mixin/index'
export default {
mixins: [authorInfo],
}
</script>1
2
3
4
5
6
7
8
9
10
11export const authorInfo = {
data() {
return {
authorInfo: {
name: 'coder-itl',
age: 18,
},
}
},
}mixin
编程式路由导航
-
作用: 不借助
<route-link>实现路由跳转, 让路由跳转更加灵活 -
具体编码
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// $router
的两个 api
pushShow(message) {
console.log(this.$router);
this.$router.push({
path: '/about/detail',
query: {
id: message.id,
title: message.title
}
})
},
replaceShow(message) {
this.$router.replace({
path: '/about/detail',
query: {
id: message.id,
title: message.title
}
})
}
this.$router.back() // 后退
this.$router.forward() // 前进
this.$router.go() // 后退 | 前进 (需要传递参数,正负决定前进或后退)
Vuex(状态管理)
-
状态管理
-
下载
1
npm install --save vuex
-
报错
报错 
-
解决
由于在
2022,年 2 月 7 日 vue3成为了默认版本, 现在的 npm i vue安装的直接就是 vue3了,而在 vue3成为默认版本的同时, vuex也更新到了 4版本,所以现在的 npm i vuex安装的是 vuex4,vuex,的 4 版本只能在 vue3 中使用 vue2中, 要用 vuex的 3版本, vue3中, 要用 vuex的 4版本 1
npm install --save vuex@3
成功安装 vuex
-
-
概念: 专门用在
Vue中实现集中式状态 (数据) 管理的一个 Vue插件, 对 Vue应用中多个组件共享状态进行集中式的管理 (读 / 写), 也是一种组件间通信的方式, 且适用于任意组件间通信 -
什么时候使用
vuex- 多个组件依赖于同一状态
(数据) - 来自不同组件的行为需要变更同一状态
(数据)
- 多个组件依赖于同一状态
-
数据读写
全局事件总线数据读写 Vuex双向,读写共享数据变得简单 

-
分析
单项数据流 vuex-官网

分析过程 
-
安装,
注意脚手架版本 -
引入
vuex,并在main.js中挂载 store -
创建
store文件夹, 在其目录下创建 index.jsindex.js中应用 (安装) vuex- 创建
state、action、mutations对象
-
action没有相关业务逻辑时可以省略 省略 action
-
组件中读取
vuex中的数据, $store.state.sum(this如果在 js 脚本文件中需要添加) -
组件中修改
vuex中的数据: $store.dispatch('action中的方法名', 数据) 或 $store.commit('mutations中的方法名', 数据) -
若没有网络请求或其他业务逻辑,
组件中也可以越过 actions,即不写dispatch,直接写commit
-
安装
Vue.js devtools5.3.4
-
F12打开控制台,选择如下的 vue(6.0+)
-
视图类型切换
视图工具切换 
-
vuex使用分析 vuex监视面板 
-
视图
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<template>
<div class="vue-before">
<h1>(Vuex使用中) 当前求和为:{{ $store.state.sum }} </h1>
<div>
<el-select v-model.number="number" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<el-button @click="increment" type="primary">+</el-button>
<el-button @click="decrement" type="primary">-</el-button>
<el-button @click="incrementOdd" type="primary">当前和为奇数再加 </el-button>
<el-button @click="incrementWait" type="primary">等一等再加 </el-button>
</div>
</div>
</template>
<script>
export default {
name: "InVuex",
data() {
return {
// sum: 0,
number: 1, // 默认值为 1
options: [{
value: '1',
label: '1'
}, {
value: '2',
label: '2'
}, {
value: '3',
label: '3'
}, {
value: '4',
label: '4'
}, {
value: '5',
label: '5'
}]
}
},
methods: {
// 求和
increment() {
this.$store.commit('Increment', this.number)
},
// 做差
decrement() {
this.$store.commit('Decrement', this.number)
},
// 奇数相加
incrementOdd() {
this.$store.dispatch('incrementOdd', this.number)
},
// 等待后再加
incrementWait() {
this.$store.dispatch('incrementWait', this.number)
},
},
mounted() {
console.log(this)
}
}
</script>
<style scoped>
.el-select {
margin-right: 10px;
}
</style> -
store1
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// 该文件用于创建 vuex
中最为核心的 store
import Vue from "vue";
// 引入 vuex
import Vuex from 'vuex'
// 安装
Vue.use(Vuex)
// 准备 state 用于存储数据
const state = {sum: 0}
// 装备 actions 用于响应组件中的动作
const actions = {
// increment(context, value) {
// console.log('action aa', value)
// context.commit('Increment', value)
// },
// decrement(context, value) {
// console.log('action aa', value)
// context.commit('Decrement', value)
// },
incrementOdd(context, value) {
console.log('action aa', value)
context.commit('IncrementOdd', value)
},
incrementWait(context, value) {
console.log('action aa', value)
context.commit('IncrementWait', value)
},
}
// 准备 mutations 用于操作数据(state)
const mutations = {
Increment(state, value) {
console.log('mutations Increment', state, value)
state.sum += value
},
Decrement(state, value) {
state.sum -= value
},
IncrementOdd(state, value) {
if (state.sum % 2) {
state.sum += value
}
},
IncrementWait(state, value) {
setTimeout(() => {
state.sum += value
}, 500)
}
}
// 创建并暴露 store
export default new Vuex.Store({
actions, mutations, state
}) -
效果渲染
store
-
概念: 当
state中的数据需要经过加工后在使用, 可以使用 getters加工 -
定义
1
2
3
4
5
6
7
8
9
10
11const getters = {
bigSum(state){
return state.sum*10
}
}
// 创建并暴露 store
export default new Vuex.Store({
...
getters
}) -
组件中读取数据:
$store.getters.bigSum
-
mapState方法:用于帮助我们映射 state中的数据为计算属性 问题显示 
-
优化
使用 computed
1
2
3
4
5
6
7
8
9// 优化
import {mapState} from 'vuex'
computed:{
// 借助 mapState 生成计算属性: sum school subject(对象写法)
...mapState({sum:'sum',school:'school','subject:'subject'})
// 借助 mapState 生成计算属性: sum school subject(数组写法) 使用前提是 state 属性与计算属性函数名同名
...mapState(['sum','school','subject'])
}
-
-
mapGetters方法: 用于帮助我们映射 getters中的数据为计算属性 1
2
3
4
5
6
7
8
9// 优化
import {mapState} from 'vuex'
computed:{
// 借助 mapState 生成计算属性: bigSum (对象写法)
...mapState({bigSum:'bigSum'})
// 借助 mapState 生成计算属性: bigSum (数组写法)
...mapState(['bigSum'])
} -
mapActions方法: 用于帮助我们生成与 actions对话的方法, 即包含 $store.dispatch(xx)的函数 1
2
3import {mapMutations} from 'vuex'
mapMutations简化 dispatch
-
mapMutations方法:用于帮助我们生成与 mutations对话的方法, 即: 包含 $store.commit(xxx)的函数 1
2
3
4
5
6
7
8import {mapMutations} from 'vuex'
methods: {
// 对象写法
...mapMutations({increment: 'Increment', decrement: 'Decrement'})
// 数组写法
...mapMutations([ 'Increment', 'Decrement'])
}优化对象写法 数组写法 

-
模块化
模块化 
-
具体实现
-
Count组件 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<template>
<div class="vue-before">
<h1>(Vuex使用中) 当前求和为:{{ sum }} </h1>
<h1>(Vuex使用中) 将 sum 扩大 10 倍为:{{ bigSum }} </h1>
<h1>(Vuex使用中) 学校名称为:{{ school }} </h1>
<h1>(Vuex使用中) 学习科目为:{{ subject }} </h1>
<h1 style="color: red">(Vuex使用中)Persons 人员总数为:{{ personList.length }} </h1>
<div>
<el-select v-model.number="number" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<el-button @click="Increment(number)" type="primary">+</el-button>
<el-button @click="Decrement(number)" type="primary">-</el-button>
<el-button @click="incrementOdd(number)" type="primary">当前和为奇数再加 </el-button>
<el-button @click="incrementWait(number)" type="primary">等一等再加 </el-button>
</div>
</div>
</template>
<script>
import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'
export default {
name: "InVuex",
data() {
return {
// sum: 0,
number: 1, // 默认值为 1
options: [{
value: '1',
label: '1'
}, {
value: '2',
label: '2'
}, {
value: '3',
label: '3'
}, {
value: '4',
label: '4'
}, {
value: '5',
label: '5'
}]
}
},
methods: {
...mapMutations('aboutCount', ['Increment', 'Decrement']),
...mapActions('aboutCount', ['incrementOdd', 'incrementWait'])
},
mounted() {
console.log(this)
},
computed: {
// 对象写法: ...mapState('aboutCount',{sum:'sum'})
...mapState('aboutCount', ['sum', 'school', 'subject']),
...mapState('aboutPersons', ['personList']),
...mapGetters('aboutCount', ['bigSum'])
}
}
</script>
<style scoped>
.el-select {
margin-right: 10px;
}
</style> -
count的 store模块 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
43export default {
namespaced: true,
actions: {
incrementOdd(context, value) {
console.log('action aa', value)
context.commit('IncrementOdd', value)
},
incrementWait(context, value) {
console.log('action aa', value)
context.commit('IncrementWait', value)
}
},
mutations: {
Increment(state, value) {
console.log('mutations Increment', state, value)
state.sum += value
},
Decrement(state, value) {
state.sum -= value
},
IncrementOdd(state, value) {
if (state.sum % 2) {
state.sum += value
}
},
IncrementWait(state, value) {
setTimeout(() => {
state.sum += value
}, 500)
}
},
state: {
sum: 0,
school: '育才中学',
subject: 'Java',
bigSum: 0
},
getters: {
bigSum(state) {
return state.sum * 10
}
}
}
-
-
Persons组件 -
Persons1
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<template>
<div class="persons">
<h1>人员列表 </h1>
<h1 style="color: red">Count 组件的和为: {{ sum }}</h1>
<h1 style="color: red">列表中第一个人的名字是: {{ firstPersonName }}</h1>
<el-input type="text" v-model="personListInfo" placeholder="请输入名字"></el-input>
<el-button type="success" @click="add">添加人员信息 </el-button>
<el-button type="success" @click="addLiu">添加一个姓刘的人员 </el-button>
<el-button type="success" @click="addDay">添加 server 人员信息 </el-button>
<ul>
<!-- <li v-for="person in $store.state.personList" :key="person.id">{{ person.name }}</li> -->
<li v-for="person in personList" :key="person.id">{{ person.name }}</li>
</ul>
</div>
</template>
<script>
import {nanoid} from "nanoid";
export default {
name: "Persons",
data() {
return {
personListInfo: ''
}
},
mounted() {
console.log('aaaaaaaaa', this.$store)
},
computed: {
// 通过计算属性简化 state 数据获取
personList() {
return this.$store.state.aboutPersons.personList
},
sum() {
return this.$store.state.aboutCount.sum
},
firstPersonName() {
return this.$store.getters["aboutPersons/firstName"]
}
},
methods: {
add() {
const personObj = {id: nanoid(), name: this.personListInfo}
// commit 提交给 mutations
this.$store.commit('aboutPersons/ADD_PERSON', personObj)
this.personListInfo = ''
},
addLiu() {
const personObj = {id: nanoid(), name: this.personListInfo}
// commit 提交给 mutations
this.$store.dispatch('aboutPersons/addPersonLiu', personObj)
},
addDay(){
this.$store.dispatch('aboutPersons/addServer')
}
}
}
</script>
<style scoped>
.el-input {
width: 30%;
margin-right: 15px;
}
</style> -
Persons的 store1
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
71import Vue from "vue";
import {nanoid} from "nanoid";
import axios from "axios";
export default {
namespaced: true,
actions: {
addPersonLiu(context, value) {
if (value.name.indexOf('刘') === 0) {
context.commit('ADD_PERSON', value)
// console.log('权限不足!')
Vue.prototype.$message({
message: '添加人员信息成功!',
type: 'success'
});
} else {
// console.log('权限不足!')
Vue.prototype.$message({
message: '添加的人员必须姓 刘!',
type: 'error'
});
}
},
addServer(context) {
axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then(res => {
console.log('res',res);
context.commit('ADD_PERSON', {id: nanoid(), name: res.data})
Vue.prototype.$message({
message: '请求成功!',
type: 'success'
})
}, error => {
Vue.prototype.$message({
message: '请求失败!',
type: 'error'
});
})
}
},
mutations: {
ADD_PERSON(state, value) {
// 列表添加 通过数组方法
if (value.name.length > 0) {
state.personList.unshift(value)
Vue.prototype.$message({
message: '添加人员信息成功!',
type: 'success'
});
} else {
Vue.prototype.$message({
message: '添加人员不能为空!',
type: 'warning'
});
}
}
},
state: {
personList: [
{id: nanoid(), name: '张三'},
{id: nanoid(), name: '里斯'},
{id: nanoid(), name: '王五'}
]
},
getters: {
firstName(state) {
console.log(state)
return state.personList[0].name
}
}
}
-
-
主
store的 index文件 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 该文件用于创建 vuex
中最为核心的 store
import Vue from "vue";
// 引入 vuex
import Vuex from 'vuex'
import aboutCount from './count'
import aboutPersons from './person'
// 安装
Vue.use(Vuex)
// 创建并暴露 store
export default new Vuex.Store({
modules: {
aboutCount,
aboutPersons
}
}) -
模块中开启命名空间后,
数据读取 -
组件中读取
state数据 1
2
3
4// 方式一: 自己直接读取
this.$store.state.personAbout.sum
// 方式二: 借助 mapState 读取
...mapState('personAbout',['sum','xxx']) -
组件中读取
getters数据 1
2
3
4// 方式一: 自己直接读取
this.$store.getters['personAbout/bigSum']
// 方式二: 借助 mapGetters 读取
...mapGetters('personAbout',['bigSum','xxx']) -
组件中读取
dispatch数据 1
2
3
4// 方式一: 自己直 dispatch
this.$store.dispatch('personAbout/addPersonLiu',person)
// 方式二: 借助 mapActions
...mapActions('personAbout',['incrementOdd']) -
组件中调用
commit1
2
3
4// 方式一: 自己直接 commit
this.$store.commit('personAbout/ADD_PERSON',person)
// 方式二: 借助 mapMutations
...mapMutations('countAbout',['increment'])
-
-
Vuex-面包屑导航思想
-
vuex实现面包屑导航问题 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<template>
<div class="food">
<ul>
<li v-for="(value, name, index) in authorInfo">
{{ value }} - {{ name }} - {{ index }}
</li>
</ul>
<h1 @click="show">显示活跃路由:</h1>
<ul>
<li v-for="path in this.$store.state.activeRouterPath">
{{ path }}
</li>
</ul>
</div>
</template>
<script>
import { authorInfo } from '@/mixin/index'
export default {
mixins: [authorInfo],
methods: {
show() {
// 调用 dispatch => 将活跃的路由路径传入
this.$store.dispatch('activePath', this.$route.path)
},
},
}
</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
33import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
// 活跃时显示的路由
activeRouterPath: [],
},
actions: {
activePath(context, value) {
console.log('active....', value)
// 处理路径
const activePath = {
id: '001',
path: value,
}
// 接着调用 commit
context.commit('Mul', activePath)
},
},
mutations: {
Mul(state, value) {
// 将 commit => path 加入 state 管理的 activeRouterPath
console.log('mul....', state, value)
state.activeRouterPath.push(value)
},
},
getters: {},
modules: {},
})面包屑导航 vuex实现思想 
Axios
-
axios
-
下载
1
npm install --save axios
-
封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35import axios from 'axios'
export function request(config) {
// 1.创建 axios 的实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000,
})
// 2.axios的拦截器
// 2.1.请求拦截的作用
instance.interceptors.request.use(
(config) => {
return config
},
(err) => {
console.log(err)
},
)
// 2.2.响应拦截
instance.interceptors.response.use(
(res) => {
return res.data
},
(err) => {
console.log(err)
},
)
// 3.发送真正的网络请求
return instance(config)
} -
使用
1
2
3
4
5
6
7
8import { request } from './request'
// 定义新的文件进行引用,单独管理文件
export function getHomeMultidata() {
return request({
url: '/home/multidata',
})
}
-
脚手架配置
解决跨域
-
配置代理
1
2
3devServer: {
proxy: "http://localhost:5000"
}- 说明
- 优点: 配置简单,
请求多个资源直接发个前端 (8080) 即可 - 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器
(优先匹配前端资源)
- 优点: 配置简单,
- 说明
-
编写
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
23devServer: {
proxy: {
'/api1': {
// 匹配以所有 '/api1'开头的请求
target: 'http://localhost:5000', // 代理目标基础路径
changeOrigin: true,
pathRewrite: {'^/api1': ''}
},
'/api2': {
// 匹配以所有 '/api2'开头的请求
target: 'http://localhost:5001', // 代理目标基础路径
changeOrigin: true,
pathRewrite: {'^/api2': ''}
}
}
}
changeOrigin 设置为 true 时,服务器收到的请求头中的 host 为: localhost:5000
changeOrigin 设置为 false 时,服务器收到的请求头中的 host 为: localhost:8080
changeOrigin 默认值为 true -
案例练习
-
测试请求
测试接口 
-
数据渲染
数据渲染 
-
启动项目时自动打开浏览器
-
配置
-
方式一
1
2
3
4
5// package.json
"scripts": {
"serve": "vue-cli-service serve --open --host localhost", // localhost 也可以为 127.0.0.1
...
}, -
方式二
1
2
3
4
5
6
7
8
9
10
11const {defineConfig} = require('@vue/cli-service');
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false, // 关闭 eslint 语法检查
// 配置启动
devServer: {
open: true, // npm run server 时打开浏览器
host: 'localhost' // ip 配置,否则为 0.0.0.0:8080 无效地址
}
});
-