Vue2总结

一、Vue核心

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

1. Vue简介

1.1 介绍和描述

Vue是一套用来动态构建用户界面的渐进式JavaScript框架

1.2 vue特点

  • 遵循MVVM模式
  • 编码简介,体积小,运行效率高,适合移动/pc端开发
  • 本身只关注UI,可以引入其他第三方库开发项目
  • 采用组件化模式,提高代码复用率、且让代码更好维护

1.3 SPA页面

单页面应用程序,也就是整个应用只有一个html文件

优点

  • 即时性
  • 不需要加载整个页面就可以修改内容
  • 页面之间的切换不会出现白屏的现象
  • 服务器压力小

缺点

1
2
3
4
- 首次加载耗时比较多(因为首次需要一次性加载完所有的资源)
- 不利于 SEO
- CSS 命名冲突
- 前进后退功能复杂度较高

2. Vue初识

2.1 模板语法

1. 插值语法
1
2
3
4
5
6
7
8
9

描述:在标签之间使用后 {{}} 进行插值,页面渲染后,{{}} 内的语法会替换为 data 中的数据。

应用:

- 直接渲染:<h1>{{msg}}</h1>
- 简单的加减乘除:{{num + 1}}
- 调用函数:{{new Date() }}
- 三元运算符:{{条件?真:假 }}

注意:不能使用 if else 、switch、for

2. v-bind

描述:用于解析标签

应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<p v-bind:title="myTitle"></p>
<p v-bind:id="'list' + myId">{{msg}}</p>

// 简写
<p :title="myTitle"></p>
<p :id="'list' + myId">{{msg}}</p>

// 样式绑定
<p :class="{active: isActive}"></p> // active生效
<p :class="{active: isActive, text-danger: hasError}"></p> // 多个 class 控制
<p :class="[class1, class2]">数组 class</p> // 数组
<p :class="[isActive ? 'danger' : 'success']">三元运算</p>
<p :style="myStyle">style绑定对象</p>
<p :style="[myStyle1, myStyle2]">style绑定对象</p>
3. v-once

描述:表示只执行一次插值,当数据改变后,插值处的内容不会更新

应用:<p v-once>{{msg}}</p>

4. v-text 和 v-html

描述:类似于 innerTextinnerHTML

应用:<p v-text="myText"></p>

<p v-html="myText"></p>

5. template模板编译

vue中的模板template无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的HTML语法,所有需要将template转化成一个JavaScript函数,这样浏览器就可以执行这一个函数并渲染出对应的HTML元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段:

  • 解析阶段: 使用大量的正则表达式对template字符串进行解析,将标签、指令、属性等转化为抽象语法树AST。
  • 优化阶段: 遍历AST,找到其中的一些静态节点并进行标记,方便在页面重渲染的时候进行diff比较时,直接跳过这一些静态节点,优化runtime的性能。
  • 生成阶段: 将最终的AST转化为render函数字符串。

2.2 数据绑定

1. 单向绑定 v-bind

v-bind 数据只能从 data 流向页面

2. 双向绑定 v-model

v-model 数据不仅能从 data 流向页面,还可以从页面流向 data

<input v-model="text" />相当于

<input :value="text" @input="text = $event.target.value"/>

组件绑定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
// 父组件
<Component v-model='data'></Component>

// 子组件
<template>
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
</template>

<script>
export default {
name:'Component',
model:{
prop: 'checked',
event: 'change'
},
props:{
checked:Boolean
},
data() {
return {

}
}
}

2.3 MVVM模型

1. 模型介绍

M:模型Model,data中的数据

V:视图View,模板代码

VM:视图模型ViewModel,Vue实例

总结:

  • data中所有的属性,最后都会出现在 vm 身上
  • vm 身上所有的属性及 Vue原型 身上所有的属性,在Vue 模板 中都可以直接使用
2. 响应式原理

Object.defineproperty 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var obj = {
name: 'jack'
}
// 参数:劫持对象,对象属性,属性值
Object.defineProperty(obj, 'age', {
//value:18, // 属性值
//writeable:true, // 是否可以被修改 默认为false,不可以
configurable: true, // 是否可以被删除
enumerable: true, // 是否可以枚举
get: function () { // 访问 age 属性时会调用该方法,返回值就是age的值
return 20
},
set: function () { // 修改age的值得时候,会调用该方法

}
})
console.log(obj);

原理:

vue是采用数据劫持配合发布者-订阅者的模式的方式,通过Object.defineProperty()来劫持各个属性的getter和setter,在数据发生变动时发布消息给订阅者,触发相应的监听回调。具体为:对data中的属性进行递归遍历,并将之转化为getter和setter,同时watcher实例对象会在组件渲染时将属性添加到Dep中,负责管理数据的依赖列表,compile解析模板指令,生成指令对象,然后数据更新时就会触发setter,这时候相应的watcher就会重新计算,如果值确实发生变化了,就会通知相应的指令,调用指令的update方法,由对DOM做更新,这就实现了数据驱动DOM的变化。同时vue还会对DOM做事件监听,如果DOM发生变化,vue监听到,就会修改相应的data。

2.4 条件渲染

1. v-if

<button v-if="isShow">按钮</button>

2. v-else-if v-else

v-else-if 必须跟在 v-if 或者 v-else-if 的后面

同时控制多个节点的渲染

3. v-show

<p v-show="isShow"></p>

4. 区别
  • v-if 是真正的渲染,会有插入节点和移除节点的过程
  • v-show 任何情况下都会被渲染,只是切换 display 属性
  • v-if 有比较高的切换开销,而 v-show 有更高的初始化渲染开销

2.5 列表渲染

1. v-for

用于展示列表数据,可用于遍历数组,对象

<li v-for="(item, index) in items" :key="index">

key 属性唯一值,从而重用和重新排序现有元素,建议使用唯一id

2.6 事件处理

1. v-on

使用 v-on:xxx 或者 @xxx 绑定事件,xxx为事件名称

事件的回调需要配置在methods对象中,最终挂载到 vm 组件实例对象上

<button v-on:click="sayHi"></button>

简写<button @click="sayHi"></button>

1
2
3
methods: {
sayHi() {}
}

传递参数并注入事件对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<button v-on:click="getOther2(123, $event)">传递参数及事件对象</button>


{
data() {

},
methods:{
getOther2(num, e) {
console.log(e, num)
console.log(arguments)
}
}
}
2. 修饰符

事件修饰符

  • .stop 阻止事件冒泡(常用)
  • .precent 阻止默认事件(常用)
  • .once 事件只触发一次(常用)
  • .self 事件只在元素本身触发,子元素不会触发
  • .sync 字组件可修改父组件的传的数据,实现简单的双向绑定
1
2
3
4
5
6
// 父组件
<zi :price.sync="price"/>

// 子组件
<div>子{{price}}</div>
<button @click="$emit('update:price', price - 100)">花钱</button>

键盘修饰符

  • .enter 回车键
  • .space 空格键

2.7 自定义指令

1. 局部指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 // 局部指令
{
data() {},
directives: {
'big-number'(element,binding) {
element.innerText = binding.value * 10
},
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
}

// 使用
<span v-big-number="n"></span>
2. 全局指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Vue.directive('fbind', {
// 指令与元素成功绑定时(一上来)
bind(element, binding) { // element就是DOM元素,binding就是要绑定的
element.value = binding.value
},
// 指令所在元素被插入页面时
inserted(element, binding) {
element.focus()
},
// 指令所在的模板被重新解析时
update(element, binding) {
element.value = binding.value
}
})

3. Vue核心

3.1. 脚手架

1. 安装

npm install -g @vue/cli

2. 检查安装

vue --version

3. 搭建项目

vue create 项目名

根据需求进行项目配置

4. 启动项目

cd 项目名称

npm run serve

3.2 组件化编程

1. 概念

组件就是一个提供特定功能的一段代码,包含html,css,js,用来实现局部功能的代码和资源的集合。

2. 作用

解决代码高耦合、低内聚、无重用的问题。

3. 单文件组件

该组件是一个文件,以.vue结尾

1
2
3
4
5
6
7
8
9
10
11
<template>
这里编写html代码,并且只能有一个根标签
<template/>

<script>
这里编写js代码
<script/>

<style>
这里编写css代码
<style/>

注:data 必须是一个函数,如果组件中 data 写为对象,那么每一个组件的data都会互相影响。

4. 使用组件
  • 创建子组件
  • 通过import from 引入子组件
  • 注册子组件
  • 在 template 中直接当做标签使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 局部组件
import MyModal from './MyModal.vue'

components: { MyModal},

<MyModal />

// 全局组件
//main.js
import MyModal from './MyModal.vue'
Vue.component('MyModal', MyModal)

// 在其他任意组件内不需要在 components 中注册了,而是直接使用
<MyModal />
5. 动态组件

概述:渲染一个元组件,根据is 的值来决定渲染那一个组件

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
<el-button @click="changeLogin">切换</el-button>
<component :is="whichWay"></component>

import QrUser from '../components/QrUser.vue'
import UserAccount from '../components/UserAccount.vue'

export default {
name: 'LoginKeep',
data() {
return {
whichWay: 'UserAccount'
}
},
components: {
UserAccount,
QrUser
},
methods: {
changeLogin() {
if(this.whichWay === 'UserAccount') {
this.whichWay = 'QrUser'
} else {
this.whichWay = 'UserAccount'
}
}
}
}
6. 组件缓存

概述:通过 keep-alive 组件去包裹组件,那么会将当前组件缓存,切换组件时就不会销毁组件了。

语法:

1
2
3
4
5
6
7
8
9
10
11
<keep-alive>
组件
</keep-alive>

// 缓存时,默认是缓存所有已经渲染的组件
// 现在只想缓存部分组件,那么使用 include 属性来决定只缓存哪些组件
<keep-alive include="UserAccount">
<component :is="whichWay"></component>
</keep-alive>

// 或者使用 exclude 来决定哪些组件不缓存。

3.3 组件间通信

1. props

概述:props 是父组件传递给子组件的数据,子组件只能使用,而不能修改。

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 父组件 app.vue
<template>
<TodoMvc title="代办列表"></TodoMvc>
</template>

// 子组件 接受title
<template>
<div class="todo">
<h2>{{title}}</h2>
</div>
</template>

<script>
export default {
name: 'TodoMvc', // 组件名
props: ['title']
}
</script>

第二种接受数据类型

1
2
3
4
5
6
7
props: {
name: {
type: String, // 类型
required: true,// 必要性
default: 'cess'// 默认值
}
}
2. $emit

概述:子组件向父组件传递数据。

实现方法:父组件向子组件传递方法,子组件通过$emit去触发这个方法

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>
<MyBrotherA @add="addText" />
</template>

<script>
import MyBrotherA from './MyBrotherA.vue'

export default {
name: 'MyFather',
components: {
MyBrotherA
},
data() {
},
methods: {
addText(text) {
console.log('接收到子组件传递的数据:',text);

this.list.push(text);
}
}
}
</script>

// 子组件
<template>
<input type="text" v-model="text" placeholder="子组件A 输入框" @keydown.enter="addChild">
</template>

<script>
export default {
name: 'MyBrotherA',
data() {
return {
text: ''
}
},
methods: {
addChild() {
// 父组件给子组件添加了一个自定义事件,事件名为 add,那么该事件可以在子组件中通过 $emit 去触发
this.$emit('add', this.text);
}
}
}
</script>
3. .sync

概述:字组件可修改父组件的传的数据,实现简单的双向绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 父组件
<template>
<div>
<buttn @click="show">点我显示子组件</button>
// 引入的子组件
<child :isShow="isShow" @closeShow="closeShow" v-show="!show"></child>
// 相当于
<child :isShow.sync="isShow" @closeShow="closeShow" v-show="!show"></child>
</div>
</template>

// 子组件
<template>
<div>
<button @click="changeShow" v-show="show">点我隐藏自己</button>
// 相当于
<button @click="$emit('update:isShow',isShow)" v-show="show">点我隐藏自己</button>
<div>
</template>
4. EventBus

概述:又称为中央事件总线,作为Vue 组件沟通的桥梁,所有的组件会共同使用一个事件中心,可以向该中心去注册事件或者发送事件。

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
33
// 第一步、创建一个单独的空 Vue 实例作为事件中心
// Bus.js
import Vue from 'vue'
export default new Vue()


// 第二步、在组件 A 中监听事件
import Bus from './Bus.js'

exoprt default {
created() {
// 监听 Bus 中的一个叫 send 的事件
Bus.$on('send', function(arg) {
// 监听到该事件后,怎么处理,该回调函数,可以接受参数,参数是触发该事件时传递的
})
},
// 在组件销毁前取消事件监听,否则可能会出现多次监听同一个事件
beforeDestroy() {
Bus.$off('text');
}
}


// 第三步、在组件 B 中触发组件 A 监听的事件
import Bus from './Bus.js'

export default {
methods: {
sendMsg() {
Bus.$emit('send', 参数)
}
}
}
2. 优缺点

优点:

  • 可以全局使用
  • 比较灵活,代码量少

缺点:

  • 时间必须成对出现(监听,触发)
  • 事件只能单向传递
  • 事件监听须在事件触发之前完成
  • 一定要在组件销毁之前取消事件监听,否侧会出现多次监听的效果
5. Vuex

概述:集中式存储管理当前应用的所有组件状态,保证状态以一种可预测的方式进行变化。

应用场景:任何两个组件甚至是多个组件之间传递数据。

1. store

概述:每一个Vue实例只能包含一个store实例,该store相当于一个仓库,所有需要跨组件通信的组件都从store中读取数据,修改数据的时候也通过store提供的方法来修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
}
})
2. state

理解:作为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
// store中
state: {
// 这里存放组件通信的数据
num: 0,
msg:'hi'
},

// 组件中
this.$store.state.num
// 或者 mapState辅助函数
import {mapState} from 'vuex'

export default {
computed: mapState({
// 简化方式1 - 函数返回需要用到的 state
num: state => state.num,
// 方式2 - 直接传入字符串,将 store 中的 num 映射为当前组件 的 numStr
numStr: 'num',
}),
// 方式3 - 字符串的写法,如果属性名与属性值一致
computed: {
...mapState([
'num'
])
}
}

3. getters

理解:可以认为是Vuex的计算属性,getters的返回值会根据他的依赖被缓存起来,只有他的以来发生了改变才会重新被计算。

语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// store中
getters: {
doubleNum(state) { // 调用 getters 可以得到 num 的2倍值,但是 num 的值是没有改变的
return state.num * 2
},
// getters 没有第二个参数,但是可以在一个函数中返回一个 geeter,这样该函数就可以设置 参数了
countNumN(state) { // 此时调用 getter 返回的是一个函数而不是值
return (n) => state.num * n
}
},

// 组件中
this.$store.getters.doubleNum
// 或者 mapGetters辅助函数
export default {
computed: {
...mapState([
'num'
])}
}
4. mutations

理解:修改store中的数据必须通过mutations修改

该函数内部只能是同步操作,不能有其他与数据修改无关的操作,比如请求、计时器、本地存储等等。

语法:

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
// store中
mutations: { // 定义修改数据的方法
increment(state) { // 参数 state 就是状态
state.num += 1;
},
incrementPayload(state, payload) { // 参数2 就是调用 mutation 时传递的数据
state.num += payload;
}
},

// 组件中调用
this.$store.commit('increment')
this.$store.commit('incrementPayload', 5) // 传参

// 或者 mapMutation
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

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

理解:可以用于异步操作、或者其他非纯函数操作(本地存储),当异步完成后,可以去提交 mutations,从而改变状态。

actions不能修改数据,但是可以在actions中国提交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
// store 中
actions: {
// 异步操作、需要在 1s 之后修改 num
incrementAsync(context) { // context 是一个与 store 实例具有相同方法和属性的对象
setTimeout(() => {
// 调用 mutataion
context.commit('increment')
}, 1000)
},
incrementAsyncP(context, payload) {
setTimeout(() => {
context.commit('incrementPayload', payload)
}, 1000)
}
},

// 组件中
this.$store.dispatch('incrementAsync');
this.$store.dispatch('incrementAsyncP', 5)
// 或者 mapActions
methods: {
...mapActions(['incrementAsync']),
}
this.incrementAsync() {
...
}
6. modules

语法:

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
// store index.js文件
import Vue from 'vue'
import Vuex from 'vuex'
import serviceOrder from './create'

Vue.use(Vuex)

export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
serviceOrder,
banner: bannerStore,
}
})

// create index.js文件
export default {
namespaced:true // 命名空间 自己的作用域
state:{}
}

// 组件中
computed: {
...mapState(
{
tableData: state => state.serviceOrder.tableData,
total: state => state.serviceOrder.total,
count: state => state.serviceOrder.count
}),

methods: {
...mapActions({
getData: 'serviceOrder/getDataAction',
findOrder: 'serviceOrder/searchOrderAction',
findService: 'serviceOrder/searchServiceAction',
findCounselor: 'serviceOrder/searchCounselorAction',
changeTab: 'serviceOrder/changeTabAction'
}),
}
6. $root

所有组件通过 this.$root 访问根组件。

7. $parent

子组件通过 this.$parent 可以获取父组件实例

8. $children

父组件通过 this.$children 获取子组件实例

9. $ref

获取 DOM 节点 或者 组件实例的

3.4 生命周期

概述:每一个Vue实例被创建时都要经历的一系列的初始化过程,在这个过程中自动运行的一系列的函数。

1. vue生命周期
  • beforeCreate:实例初始化完成后,在数据观测和事件配置之前调用,在当前阶段 data、methods等还无法被访问
  • created:实力已经创建,可以访问data,但是不能访问dom节点,可以用于在这里发送请求
  • beforeMount:在节点挂载之前调用,此时页面呈现的都是未经Vue编译的Dom结构。在这里对所有的DOM的操作,都是不奏效的。
  • mounted:节点挂载完成后调用,可以访问data,也可以访问节点,可以用于在这里发送请求。
  • beforeUpdate:数据更新之前调用
  • updated:发生在更新之后。注意:不能再此修改数据,因为修改数据后又会导致该函数调用,就会陷入死循环。
  • beforeDestroy:实例销毁前,一般在这里进行一些计时器、事件的清除工作。
  • destroyed:实例销毁后。
2. 组件生命周期

加载渲染过程:

⽗ beforeCreate -> ⽗ created -> ⽗ beforeMount -> ⼦ beforeCreate -> ⼦ created -> ⼦ beforeMount -> ⼦ mounted -> ⽗ mounted

子组件更新过程:

⽗ beforeUpdate -> ⼦ beforeUpdate -> ⼦ updated -> ⽗ updated

父组件更新过程:

⽗ beforeUpdate -> ⽗ updated

销毁过程:

⽗ beforeDestroy -> ⼦ beforeDestroy -> ⼦ destroyed -> ⽗ destroyed

3.5 计算属性和监听属性

1. 计算属性 computed

概述:主要用于对数据进行改造输出的,适用于复杂数据的转换,统计,格式编辑等等场景。

原理:底层借助了Object.defineproperty()方法提供的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
data: {
firstName:'张',
lastName:'三',
x:'你好'
},
computed: {
//完整写法
// fullName: {
// get() {
// console.log('get被调用了')
// return this.firstName + '-' + this.lastName
// },
// set(value) {
// console.log('set', value)
// const arr = value.split('-')
// this.firstName = arr[0]
// this.lastName = arr[1]
// }
// }

// 简写
fullName() {
console.log('get被调用了')
return this.firstName + '-' + this.lastName
}
}
2. 监听属性 watch

概述:监控已有的属性,一单属性发生改变就会自动调用相应的方法。

应用场景:根据数据变化去请求。监控用户输入。分页页码发生改变需要请求新的数据。

配置项属性 **immediate:false**,改为true,则初始化时调用一次。

1
2
3
4
5
6
7
8
9
10
11
12
data: {
isHot: true,
},

watch:{
isHot:{
immediate:true,
handler(newValue,oldValue){
console.log('isHot被修改 了',newValue,oldValue)
}
}
}

深度监听:Vue中的watch默认不检测对象内部属性值的改变。为了提高效率。

在watch中配置deep:true可以监听对象内部属性值的改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
watch: {
// 监视多级结构中某个属性的变化
/* 'numbers.a':{
handler(){
console.log('a被改变了')
}
} */
// 监视多级结构中所有属性的变化
numbers: {
deep: true,
handler() {
console.log('numbers改变了')
}
}
}
3. 区别
  • computed能完成的功能,watch都能完成,但watch能完成的,但computed不一定能完成。
  • computed一般应用于tabs切换列表数据
  • computed只能执行同步操作,watch能执行异步操作。
  • computed处理的数据有缓存,只有依赖的 data 或者 props 发生改变后才会重复执行,

3.5 插槽

概述:允许开发者进行组件的扩展,可以更好的复用组件以及定制化处理

1. 匿名插槽
1
2
3
4
5
6
7
8
9
10
11
12
父组件中:
<Category>
<div>html结构1</div>
</Category>

子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot>插槽默认内容...</slot>
</div>
</template>
2. 具名插槽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

<Category>
<template slot="center">
<div>html结构1</div>
</template>

<template v-slot:footer>
<div>html结构2</div>
</template>
</Category>

子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot name="center">插槽默认内容...</slot>
<slot name="footer">插槽默认内容...</slot>
</div>
</template>
3. 作用域插槽

概述:数据在子组件自身,但根据数据生产翁的结构需要组件的使用者(父组件)来决定。

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
父组件中:
<Category>
<template scope="scopeData">
<!-- 生成的是ul列表 -->
<ul>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ul>
</template>
</Category>

<Category>
<template slot-scope="scopeData">
<!-- 生成的是h4标题 -->
<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
</template>
</Category>
子组件中:
<template>
<div>
<slot :games="games"></slot>
</div>
</template>

<script>
export default {
name:'Category',
props:['title'],
//数据在子组件自身
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽']
}
},
}
</script>

应用场景:一般情况应用于编辑,删除等按钮来获取这一行的数据。

3.6 vue-router

概述:前端路由理解为单页开发中,负责页面内容的开发,根据不同的url地址去展示不同的内容。

1. 配置:
  1. main.js配置:
1
2
3
4
5
6
7
import router from './router'

new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
  1. 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
import Vue from 'vue'
import VueRouter from 'vue-router'
import LayoutView from '../views/LayoutView.vue'

Vue.use(VueRouter) // vue 中所有使用的三方模块引入后都必须注册

const routes = [ // 路由配置
{
path: '/', // 路径
name: 'home', // 路由名
component: HomeView // 当前路径匹配的组件,即访问当前路径时,显示该组件
},
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue') // 路由懒加载,表示当访问该路径时,才去执行该函数,然后加载该组件,用于优化
}
]

const router = new VueRouter({ // 路由实例化
mode: 'history', // 路由模式,另一种模式是 hash
base: process.env.BASE_URL,
routes
})

// 路由守卫
router.beforeEach((to, from, next) => {
// 用户登录的凭证
const token = store.state.token

if (to.meta.auth) {
if (token) {
next()
} else {
next('/login')
}
}else {
next()
}
})

export default router
2. 实现切换
1
<router-link active-class="active" to="/about">About</router-link>
3. 指定展示位置
1
<router-view></router-view>
4. 注意点
  • 路由组件通常存放在pages文件夹,一般组件q 通常存放在components文件夹。
  • 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
  • 每个组件都有自己的$route属性,里面存储着自己的路由信息。
  • 整个应用只有一个router,可以通过组件的$router属性获取到。
5. 多级路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
routes:[
{
path:'/about',
component:About,
},
{
path:'/home',
component:Home,
children:[ //通过children配置子级路由
{
path:'news', //此处一定不要写:/news
component:() => import('../views/NewView.vue')
},
{
path:'message',//此处一定不要写:/message
component:Message
}
]
}
]

跳转:

1
<router-link to="/home/news">News</router-link>
6. 路由传参
1. query传参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 跳转并携带query参数,to的字符串写法 -->
<router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>

<!-- 跳转并携带query参数,to的对象写法 -->
<router-link
:to="{
path:'/home/message/detail',
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link>

// 接受参数
$route.query.id
$route.query.title
2. params传参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<!-- 跳转并携带params参数,to的对象写法 -->
<router-link
:to="{
name:'/home/message/detail',
params:{
id:666,
title:'你好'
}
}"
>跳转</router-link>

// 接受参数
$route.query.id
$route.query.title
3. 动态路由

在router目录下的index.js文件中,对path属性加上/:id。使用router对象的params.id获取动态参数

1
2
3
4
5
6
7
8
9
10
11
{
path: 'student/:id', // 表示匹配 /student/ 下任意路径,比如 /student/abc, /student/123
name: 'ViewStudent',
component: () => import('../views/ViewStudent.vue')
}

// 路径跳转
this.$router.push('/student/' + id);

// 获取参数
this.$route.params.id
4. query和params区别

params 传参类似于网络请求中的 post 请求,params 传过去的参数不会显示在地址栏中(但是不能刷新), params 传参后,刷新页面会失去拿到的参数。params 只能配合 name 使用,如果提供了 path,params 会失效。

query 传参类似于网络请求中的 get 请求,query 传过去的参数会拼接在地址栏中(?name=xx)。query 较为灵活既可以配合 path 使用,也能配合 name 使用(亲测可用)。

7. 编程式路由导航
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
// params传参
this.$router.push({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})

this.$router.replace({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})

// query传参
his.$router.push({
path:'/xiangqing',
query:{
id:xxx,
title:xxx
}
})

this.$router.replace({
path:'xiangqing',
query:{
id:xxx,
title:xxx
}
})
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //可前进也可后退

// 接收参数 query
this.$route.query
// 接受参数 params
this.$route.params

router-link的replace属性:控制路由跳转时操作浏览器历史记录的模式,浏览器的历史记录有两种写入方式:分别为pushreplacepush是追加历史记录,replace是替换当前记录。路由跳转时候默认为push如何开启replace模式:```<router-link replace …….>News``

8. 路由组件钩子

概述:keep-alive 让步展示的路由组建保持挂载,不被销毁

1
2
3
<keep-alive include="News"> 
<router-view></router-view>
</keep-alive>

activated和deactivated

作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。

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

```deactivated```路由组件失活时触发。

应用:可以将定时器写在被激活时里,销毁定时器写在失活里,组件跳转时会被触发

##### 9. 路由守卫

###### 1. 全局守卫

概述:对路由进行权限控制

```js
//全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
console.log('beforeEach',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next() //放行
}
})

//全局后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
console.log('afterEach',to,from)
if(to.meta.title){
document.title = to.meta.title //修改网页的title
}else{
document.title = 'vue_test'
}
})
2. 独享守卫

概述:当前路由独享的守卫,可判断当前路由是否需要权限控制

1
2
3
4
5
6
7
8
9
10
11
12
13
beforeEnter(to,from,next){
console.log('beforeEnter',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){
next()
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next()
}
}
3. 组件内守卫
1
2
3
4
5
6
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
10. 路由模式
1. hasn模式
  • hash 模式是一种把前端路由的路径用井号 # 拼接在真实 url 后面的模式。当井号 # 后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发 onhashchange 事件。不会被包括在 http 请求中。
  • hash变化会触发网页跳转,即浏览器的前进和后退。
  • hash 通过 window.onhashchange 的方式,来监听 hash 的改变,借此实现无刷新跳转的功能
  • hash 可以改变 url ,但是不会触发页面重新加载,不会出现在``http`中
  • 首先,hash本来是拿来做页面定位的,如果拿来做路由的话,原来的锚点功能就不能用了。其次,``hash的传参是基于url`的,如果要传递复杂的数据,会有体积的限制
2. history模式
  • 利用H5的 history中新增的两个API pushState()replaceState() 和一个事件``onpopstate`监听URL变化
  • history每次刷新会重新像后端请求整个网址,也就是重新请求服务器。如果后端没有及时响应,就会报错404!
  • history模式不仅可以在``url`里放参数,还可以将数据存放在一个特定的对象中。
  • 修改历史状态: 包括了 HTML5 History Interface 中新增的 pushState()replaceState() 方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了 url,但浏览器不会立即向后端发送请求。如果要做到改变 url 但又不刷新页面的效果,就需要前端用上这两个 API。
  • 切换历史状态: 包括 forward()back()go() 三个方法,对应浏览器的前进,后退,跳转操作。
3. 两种模式对比
  • history 模式的 pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL;
  • pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
  • pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;
  • pushState() 可额外设置 title 属性供后续使用。
  • hash 模式下,仅 hash 符号之前的 url 会被包含在请求中,后端如果没有做到对路由的全覆盖,也不会返回 404 错误;history 模式下,前端的 url 必须和实际向后端发起请求的 url一致,如果没有对用的路由处理,将返回 404 错误。

3.7 过滤器(v3已删)

概述: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
<template>
<div id="app">
<ul v-for="(item, index) in arr" :key="index">
<!-- 使用过滤器语法 -->
<li>运输状态:{{ item.expressState | showState }}</li>
</ul>
</div>
</template>

<script>
export default {
// data ...... 篇幅有限直接省略掉
// 在组件内定义,然后根据不同的状态返回不同的值内容
filters: {
showState(state) {
switch (state) {
case "1":
return "待发货";
break;
case "2":
return "已发货";
break;
case "3":
return "运输中";
break;
case "4":
return "派件中";
break;
case "5":
return "已收货";
break;
default:
return "快递信息丢失";
}
},
},
};
</script>

删除原因:在v3中为了精简代码,删除了过滤器,因为在computed计算属性和methods方法中也可以对数据进行加工。

4. Vue 引入

4.1 图片引入

1. 相对路径

<img src="@/assets/images/g1.jpeg" alt="">

最终图片路径会发生改变

2. require
1
2
3
4
5
6
7
8
9
<img :src="logo" alt="">
{
...
data() {
return {
logo: require('../assets/pic.jpg')
}
}
}
3. import
1
2
3
4
// import
<img :src="logo" alt="">

import logo from '../assets/logo.png'

4.2 动画引入

概述:Vue 中可以实现在 插入、更新、移除 DOM 的时候,添加过度效果。

条件:
  • 条件渲染(v-if,v-show)
  • 动态组件
  • 列表渲染
使用

需要添加动画的标签或者组件,在外部添加 transition 组件包装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<transition>
<p v-if=""></p>
</transition>

v-enter: 表示开始进入过度

v-enter-active:正在进入过度

v-enter-to:进入过度结束

v-leave:准备离开的过度

v-leave-active:正在离开的过度

v-leave-to:已经离开

5. Vue属性

1. scoped 属性

概述: 因为 css 没有局部和全局的概念,所有的css都是全局css,所以默认情况下 <style> 就是全局,各个组件之间如果 class 名字相同,则会相互影响。

解决:给 style 添加 scoped 属性,则当前 style 中的 css 会变为局部 css。

作用:组件样式不相互污染。

原理:

  • 给``HTML的dom节点添加一个不重复的data`属性(例如: data-v-5558831a)来唯一标识这个dom 元素
  • 在每句css选择器的末尾(编译后生成的css语句)加一个当前组件的data属性选择器(例如:[data-v-5558831a])来私有化样式

样式穿透: 当我们引入第三方组件库时,需要在局部组件中修改第三方组件库的样式,而又不想去除scoped属性造成组件之间的样式覆盖。

使用:

  • stylus的样式穿透 使用>>> 外层 >>> 第三方组件
  • sass和less的样式穿透 使用/deep/ 外层 /deep/ 第三方组件

2. nextTick

概述:等待下一次 DOM 更新刷新的方法

Vue有个异步更新策略,意思是如果数据变化,Vue不会立刻更新DOM,而是开启一个队列,把组件更新函数保存在队列中,在同一时间循环发生的所有数据变更会异步的批量更新。这一策略导致我们对数据的修改不会立刻体现在DOM上,此时如果想要获取更新后的DOM状态,就需要使用nextTick.

使用场景: created中想要获取DOM时,响应式数据变化后获取DOM更新后的状态,比如希望获取列表更新后的高度

1
2
3
4
5
6
created(){
let that=this;
that.$nextTick(function(){ //不使用this.$nextTick()方法会报错
that.$refs.aa.innerHTML="created中更改了按钮内容"; //写入到DOM元素
});

二、Vue 周边

1. element-ui

1.1 环境安装

npm i element-ui -S

1.2 引入框架

1. 全局引入

main.js文件

1
2
3
4
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);
2. 按需加载

安装模块:npm install babel-plugin-component -D

babel.config.js文件

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
"presets": ['@vue/cli-plugin-babel/preset'],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}

main.js中:

1
2
3
4
5
import Vue from 'vue';
import { Button, Select } from 'element-ui';

Vue.use(Button)
Vue.use(Select)

2. sass

概述:属于 css 的扩展语言、支持所有 css 的语法,然后新增语法。

1. 安装

npm i sass/node-sass/dart-sass

2. sass 语法

1. 变量
1
2
3
4
5
6
7
8
9
10
// 定义:以 $ 开头,后面跟上变量名
$theme-color: #ff0;
$b: border;

// 使用
nav {
background: $theme-color;
}
footer {
border: 1px solid $theme-color;
2. 插值
1
2
3
p .#{$b} {
#{$b}-left: 1px solid $theme-color;
}
3. 导入

@import ‘文件名’; // 文件后缀如果是 .scss 那么可以省略不写

4. 嵌套
1
2
3
4
5
6
7
8
9
10
11
.body {
width: 200px;

p {
text-align: center;
}

a {
color: red;
}
}
5. 混合

概述:当出现大段重复的代码时,就可以使用混合趋势线打断样式的复用

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
// 定义混合 - 此段代码可以重复利用
@mixin center {
width: 800px;
margin-left: auto;
margin-right: auto;
}

// 使用 - 通过 @include 来标识
.content {
@include center;
background: red;
}

// 混合允许使用变量 - 类似混合是函数,函数也可以接受参数,并且参数可以有默认值
@mixin center($w) { // $w 就是参数
width: $w;
margin-left: auto;
margin-right: auto;
}
// 使用
.content {
@include center(1000px);
}

@mixin center($w:800px) { // 默认值
width: $w;
margin-left: auto;
margin-right: auto;
}
// 使用
.content {
@include center(); // 没有传值就是默认值
}
6. 继承

概述:一个选择器继承另外一个选择器。

1
2
3
4
5
6
7
8
9
.error {
background: red;
color: #fff;
}

.seriousError {
@extend .error; // 通过 @extend 去继承另外一个选择器中所有的 css 代码
background: darkred;
}

3. axios

概述:axios 是一个基于 Promise 的 HTTP 请求库,可以在浏览器和 nodejs 中使用。有非常多的特性:请求拦截和响应拦截、取消请求、客户端 XSRF 防御。

1. 安装

npm i axios

2. 使用axios

npm i axios vue-axios

main.js配置

1
2
3
import http from 'axios'

Vue.prototype.$http = http

使用:

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
 // GET 请求,参数放在 url 中 
axios.get('/user?ID=12345')
.then(function (response) {
// 处理成功情况
console.log(response);
})
.catch(function (error) {
// 处理错误情况
console.log(error);
})

// GET请求 参数放在params中
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})

// axios 发送 get
axios({
method: 'get',
url: '/user/12345',
params: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// POST 请求 
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

// axios 发送 post
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 并发请求
function getUserAccount() {
return axios.get('/user/12345');
}

function getUserPermissions() {
return axios.get('/user/12345/permissions');
}

// promise.all 此实例参数内所有的promise都完成,如果有一个失败,则实例回调失败,失败原因的是第一个失败的原因
Promise.all([getUserAccount(), getUserPermissions()])
.then(function (results) {
const acct = results[0];
const perm = results[1];
});

3. 封装

原因:

  • 方便修改baseUrl
  • loading 处理
  • token 处理
  • 错误处理
  • 返回数据过虑
  • 取消请求
  • 过滤相同请求

具体封装见axios总结