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-model
1
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-model
1
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
-
MVVM
mvvm
-
基础选项
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)
} -
渲染
事件参数
-
stop
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------------------------------
【@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.once
1
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
作为 key
index
作为 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
默认不做响应式处理 -
如需给后添加的属性做响应式,
请使用如下 API
Vue.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
-
数组-高阶函数
-
filter
1
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); -
map
1
2
3
4
5// 2. map 高阶函数
let mapNums = nums.map(function (n) {
return n * 2
})
console.log("乘积: ", mapNums); -
reduce
1
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.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<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');-
rander
1
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-CLI
vue.config.js
-
lintOnSave: false
关闭语法检查 -
最新脚手架项目中的配置项
transpileDependencies
-
-
Ref 属性
-
获取
dom
1
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>-
渲染
存储数据
-
-
清空所有
localStorage
1
localStorage.clear()
-
-
sessionStorage
-
webStorage
是两者的统称 - 存储内容大小一般支持
5MB
左右 (不同浏览器可能不一样) - 浏览器通过
Window.sessionStorage
和 Window.localStorage
属性来实现本地存储机制 - 备注
sessionStorage
存储的内容会随着浏览器窗口关闭而消失 lolcaStorage
存储的内容, 需要手动清除才会消失 xxxStorage.getItem(xx)
如果 xxx
对应的 value
获取不到, 那么 getItem
的返回值是 null
JSON.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
-
什么是
webpack
webpack
是前端醒目工程化的具体解决方案 -
主要功能
他提供了有好的
前端模块化开发支持
,以及代码压缩混淆、处理浏览器端 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.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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.js
1
// vue: webpack 配置
{ test:/\.vue$/, use:['vue-loader']} -
webpack 配置
Vue -
main.js
1
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.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39<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-router
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/*
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> -
路由的
props
1
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
是追加历史记录,默认 push
replace
替换当前记录 - 开启
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,
}); -
后端插件
node
1
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.js
index.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 devtools
5.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> -
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
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
组件 -
Persons
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<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
的 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
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']) -
组件中调用
commit
1
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 无效地址
}
});
-