Vue Learning

Vue3 官网:https://v3.cn.vuejs.org/

Install

CDN 引入:<script src="https://unpkg.com/vue@next"></script>

静态引入:<script src="js/vue.js"></script>

Counter 计数器

js 原生实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<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>Document</title>
</head>
<body>
<h2 class="counter">0</h2>
<button class="increment">+1</button>
<button class="decrement">-1</button>
</body>
<script>
const counterEl = document.querySelector(".counter");
const incrementEl = document.querySelector(".increment");
const decrementEl = document.querySelector(".decrement");

let counter = 0;
counterEl.innerHTML = counter;

incrementEl.addEventListener("click", () => {
counter++;
counterEl.innerHTML = counter;
});

decrementEl.addEventListener("click", () => {
counter--;
counterEl.innerHTML = counter;
});
</script>
</html>

vue 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<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>Document</title>
</head>
<script src="js/vue.js"></script>
<body>
<div id="app"></div>
</body>

<script>
Vue.createApp({
template: `
<div>
<h1>{{message}}</h1>
<h2>{{counter}}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
`,
// vue3 中 data 必须是一个函数
data: function () {
return {
counter: 0,
message: "Hello World",
};
},
methods: {
increment() {
this.counter++;
},
decrement() {
this.counter--;
},
},
}).mount("#app");
</script>
</html>

Vue基础

methods 中函数不能使用箭头函数,如果箭头函数,箭头函数里面的 this 就指向了 window。

vscode 生成代码片段:文件->首选项->用户片段->输入 html,打开 Vscode代码片段生成 复制进去即可。

Mustche 语法

即双括号语法,其中可以写属性名、表达式、函数调用、三元运算符。不能写语句(赋值,循环等)。

v-bind

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
<body>
<div id="app"></div>

<template id="my-app">
<img v-bind:src="imgUrl" alt="">

<a href="aUrl">百度一下</a>
</template>

</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data(){
return {
imgUrl: "https://github.githubassets.com/images/modules/profile/badge--acv-64.png",
aUrl: "https://www.baidu.com"
}
}
}

Vue.createApp(App).mount("#app");
</script>

动态绑定 class

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
<style>
.active {
background-color: pink;
}
</style>
<body>
<div id="app"></div>

<template id="my-app">
<div :class="{active: isActive}">嘻嘻嘻</div>
<div :class="classGroup">哈哈哈</div>
<button @click="toggle">转换</button>
</template>

</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data(){
return {
isActive: true,
classGroup: ['active', 'title', 'abc']
}
},
methods:{
toggle(){
this.isActive = !this.isActive
}
}
}

Vue.createApp(App).mount("#app");
</script>

动态绑定 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
<body>
<div id="app"></div>

<template id="my-app">
<div :style="myStyle">嘻嘻嘻</div>
<div :style="{'font-size': '100px', color: 'red'}">嘻嘻嘻</div>
</template>

</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data(){
return {
myStyle: {
fontSize: "24px",
fontWeight: 700
}
}
}
}

Vue.createApp(App).mount("#app");
</script>

动态绑定属性,此处的 name 也根据 data 声明来决定

1
2
3
4
5
6
<div :[name]="value">哈哈</div>

data(){
name: 'abc',
value: 'cdf'
}

v-bind 直接绑定对象,如下会生成 <div name=”ReaJason” age=”18”>哈哈</div>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<body>
<div id="app"></div>

<template id="my-app">
<div v-bind="info">哈哈</div>
</template>
</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data(){
return {
info: {
name: "ReaJason",
age: 18
}
}
}
}


Vue.createApp(App).mount("#app");
</script>

v-on

v-on 绑定事件,简写为 @

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<body>
<div id="app"></div>

<template id="my-app">
<button v-on:click="btnClick">{{counter}}</button>
<button @click="btnClick">按钮</button>
</template>
</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data(){
return {
counter: 0
}
},
methods: {
btnClick(){
this.counter++;
}
}
}


Vue.createApp(App).mount("#app");
</script>

v-on 传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<body>
<div id="app"></div>

<template id="my-app">
<!-- 默认传递 event 对象 -->
<button @click="btnClick">按钮1</button>
<!-- 当指定传参时,使用 event 对象需要显式指定 -->
<button @click="btnClick1($event, '18')">按钮2</button>
</template>
</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
counter: 0
}
},
methods: {
btnClick(event) {
console.log(event);
},
btnClick1(event, age) {
console.log("==========");
console.log(event);
console.log(age);
}
}
}


Vue.createApp(App).mount("#app");
</script>

v-on 使用修饰符

.stop:阻止事件冒泡

@keyup.enter:绑定回车键,可自定义和其他键位

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
<body>
<div id="app"></div>

<template id="my-app">
<div @click="btnClick">
<button @click.stop="btnClick1">按钮</button>
</div>

<input type="text" value="" @keyup.enter="printV">
</template>
</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
counter: 0
}
},
methods: {
btnClick() {
console.log("btnClick");
},
btnClick1() {
console.log("btnClick1");
},
printV(event){
console.log(event.target.value);
}
}
}


Vue.createApp(App).mount("#app");
</script>

v-if

v-if 是惰性的,条件为 false,不会渲染,条件为 true 才能看见

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
<body>
<div id="app"></div>

<template id="my-app">
<h2 v-if="score > 90">优秀</h2>
<h2 v-else-if="score > 60">良好</h2>
<h2 v-else>不及格</h2>

</template>
</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
score: 61
}
},
methods: {
}
}


Vue.createApp(App).mount("#app");
</script>

v-show

v-show 为 false 时,相当于给元素添加了 display=none 属性

1
<p style="display: none;">嘻嘻嘻</p>
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
<body>
<div id="app"></div>

<template id="my-app">
<p v-if="isShow">哈哈哈</p>
<p v-show="isShow">嘻嘻嘻</p>

</template>
</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
isShow: false
}
},
methods: {
}
}


Vue.createApp(App).mount("#app");
</script>

v-for

列表渲染,支持数组、对象、数字,可以是用 in 也可以用 of。

当数组使用更新数组的方法会改变原来的值会更新视图,而生成新数组的方法不会更新回原数组,如 filter、concat、slice。

v-for 中的 key 是为了高效进行数组渲染时候对数组中多余或需要添加的元素的修改。

  • 没有 key,vue 会尽可能复用原先的 VNode 节点填充,最后进行添加和删除节点
  • 有 key 时,会先从前进行比对,再从后往前比对,中间使用 map 进行比对,再决定需要删除和添加的元素

对于 Vue 来说,html 会转换为 VNode Tree,再渲染到页面上。

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
<body>
<div id="app"></div>

<template id="my-app">
<h3>遍历数组:一个参数</h3>
<ul>
<li v-for="movie in movies" :key="movie">{{movie}}</li>
</ul>
<h3>遍历数组:两个参数</h3>
<ul>
<li v-for="(movie,index) in movies" :key="movie">{{index}}---{{movie}}</li>
</ul>
<h3>遍历对象:一个参数</h3>
<ul>
<li v-for="value in info" :key="value">{{value}}</li>
</ul>
<h3>遍历对象:两个参数</h3>
<ul>
<li v-for="(value,key) in info" :key="key">{{key}}---{{value}}</li>
</ul>
<h3>遍历对象:三个参数</h3>
<ul>
<li v-for="(value,key,index) in info" :key="key">{{index}}---{{key}}---{{value}}</li>
</ul>

<h3>遍历数字:一个参数</h3>
<ul>
<li v-for="value in 4" :key="value">{{value}}</li>
</ul>

<h3>遍历数字:两个参数</h3>
<ul>
<li v-for="(value,index) in 4" :key="value">{{index}}---{{value}}</li>
</ul>

</template>
</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
movies: ["朝花夕誓","烟火","你的名字"],
info: {name: "ReaJason", gender: "male", age: 18}
}
},
methods: {
}
}


Vue.createApp(App).mount("#app");
</script>

computed

计算属性,计算属性是有缓存的,状态改变时只会计算一次,而 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
<body>
<div id="app"></div>

<template id="my-app">
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>
<h2>{{fullName}}</h2>

<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>
<h2>{{getFullName()}}</h2>

</template>
</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
firstName: "Tom",
lastName: "Jason"
}
},
methods: {
getFullName(){
console.log("方法调用了")
return this.firstName + " " + this.lastName
}
},
computed: {
fullName(){
console.log("计算属性调用了");
return this.firstName + " " + this.lastName
}
}
}


Vue.createApp(App).mount("#app");
</script>

计算属性的 get 和 set,如果单函数就是 get 方法,如果需要加 set 方法使用对象即可

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
<body>
<div id="app"></div>

<template id="my-app">
<h2>{{fullName}}</h2>
<button @click="changeFullName">change</button>

</template>
</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
firstName: "Tom",
lastName: "Jason"
}
},
methods: {
changeFullName(){
this.fullName = "Code Why"
}
},
computed: {
fullName: {
get: function(){
return this.firstName + " " + this.lastName
},
set: function(value){
var str = value.split(" ")
this.firstName = str[0]
this.lastName = str[1]
}
}
}
}


Vue.createApp(App).mount("#app");
</script>

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
<body>
<div id="app"></div>

<template id="my-app">
<input type="text" v-model="message"> <br>


</template>
</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
message: "hahah"
}
},
methods: {
},
watch: {
/*
mmessage:侦听的属性名
*/
message(newValue, oldValue){
console.log("新值:", newValue, "旧值:", oldValue);
}
}
}


Vue.createApp(App).mount("#app");
</script>

默认只能侦听属性本身,无法侦听到内部数据的改变

deep:true,开启深度侦听,可侦听内部属性的变化,无论多深

immediate:true,立即执行,无论数据是否改变都会执行一次

由于对象是引用类型,因此 oldValue 和 newValue 会执行同一个对象导致打印同一个值

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
<body>
<div id="app"></div>

<template id="my-app">
<h2>name:{{info.name}}</h2>
<h2>age:{{info.name}}</h2>
<h2>friend.name:{{info.friend[0].name}}</h2>
<button @click="changeInfo">改变info</button>
<button @click="changeName">改变info的name</button>
<button @click="changeFriendName">改变info的friend中的name</button>

</template>
</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
info: { name: "Tom", age: 18, friend: [{ name: "lucy", age: 18 }] }
}
},
methods: {
changeInfo() {
this.info = { name: "Mory", age: 20, friend: [{ name: "Jack", age: 20 }] }
},
changeName() {
this.info.name = "Jery"
},
changeFriendName() {
this.info.friend[0].name = "lily"
}
},
watch: {
info: {
handler: function (newValue, oldValue) {
console.log("新值:", newValue);
console.log("旧值:", oldValue);
},
deep: true,
immediate: true
}
}
}


Vue.createApp(App).mount("#app");
</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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
<!DOCTYPE html>
<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>购物车案例</title>
</head>
<style>
#app {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 0px auto;
}

table {
border: 1px solid #e9e9e9;
border-collapse: collapse;
border-spacing: 0;
}


th,
td {
padding: 8px 16px;
border: 1px solid #e9e9e9;
text-align: center;
}

th {
background-color: #f7f7f7;
color: #5c6b77;
font-weight: 600;
}
</style>

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

<template id="my-app">
<template v-if="books.length > 0">
<h3>{{title}}</h3>
<div class="priceBox">
<span class="toP">总价格:{{totalPrice}}</span>
</div>
<table>
<thead>
<tr>
<th></th>
<th>书名</th>
<th>时间</th>
<th>价格</th>
<th>数量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(book, index) of books">
<td>{{book.id}}</td>
<td>{{book.name}}</td>
<td>{{book.time}}</td>
<td>{{book.price}}</td>
<td>
<button @click="decreCount(index)">-1</button>
<span style="margin: 0 5px;">{{book.count}}</span>
<button @click="increCount(index)">+1</button>
</td>
<td><button @click="deleteBook(index)">移除</button></td>
</tr>
</tbody>
</table>
</template>
<template v-else>
<h2>购物车为空</h2>
</template>
</template>

</body>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.1.5/vue.global.min.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
title: "购物车案例",
books: [
{
id: 1,
name: "算法导论",
time: "2020-01",
count: 1,
price: 80
},
{
id: 2,
name: "设计模式",
time: "2021-01",
count: 1,
price: 60
},
{
id: 3,
name: "CS61B",
time: "2020-04",
count: 1,
price: 100
},
{
id: 4,
name: "JavaScript百炼成仙",
time: "2020-02",
count: 1,
price: 34
},
]
}
},
computed: {
totalPrice(){
let finalPrice = 0;
for(var book of this.books){
finalPrice += book.price * book.count
}
return finalPrice
}
},
methods: {
decreCount(index) {
this.books[index].count--
},
increCount(index) {
this.books[index].count++
},
deleteBook(index) {
this.books.splice(index, 1)
}

}
}

Vue.createApp(App).mount("#app");
</script>

</html>

v-model

v-model 可以在表单 input、textarea 以及 select 等元素上创建双向数据绑定,实质就是语法糖,将 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
46
47
48
49
<body>
<div id="app"></div>

<template id="my-app">
<form action="#" method="post">
普通输入框:<input type="text" v-model="text">&emsp;{{text}} <br>
单选复选框:<input type="checkbox" v-model="singleCheckBox" value="agree">同意协议
{{singleCheckBox}}<br>
多选复选框:<input type="checkbox" v-model="multiChcekBox" value="篮球">篮球
<input type="checkbox" v-model="multiChcekBox" value="足球">足球
{{multiChcekBox}}<br>
单选按钮:<input type="radio" v-model="gender" value="female">
<input type="radio" v-model="gender" value="male">
{{gender}}<br>
下拉框:<select v-model="point" multiple>
<option value="打游戏">打游戏</option>
<option value="看电影">看电影</option>
<option value="逛街">逛街</option>
<option value="抓娃娃">抓娃娃</option>
</select> {{point}} <br>
文本域:<textarea v-model="body" cols="10" rows="5"></textarea>
{{body}} <br>


</form>

</template>
</body>
<script src="../js/vue.js"></script>
<script>
const App = {
template: "#my-app",
data() {
return {
text: "Hello World",
singleCheckBox: "",
multiChcekBox: [],
gender:"",
point: "",
body:"你好你好你好"
}
},
methods: {
}
}


Vue.createApp(App).mount("#app");
</script>

默认监听 input 框的 change 事件,使用 .lazy 修饰符,监听 change 事件

v-model 绑定的数据总为 string,如果需要为数字类型,使用 .number 修饰符

.trim 为自动为数据去除前后空白字符

组件化开发

全局组件,所有组件中都能使用当前组件,使用 app.component 注册的组件为全局组件

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
<body>
<div id="app"></div>

<template id="my-app">
<gl></gl>
<gl></gl>
<gl></gl>
</template>

<template id="global">
<h2>{{message}}</h2>
</template>
</body>
<script src="../js/vue.js"></script>
<script>

const gl = {
template: "#global",
data(){
return {
message: "Hello 全局组件"
}
}
}

// App 为根组件
const App = {
template: "#my-app",
data() {
return {
}
},
methods: {
}
}


const app = Vue.createApp(App)
// 当前注册组件为全局组件
app.component("gl", gl);
app.mount("#app");
</script>

局部组件,只有注册的组件才能使用,在组件内部使用 compoennts 注册的组件为局部组件

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
<body>
<div id="app"></div>

<template id="my-app">
<tel1></tel1>
<tel2></tel2>
</template>

<template id="tel2">
<h2>{{message}}</h2>
</template>

<template id="tel1">
<h2>{{message}}</h2>
</template>

</body>
<script src="../js/vue.js"></script>
<script>

const tel1 = {
template: "#tel1",
data(){
return {
message: "Hello tel1"
}
}
}

const tel2 = {
template: "#tel2",
data() {
return {
message: "Hello tel2"
}
}
}

// App 为根组件
const App = {
template: "#my-app",
components:{
tel1,
tel2
},
data() {
return {
}
},
methods: {
}
}


const app = Vue.createApp(App)
app.mount("#app");
</script>

webpack

webpack是一个静态的模块化打包工具,为现代的 JavaScript 应用程序。

webpack 能将各种各样的前端模块化开发格式文件,转为 js,html,css,以及静态资源

安装 webpack:

1
2
npm install webpack webpack-cli –g # 全局安装
npm install webpack webpack-cli –D # 局部安装

通常使用局部 webpack 来管理项目文件,而项目文件通过 package.json 来进行依赖管理,使用 npm init 即可生成当前项目的 package.json 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack"
},
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1"
}
}

使用局部 webpack 需要使用 npx webpack,在 package.json 中添加脚本之后,使用 nom run build 即可使用局部 webpack 打包

配置文件

配置文件名为:webpack.config.json,可以使用其他名字,但是使用 webpack 时需要指定配置文件

webpack 会从入口文件,生成依赖树,只有依赖的文件才会打包进去

1
2
3
4
5
6
7
8
9
const path = require("path");

module.exports = {
entry: "./src/main.js", // 指定入口文件
output: {
filename: "bundle.js", // 指定打包之后文件名
path: path.resolve(__dirname, "./dist") // 指定打包文件输出路径
}
}

css-loader

webpack 默认只会解析 js 文件,其他文件都需要 loader 支持,css-loader 支持解析 css 文件

安装 css-loader:npm install css-loader -D

loader 配置方式,module.rules:

  • test 属性:用于对 resource 进行匹配,通常设置为正则表达式
  • use 属性:
    • loader:required,loader 加载器的字符串
    • options:optional,值会传入 loader 中
    • user:[“style-loader”] 是 user:[{loader:”style-loader”}]
  • loader 属性:Rule.user:[loader] 的缩写

css-loader 只用于解析 css 文件,而不会加载样式,style-loader 会完成插入 style 的操作

安装 style-loader:npm install style-loader -D

处理 less 安装:npm install less-loader -D

loader 执行顺序是从右至左(从下到上或从后往前)

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
const path = require("path");

module.exports = {
entry: "./src/main.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "./dist")
},
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" },
]
},
{
test: /\.less$/,
use: [
{ loader: "style-loader" }, // 最后插入 css
{ loader: "css-loader" }, // 然后解析 css
{ loader: "less-loader" } // 先将 less 解析为 css
]
}
]
}
}

PostCSS

PostCSS 是一个通过 JavaScript 来转换样式的工具,可进行 CSS 的转换和适配,比如自动添加浏览器前缀、css 样式重置等

安装 postcss、postcss-cli:npm install postcss postcss-cli -D

安装 autoprefixer 插件:npm install autoprefixer -D

直接使用 postcss 并使用插件:npx postcss --use autoprefixer -o end.css ./src/css/style.css

安装 postcss-loader:npm install postcss-loader -D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
test: /\.css$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" },
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
require("autoprefixer")
]
}
}
}
]
}

使用单独配置:postcss.config.js

1
2
3
4
5
module.exports = {
plugins: [
require("autoprefixer")
]
}

postcss-preset-env 更加强大,它能将现代 CSS 特性转成大多数浏览器认识的 CSS,并内置了 autoprefixer。

安装 postcss-preset-env :npm install postcss-preset-env -D

1
2
3
4
5
6
// postcss.config.js
module.exports = {
plugins: [
"postcss-preset-env"
]
}

打包图片

安装 file-loader:npm install file-loader -D

1
2
3
4
5
6
7
8
9
10
{
test:/\.(jpg|png|gif)$/,
use: {
loader: "file-loader",
options:{
outputPath: "img", // 图片文件打包到 img 文件夹下
name: "[name]_[hash:6].[ext]"
}
}
}

url-loader 可以将较小的文件转为 base64 的 URI,

安装 url-loader:npm install url-loader -D

1
2
3
4
5
6
7
8
9
10
{
test:/\.(jpg|png|gif)$/,
use: {
loader: "url-loader",
options:{
name: "img/[name]_[hash:6].[ext]",
limit: 100 * 1024 // byte
}
}
}

asset module type

资源模板类型:webpack5 不需要下载 loader

  • asset/resource 对应 file-loader
  • asset/inline 对应 url-loader
  • asset 由 webpack 决定使用哪种 loader
1
2
3
4
5
6
7
8
9
10
11
12
{
test:/\.(jpg|png|gif)$/,
type: "asset",
generator: {
filename: "img/[name]_[hash:6][ext]"
},
parser: {
dataUrlCondition:{
maxSize: 100 * 1024
}
}
}

字体文件

……待学习

组件化开发

父子组件通信

  • 父传子使用 props 属性,没有注册的在 $attrs 中

    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
    // 父组件 在子组件的标签中直接传值
    <div>
    <h2>你好</h2>
    <header-vue class="active"></header-vue>
    </div>

    // 子组件使用 props 接收,如果没有在 props 中接受则会进到 $atrrs 中去
    // class,id,style 会默认传递给子组件的跟组件,使用 inheritAttrs:flase 禁用
    <template>
    <div>
    <h2>{{ msg }}</h2>
    <h2>{{ $attrs.class }}</h2>
    <h2 :class="$attrs.class">节点</h2>
    </div>
    </template>

    <script>
    export default {
    // props: {
    // msg: {
    // type: String,
    // require: false,
    // default: "默认值"
    // }
    // },
    inheritAttrs: false,
    props: ["msg"],
    methods: {},
    data() {
    return {};
    },
    };
    </script>
  • 子传父使用 $emit 事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 子组件使用 emits 定义传递事件,然后在触发事件的时候传递到父组件
    export default {
    emits: ["add", "sub", "addN"],
    data() {
    return {
    n: 10,
    };
    },
    methods: {
    incre() {
    this.$emit("add");
    },
    decre() {
    this.$emit("sub");
    },
    increN() {
    this.$emit("addN", this.n);
    },
    },
    };

    // 父组件监听子组件传递过来的事件进行处理
    <counter-vue @add="addOne" @sub="subOne" @addN="addNOne"></counter-vue>

Tab栏制作

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
<template>
<div>
<tab-bar :titles="title" @change="change"></tab-bar>
<h2>{{contents[curIndex]}}</h2>
</div>
</template>

<script>
import TabBar from "./TabBar.vue";
export default {
components: {
TabBar,
},
data() {
return {
curIndex: 0,
title: ["衣服","裤子","鞋子"],
contents: ["衣服页面","裤子页面","鞋子页面"]
};
},
methods:{
change(index){
this.curIndex = index
}
}
};
</script>

TabBar.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<template>
<div class="tab-control">
<div
class="tab-control-item"
:class="{ active: curIndex === index }"
v-for="(title, index) in titles"
:key="title"
@click="itemClick(index)"
>
<span>{{ title }}</span>
</div>
</div>
</template>

<script>
export default {
props: {
titles: {
type: Array,
default() {
return [];
},
}
},
data() {
return {
curIndex: 0,
};
},
emits:["change"],
methods: {
itemClick(index) {
this.curIndex = index;
this.$emit("change", index)
},
},
};
</script>

<style scoped>
.tab-control {
display: flex;
justify-content: space-between;
}

.tab-control-item.active {
color: red;
}

.tab-control-item.active span {
border-bottom: 3px solid red;
}
</style>

非父子组件通信

Provide和Inject:

  • 父组件通过 provide 为组件树的所有子组件提供数据
  • 子组件通过 inject 来获取组件树上父组件使用 provide 传递的数据
1
2
3
4
5
6
7
8
9
10
11
// 父组件
provide(){
return {
msg: "Hello My Son",
count: 10
}
}


// 子组件
inject: ["msg", "count"]

全局事件总线 mitt:

  • 可全局发出和监听事件
  • 发出事件:emitter.emit("why", {name: "why", age: "18"})
  • 监听事件:emitter.on("why", (info) -> console.log(info))
  • 取消所有监听:emitter.all.clear

插槽 slot:

  • 子组件使用 slot 标签定义插槽,父组件中使用子组件时,在子组件中使用其他标签,会被插入到子组件的插槽中
  • 匿名插槽:即 <slot></slot>,默认名字为 default,如果有多个匿名插槽,所有插槽都会渲染一遍父组件传来的标签
  • 具名插槽:即 <slot name="why"></slot>,父组件中使用 v-slot:name 可指定名字插入到子组件的哪个插槽
  • v-slot:name 可缩写为 #name
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 子组件具名插槽 -->
<div class="left">
<slot name="left"></slot>
</div>
<div class="center">
<slot name="center"></slot>
</div>
<div class="right">
<slot name="right"></slot>
</div>

<!-- 父组件插值 -->
<tab-bar>
<template #left>
<div>左边来点啥</div>
</template>
<template v-slot:center>
<div>中间来点啥</div>
</template>
<template v-slot:right>
<div>右边来点啥</div>
</template>
</tab-bar>

动态插槽名使用:v-slot:[name]

作用域插槽:父组件使用插槽传来的值,v-slot="slotProps"

动态组件:使用 component 组件,通过 is 实现,<component :is="currentTabComponent"></component>

保存组件的状态:使用 keep-alive 包裹需要缓存数据的组件即可,默认都可以缓存

  • include:只有名称匹配的组件才会被缓存
  • exclude:任何名称匹配的组件都不被缓存
  • max:最多缓存的组件数
  • include 和 exclude 使用逗号分隔字符串、正则表达式和数组

异步组件:定义路由时一般使用的就是异步组件,异步组件是为了 webpack 的分包

1
2
3
4
5
// 使用 defineAsyncComponent 异步加载组件
components: {
AsyncComponent: defineAsyncComponent(() =>
import('./components/AsyncComponent.vue'))
}

Suspense:加载状态将由 <Suspense> 控制,组件自身的加载、错误、延迟和超时选项都将被忽略

  • default:需要记载的异步组件
  • fallback:加载错误的时候显示 fallback 插槽的内容
1
2
3
4
5
6
7
8
<suspense>
<template #default>
<async-home></async-home>
</template>
<template #fallback>
<loading></loading>
</template>
</suspense>

$refs:用来操作 DOM 节点,DOM 定义 ref 属性,都会加入到实例的 $refs 中

$parent:获取父组件实例

$root:获取根组件实例

$el:获取 DOM 节点

生命周期

func desc
beforeCreate 组件实例创建之前
created 组件实例创建之后
beforeMount 挂载之前
mounted 挂载完成
beforeUpdate 数据变化界面刷新之前
updated 数据刷新之后
beforeUnmount 组件销毁取消挂载之前
unmonted 组件销毁移除之后
activated 组件活跃时
deactivated 组件缓存后

组件 v-modal

子组件中使用 v-modal 相当于:

  • modalValue 属性传给了子组件
  • 子组件使用 update:modalValue 传递给事件给父组件

自定义多个 v-modal:<nav-bar v-modal="message" v-modal:title="title"></nav-bar>

  • props: [“modalValue”,”title”]
  • emits: [“update:modalValue”, “update:title”]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default {
props: ["modelValue"],
emits: ["update:modelValue"],
computed: {
value: {
get(){
return this.modelValue
},
set(value){
this.$emit("update:modelValue", value)
}
}
}
}

过渡与动画

使用 transition 标签包裹需要使用过渡的标签或组件即可

  • v-enter-from:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
  • v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
  • v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除),在过渡/动画完成之后移除。
  • v-leave-from:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
  • v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
  • v-leave-to:离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被移除),在过渡/动画完成之后移除。

使用 css 动画需要使用 animation ,@keyprame

过渡模式 mode:out-in 常用

  • in-out: 新元素先进行进入过渡,完成之后当前元素过渡离开。
  • out-in: 当前元素先进行离开过渡,完成之后新元素过渡进入。

animate.css

使用自定义过渡类完成动画:

  • enter-from-class
  • enter-active-class
  • enter-to-class
  • leave-from-class
  • leave-active-class
  • leave-to-class

安装:npm install animate.css

引入:import 'animate.css'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
<button @click="show = !show">转换</button>
<transition
name="custom-classes-transition"
enter-active-class="animate__animated animate__tada"
leave-active-class="animate__animated animate__bounceOutRight"
>
<p v-if="show">hello</p>
</transition>
</div>
</template>

<script>
export default {
data() {
return {
show: true,
};
},
};
</script>

gsap

使用 js 钩子函数完成动画:

1
2
3
4
5
6
7
8
9
10
11
12
13
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="leaveCancelled"
:css="false"
>
<!-- ... -->
</transition>

安装:npm install gsap

引入:import gsap from 'gsap'

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>
<input type="num" step="100" v-model="counter" />
<h2>{{ showValue.toFixed(0) }}</h2>
</div>
</template>

<script>
import gsap from "gsap";
export default {
data() {
return {
show: true,
counter: 0,
showValue: 0,
};
},
watch: {
counter(newValue) {
gsap.to(this, { duration: 1, showValue: newValue });
},
},
};
</script>

列表过渡

  • 使用 transition-group 包裹列表渲染
  • 元素动画使用 name 属性定义 css 动画
  • 移动使用 name 属性的 move
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
<template>
<div>
<button @click="add">Add</button>
<button @click="remove">Remove</button>
<button @click="shuffle">Shuffle</button>
<transition-group name="list" tag="p">
<span v-for="num in nums" :key="num" class="list-item">
{{ num }}
</span>
</transition-group>
</div>
</template>

<script>
import _ from "lodash";
export default {
data() {
return {
nums: [1, 2, 3, 4, 5, 6, 7],
count: 10,
};
},
methods: {
randomIndex() {
return Math.floor(Math.random() * this.nums.length);
},
add() {
this.nums.splice(this.randomIndex(), 0, this.count++);
},
remove() {
this.nums.splice(this.randomIndex(), 1);
},
shuffle() {
this.nums = _.shuffle(this.nums);
},
},
};
</script>

<style scoped>
.list-item {
display: inline-block;
margin: 0 5px;
}

.list-enter-active,
.list-leave-active {
transition: all 1s ease;
}

.list-move {
transition: transform 0.8s ease;
}

.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateY(30px);
}

.list-leave-active {
position: absolute;
}
</style>

Mixin

Mixin 能分发组件中可复用的功能,使用 mixins:[mixin1] 接收,当其中的属性相同时会产生冲突:

  1. data 函数中返回的对象
    • 默认情况进行合并
    • 属性冲突,保留组件自身的
  2. 生命周期钩子函数
    • 会合并到数组中,都会调用
  3. 其他都会进行合并
    • key 发生冲突选择组件自身的

全局混入:app.mixin()

extends:继承组件的对象属性

Composition API

setup 函数

无法使用 this,原因是 setup 没有绑定组件实例,setup 执行时,data,components,methods 都还没执行

参数:

  • props,父组件所传过来的属性
  • context,SetupContext 上下文对象
    • attrs,非 props 的属性
    • slots,插槽
    • emit,setup 中没有 this,只能使用 emit 去取代它发送事件

返回值:返回一个对象,返回值可在 template 中使用,返回的属性不具有响应式,想要响应式需要对应函数包裹属性

基础

reactive():传入对象和数组

ref():传入基本数据类型,取值需要 .value,template 中则使用不需要 .value

readonly():属性只读无法修改

isProxy,检查对象是否是 reactive 或 readonly 创建的 proxy

isReactive,检查对象是否是 reactive 创建的

isReadonly,检查对象是否是 readonly 创建的

shallowReactive,不执行深层的响应

shallowReadonly,不执行深层的只读

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
<template>
<div>
<h2>计数:{{ counter }}</h2>
<button @click="increment">+1</button>

<h2>信息:{{ info.uname }}</h2>
<input type="text" v-model="info.uname" />
</div>
</template>

<script>
import { reactive, ref } from "vue";
export default {
setup(props, context) {
const counter = ref(0);
const increment = () => {
counter.value++;
};
const info = reactive({
uname: "ReaJason",
age: 20,
});

return {
counter,
increment,
info,
};
},
};
</script>

toRefs(),传入 reactive 对象,使其解构出来的仍具有响应式

toRef(reactive, “name”),传入 reactive 对象,指定其中 name 属性仍具有响应式并返回

unref,传入一个对象,如果是 ref 返回值,如果不是直接返回原对象

isRef,判断对象是否是 ref 对象

shallowRef,创建浅层的 ref 对象

triggerRef,手动触发 shallowRef 相关联的副作用

customRef,自定义 ref,对其自定义跟踪和更新触发,track,trigger

computed,计算属性

1
2
3
4
5
6
7
8
9
10
11
12
13
import { computed, ref } from "vue";

export default {
setup() {
const firstName = ref("Rea");
const lastName = ref("Jason");
const fullName = computed(() => firstName.value + " " + lastName.value);

return {
fullName
};
},
};

provide(name,value),为子组件及其以下的组件提供数据

inject(name,default),获取父组件链的数据

侦听数据变化

watchEffect:自动收集依赖,并且会立即执行一次

停止侦听:watchEffect 返回一个函数,调用这个函数则会停止侦听

清除副作用:watchEffect 中的箭头函数接收一个参数 onInvalidate

使用 ref 获取 dom 节点,并调整 watchEffect 执行时机

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
<template>
<div>
<h2 ref="title">哈哈哈</h2>
</div>
</template>

<script>
import { ref, watchEffect } from "vue";

export default {
setup() {
const title = ref(null);

watchEffect(
() => {
console.log(title.value);
},
{ flush: "post" } // 默认是 pre,如果需要操作 dom 节点需要设置 post,不然第一次为 null
);

return {
title,
};
},
};
</script>

watch:手动指定侦听的 属性,可获取状态前后的值,等同于 options api 的 watch

  • watch 支持侦听,getter 函数,ref 对象,数组
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
<template>
<div>
<h2>{{ info.name }}</h2>
<button @click="changeData">change</button>
<h2>{{ count }}</h2>
<button @click="changeCount">+1</button>
</div>
</template>

<script>
import { reactive, ref, watch } from "vue";

export default {
setup() {
const info = reactive({ name: "Rea", age: "20" });

// 侦听,传入 getter 函数
watch(
() => info.name,
(newValue, oldValue) => {
console.log(newValue + " " + oldValue);
}
);
const count = ref(0);

// 传入 ref 函数
watch(
count,
(newValue, oldValue) => {
console.log(newValue + " " + oldValue);
},
{ immediate: true, deep: true }
);

const changeCount = () => count.value++;

const changeData = () => {
info.name = "Jason";
};

return {
info,
changeData,
changeCount,
count,
};
},
};
</script>

生命周期钩子

组件创建前和创建完成的生命周期直接在 setup 中书写即可

hook desc
onBeforeMount(()=> {}) 挂载前
onMounted(()=> {}) 挂载完成
onBeforeUpdate(()=> {}) 更新前
onUpdated(()=> {}) 更新完成
onBeforeUnmount(()=> {}) 卸载之前
onUnmounted(()=> {}) 卸载完成之后
onActivated(()=> {}) 活动时
onDeactivated(()=> {}) 缓存时

VueRouter

安装:npm install vue-router@4

基本使用:

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
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'

const routes = [
{
path: "/",
redirect: "/home" // 重定向
},
{
path: '/home',
name: 'home',
component: Home
},
{
path: '/about',
component: () => import(/* webpackChunkName: "home-chunk" */'../views/About.vue') // 组件异步加载,路由分包,前面使用 magic comment 来对分包命名
}
]

const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})

export default router
1
2
3
4
5
6
7
<template>
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</template>

<router-view /> 是用来为渲染组件占位的

<router-link> 是用来指定路由去向

  • to 属性:字符串(path 的字符串)或对象
  • repalce 属性:点击调用 replace,默认是 push
  • active-class 属性:设置激活后应用的 class,默认为 router-link-active
  • exact-active-class 属性:链接精准激活时应用的 class,默认是 router-link-exact-active

动态路由匹配

路由设置 path:path: '/user/:username'

页面路由:<router-link *to*="/user/reajason">User</router-link>

获取值:所有组件都可以使用 this.$route.params 获取,setup 中使用 useRouter 返回的 route

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<h1>User:{{ $route.params.username }}</h1>


import { useRoute } from "vue-router";

export default {
setup() {
const route = useRoute();

const username = route.params.username;

return {
username,
};
},
};

NotFound

配置 notfound 路由:

1
2
3
4
5
{
path: '/:pathMatch(.*)*', // 最后一个 * ,会将错误路径变成数组,如果没有会是字符串
name: 'NotFound',
component: import("../views/NotFound.vue")
}

获取错误路径:

1
2
3
4
5
6
<template>
<div>
<h1>臣妾做不到</h1>
<h1>{{$route.params.pathMatch}}</h1>
</div>
</template>

嵌套路由

配置路由 children 属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
path: '/home',
component: Home,
children: [
{
path: "message",
component: import('../views/HomeMessage.vue')
},
{
path: "other",
component: import('../views/HomeOther.vue')
}
]
}

页面跳转:

1
2
3
4
5
6
7
8
9
<template>
<div class="home">
<h1>This is an home page</h1>
<router-link to="/home/message">消息</router-link> |
<router-link to="/home/other">其他</router-link>

</div>
<router-view />
</template>

编程式导航

使用 route 对象:

  • router.push 等同于 window.history.pushState
  • router.replace 等同于 window.history.replaceState
  • router.go 等同于 window.history.go
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
// 字符串路径
router.push('/users/eduardo')

// 带有路径的对象
router.push({ path: '/users/eduardo' })

// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })

// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })

// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })

// replace
router.push({ path: '/home', replace: true })
// 相当于
router.replace({ path: '/home' })

// 向前移动一条记录,与 router.forward() 相同
router.go(1)

// 返回一条记录,与router.back() 相同
router.go(-1)

// 前进 3 条记录
router.go(3)

// 如果没有那么多记录,静默失败
router.go(-100)
router.go(100)

v-slot

router-link 中的 v-slot 有如下对象:

  • href:解析后的 URL。将会作为一个 <a> 元素的 href 属性。如果什么都没提供,则它会包含 base
  • route:解析后的规范化的地址。
  • navigate:触发导航的函数。 会在必要时自动阻止事件,和 router-link 一样。例如:ctrl 或者 cmd + 点击仍然会被 navigate 忽略。
  • isActive:如果需要应用 active class,则为 true。允许应用一个任意的 class。
  • isExactActive:如果需要应用 exact active class,则为 true。允许应用一个任意的 class。
1
2
3
4
5
6
7
8
9
<router-link
to="/about"
custom
v-slot="{ href, route, navigate, isActive, isExactActive }"
>
<NavLink :active="isActive" :href="href" @click="navigate">
{{ route.fullPath }}
</NavLink>
</router-link>

router-view 中 的 v-slot 有如下对象:

  • Component: 要传递给 <component> 的 VNodes prop。
  • route: 解析出的标准化路由地址
1
2
3
4
5
6
7
8
9
10
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'" mode="out-in">
<keep-alive>
<component
:is="Component"
:key="route.meta.usePathKey ? route.path : undefined"
/>
</keep-alive>
</transition>
</router-view>

动态路由

动态添加路由:

  1. 添加顶级路由,addRoute

    1
    router.addRoute({ path: '/about', component: About })
  2. 添加嵌套路由

    1
    router.addRoute('admin', { path: 'settings', component: AdminSettings })
  3. 删除路由,

    1
    2
    3
    4
    5
    6
    // 根据对象删除
    const removeRoute = router.addRoute(routeRecord)
    removeRoute() // 删除路由如果存在的话

    // 根据路由名字删除
    router.removeRoute('about')

导航守卫

在路由导航生命周期中进行回调。

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫(2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

beforeEach:前置路由

  • to:即将进入的目标
  • from:当前导航正要离开的路由
  • 返回值 false,取消当前路由
  • 返回值 路由地址,与 route.push 传入的参数一样
1
2
3
4
5
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})

Vuex

安装:npm install vuex@next --save

简单使用,store 中存储数据,mutations 中提供修改数据的方法,全局组件都能访问 this.$store,commit 执行方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { createStore } from 'vuex'

export default createStore({
state: {
counter: 0
},
mutations: {
increment(state) {
state.counter++
},
decrement(state) {
state.counter--
}
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<h2>这是一个数字:{{ $store.state.counter }}</h2>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</template>

<script>
export default {
methods: {
increment() {
this.$store.commit("increment");
},
decrement() {
this.$store.commit("decrement");
},
},
};
</script>

mapState

computed 展开 state 中的所有属性:

1
2
3
4
5
6
7
8
9
10
11
// 传入数组,取出 state 中的值
...mapState(["counter","name", "age"]);



// 传入对象,可自定义名字
...mapState({
sCounter: state => state.counter,
sName: state => state.name,
sAge: state => state.age
})

setup 中使用 mapState:

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

const store = useStore();

// computed 函数包裹
const sCounter = computed(()=> store.state.counter)


// 遍历解构
const storeStateFn = mapState(["counter", "name", "age"]);
const storeState = {}
Object.keys(storeStateFn).forEach(fnKey =>{
const fn = storeStateFn[fnKey].bind({$store: store})
storeState[fnKey] = computed(fn)
})

return {
...storeState
};

getters

相当于 state 的计算属性,但是没有缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
state: {
goods: [
{id: 1, price: 30, text: "java"},
{id: 2, price: 40, text: "js"},
{id: 3, price: 20, text: "html"},
]
},
getters: {
expensiveBook: (state,getters)=>{
return state.goods.filter(item => item.price > 30)
},
getGreaterN: (state,getters) => {
return (n) => state.goods.filter(item => item.price > n)
}
}
}

// 通过 $store.getters 获取属性值
<h2>{{$store.getters.expensiveBook}}</h2>
<h2>{{$store.getters.getGreaterN(20)}}</h2>

mapGetters:

1
2
3
4
5
6
computed: {
...mapGetters([
"expensiveBook",
"getGreaterN"
])
}

mutations

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。mutation 只能使用同步函数

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
// 函数传参,传一个值
mutations: {
increment(state, payload) {
state.counter += payload
},
decrement(state) {
state.counter--
}
}

methods: {
increment() {
this.$store.commit("increment", 10);
},
decrement() {
this.$store.commit("decrement");
},
}

// 函数传参,传入对象
this.$store.commit("increment", {id: 1, name: "ReaJason"});

// 另一种提交风格
mutations: {
increment(state, payload) {
state.counter += payload.value
}
}

methods: {
increment() {
this.$store.commit({
type: "increment",
value: 10
});
}
}

// 使用常量函数名
const store = createStore({
state: { ... },
mutations: {
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[SOME_MUTATION] (state) {
// 修改 state
}
}
})

mapMutations:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}

actions

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

actions 中函数的 context 包含的参数:

  • commit,调用 mutation
  • dispatch,调用 action
  • getters,获取 getters 中属性
  • state,获取 state 中属性
  • rootGetters,获取父模块的 getters
  • rootState,获取父模块的 state
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
const store = createStore({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
},
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
})

// 使用 dispatch 获取 action 属性
store.dispatch('increment')

// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})

// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})

mapActions,actions 中可以返回 Promise 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { mapActions } from 'vuex'

export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}

module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}

const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}

const store = createStore({
modules: {
a: moduleA,
b: moduleB
}
})


// 获取的是 a 的 state 中的属性
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

命名空间:模块默认是全局注册的,因此都会合并到主模块上

使用 namespace:true 隔离开

1
2
3
4
5
6
7
8
// 获取子模块的 getters,需要加上模块名称
this.$store.getters("/home/getName");

// 调用子模块的 mutation
this.$store.commit("/home/changeName")

// 调用子模块的 actions
this.$store.dispatch("/home/changeName")

子模块调用父模块的 mutation 和 action

1
2
3
commit('someMutation', null, { root: true })

dispatch('someMutation', null, { root: true })

createNamespacedHelpers 辅助函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'foo',
'bar'
])
}
}

后台管理项目


Vue Learning
https://reajason.vercel.app/2021/11/30/VueLearning/
作者
ReaJason
发布于
2021年11月30日
许可协议