Vue2

Vue实例结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const app = new Vue({
el:'#app', // 指定包含vue内容的标签的id
data:{
// 这里存放vue中的变量,变量以键值对的方式存放
},
methods:{
// 这里存放vue的方法
},
computed:{
// 这里存放基于现有数据计算得出的属性,例如总的商品数量
},
watch:{
// 当数据发生改变时触发这里的逻辑
}
})

computed

一般情况下,如果只是计算简单的数字,一般直接用简写即可例如,相对于method,computed数据有缓存

1
2
3
4
5
6
7
8
9
10
11
const app = new Vue({
el:'#app',
data:{
a: 8,
b: 6
},
computed:{
add(){return this.a + this.b},
sub(){return this.a - this.b}
}
})

但有时候计算涉及了复杂的拼接,并且需要额外修改,则需要使用完整写法

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">
姓:<input type="text" v-model="firstName"> +
名:<input type="text" v-model="lastName"> =
<span>{{ fullName }}</span><br><br>

<button @click="changeName">改名卡</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
firstName: '刘',
lastName: '备',
},
methods: {
changeName () {
this.fullName = '黄忠'
}
},
computed: {
fullName: {
get () {
return this.firstName + this.lastName
},
// 当fullName被修改时,执行set()函数
set (value) {
this.firstName = value.slice(0, 1)
this.lastName = value.slice(1)
}
}
}
})
</script>
</body>

watch

使用方法为,一般情况下,watch里需要防抖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 const app = new Vue({
data: {
words: "苹果",
obj: {
words: "苹果",
},
},
watch: {
// 该方法会在数据变化时,触发执行,oldValue可选
数据属性名(newValue, oldValue) {},
// js不允许有.存在
"对象.属性名"(newValue, oldValue) {},
},
});

上面也属于简写,完整写法如下

1
2
3
4
5
6
7
8
9
watch: {
obj: {
deep: true, // 深度监视,对复杂数据结构内部改变也会监视
immediate: true, // 立刻执行,一进入页面handler就立刻执行一次
handler (newValue) {
// 逻辑实现
}
}
}

Vue指令

插值表达式

Vue可以在插值表达式中使用变量,方法,js语句,并且这些内容是相应式的,可以实时修改

1
2
3
4
5
6
7
8
<div id="app">
<p>{{ nickname }}</p>
<p>{{ nickname.toUpperCase() }}</p>
<p>{{ nickname + '你好' }}</p>
<p>{{ age >= 18 ? '成年' : '未成年' }}</p>
<p>{{ friend.name }}</p>
<p>{{ friend.desc }}</p>
</div>

v-text

将内容以文本的形式渲染到标签中,并覆盖原有的值,类似innerText

v-html

将文本内容作为html元素渲染,并覆盖原有的值,类似innerHTML

v-show

接受布尔值,true时为显示,false隐藏

通过控制display属性实现

v-if

接受布尔值,true时创建元素,false时不创建元素

当为false时,v-show是隐藏元素,v-if是直接没有这个元素

v-else v-else-if

这两个不能单独使用,必须配合v-if

与js中的判断语句相同,作为判断元素是否显示的条件补充

v-on

对操作进行监听,例如对点击事件进行监听

1
2
3
<button  v-on:click="count--">-</button>
// 简写
<button @click="count--">-</button>

可以绑定方法并传递参数,例如下面这个例子,当按下对应按钮就会扣除对应的金额:

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
<!DOCTYPE html>
<body>
<div id="app">
<div class="box">
<h3>小黑自动售货机</h3>
<button @click="buy(5)">可乐5元</button>
<button @click="buy(10)">咖啡10元</button>
<button @click="buy(8)">牛奶8元</button>
</div>
<p>银行卡余额:{{ money }}元</p>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
money: 100
},
methods: {
buy (price) {
this.money -= price
}
}
})
</script>
</body>

@keyup.enter

当按下回车时触发,例如搜索框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<div id="app">
<h3>@keyup.enter → 监听键盘回车事件</h3>
<input @keyup.enter="fn" v-model="username" type="text">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: ''
},
methods: {
fn () {
console.log('键盘回车的时候触发', this.username)
}
}
})
</script>
</body>

@event.stop

阻止冒泡

@event.prevent

阻止默认行为

@event.stop.prevent

可以连用,阻止冒泡和默认行为

v-bind

对标签的属性进行绑定操作,例如对图片元素绑定src、title

1
2
3
<img v-bind:src="imgUrl" v-bind:title="msg" alt="">
// 简写
<img :src="imgUrl" :title="msg" alt="">

可以通过绑定class和style来控制元素的样式

1
2
3
4
5
6
// 当为对象时,通过布尔值来控制样式
<div class="box" :class="{ pink: true, big: false }">黑马程序员</div>
// 当为数列时全部应用
<div class="box" :class="['pink', 'big']">黑马程序员</div>
// 也可以直接操作style
<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></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
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
<!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>
<style>
* {
margin: 0;
padding: 0;
}
ul {
display: flex;
border-bottom: 2px solid #e01222;
padding: 0 10px;
}
li {
width: 100px;
height: 50px;
line-height: 50px;
list-style: none;
text-align: center;
}
li a {
display: block;
text-decoration: none;
font-weight: bold;
color: #333333;
}
li a.active {
background-color: #e01222;
color: #fff;
}

</style>
</head>
<body>

<div id="app">
<ul>
<li v-for="(item, index) in list" :key="item.id" @click="activeIndex = index">
<a :class="{ active: index === activeIndex }" href="#">{{ item.name }}</a>
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
activeIndex: 2, // 记录高亮
list: [
{ id: 1, name: '京东秒杀' },
{ id: 2, name: '每日特价' },
{ id: 3, name: '品类秒杀' }
]
}
})
</script>
</body>
</html>

v-for

用于创建多个元素,其模版如下:

1
2
3
4
5
6
(item, index) in arr
/*
arr:数组
item:数组中的每一项
index:可选,索引值
*/

示例:

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
<body>
<div id="app">
<h3>小黑的书架</h3>
<ul>
<li v-for="(item, index) in booksList" :key="item.id">
<span>{{ item.name }}</span>
<span>{{ item.author }}</span>
<!-- 注册点击事件 → 通过 id 进行删除数组中的 对应项 -->
<button @click="del(item.id)">删除</button>
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
booksList: [
{ id: 1, name: '《红楼梦》', author: '曹雪芹' },
{ id: 2, name: '《西游记》', author: '吴承恩' },
{ id: 3, name: '《水浒传》', author: '施耐庵' },
{ id: 4, name: '《三国演义》', author: '罗贯中' }
]
},
methods: {
del (id) {
this.booksList = this.booksList.filter(item => item.id !== id)
}
}
})
</script>
</body>

:key=""是vue中的一个特殊属性,用来标识某一个元素,保证数据变化时保持排序,所以需要绑定一个固定的值

v-model

专门给表单用的,实时将表单中的内容更新到变量中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<body>
<div id="app">
账户:<input type="text" v-model="username"> <br><br>
密码:<input type="password" v-model="password"> <br><br>
<button @click="login">登录</button>
<button @click="reset">重置</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
password: ''
},
methods: {
login () {
console.log(this.username, this.password)
},
reset () {
this.username = ''
this.password = ''
}
}
})
</script>
</body>

v-model原理

v-model其实是一个语法糖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div class="app">
<input type="text" v-model="msg1" />
<br />
<!-- v-model的底层其实就是:value和 @input的简写 -->
<!-- 注意:$event用于获取时间形参,相当于e -->
<input type="text" :value="msg2" @input="msg2 = $event.target.value" />
</div>
</template>

<script>
export default {
data() {
return {
msg1: '',
msg2: '',
}
},
}
</script>

<style>
</style>

v-model.trim

去除首位空格

v-model.number

转数字

自定义指令

vue自带的指令未必能满足我们的需求,vue可以根据需求自定义指令

局部自定义指令注册

1
2
3
4
5
6
7
8
9
10
11
<script>
export default {
directives: {
focus: {
inserted (el) {
el.focus()
}
}
}
}
</script>

全局自定义组件注册

1
2
3
4
5
6
//在main.js中
Vue.directive('指令名', {
  inserted (el) {
el.focus()
  }
})

指令的值

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
<template>
<div>
<h1 v-color="color1">指令的值1测试</h1>
<h1 v-color="color2">指令的值2测试</h1>
</div>
</template>

<script>
export default {
data() {
return {
color1: "red",
color2: "orange",
};
},
directives: {
color: {
// 1. inserted 提供的是元素被添加到页面中时的逻辑,即只是初始化的值
inserted(el, binding) {
// console.log(el, binding.value);
// binding.value 就是指令的值
el.style.color = binding.value;
},
// 2. update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑
update(el, binding) {
console.log("指令的值修改了");
el.style.color = binding.value;
},
},
},
};
</script>

<style></style>

Vue生命周期和钩子

Vue生命周期分为四个阶段

  1. 创建阶段:创建响应式数据
  2. 挂载阶段:渲染模板
  3. 更新阶段:修改数据,更新视图
  4. 销毁阶段:销毁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
const app = new Vue({
el: '#app',
data: {
count: 100,
title: '计数器'
},
// 1. 创建阶段(准备数据)
beforeCreate () {
console.log('beforeCreate 响应式数据准备好之前', this.count)
},
created () {
console.log('created 响应式数据准备好之后', this.count)
// this.数据名 = 请求回来的数据
// 可以开始发送初始化渲染的请求了
},

// 2. 挂载阶段(渲染模板)
beforeMount () {
console.log('beforeMount 模板渲染之前', document.querySelector('h3').innerHTML)
},
mounted () {
console.log('mounted 模板渲染之后', document.querySelector('h3').innerHTML)
// 可以开始操作dom了
// 可以在这里设置焦点
},

// 3. 更新阶段(修改数据 → 更新视图)
beforeUpdate () {
console.log('beforeUpdate 数据修改了,视图还没更新', document.querySelector('span').innerHTML)
},
updated () {
console.log('updated 数据修改了,视图已经更新', document.querySelector('span').innerHTML)
},

// 4. 卸载阶段
beforeDestroy () {
console.log('beforeDestroy, 卸载前')
console.log('清除掉一些Vue以外的资源占用,定时器,延时器...')
},
destroyed () {
console.log('destroyed,卸载后')
}
})

@vue/cli脚手架开发

脚手架实现和部件介绍

使用步骤:

  1. 全局安装(只需安装一次即可) yarn global add @vue/cli 或者 npm i @vue/cli -g
  2. 查看vue/cli版本: vue --version
  3. 创建项目架子:vue create projectName(项目名不能使用中文)
  4. 启动项目:yarn serve 或者 npm run serve(命令不固定,找package.json)

组件目录如下

1682092148521

index.html

模版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
<!DOCTYPE html>
<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>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>

<!-- Vue所管理的容器:将来创建结构动态渲染这个容器 -->
<div id="app">
<!-- 工程化开发模式中:这里不再直接编写模板语法,通过 App.vue 提供结构渲染 -->
</div>

<!-- built files will be auto injected -->
</body>
</html>

main.js

功能实现入口,用于导入vue根组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false
// 当前所处于的环境

new Vue({
render: h => h(App),
// 基于APP创建元素,简写,完整写法如下
/*
render: (createElement) => {
return createElement(APP)
}
*/
}).$mount('#app') // 相当于在里面写el:"#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
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>

<script>
// 导入子组件
import HelloWorld from './components/HelloWorld.vue'

export default {
// 向外导出名字
name: 'App',
components: {
// 组件注册
HelloWorld
}
}
</script>

<style>
/*样式,可添加lang="less"添加预处理器,前提需要对应的包 yarn add less less-loader -D*/
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

普通组件注册

局部注册

在compone文件下编写.vue文件,其他组件需要先导入再注册才能使用组件

.vue文件需要采用大驼峰命名法,而且需要有足够的长度才行

下面是在App.vue中使用子组件的注册使用方法

1
2
3
4
5
6
7
8
9
10
11
<script>
import HtmlHeader from './components/HtmlHeader'
import HtmlMain from './components/HtmlMain'
export default {
components: {
// '组件名': 组件对象
HtmlHeader: HtmlHeader,
HtmlMain
}
}
</script>

全局注册

要求与局部注册相同,但全局注册要在main.js中注册,注册之后所有组件都可以直接使用,一般用于多个组价都拥有的组件

1
2
import HtmlButton from './components/HtmlButton'
Vue.component('HmButton', HmButton)

scoped样式独立

有时不同的组件有相同的类名元素,这样会发生冲突

1
2
3
4
5
6
7
8
9
10
<style scoped>
/*
1. 当前组件内标签都被添加data-v-hash值的属性
2. css选择器都被添加 [data-v-hash值] 的属性选择器
*/
div{
border: 3px solid red;
margin: 30px;
}
</style>

data函数

一个组件的 data 选项必须是一个函数。目的是为了:保证每个组件实例,维护独立的一份数据对象。

1
2
3
4
5
6
7
8
9
<script>
export default {
data: function () {
return {
count: 100,
}
},
}
</script>

组件通信

不同组件之间相互处理数据

父子通信

  1. 父组件通过 props 将数据传递给子组件
  2. 子组件利用 $emit 通知父组件修改更新

子组件接收父组件的数据,一共分为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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!-- 父组件App.vue -->
<template>
<div class="app" style="border: 3px solid #000; margin: 10px">
我是APP组件
<!-- 2. 将数据作为属性传给子组件-->
<Son :title="myTitle"></Son>
</div>
</template>

<script>
import Son from './components/Son.vue'
export default {
name: 'App',
data() {
return {
<!-- 1. 父组件的数据 -->
myTitle: '你好,Vue',
}
},
components: {
Son,
},
}
</script>

<style>
</style>
<!-- 父组件App.vue -->
<!-- ********************************************************* -->
<!-- 子组件Son.vue -->
<template>
<div class="son" style="border:3px solid #000;margin:10px">
<!-- 4. 直接使用props的值 -->
我是Son组件 {{title}}
</div>
</template>

<script>
export default {
name: 'Son-Child',
// 3. 通过props来接收
props:['title']
}
</script>

<style>
</style>
/<!-- 子组件Son.vue -->

子组件通知父组件修改更新数据,分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
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
<!-- 子组件Son.vue -->
<template>
<div class="son" style="border: 3px solid #000; margin: 10px">
我是Son组件 {{ title }}
<!-- 1. 触发数据改变的事件 -->
<button @click="changeFn">修改title</button>
</div>
</template>

<script>
export default {
name: 'Son-Child',
props: ['title'],
methods: {
changeFn() {
<!-- 2. 通知父组件更新修改,第一个值为修改的事件名,后面的为可选变量 -->
this.$emit('changTitle','修改后的标题')
},
},
}
</script>

<style>
</style>
<!-- 子组件Son.vue -->
<!-- ********************************************************* -->
<!-- 父组件App.vue -->
<template>
<div class="app" style="border: 3px solid #000; margin: 10px">
我是APP组件
<!-- 3.父组件对子组件的消息进行监听,并给出处理函数 -->
<Son :title="myTitle" @changTitle="handleChange"></Son>
</div>
</template>

<script>
import Son from './components/Son.vue'
export default {
name: 'App',
data() {
return {
myTitle: '你好,Vue',
}
},
components: {
Son,
},
methods: {
<!-- 4.更新数据 -->
handleChange(newTitle) {
this.myTitle = newTitle
},
},
}
</script>

<style>
</style>
<!-- 父组件App.vue -->
props校验

为了保证数据不出问题,需要对传来的数据进行校验,这时候就需要将props改为对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
export default {
props: {
校验的属性名: {
type: 类型, // Number String Boolean ...
required: true, // 是否必填
default: 默认值, // 默认值
validator(value) {
// 自定义校验逻辑
return 是否通过校验;
},
},
},
};
</script>
props和data数据的区别

props和data都可以为组件提供数据,但是他们实际上是有区别的

  • props的数据不是自己的,不能随意更改,只能向父组件发送更改的请求,利用父组件的方法进行更改
  • data的数据是自己的,可以随意更改

这其中的原因是Vue的单向数据流管理,每个组件只能管好自己的数据,通过props影响子组件的数据,如果数据可以随意修改就难以管理

父子组件表单模块

有的时候子模块只负责让用户能够选择数据,而真正的数据存放在父组件中,这个时候就不能直接使用v-model绑定数据,而要通过原理下手

对于子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div>
<select :value="value" @change="selectCity">
<!-- 从父组件处获取值并显示,当改变值时向父组件发送数据更改请求-->
<option value="101">北京</option>
<option value="102">上海</option>
<option value="103">武汉</option>
<option value="104">广州</option>
<option value="105">深圳</option>
</select>
</div>
</template>

<script>
export default {
props: {
value: String,
},
methods: {
selectCity(e) {
this.$emit('input', e.target.value)
},
},
}
</script>

<style>
</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
<template>
<div class="app">
<!-- 完整写法,数据用:绑定,修改用@绑定事件,通过$event拿到形参
<BaseSelect
:selectId="selectId"
@changeCity="selectId = $event"
></BaseSelect>
-->
<BaseSelect
v-model="selectId"
></BaseSelect>
</div>
</template>

<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
data() {
return {
selectId: '102',
}
},
components: {
BaseSelect,
},
}
</script>

<style>
</style>

非父子通信

对于非父子组件的简易消息传输,使用EventBus进行通信,在复杂场景下使用vuex共享数据

实际上非父子通信利用的是创建一个所有组件都能访问的vue实例,因此先要在创建utils/EventBus.js

1
2
3
import Vue from 'vue'
const Bus = new Vue()
export default Bus

例如让两个App.vue下的两个同级子组件BaseA.vue和BaseB.vue进行通信

首先让BaseA监听事件总线中的消息:

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
<template>
<div class="base-a">
我是A组件(接受方)
<p>{{msg}}</p>
</div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
data() {
return {
msg: '',
}
},
<!-- 在创建完成之后就进行消息监听sendMsg-->
created() {
Bus.$on('sendMsg', (msg) => {
this.msg = msg
})
},
}
</script>

<style scoped>
.base-a {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>

BaseB要向BaseA发送消息

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
<template>
<div class="base-b">
<div>我是B组件(发布方)</div>
<button @click="sendMsgFn">发送消息</button>
</div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
methods: {
sendMsgFn() {
<!-- 发送sendMsg消息 -->
Bus.$emit('sendMsg', '今天天气不错,适合旅游')
},
},
}
</script>

<style scoped>
.base-b {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>

跨层级通信

子组件和祖先组件进行通信使用provide和inject跨层级共享数据

祖先组件提供数据

1
2
3
4
5
6
7
8
9
10
export default {
  provide () {
    return {
// 普通类型【非响应式】
color: this.color,
// 复杂类型【响应式】
userInfo: this.userInfo,
    }
  }
}

子组件接收数据

1
2
3
4
5
6
export default {
  inject: ['color','userInfo'],
  created () {
    console.log(this.color, this.userInfo)
  }
}

但是一般非父子通信都会使用vuex进行数据共享,因为跨层级通信存在缺陷

  • provide提供的简单类型的数据不是响应式的,复杂类型数据是响应式
  • 子组件通过inject获取的数据,不能在自身组件内修改

.sync修饰符

实现父子组件数据双向绑定,和v-model不同的的是.sync可以用于表单之外的属性,例如通过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
<template>
<div class="app">
<button @click="openDialog">退出按钮</button>
<!-- isShow.sync => :isShow="isShow" @update:isShow="isShow=$event" -->
<!-- 共享数据并且接收数据修改 -->
<BaseDialog :isShow.sync="isShow"></BaseDialog>
</div>
</template>

<script>
import BaseDialog from './components/BaseDialog.vue'
export default {
data() {
return {
isShow: false,
}
},
methods: {
openDialog() {
this.isShow = true
},
},
components: {
BaseDialog,
},
}
</script>

<style>
</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
<template>
<div class="base-dialog-wrap" v-show="isShow">
<div class="base-dialog">
<div class="title">
<h3>温馨提示:</h3>
<button class="close" @click="closeDialog">x</button>
</div>
<div class="content">
<p>你确认要退出本系统么?</p>
</div>
<div class="footer">
<button>确认</button>
<button>取消</button>
</div>
</div>
</div>
</template>

<script>
export default {
props: {
isShow: Boolean,
},
methods:{
closeDialog(){
<!-- 固定写法updata:修改的数据名 -->
this.$emit('update:isShow',false)
}
}
}
</script>

<style>
</style>

ref和$refs获取元素

js中document.querySelect(’.box’) 查找的范围是整个页面元素,而vue是组件化开发,继续用这个方法查找元素显然是不合适的

我们用ref标记需要查找的元素

1
<div class="base-chart-box" ref="baseChartBox">子组件</div>

在dom元素完成渲染之后获取,使用这种方法只会获取当前组件的元素

1
2
3
mounted(){
var myChart = this.$refs.baseChartBox
}

如果是一个组件被绑定了ref,那么可以通过$refs获取组件方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<BaseForm ref="BaseForm"></BaseForm>
</template>

<script>
export default {
methods: {
// 通过子组件的方法获取表单数据
getFormData() {
return this.$refs.BaseForm.getFormData();
},
},
};
</script>

<style></style>

vue异步更新和.$nextTick

有的时候当增加dom元素之后想要操作对应元素,会操作失败,因为vue的dom元素更新是异步的,这时候需要用到.$nextTick,这个方法会等待dom元素更新完成之后再进行对dom元素的操作

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>
<BaseForm ref="BaseForm" v-if="handleEdit"></BaseForm>
</template>

<script>
export default {
data() {
return {
isCreated: false,
};
},
methods: {
handleEdit() {
this.isCreated = true;
this.$nextTick(() => {
this.$refs.BaseForm.focus();
});
},
},
};
</script>

<style></style>

插槽

默认插槽

当一个组件多次被使用而只有部分内容发送变化时,例如弹窗警告,就需要用到插槽

基本语法为:

  • 组件内需要定制的结构部分,改用**<slot>默认内容</slot>**占位
  • 给组件标签内传入内容,可以传入纯文本、html标签、组件,替换slot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- MyDialog组件-->
<template>
<div class="dialog">
<div class="dialog-header">
<h3>友情提示</h3>
<span class="close">✖️</span>
</div>

<div class="dialog-content">
<!-- 1. 在需要定制的位置,使用slot占位 -->
<slot>我是默认内容</slot>
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- App.vue使用 -->
<template>
<div>
<!-- 2. 在使用组件时,组件标签内填入内容 -->
<MyDialog>
<div>你确认要删除么</div>
</MyDialog>

<MyDialog>
<p>你确认要退出么</p>
</MyDialog>
</div>
</template>

具名插槽

有的时候一个插槽不够用,就需要给每个插槽取一个名字,调用的时候按照名字传值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- MyDialog组件-->
<template>
<div class="dialog">
<div class="dialog-header">
<!-- 一旦插槽起了名字,就是具名插槽,只支持定向分发 -->
<slot name="head">页眉</slot>
</div>

<div class="dialog-content">
<slot name="content">内容</slot>
</div>
<div class="dialog-footer">
<slot name="footer">页脚</slot>
</div>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- App.vue使用 -->
<template>
<div>
<MyDialog>
<!-- 需要通过template标签包裹需要分发的结构,包成一个整体 -->
<template v-slot:head>
<div>我是大标题</div>
</template>

<template v-slot:content>
<div>我是内容</div>
</template>
<!-- 可以直接简写成一个# -->
<template #footer>
<button>取消</button>
<button>确认</button>
</template>
</MyDialog>
</div>
</template>

作用域插槽

可以给插槽绑定上数据,可以传值

1
2
<!-- 通过给子组件slot赋予属性名 -->
<slot :id="item.id" msg="测试文本"></slot>
1
2
<!-- 在父组件中接收数据,所有的数据都会以对象的形式储存,组件默认名为default-->
<template #default="obj"></template>

路由

创建单页面程序,页面能按需求更新

VueRouter的使用

  1. 下载 VueRouter 模块到当前工程,版本3.6.5

    1
    yarn add vue-router@3.6.5
  2. 在src目录下建立router目录创建index.js文件管理工程的路由

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import Find from '@/views/Find'
    import My from '@/views/My'
    import Friend from '@/views/Friend'

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    Vue.use(VueRouter) // VueRouter插件初始化

    // 创建了一个路由对象
    const router = new VueRouter({
    // routes 路由规则们
    // route 一条路由规则 { path: 路径, component: 组件 }
    routes: [
    { path: '/find', component: Find },
    { path: '/my', component: My },
    { path: '/friend', component: Friend },
    ]
    })

    export default router
  3. 在src/views文件夹中存放路由界面

  4. App.vue中放置路由出口,即组件所展示的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
<div class="footer_wrap">
<a href="#/find">发现音乐</a>
<a href="#/my">我的音乐</a>
<a href="#/friend">朋友</a>
</div>
<div class="top">
<!-- 路由出口 → 匹配的组件所展示的位置 -->
<router-view></router-view>
<!--<router-view/>自闭和 -->
</div>
</div>
</template>

<script>
export default {};
</script>
  1. main.js中注册
1
2
3
4
5
6
7
8
9
10
import Vue from 'vue'
import App from './App.vue'
import router from './router/index'

Vue.config.productionTip = false

new Vue({
render: h => h(App),
router
}).$mount('#app')

导航链接

如果采取传统JS写法要高亮当前导航栏要不断添加和移除样式,为此Vue专门有解决方法

1
2
3
4
5
6
7
8
9
10
11
<div>
<div class="footer_wrap">
<router-link to="/find">发现音乐</router-link>
<router-link to="/my">我的音乐</router-link>
<router-link to="/friend">朋友</router-link>
</div>
<div class="top">
<!-- 路由出口 → 匹配的组件所展示的位置 -->
<router-view></router-view>
</div>
</div>

使用router-link跳转后,当前点击的链接默认加了两个class的值 router-link-exact-activerouter-link-active

  1. router-link-active:模糊匹配,其子路由都会应用该样式,如/my/a、/my/b都起作用,多用这个类名
  2. router-link-exact-active:精准匹配

也可以自定义这两个名称,在创建路由对象时可以加上:

1
2
3
4
5
const router = new VueRouter({
  routes: [...],
  linkActiveClass: "类名1",
  linkExactActiveClass: "类名2"
})

跳转传参

现在我们在搜索页点击了热门搜索链接,跳转到详情页,需要把点击的内容带到详情页

查询参数传参

传递参数写法:

1
<router-link to="/path?参数名=值"></router-link>

接收参数的一方:

1
$route.query.参数名
动态路由传参

动态路由也可以传多个参数,但一般只传一个

首先创建路由时就需要配置好,?表示可选参数

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
  routes: [
...,
    {
path: '/search/:words?',
component: Search
}
  ]
})

然后需要发送参数的一方直接写要传递的内容

1
<router-link to="/search/前端培训">前端培训</router-link>

接收的一方接受参数,最后需要和创建路由时的参数名一致

1
<p>搜索关键字: {{ $route.params.words }} </p>

路由重定向

网页打开时默认是根路径,但是一般主页在/home中,没有匹配项就会出现空白,就需要用到重定向

或者错误的页面会提示没有该界面

1
2
3
4
5
6
7
8
9
const router = new VueRouter({
routes: [
{ path: '/', redirect:'/home'},
{ path: '/home', component: Home },
{ path: '/about', component: About },
{ path: '/contact', component: Contact },
{ path: '*', component: NotFind}
]
})

路由模式

路由的路径看起来不自然, 有#,能否切成真正路径形式?

1
2
3
4
const router = new VueRouter({
mode:'histroy', //默认是hash
routes:[]
})

路由跳转和传参

实现点击搜索按钮传递参数

  • path路径跳转(简易方便)
  • name路径跳转(需要在注册路由时添加name)

步骤:

  1. 按钮绑定点击事件
  2. 添加method方法
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
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input v-model="inpValue" type="text">
<button @click="goSearch">搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>

<script>
export default {
name: 'FindMusic',
data () {
return {
inpValue: ''
}
},
methods: {
goSearch () {
// 1. 通过路径的方式跳转
// (1) this.$router.push('路由路径') [简写]
// this.$router.push('路由路径?参数名=参数值')
// this.$router.push('/search')
// this.$router.push(`/search?key=${this.inpValue}`)
// this.$router.push(`/search/${this.inpValue}`)

// (2) this.$router.push({ [完整写法] 更适合传参
// path: '路由路径'
// query: {
// 参数名: 参数值,
// 参数名: 参数值
// }
// })
// this.$router.push({
// path: '/search',
// query: {
// key: this.inpValue
// }
// })
// this.$router.push({
// path: `/search/${this.inpValue}`
// })



// 2. 通过命名路由的方式跳转 (需要给路由起名字) 适合长路径
// this.$router.push({
// name: '路由名'
// query: { 参数名: 参数值 },
// params: { 参数名: 参数值 }
// })
this.$router.push({
name: 'search',
// query: {
// key: this.inpValue
// }
params: {
words: this.inpValue
}
})
}
}
}
</script>

二级路由

首先需要配置:

1
2
3
4
5
6
7
8
9
10
{
path: '/',
component: Layout,
children: [
{ path: '/home', component: Home },
{ path: '/category', component: Cartgory },
{ path: '/cart', component: Cart },
{ path: '/user', component: User }
]
}

然后在对应的一级路由配置路由出口即可

VueCli自定义项目创建

1.安装脚手架 (已安装)

1
npm i @vue/cli -g

2.创建项目

1
vue create hm-exp-mobile

配置项介绍:

  1. Babel:es语法降级
  2. Router:路由
  3. CSS Pre-Processors:css预处理器
  4. Linter/Formatter:代码格式校验

ESlint代码规范

一般使用标准规范:

  1. 单引号
  2. 无分号

Vuex状态管理工具

管理多组件共享数据,如购物车这样包含多个组件的功能

Vuex创建仓库

  1. 安装vuex

    1
    yarn add vuex@3
  2. 创建新建 store/index.js 存放 vuex配置,类似router

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 导入 vue
    import Vue from 'vue'
    // 导入 vuex
    import Vuex from 'vuex'
    // vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
    Vue.use(Vuex)

    // 创建仓库 store
    const store = new Vuex.Store()

    // 导出仓库
    export default store
  3. 在main.js导入挂载到vue示例上

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import Vue from 'vue'
    import App from './App.vue'
    import store from './store'

    Vue.config.productionTip = false

    new Vue({
    render: h => h(App),
    store
    }).$mount('#app')

state提供数据

1
2
3
4
5
6
7
8
9
10
// 创建仓库 store
const store = new Vuex.Store({
// state 状态, 即数据, 类似于vue组件中的data,
// 区别:
// 1.data 是组件自己的数据,
// 2.state 中的数据整个vue项目的组件都能访问到
state: {
count: 101
}
})

访问数据

$store直接访问
  1. 模板中使用

    1
    <h1>state的数据 - {{ $store.state.count }}</h1>
  2. 组件逻辑中使用

    1
    2
    3
    4
    5
    6
    7
    8
    <h1>state的数据 - {{ count }}</h1>

    // 把state中数据,定义在组件内的计算属性中
    computed: {
    count () {
    return this.$store.state.count
    }
    }
  3. js文件中使用

    1
    2
    import store from "@/store"
    console.log(store.state.count)
辅助函数mapState

mapState是辅助函数,帮助我们把store中的数据映射到 组件的计算属性中, 它属于一种方便的用法

  1. 第一步:导入mapState (mapState是vuex中的一个函数)

    1
    import { mapState } from 'vuex'
  2. 第二步:采用数组形式引入state属性

    1
    mapState(['count']) 
    1
    2
    3
    4
    // 类似于:
    count () {
    return this.$store.state.count
    }
  3. 第三步:利用展开运算符将导出的状态映射给计算属性

    1
    2
    3
    computed: {
    ...mapState(['count', 'title'])
    }
    1
    <div> state的数据:{{ count }}</div>

修改Vuex中的数据

vuex 同样遵循单向数据流,组件中不能直接修改仓库的数据,通过开启严格模式直接修改state会报错

开启严格模式

1
2
3
4
5
6
7
8
const store = new Vuex.Store(
{
strict:true,
state:{
title:"标题“
}
}
)

同步修改mutations

state数据的修改只能通过mutations,并且mutations必须是同步的,同样的修改数据要通过调用其中的方法进行修改

注意:

  1. 第一个参数固定为state
  2. 传入多个参数要使用对象传入
  3. mutations中不能存在异步操作,异步操作需放到actions中
1
2
3
4
5
6
7
8
9
10
11
12
13

const store = new Vuex.Store({
state: {
count: 0,
},
// 定义mutations
mutations: {
addCount(state) {
state.count += 1;
},
},
});

组件调用

1
2
3
4
5
methods: {
handleInput () {
this.$store.commit('addCount')
}
}

辅助函数mapMutations

更加简单的调用mutations,类似mapState

  1. 先导入并且加入到方法当中

    1
    2
    3
    4
    import  { mapMutations } from 'vuex'
    methods: {
    ...mapMutations(['addCount'])
    }
  2. 直接使用

    1
    <button @click="addCount">值+1</button>

异步修改actions

state存放数据,mutations同步更新数据,actions则负责进行异步操作

  1. 定义actions

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const store = new Vuex.Store({
    mutations: {
    changeCount(state, newCount) {
    state.count = newCount;
    },
    },
    actions: {
    setAsyncCount(context, num) {
    // 一秒后, 给一个数, 去修改 num
    setTimeout(() => {
    context.commit("changeCount", num);
    }, 1000);
    },
    },
    });

  2. 组件调用

    1
    2
    3
    setAsyncCount () {
    this.$store.dispatch('setAsyncCount', 666)
    }

即actions中存放的只是异步操作,修改还是交给mutations

辅助函数mapActions

与上面的辅助函数功能相同

导入

1
2
3
4
import { mapActions } from 'vuex'
methods: {
...mapActions(['changeCountAction'])
}

使用

1
<button @click="changeCountAction(200)">+异步</button>

筛选Vuex中的数据

使用getters筛选数据:

1
2
3
4
5
6
7
8
getters: {
// 注意点:
// 1. 形参第一个参数,就是state
// 2. 必须有返回值,返回值就是getters的值
filterList (state) {
return state.list.filter(item => item > 5)
}
}

原始使用getters

1
<div>{{ $store.getters.filterList }}</div>

辅助函数mapGetters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
</div>
</template>

<script>
import { mapGetters } from 'vuex';

export default {
computed: {
...mapGetters(['count', 'doubleCount'])
}
};
</script>

Vuex module

当状态太庞大时,就需要进行模块化

创建 modules/user.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const state = {
userInfo: {
name: 'zs',
age: 18
}
}

const mutations = {}

const actions = {}

const getters = {}

export default {
state,
mutations,
actions,
getters
}

创建 modules/setting.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const state = {
theme: 'dark'
desc: '描述真呀真不错'
}

const mutations = {}

const actions = {}

const getters = {}

export default {
state,
mutations,
actions,
getters
}

store/index.js中注册

1
2
3
4
5
6
7
8
9
import user from './modules/user'
import setting from './modules/setting'

const store = new Vuex.Store({
modules:{
user,
setting
}
})

获取模块内的state数据

  1. 直接访问 : $store.state.模块名.xxx
  2. mapState映射
    1. 默认根级别的映射 :mapState([ ‘xxx’ ])
    2. 子模块的映射 :mapState(‘模块名’, [‘xxx’]) - 需要在模块导出添加命名空间 namespaced:true

获取模块内的getters数据

  1. 直接通过模块名访问`$store.getters['模块名/xxx ']
  2. 通过 mapGetters 映射
    1. 默认根级别的映射 : mapGetters([ ‘xxx’ ])
    2. 子模块的映射 : mapGetters(‘模块名’, [‘xxx’]) 需要开启命名空间

获取模块内的mutations方法

默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块

  1. 直接通过 store 调用 $store.commit('模块名/xxx ', 额外参数)
  2. 通过 mapMutations 映射
    1. 默认根级别的映射 mapMutations([ ‘xxx’ ])
    2. 子模块的映射 mapMutations(‘模块名’, [‘xxx’]) - 需要开启命名空间

获取模块内的actions方法

  1. 直接通过 store 调用 $store.dispatch('模块名/xxx ', 额外参数)
  2. 通过 mapActions 映射
    1. 默认根级别的映射 mapActions([ ‘xxx’ ])
    2. 子模块的映射 mapActions(‘模块名’, [‘xxx’]) - 需要开启命名空间