vue
1. 什么是 vue
-
官方给出的概念:vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式****框架。
-
构建用户界面:前端每天就是在做画用户界面的工作,vue 帮助我们以更简单的方式来做这些事情。
-
渐进式:vue 不强求程序员一次性接受并使用它的全部功能和特性。
-
框架:框架指的就是程序员必须遵守的规则或约束。
2. vue指令 - vue 中6中常见的指令
内容渲染指令(对应dom中的文本节点)
{{ }}
v-html
属性绑定指令(对应dom中的属性节点)
v-bind
:
事件绑定指令(对应dom中的事件)
v-on:事件类型="事件处理函数"
@事件类型="事件处理函数"
- 事件修饰符
.stop
.prevent
- 按键修饰符
.enter
- …
双向绑定指令(vue中特有的,react中想做需要自己实现)
v-model
- 专属修饰符
.number
.trim
.lazy
条件渲染指令(在页面模板中写
if
)列表渲染指令(在页面模板中写
for
)
- 概念:指令(Directives)是 vue 为开发者提供的一套特殊语法。
- vue模板:指的是被vue控制区域里面的html部分
2.1 内容渲染指令
-
{{ }}
又名:插值表达式
作用:将数据动态的渲染到 DOM 的内容区域
<p>姓名:{{ username }}</p> <p>性别:{{ gender }}</p>
-
v-html
作用:可以将带有HTML标签的字符串渲染成真正的 HTML 元素
<p v-html="str"></p>
2.2 属性绑定指令
-
v-bind
作用:将数据动态的绑定到元素的属性身上
语法:v-bind:属性名=“属性值”
<input type="text" v-bind:placeholder="inpValue">
-
简写:
:
[推荐]<input type="text" :placeholder="inpValue">
2.3 事件绑定指令
-
语法:
v-on:事件类型="事件处理函数"
<button v-on:click="addCount"> 点击 +1 </button>
-
简写:
@事件类型="事件处理函数"
[推荐]<button @click="redCount"> 点击 -1 </button>
-
注意
-
如果事件处理函数只有一行代码,可以写到行内
<button @click="count = 0"> 点击恢复初始值 0 </button>
-
事件传参
<button @click="take(2)"> 点击 * 2 </button> <button @click="take(10)"> 点击 * 10 </button>
-
-
示例
<!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> <script src="./lib/vue.js"></script> </head> <body> <div id="app"> <h3>cont 的值是:{{ count }}</h3> <!-- v-on --> <!-- 语法:v-on:事件类型="事件处理函数" --> <button v-on:click="addCount"> 点击 +1 </button> <!-- 简写:@事件类型="事件处理函数" --> <button @click="redCount"> 点击 -1 </button> <hr> <!-- 如果事件处理函数只有一行代码,可以写到行内 --> <button @click="count = 0"> 点击恢复初始值 0 </button> <hr> <!-- 事件传参 --> <button @click="take(2)"> 点击 * 2 </button> <button @click="take(10)"> 点击 * 10 </button> </div> <script> const vm = new Vue({ el: '#app', data: { count: 0 }, methods: { // addCount: function() { // 下一行为这一行的简写 addCount() { // 上一行的简写,ES6中,对象里面函数的简写方式 // 方法中通过 this.数据名 来拿到data中定义的数据 this.count++ }, redCount() { this.count-- }, take(num) { this.count *= num } } }) </script> </body> </html>
2.3.1 事件修饰符
-
.prevent
阻止事件的默认行为<a :href="www" @click.prevent="toBai">{{ txt }}</a>
-
.stop
阻止事件冒泡,谁产生的冒泡加给谁<div class="outer-box" @click="outerClick"> <!-- .stop 阻止事件冒泡,谁产生的冒泡加给谁 --> <div class="inner-box" @click.stop="innerClick"></div> </div>
2.3.2 按键修饰符
-
enter
只有按下enter键才会触发<input type="text" @keyup.enter="submit">
-
esc
只有按下esc键才会触发<input type="text" @keyup.esc="clearsubmit">
2.4 双向绑定指令
-
v-model
-
特点:数据变化视图更新,视图变化数据更新。
-
注意:v-model 只能运用在表单元素上。
<input type="text" v-model="username">
2.4.1 专属修饰符
-
.number
- 作用:将v-model 绑定的数据装换成数值类型(number类型)
<input type="text" v-model.number="n1"> + <input type="text" v-model.number="n2"> = {{ n1 + n2 }}
-
.trim
- 作用:自动的将 v-model 绑定的数据去掉前后的空格
<input type="text" v-model.trim="str">
-
.lazy
- 作用:将 v-model 默认的input事件(改变就触发),改变为change事件(失去焦点并且改变了触发)
<input type="text" v-model.lazy="str2">
2.5 条件渲染指令
-
v-if
(推荐)v-show
- v-if 和 v-show 都是用来控制 DOM 元素的显示与隐藏
- v-if 和 v-show 绑定的 data 数据值为 true 显示,false 隐藏
<!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> <script src="./lib/vue.js"></script> </head> <body> <div id="app"> <!-- 显示隐藏 --> <button @click="flag = !flag">显示与隐藏</button> <hr> <!-- v-if 和 v-show 都是用来控制 DOM 元素的显示与隐藏 --> <!-- v-if 和 v-show 绑定的 data 数据值为 true 显示,false 隐藏 --> <!-- 推荐 v-if --> <p v-if="flag">这是 v-if 控制的 DOM 元素</p> <p v-show="flag">这是 v-show 控制的 DOM 元素</p> <hr> </div> <script> const vm = new Vue({ el: '#app', data: { a: 10, b: 5, flag: true } }) </script> </body> </html>
2.5.1 v-if的配套指令
-
v-else-if
v-else
-
注意:v-else 和 v-else-if 指令必须配合 v-if 一起使用,否则报错
<!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>v-if的配套指令</title> <script src="./lib/vue.js"></script> </head> <body> <div id="app"> <!-- v-if 的配套指令:v-else-if v-else --> <!-- 注意:v-else 和 v-else-if 指令必须配合 v-if 一起使用,否则报错 --> <p v-if="score === 'A'">优秀</p> <p v-else-if="score === 'B'">良好</p> <p v-else-if="score === 'C'">一般</p> <p v-else>差</p> </div> <script> const vm = new Vue({ el: '#app', data: { score: 'A' } }) </script> </body> </html>
2.6 列表渲染指令
v-for
- 语法:v-for=“数组中的每一项 in 数组”
- v-for需要循环渲染哪个元素,就在哪个元素身上添加 v-for
- v-for指令还支持一个可选的第二个参数,即当前的索引
- 注意:
- 使用 v-for 指令时 一定要指定 key 的值(既提升性能,又防止列表渲染混乱)
- key 的值只能是字符串或数字类型
- key 的值必须具有唯一性(建议把数据项id属性的值作为key的值)
<!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>
<script src="./lib/vue.js"></script>
</head>
<body>
<div id="app">
<!--
注意事项:
1. 使用 v-for 指令时 一定要指定 key 的值(既提升性能,又防止列表渲染混乱)
2. key 的值只能是字符串或数字类型
3. key 的值必须具有唯一性(建议把数据项id属性的值作为key的值)
-->
<ul>
<!-- v-for需要循环渲染哪个元素,就在哪个元素身上添加 v-for -->
<!-- 语法:v-for="数组中的每一项 in 数组" -->
<li v-for="item in list" :key="item.id">姓名:{{ item.name }}</li>
<hr>
<!-- v-for指令还支持一个可选的第二个参数,即当前的索引 -->
<li v-for="(item, index) in list" :key="item.id"> {{ list[index] }} </li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
list: [
{id: 1, name: '张三', age: 23},
{id: 2, name: '李四', age: 24},
{id: 3, name: '王五', age: 25},
{id: 4, name: '赵六', age: 25}
]
}
})
</script>
</body>
</html>
3. MVVM 的概念
3.1 vue 的特性
- vue 框架的特性,主要体现在两方面:
- 数据驱动视图
- 在使用了 vue 的页面中,data 数据的变化,会导致页面结构的重新渲染。
- 好处:减少了程序员对 DOM 的
- 注意:数据驱动视图是单向的数据绑定。
- 双向数据绑
- data 数据的变化,会导致页面的重新渲染
- 表单数据的变化,会被自动更新到 data 数据中
- 好处:开发者不再需要手动操作 DOM 元素,来获取表单元素最新的值
- 数据驱动视图
3.2 MVVM
MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。
MVVM 指的是 Model、View 和 ViewModel。
它把每个 HTML 页面都拆分成了这三个部分,如图所示:
4. vue 过滤器
定义过滤器
Vue.filter(‘过滤器名字’, fn)
使用过滤器
{{ 要转换格式的数据 | 过滤器的名字 }}
-
作用:
过滤器时给在模板中使用的数据做格式转换的
-
用途:过滤器可以使用在两个地方:
- 插值表达式
- v-bind 属性绑定
-
使用步骤:
-
定义过滤器
Vue.filter('dollar', (str) => { return '$' + str })
-
使用过滤器
<h2>{{ money | dollar }}<h2>
data: { money: 100 }
-
-
注意:
- 过滤器必须定义在
new Vue
之前。 - fn 函数的格式:(要转换格式的数据) => { return 转换格式后的结果 }
- 过滤器仅在 vue 2.x 和 vue1.x 中支持,在 vue3.x 的版本中剔除了过滤器相关功能。如果时 vue 3.x 的项目,官方建议使用计算属性或
methods
方法实现对应功能就好了。
- 过滤器必须定义在
5. 侦听器
5.1 函数形式 - 侦听器
-
作用:watch 侦听器可以监听数据的变化,从而针对数据的变化做特定的操作。
-
使用步骤:
<div id="app"> <input type="text" v-model="username"> </div>
data: { username: 'zs' }, watch: { // 这里的函数名要对应你要监听的数据名 username(newVal, oldVal) { // newVal 是变化后的新值,oldVal 是变化前的老值 console.log(newVal, oldVal) } }
-
注意:不需要带选项得时候这样写,这样写无法带选项
5.2 对象形式 - 侦听器
- 作用:watch 侦听器可以监听数据的变化,从而针对数据的变化做特定的操作。
- 特点可以带选项
5.2.1 innediate 选项
-
功能:默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器一进入页面就立即被 调用一次,则需要使用 immediate 选项。
<input type="text" v-model="username">
watch: { username: { handler(newVal, oldVal) { console.log(newVal, oldVal) }, immediate: true } }
-
注意:
- 侦听器要写成对象形式,参数放在
handler
函数里面 - 添加
immediate
选项为true
- 侦听器要写成对象形式,参数放在
5.2.2 deep 选项
-
用途:如果 watch 侦听的是一个对象,对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep
-
示例:
<input type="text" v-model="user.age">
data: { user: { age: 18 } }, watch: { user: { handler(newVal, oldVal) { console.log(newVal.age, oldVal.age) }, deep: true } }
-
注意:
- 侦听器要写成对象形式,参数放在
handler
函数里面 - 如果监听的数据是一个对象,想要对象里任何一个属性发生变化监听到,就要设置
deep
选项值为true
- 侦听器要写成对象形式,参数放在
5.2.3 监听对象中单个属性的变化
-
如果只想监听对象中单个属性的变化,则可以按照如下的方式定义 watch 侦听器:
-
示例:
<input type="text" v-model="user.age">
watch: { 'user.age'(newVal, oldVal) { console.log(newVal, oldVal) } }
6. 计算属性
6.1 计算属性 - 基本使用
-
定义:计算属性是依赖于 data 中的数据动态计算出来的一个值
-
特点:计算属性所依赖的任何一个 data 数据发生变化,这个计算属性也会跟着变化。
-
示例:
-
定义计算属性:
data: { n1: 1, n2: 1 }, // 计算属性声明在 computed 选项下面 computed: { // 计算属性声明的时候被定义为一个方法,使用的时候是作为一个属性去使用。 // 计算属性中,必须 return 一个计算的结果 chengJi() { return this.n1 * this.n2 } }
2. 使用计算属性: ```html <!-- 计算属性 --> <input type="number" v-model="n1"> * <input type="number" v-model="n2"> = {{ chengJi }}
-
-
注意:
- 计算属性声明在 computed 选项下面
- 计算属性声明的时候被定义为一个方法,使用的时候是作为一个属性去使用。
- 计算属性中,必须 return 一个计算的结果
6.2 计算属性 - 计算属性的缓存
-
计算属性能做的事,都可以使用方法代替。
-
区别:计算属性会缓存计算的结果,只有计算属性依赖的数据变化时,才会重新进行计算。而函数不会缓存计算的 结果,所以计算属性的性能比函数要好。
-
示例:
-
声明一个计算属性和一个方法
// 使用 computed 选项声明计算属性 computed: { // 计算属性定义的的时候是一个函数 multi() { // 不管调用调用几次,只要data里的值不变就会调用一次,因为计算结果会缓存 // 只有计算属性依赖的数据变化时,再会再次调用一次 console.log('调用了计算属性'); // 计算属性中,要通过 return 返回一个值 return this.n1 * this.n2 } }, // 使用方法也可以是实现计算 methods: { multiByMethod() { // 调用几次,打印几次 console.log('调用了计算方法'); return this.n1 * this.n2 } }
-
在模板中分别多次调用使用计算属性和方法
<!-- 使用 computed 计算属性 --> <div>通过 computed 计算属性,计算 n1 * n2 的值:{{ multi }}</div> <div>通过 computed 计算属性,计算 n1 * n2 的值:{{ multi }}</div> <div>通过 computed 计算属性,计算 n1 * n2 的值:{{ multi }}</div> <!-- 使用 methods 中的方法 --> <div>通过 methods 计算属性,计算 n1 * n2 的值:{{ multiByMethod() }}</div> <div>通过 methods 计算属性,计算 n1 * n2 的值:{{ multiByMethod() }}</div> <div>通过 methods 计算属性,计算 n1 * n2 的值:{{ multiByMethod() }}</div>
-
打印结果:
调用了计算属性
调用了计算方法
调用了计算方法
调用了计算方法
-
-
注意:
- 使用 computed 选项声明计算属性
- 计算属性定义的的时候是一个函数
- 不管调用调用几次,只要data里的值不变就会调用一次,因为计算结果会缓存
- 只有计算属性依赖的数据变化时,再会再次调用一次
- 计算属性中,要通过 return 返回一个值
- 使用方法,调用几次,打印几次
7. vue - cli - 安装和使用
-
全局安装
vue-cli npm install -g @vue/cli
使用
vue -V
命令检查是否安装成功 -
基于 vue-cli 快速生成工程化的 Vue 项目
vue create 项目的名称(如:vue create demo-1)
-
启动项目
cd 项目的名称
npm run serve
8. Vue 组件
8.1 什么是组件化开发
组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件,提高页面 UI 结构的复用性, 从而方便项目的开发和维护。
8.2 vue 组件的三个组成部分
- 每个 vue 组件都由 3 部分构成,分别是:
template
-> 组件的模板结构script
-> 组件的 JavaScript 行为style
-> 组件的样式
- 其中,每个组件中必须包含
template
模板结构,而script
行为和style
样式是可选的
8.3 模板
示例:App.vue
<!-- vue 规定:每个组件对应的模板结构,需要定义到 <template> 节点中 -->
<template>
<div id="app">
<!-- 当前组件的 DOM 结构,需要定义在 template 标签内部 -->
<h1>这是 App.vue 组件</h1>
<!--
注意:
1. template 是 vue 提供的容器标签,只起到包裹的作用,它不会被渲染为真正的 DOM 元素
2. template 中只能包含唯一的根节点
-->
</div>
</template>
<!-- vue 规定:开发者可以在 <script> 节点中封装组件的 JavaScrript 业务逻辑 -->
<script>
// 今后,组件相关的 data 数据、methods 方法等。
// 都需要定义到 export default 所导出的对象中
export default {
// vue 规定:vue 组件中的 data 必须是一个函数,不能直接指向一个数据对象。
data() {
return {
username: 'HI'
}
}
}
</script>
<!-- vue 规定:开发者可以在 <style> 节点中编写样式美化当前组件的 UI 结构,组件内的 <style> 节点是可选的 -->
<!--
让 style 中支持 less 语法
在 <style> 标签上添加 lang="less" 属性,即可使用 less 语法编写组件的样式:
前提是需要安装 less:npm install less less-loader@7.3.0
-->
<style lang="less">
h1 {
color: green;
}
</style>
8.4 使用组件
8.4.1 组件的私有注册
通过 components 注册的是私有组件,它只能在注册它的组件中使用,不能在其他组件中使用。
-
步骤一:使用
import
语法导入需要的组件示例:App.vue
import Left from '@/components/Left.vue' import Right from '@/components/Right.vue'
-
步骤二:使用
components
节点注册组件示例:App.vue
export default { components: { Left, Right } }
-
步骤三:以标签形式使用刚才注册的组件
示例:App.vue
<div id="app"> <Left></Left> <Right></Right> </div>
8.4.2 组件的全局注册
在项目的入口文件 main.js
中,注册全局组件。
示例:main.js
// 全局注册
// 1. 导入需要全局注册的组件
import Count from '@/components/Count.vue'
// 2. 全局注册
// 注意:需要在 main.js 中,new Vue() 构造函数之前,进行全局组件的注册
// 语法:Vue.component('组件的注册名字', 导入的全局组件的名称)
Vue.component('Count', Count)
9. prop
9.1 prop 是什么
prop 是组件的自定义属性。在封装通用组件的时候,合理地使用 prop 可以极大的提高组件的复用性!
可以理解为 prop 是在使用组件的时候,从==外面传递给组件的一个参数==。
-
需求:在 Count 组件中,实现 +1 功能
示例:Count.vue
<p>count 的值是:{{ count }}</p> <button @click="count += 1">点击 +1</button>
data() { return { count: 0 } }
9.2 prop 的使用 - prop['']
prop: [’ ']
-
需求:在 Left 中 Count 实现 +1,在 Right 中的 Count 实现 +2
-
使用 prop 实现
示例:
Count.vue
// 定义 props 选项,用来接收外界传进来的参数 props: ['n']
Left.vue
<!-- 通过自定义属性给组件传参 --> <Count :n="1"></Count>
Right
<!-- 通过自定义属性给组件传参 --> <Count :n="2"></Count>
9.3 prop 的配置选项 - type
type
在声明 prop
时,可以通过 type
来规定 prop
的数据类型。
如果传递过来的数据不符合 type 设置的数据类型,vue 会在控制台中给出报错信息。
-
注意:给
prop
设置配置选项,需要使用对象形式 -
示例:
// 注意:给 prop 设置配置选项,需要使用对象形式 props: { n: { // type 可以规定传递过来的 prop 的数据类型 // 常用的数据类型:String Number Boolean Object Array type: Number } }
9.4 prop 设置默认值 - default
default
在声明 prop
时,可以通过 default
来设置 prop
的默认值。
当外界在使用组件的时候,没有传递自定义属性,prop 就使用这个默认值。
-
注意:给
prop
设置配置选项,需要使用对象形式 -
示例:
// 注意:给 prop 设置配置选项,需要使用对象形式 props: { n: { // default 用来给 prop 设置默认值 // 外界使用组件的时候,没有传递自定义属性,prop 就使用这个默认值 default: 1 } }
9.5 prop 设置是否必填 - required:true
required: true
在声明 prop
时,可以通过 required
选项,将该 prop
设置为必填项,强制外界使用组件的时候必须传递自定义属性。 否则 vue 会给出报错。
-
注意:给
prop
设置配置选项,需要使用对象形式 -
注意:优先级比 设置默认值default 高,所以两者尽量不要同时出现
-
示例:
// 注意:给 prop 设置配置选项,需要使用对象形式 props: { n: { // required: true 规定该 prop 是必填项 // 强制使用组件的时候必须传递自定义属性,否则报错 required: true } }
9.6 prop 是只读的
9.6.1 不可修改
vue 规定:组件中封装的自定义属性是只读的,我们不能直接修改 prop 的值。否则会直接报错。
-
示例:
props: { init: { type: Number, default: 0 } }
<!-- prop 是只读的,不能直接修改 --> <p>init 的值是:{{ init }}</p> <button @click="init += n">这个按钮不要点击,会报错,因为prop是只读的</button>
9.6.2 解决办法
要想修改 prop 传进来的初始值,可以把 prop 的值转存到 data 中,然后修改 data,因为 data 中的数据都 是可读可写的!
- 示例:
data() {
return {
count: 0,
// 如果想修改 prop 传递过来的初始值,可以将 prop 的值转存到 data 中,然后修改 data
coun: this.init
}
}
<!-- 利用转存到 data 中在修改 -->
<p>init 的值是:{{ coun }}</p>
<button @click="coun += n">利用转存到 data 解决不可修改,点击 +{{ n }}</button>
10. 解决组件之间的样式冲突
10.1 样式冲突的产生
默认情况下,写在 vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。
-
示例:Left.vue 和 Right.vue 都有 h3 标签。我们给 Left.vue 中的 h3 设置一个样式。
<style> h3 { color: green; } </style>
-
导致组件之间样式冲突的原因是:
- 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
- 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素
10.2 解决样式冲突 - scoped
scoped
-
style 节点的 scoped 属性
vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题:
-
示例:Left.vue
<style scoped> /* 给组件的 style 标签设置 scoped 属性,该样式只会总用于当前组件,并不会影响其他组件的样式 */ h3 { color: green; } </style>
-
注意:实际开发中,建议在每个组件的 style 身上都加上 scoped 属性
10.3 样式穿透 - 深度选择器
css中
>>>
less中
/deep/
在给当前组件的 style 添加了 scoped 属性后,如果想在父组件中修改子组件的样式,就需要使用 深度选择器。
-
在 css 中使用
>>>
示例:Left.vue
<style scoped> /* 在 css 中使用 >>> 实现样式穿透 */ div >>> h4 { color: skyblue; } </style>
-
在 less 中使用
/deep/
示例:Right.vue
<style scoped lang="less"> /* 在 less 中使用 /deep/ 实现样式穿透 */ div /deep/ h4 { color: skyblue; } </style>
- 应用场景:需要修改第三方组件库中组件的原有样式的时候。
11. 组件的生命周期
11.1 生命周期 & 生命周期函数(钩子函数)
-
生命周期:
是指一个组件从创建 -> 更新 -> 销毁的整个过程
-
生命周期函数(钩子函数):
指的是伴随着组件的生命周期,自动按次序执行的一些 vue 框架内部提供的内置函数。这些 函数也叫做生命周期钩子函数。
-
注意:
生命周期强调的是时间段,生命周期函数强调的是时间点。
11.2 组件生命周期函数的分类
beforeCreate、created、beforeMount、mounted | beforeUpdate、updated | beforeDestroy、destroyed
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WXmXNGlJ-1653472695034)(./images/生命周期图.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1NQ8Zxzq-1653472695043)(./images/生命周期图2.png)]
-
示例:Lifecycle.vue
<template> <div> <h2>生命周期演示</h2> <h3>当前n的值是:{{ n }}</h3> <button @click="add">点我 n + 1</button> <button @click="bye">点击销毁</button> </div> </template> <script> export default { data() { return { n: 0 } }, methods: { add() { this.n++ }, bye() { this.$destroy() } }, // 1. 组件创建阶段 // 1.1 beforeCreate 钩子函数:此时,无法访问 data 中的数据还有 methods 中的方法 beforeCreate() { console.log('beforeCreate执行了') // beforeCreate执行了 console.log(this.n) // undefined }, // 1.2 created 钩子函数:此时,可以访问 data 中的数据还有 methods 中的方法了 created() { console.log('created执行了') // created执行了 console.log(this.n) // 0 }, // 1.3 beforeMount 钩子函数: // 1.3.1 页面呈现的是未经Vue编译的DOM结构 // 1.3.2 所有对DOM的操作,最终都不奏效 beforeMount() { console.log('beforeMount执行了') // beforeMount执行了 console.log(document.querySelector('button')) // null }, // 1.4 mounted钩子函数: // 1.4.1 页面中呈现的是经过Vue编译的DOM // 1.4.2 对DOM的操作均有效,一般这里进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作 mounted() { console.log('mounted执行了') // beforeMount执行了 console.log(document.querySelector('button')) // <button @click="add">点我 n + 1</button> }, // 2. 组件更新阶段 // 2.1 beforeUpdate钩子函数:此时数据是新的,但是页面是旧的,即:页面尚未和数据保持同步 beforeUpdate() { console.log('beforeUpdate执行了'); // beforeUpdate执行了 console.log(this.n); // 1 }, // 2.2 updated钩子函数:此时数据是新的,页面也是新的,即:页面和数据保持同步 updated() { console.log('updated执行了') //updated执行了 console.log(this.n); // 1 }, // 3. 组件销毁阶段 // 3.1 beforeDestroy钩子函数: // 3.1.1 此时,data、methods、指令等等,都处于可用状态,马上要执行销毁过程, // 3.1.2 一般在此阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作 beforeDestroy() { console.log('beforeDestroy执行了') // beforeDestroy执行了 console.log(this.n); // 5 }, // 3.2 destroyed钩子函数:一切已经结束,忽略这个钩子函数即可 destroyed() { console.log('beforeDestroy执行了') // beforeDestroy执行了 } } </script>
12. 组件间的通信
12.1 父向子传递数据
父组件向子组件传递数据需要使用自定义属性和 prop
。
-
示例:
父组件:App.vue
<Left :msg="message" :user="userinfo"></Left>
data() { return { message: 'Hello Vue', userinfo: { name: 'HI', age: 24 } } }
子组件:Left.vue
<template> <div> <h3>Left 组件</h3> <p>父组件传递过来的 msg :<br> {{ msg }}</p> <p>父组件传递过来的 user :<br> {{ user }}</p> </div> </template>
export default { props: ['msg', 'user'] }
12.2 子向父传数据
子组件向父组件传递数据使用自定义事件和 $emit
方法
-
步骤:
-
父组件(接收方)
在要接收的子组件的标签上,注册一个自定义事件,并设置事件处理函数,把事件处理函数写到
methods
的方法中,事件处理函数的形参就是接收的值 -
子组件(传送方)
使用语法:
$emit('接收方定义的事件名', 要传递的数据)
就会传递到接收方设置的事件处理函数的形参上
-
-
示例:
父组件(接收方):App.vue
<Left @countChange="getCount"></Left>
export default { data() { return { countFromLeft: 0 } }, methods: { // 用来接收传递过来的数据 getCount(res) { this.countFromLeft = res } } }
子组件(发送方):Left.vue
export default { data() { return { count: 0 } }, methods: { // 每次调用这个方法,count 就会自身加一,然后把count的数据传到定义countChange这个事件的组件中。 add() { this.count++ this.$emit('countChange', this.count) } } }
12.3 兄弟之间传递数据
他们之间就没有一个直接的关联关系。所以想要进行通信,就需要借助第三方来作为 一个桥梁,才能实现通信。这个桥梁就是 EventBus。
- 结论:
- 在接收数据的组件中,使用 EventBus.$on 来注册一个自定义事件,用于接收数据。
- 在发送数据的组件中,使用 EventBus.$emit 来触发一个自定义事件,将数据传递出去。
-
步骤:
-
创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
-
数据接收方:调用 bus.$on(‘事件名称’, 事件处理函数) 方法注册一个自定义事件
注意:接收方把接收注册的自定义事件要写在 created钩子函数里
-
数据发送方:调用 bus.$emit(‘事件名称’, 要发送的数据) 方法触发自定义事件
-
-
示例:
eventBus.js 模块
// 导入 vue import Vue from 'vue' // 向外共享 Vue 的实例对象 export default new Vue()
兄弟组件(数据接收方):Right
import bus from '@/eventBus.js' export default { data() { return { countFromLeft: 0 } }, // created钩子函数,页面加载后渲染前就开始注册事件 created() { bus.$on('share', res => { this.countFromLeft = res }) } }
兄弟组件(数据发送方):Left
import bus from '@/eventBus.js' export default { data() { return { count: 0 } }, methods: { add() { this.count++ bus.$emit('share', this.count) } } }
13. ref 引用
用来获取页面上的DOM元素或组件实例的引用
-
把ref属性添加在标签上就是获取DOM元素
-
把ref属性添加在组件标签上就是获取组件实例
13.1 ref - 获取 DOM 元素
-
步骤:
- 使用 ref 属性,为对应的元素标签上添加引用名称
- 通过 this.$refs.索引名称,就可以获取DOM
-
示例:Right.vue
<h3 ref="myh3">Right 组件</h3> <button @click="getDom">点我把标题变绿</button>
export default { methods: { getDom() { this.$refs.myh3.style.color = 'green' } } }
13.2 ref - 获取 组件实例
-
步骤:
- 使用 ref 属性,为对应的组件元素标签上添加引用名称
- 通过 this.$refs.索引名称,就可以获取组件的示例
-
示例:App.vue
<Left ref="leftRef"></Left> <button @click="meToo">点我也能 +1 </button>
methods: { // 用来接收传递过来的数据 getCount(res) { this.countFromLeft = res }, meToo() { // 打印的是 Left 组件里 count 的数据 console.log(this.$refs.leftRef.count) // 调用组件上的方法 this.$refs.leftRef.add() } }
14. $nextTick()
方法
-
$nextTick(cb)
方法中的 cb 为回调函数,会在组件的 DOM 更新完成之后,再执行。从而能保证在 cb 回调函数里 面可以拿到最新的 DOM 元素 -
使用场景:当数据变化,想拿到最新的 DOM,要将获取 DOM 的代码放到 this.$nextTick(cb) 的 cb 回调函数中去
-
示例:
showInput() { this.isShow = true this.$nextTick(() => { // focus() 方法:自动获取焦点 this.$refs.onputRef.focus() }) }
15. 动态组件
-
什么是动态组件
-
动态组件指的是,基于
<component>
组件,来动态的切换组件的显示与隐藏 -
<component>
上 is 属性的值是谁,就是先哪个组件
-
15.1 动态组件的使用方法
-
示例:App.vue
<!-- 动态组件 <component> --> <!-- 通过 is 属性,指定要渲染的组件的名称 --> <component :is="compName"></component> <button @click="compName = 'Left'">展示 Left 组件</button> <button @click="compName = 'Right'">展示 Right 组件</button>
esport default { data() { return { compName: 'Left' } } }
15.2 keep-alive 缓存组件
-
切换组件时,会将隐藏的组件销毁掉,显示时,会重新创建。因此无法保持组件的状态。
-
所以可以使用
<keep-alive>
将<component>
进行包裹,将组件缓存,从而保持动态组件的状态。 -
示例:
<!-- 使用 keep-alive 缓存组件,保留组件的状态 --> <keep-alive> <component :is="compName"></component> </keep-alive>
注: include 属性用来指定:只有 include 属性中包含的组件会被缓存。多个组件名之间使用英文的逗号分隔
<!-- 默认情况下,keep-alive 会缓存它包裹的所有组件 --> <!-- 使用 include 属性来告诉 keep-alive,哪些组件需要被缓存 --> <keep-alive include="Left,Right"> <component :is="compName"></component> </keep-alive>
15.3 被缓存组件的生命周期函数
-
被 keep-alive 缓存的组件的生命周期函数
-
被 keep-alive 缓存的组件,会有两个生命周期函数。
-
当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。
-
当组件被激活时,会自动触发组件的 activated 生命周期函数。
-
-
示例:
export default { // 当组件被缓存时,会自动触发 deactivated 生命周期函数 deactivated() { console.log('Left 组件被缓存了'); }, // 当组件被激活时,会自动触发 activated 生命周期函数 activated() { console.log('Left 组件被激活了'); } }
16. 插槽
-
什么是插槽
可以提高组件的复用性
使用插槽可以给组件传递一段 html 内容
-
注意:默认情况下,如果在封装组件时没有设置任何插槽,则使用组件时,在组件的开始和结束标签中间传递的内容将会被丢弃。
16.1 插槽的基本使用
-
在封装组件时,可以定义一个插槽**(坑位)**,用来接收使用组件时传递过来的内容。
-
在使用组件时,可以在组件的开始和结束标签中间传递一段 html 内容,这段内容就会被渲染到组件中插槽所在的位置。
-
注意:如果占位插槽内有收到传递内容的话是可以设置默认内容的,插槽占位的时候直接往插槽内写内容,就是默认内容
-
示例:
Left.vue
<Article> <h3>咏鹅</h3> </Article>
Article.vue
<template> <div> <!-- <h3>文章的标题</h3> --> <slot></slot> <div> <p>这是文章的段落1。</p> <p>这是文章的段落2。</p> <p>这是文章的段落3。</p> <p>这是文章的段落4。</p> </div> <!-- 文章作者的插槽 --> <slot> <!-- 插槽默认内容 --> <h6>文章的作者: xxx</h6> </slot> </div> </template>
16.2 具名插槽
当我们从外界传递过来多个内容时,这时就需要设置多个插槽进行接收,为了区分不同的插槽,就需要为插 槽指定具体的 name 名称。这种带有具体名称的插槽叫做“具名插槽”。
-
步骤:
-
在封装组件时,给插槽起一个名字。
-
在之用组件,向插槽传递内容的时候,指定要给哪个插槽传递。
两种方法:
旧语法(只能在 vue2.x 中使用)
新语法(vue2.x 和 vue3.x中都能使用)
-
-
注意:新语法中可以简写
v-slot:title
可以简写成#title
-
示例:
Article.vue
<template> <div> <!-- <h3>文章的标题</h3> --> <slot name="title"> <!-- 插槽的默认内容 --> <h3>标题</h3> </slot> <div> <p>这是文章的段落1。</p> <p>这是文章的段落2。</p> <p>这是文章的段落3。</p> <p>这是文章的段落4。</p> </div> <!-- 文章作者的插槽 --> <slot name="author"> <!-- 插槽默认内容 --> <h6>文章的作者: xxx</h6> </slot> </div> </template>
旧语法 (只能在 vue2.x 中使用):
<Article> <h3 slot="title">咏鹅</h3> <h6 slot="author">文章的作者:骆宾王</h6> </Article>
新语法 (vue2.x 和 vue3.x中都能使用):
<Article> <template v-slot:title> <h3>咏鹅</h3> </template> <!-- v-slot 的简写是 # --> <template #author> <h6>文章的作者:骆宾王</h6> </template> </Article>
16.3 默认插槽
-
注意:
- 没有指定 name 名称的插槽,叫做默认插槽
- 它有隐含的名称叫做 “
default
”。 - 将来使用组件的时 候,所有没有指定要传给具名插槽的内容,都会被默认插槽所接收。
-
示例:
Article.vue
<template> <div> <!-- <h3>文章的标题</h3> --> <slot name="title"> <!-- 插槽的默认内容 --> <h3>标题</h3> </slot> <div> <p>这是文章的段落1。</p> <p>这是文章的段落2。</p> <p>这是文章的段落3。</p> <p>这是文章的段落4。</p> </div> <!-- 文章作者的插槽 --> <slot name="author"> <!-- 插槽默认内容 --> <h6>文章的作者: xxx</h6> </slot> <!-- 默认插槽 --> <!-- 隐藏的名称叫做 default --> <!-- 注意:使用组件的时候,所有没有指定要传给具名插槽的内容,都会被默认插槽所接收 --> <!-- <slot name="default"></slot> --> <slot></slot> </div> </template>
Left.vue
<template> <div class="left-container"> <h3>Left 组件</h3> <!-- 渲染 Article 组件 --> <!-- 新语法 --> <Article> <template v-slot:title> <h3>咏鹅</h3> </template> <template v-slot:author> <h6>文章的作者:骆宾王</h6> </template> <p>111</p> <p>222</p> <p>333</p> </Article> </div> </template>
16.4 作用域插槽
使用组件时,在组件的开始和结束标签的内容中,使用的数据如果是来自组件内部,那么这个插槽就是作用 域插槽。
-
步骤:
- 封装组件时,在 slot 上绑定自定义属性,给使用组件时传递数据。
- 在使用组件时,接收组件内部传递出来的数据。
-
示例:
步骤一:Article.vue
<!-- 在插槽上,使用自定义属性,给插槽的内容传递数据 --> <slot name="author" :age="age"></slot>
步骤二:Left.vue
-
新语法(vue2.x 和 vue3.x 中都能使用)
<!-- scope 对象里,存放数组传递出来的数据 --> <template #author="scope"> <h6>文章的作者:骆宾王 <br> 年龄:-- {{ scope.age }}</h6> </template>
-
旧语法(只能在 vue2.x 中使用)
<h6 slot="author" solt-scope="scope"> 文章的作者:骆宾王 <br> 年龄:-- {{ scope.age }} </h6>
-
17. 自定义指令
17.1 什么是自定义指令
- vue 官方提供了 v-
bind
、v-on
、v-if
、v-for
、v-model
等常用的指令。除此之外 vue 还允许我们开发自定 义指令。
17.2 自定义指令的分类
- 自定义指令分为两类:
- 全局自定义指令
- 私有自定义指令
17.3 全局自定义指令 (常用)
17.3.1 自定义指令的使用
-
全局的自定义指令需要通过
Vue.directive()
进行声明 -
示例:
使用自定义属性:App.vue
<template> <div class="app-container"> <h1 v-color>App 根组件</h1> </div> </template>
声明自定义属性:main.js
// 在 main.js 中,new Vue() 之后定义全局的自定义指令 // Vue.directive('自定义指令的命令', { 配置 }) Vue.directive('color', { bind(el) { // el 就是绑定了该指令的 DOM 元素 // console.log(el); el.style.color = 'red' } })
17.3.2 为自定义指令动态绑定数值
-
步骤:
- 在使用自定义指令时,可以指令后面添加等号并赋值,为当前指令动态绑定参数值;
- 在声明自定义指令时,使用
bind
函数的第二个参数binding
来获取动态绑定的参数值。
-
示例:
使用自定义指令:App.vue
<template> <div class="app-container"> <h1 v-color="color">App 根组件</h1> </div> </template> <script> export default { data() { return { color: 'red' } } } </script>
声明自定义指令:main.js
Vue.directive('color', { bind(el, binding) { // el 就是绑定了该指令的 DOM 元素 // 可以通过 binding 参数的 value 属性,拿到动态绑定的值 el.style.color = binding.value } })
17.3.3 update
函数
-
bind
函数只在组件初始化时调用 1 次。当 DOM 更新时bind
函数不会被触发。 -
update
函数会在数据变化后,组件更新时被调用 -
示例:
Vue.directive('color', { // 组件首次渲染时,当 vue 解析绑定到该指令的元素时,就会调用 bind 函数 bind(el, binding) { el.style.color = binding.value }, // 当 DOM 更新的时候,会调用 update 函数 update(el, binding) { el.style.color = binding.value } })
17.3.4 bind
函数和 update
函数的简写形式
-
如果
bind
和update
函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式。 -
示例:
简写前:
Vue.directive('color', { // 组件首次渲染时,当 vue 解析绑定到该指令的元素时,就会调用 bind 函数 bind(el, binding) { el.style.color = binding.value }, // 当 DOM 更新的时候,会调用 update 函数 update(el, binding) { el.style.color = binding.value } })
简写后:
Vue.directive('color', function(el, binding) { // 在 bind 和 updata 的时候,都会执行这个函数 el.style.color = binding.value })
17.4 私有自定义指令
-
在每个 vue 组件中,可以在
directives
节点下声明私有自定义指令 -
示例:App.vue
export default { directives: { // 完整写法 color: { bind(el, binding) { el.style.color = binding.value }, update(el, binding) { el.style.color = binding.value } }, // 简写形式 color(el, binding) { el.style.color = binding.value } } }
vue - router
1. 路由 - 概念
1.1 URL
-
什么是 URL
URL(Uniform Resource Locator,统一资源定位符)用于定位网络上的资源。
-
URL 的组成部分
- 格式 scheme://host:port/path?query#hash
- 示例 http://www.liulongbin.top:8083/images/cat.png?name=ls#abc
-
Hash(哈希):
- # 号后面的部分,专业术语为“Hash 地址”
- Hash 地址发生变化,不会导致浏览器页面的刷新
- Hash 地址发生变化,会形成浏览器历史记录
- vue 就是基于 hash 的这些特性来做路由的跳转,实现页面的切换的
1.2 应用类型与路由的概念
-
多页应用与后端路由
MPA(Multi Page Application):在多页应用中,想要切换页面就是跳转到一个新的 html 页面。 路由工作是由后端完成的
-
单页应用与前端路由
SPA(Single Page Application):在单页应用中,只有唯一的一个 HTML 页面,所以想要切换页面就只能切换组件来达到切换页面的效果
这种切换组件达到页面切换的效果,就是前端路由。由前端程序员来完成
-
前端路由:访问不同的 Hash 地址,显示不同的组件
1.3 前端路由的工作方式
-
用户点击了页面上的路由链接
-
导致了 URL 地址栏中的 Hash 值发生了变化
-
前端路由监听了到 Hash 地址的变化
-
前端路由把当前 Hash 地址对应的组件渲染到浏览器中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T3whTLup-1653472695046)(./images/路由工作方式.png)]
- 强调:前端路由,指的是 Hash 地址与组件之间的对应关系
2. 模拟简易的前端路由
-
步骤一:在 App.vue 组件中,为
<a>
链接添加对应的hash
值,来改变浏览器地址栏中的hash
地址:<!-- 1. 为 a 连接添加跳转的 hash 地址 --> <a href="#/home">首页</a> <a href="#/movie">电影</a> <a href="#/about">关于</a>
-
步骤二:通过**
<component>
**标签,动态渲染组件。示例代码如下:App.vue<!-- 2. 使用 <component> 标签,动态的徐娜然组件。示例代码如下:App.vue --> <component :is="compName"></component>
-
步骤三:使用浏览器的
window.onhashchange
事件,监听浏览器地址栏中hash
地址的变化,然后通过location.hash
拿到页面最新的hash
值。根据这个最新的hash
值,渲染对应的组件。以达到改变hash
切换组件的目的created() { // 3. 监听 hash 地址的变化,拿到最新的 hash, 渲染与该 hash 对应的组件 window.onhashchange = () => { const hash = location.hash if (hash === '#/home') { this.compName = 'Home' } else if (hash === '#/movie') { this.compName = 'Movie' } else if (hash === '#/about') { this.compName = 'About' } } }
3. vue-router 简介
-
什么是 vue-router
-
vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。
-
vue-router 的官方文档地址:https://router.vuejs.org/zh/
-
4. vue-router 的基本使用
-
步骤:
- 安装 vue-router 包
- 创建路由模块
- 挂载路由模块
- 配置路由规则
- 声明路由链接和占位符
-
示例:
步骤一:安装 vue-router 包
# 在 vue2 的项目中,安装 vue-router@3.5.2 -S npm i vue-router@3.5.2 -S
步骤二:创建路由模块
在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下的代码
// 1. 引入 Vue 和 VueRouter 的包 import Vue from 'vue' import VueRouter from 'vue-router' // 2. 调用 Vue.use() 函数,把 VueRouter 安装为插件 Vue.use(VueRouter) // 3. 创建路由的实例对象 const router = new VueRouter() // 4. 导出路由实例对象 export default router
步骤三:挂载路由模块
在 src/main.js 入口文件中,导入并挂载路由模块
import Vue from 'vue' import App from './App.vue' // 导入路由模块 import router from '@/router/index.js' new Vue({ render: h => h(App), // 2. 挂载路由模块 // router: router // 简写如下行 router }).$mount('#app')
步骤四:配置路由规则
在 src/router/index.js 路由模块中,通过
routes
数组声明路由的匹配规则// 引入要展示的组件 import Home from '@/components/Home.vue' import Movie from '@/components/Movie.vue' import About from '@/components/About.vue' const router = new VueRouter({ // 使用 routes 选项配置路由规则 // 路由规则:hash 地址和对应的组件 routes: [ // { path: 'hash地址', component: 要展示的组件名 } // 注意:pash 里面的 hash 地址是不需要加 # 的 { path: '/home', component: Home }, { path: '/movie', component: Movie }, { path: '/about', component: About }, ] })
步骤五:声明路由链接和占位符
在 src/App.vue 组件中,使用 vue-router 提供的**
<router-link>
和<router-view>
声明路由链接和路由占位符**<template> <div class="app-container"> <h1>App 根组件</h1> <!-- 声明路由跳转连接 --> <router-link to="/home">首页</router-link> <router-link to="/movie">电影</router-link> <router-link to="/about">关于</router-link> <hr /> <!-- 声明路由占位符 --> <router-view></router-view> </div> </template>
5. vue-router 的常见用法
5.1 路由重定向
- 路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示地址 C 对应的组件页面。
- 通过路由规则的
redirect
属性,指定一个新的路由地址,可以很方便地设置路由的重定向
// 创建路由的实例对象
const router = new VueRouter({
routes: [
// 使用重定向设置访问根路径的组件为 Home.vue
// 语法:{ path: '用户要访问的 hash 地址', redirect: '重定向的 hash 地址' }
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home },
{ path: '/movie', component: Movie },
{ path: '/about', component: About },
]
})
5.2 嵌套路由
-
在本身就是通过路由展示的组件中,再使用路由去控制其他组件的展示,就会形成嵌套路由。 这种通过路由实现组件的嵌套展示,叫做嵌套路由
-
示例:
步骤一: 声明子路由规则
在 src/router/index.js 路由模块中,使用
children
属性声明子路由规则import Tab1 from '@/components/tabs/Tab1.vue' import Tab2 from '@/components/tabs/Tab2.vue' const router = new VueRouter({ routes: [ { path: '/home', component: Home }, { path: '/movie', component: Movie }, { path: '/about', component: About, // 要在哪个组件中嵌套展示其他组件,children 就是在哪个路由规则下面 // 通过 children 属性,定义子路由规则 children: [ { path: '/about/tab1', component: Tab1 }, { path: '/about/tab2', component: Tab2 } ] } ] })
步骤二:声明子路由链接和子路由占位符
在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符
<template> <div class="about-container"> <h3>About 组件</h3> <!-- 在关于页面中,声明子路由连接 --> <router-link to="/about/tab1">Tab1</router-link> <router-link to="/about/tab2">Tab2</router-link> <hr> <!-- 在关于页面中,声明子路由占位符 --> <router-view></router-view> </div> </template>
5.3 动态路由
-
概念:
把 Hash 地址中动态变化的部分定义为一个动态的参数项,从而提高路由规则的复用性。 在 vue-router 中使用**英文的冒号(😃**来定义路由的参数项。
-
示例:
// 在路由地址中声明动态参数项 // 将动态的部分使用 :动态参数项的名称 代替 { path: '/movie/:id', component: Movie }
<!-- 将以下 3 个路由规则,合并成了一个,调高了路由规则的复用性 --> <router-link to="/movie/1">电影1</router-link> <router-link to="/movie/2">电影2</router-link> <router-link to="/movie/3">电影3</router-link>
-
使用场景:一般都是在从列表页,跳转到详情页的时候,使用动态路由。
5.4 通过 $route.params
获取动态路由参数(推荐)
-
在动态路由渲染出来的组件中,可以使用
this.$route.params
对象访问到动态匹配的参数值。this.$route
里面存放的是当前页面路由的相关信息。 -
示例:
<template> <div class="movie-container"> <!-- this.$route 里面存放的是当前页面路由的相关信息 --> <h3>Movie 组件 -- {{ this.$route.params.id }}</h3> <button @click="logRoute">打印 this.$route</button> </div> </template> <script> export default { name: 'Movie', methods: { logRoute() { console.log(this.$route); } } } </script>
5.5 使用 props 接收路由参数
-
为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 传参,那么在组件中就可以使用 props 来接 收路由上的参数了。
-
示例:
步骤一:在路由规则中开启 props 选项(router/index.js)
// 开启 props 选项,允许组件使用 props 接收路由参数 { path: '/movie/:id', component: Movie, props: true }
步骤二:在组件中使用 props 接收路由参数(Movie.vue)
<template> <div class="movie-container"> <h3>Movie 组件 -- {{ id }}</h3> </div> </template> <script> export default { // 使用 props 接收路由参数 props: ['id'], } </script>
5.6 编程式导航
-
声明式导航 & 编程式导
- 声明式导航
- 在浏览器中,点击链接实现路由跳转的方式,叫做声明式导航。
- 例如:普通网页中点击
<a>
链接、vue 项目中点击<router-link>
都属于声明式导航
- 编程式导
- 在浏览器中,调用 JS API 方法实现路由跳转的方式,叫做编程式导航。
- 例如:普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航
- 声明式导航
-
vue-router 提供了许多编程式导航的 API,其中最常用的导航 API 分别是
- this.$router.push(‘hash 地址’)
- 跳转到指定 hash 地址,并增加一条历史记
- this.$router.go(数值 n)
- 实现导航历史前进、后
- this.$router.push(‘hash 地址’)
-
示例一:$router.push
调用 this.$router.push() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。
<template> <div class="home-container"> <h3>Home 组件</h3> <button @click="goAbout">跳转到关于页面</button> </div> </template> <script> export default { methods: { goAbout() { // 编程式导航 API: // 语法:this.$router.push('hash地址') this.$router.push('/about') } } } </script>
-
示例二:$router.go
调用 this.$router.go() 方法,可以在浏览历史中前进和后退。类似于浏览器的前进后退功能。
<template> <div class="about-container"> <h3>About 组件</h3> <button @click="goBack">返回</button> </div> </template> <script> export default { methods: { goBack() { // 返回到记录的上一条 // 负数回退,正数前进 this.$router.go(-1) } } } </script>
6. 导航守卫
-
什么是导航守卫
导航守卫可以对路由跳转进行拦截,从而达到控制路由的访问权限的功能。
6.1 全局的前置守卫
-
每次发生路由的导航跳转时,还没有真正发生跳转之前,会触发全局前置守卫。因此,我们可以在全局前置 守卫中,对每个路由进行访问权限的控制
// 声明全局前置守卫 // 每次发生路由跳转的时候,都会触发 beforeEach 中的 fn 回调函数 // router.brforeEach(fn) router.beforeEach(() => { console.log('触发了 beforeEach 中的 fn 回调函数') // 做一些权限控制的工作 })
6.2 前置守卫方法的3个形参
router.beforeEach((to, from, next) => {
// to: 将要访问的路由信息对象
console.log(to)
// from: 将要离开的页面的路由信息对象
console.log(from)
// next 是一个函数,调用 next, 表示放行,允许这次路由的跳转
next()
})
vue - vuex
1. vuex 概念
1.1 Vuex 是什么
-
Vuex:
Vuex 是 vue 项目中实现全局的、大范围的数据通信的解决方案。
-
作用:
能够方便、高效地实现组件之间的数据通信
-
使用 Vuex 的好处:
- 数据的存取一步到位,不需层层传递
- 数据的流动非常清晰
- 存储在 Vuex 中的数据都是响应式的
1.2 项目中安装和配置 Vuex
-
安装 Vuex 的依赖包
npm i vuex@3.6.2 -S
-
创建 store 模块
// 1. 导入 Vue 和 Vuex 依赖包 import Vue from 'vue' import Vuex from 'vuex' // 2. 把 Vuex 安装为 Vue 的插件 Vue.use(Vuex) // 3. 创建 store 的实例对象 const store = new Vuex.Store() // 4. 向外导出 store 的实例对象 export default store
-
挂载 store 的实例对象到 new Vue() 中
// 1. 导入 store 实例对象 import store from '@/store/index.js' new Vue({ render: h => h(App), // 2. 挂载 store 实例对象 store }).$mount('#app')
2. State 的基本使用
2.1 store 的概念
- state 本质上就是一个对象,用来存储全局的数据的。
2.2 state 的使用步骤
2.2.1 定义全局数据
-
定义:在 Vuex 的 state 选项对象中,定义全局数据
store/index.js
const store = new Vuex.Store({ // 在 state 中,定义全局数据 state: { count: 0 } })
2.2.2 组件访问 state 数据
-
使用方式一:
state
属性在组件中通过
this.$store.state.全局数据名称
访问全局数据Left.vue
<template> <div class="left-container"> <p>count 值: -- {{ this.$store.state.count }} </p> </div> </template>
-
使用方式二:
mapState
辅助函数在组件中通过 计算属性 访问全局数据
Right.vue
<template> <div class="right-container"> <p>count 值: -- {{ count }} </p> </div> </template>
// 1. 导入辅助函数 mapState import { mapState } from 'vuex' // mapState 函数的返回值是一个对象。里面存放的就是 state 中全局数据的映射 export default { computed: { // 将得到的 state 中全局数据的映射,通过扩展运算符,放入 computed 中 ...mapState(['count']) } }
2.2.3 解决命名冲突
-
<template> <div class="right-container"> <p>count 值: -- {{ countR }} </p> </div> </template>
-
import { mapState } from 'vuex' export default { data() { return { count: 666 } }, computed: { // 解决命名冲突 - state中的数据名,在这个组件已经被定义 // 语法:mapState({ '计算属性名字': ‘要访问的全局数据的名字’ }) ...mapState({ countR: 'count' }) } }
3. Mutation 的基本使用
注意:Vuex 规定: mutation 必须是同步函数 。
3.1 开启严格模式
-
开启 vuex 严格模式
- 开启严格模式之后,直接修改全局数据就会报错,可以防止程序员写垃圾代码
- 但是严格模式性能上有损耗,所以项目上线之前,要关掉严格模式
-
示例:store/index.js
const store = new Vuex.Store({ // 开启严格模式 // 会消耗性能,开发阶段开启,上线之前关闭 strict: true, });
3.2 mutation 定义
- mutation 是什么
- mutation 本质上是 JavaScript 函数,专门用来变更 store 中的数据。
- 特点:想要修改 state 中的数据,只能调用 mutation 方法!
- 好处:能够确保修改来源的唯一性,方便调试和后期维护。
3.3 mutation 的使用步骤
3.3.1 定义 mutation 方法
-
定义:在 Vuex 中定义 mutation 方法
-
示例:store/index.js
const store = new Vuex.Store({ strict: true, state: { count: 0, n1: 0, n2: 0 }, // 【在 mutations 中,声明修改全局数据的方法】 mutations: { addCount(state, n = 1) { // mutation 函数中,接受的第一个参数是 state // mutation 函数中,使用第二个参数,来接受调用时传递过来的参数 state.count += n; } } });
-
注意:
- mutation 函数中,接受的第一个参数是 state
- mutation 函数中,使用第二个参数,来接受调用时传递过来的参数
3.3.2 调用 mutation 函数
-
调用方式一:
commit
方法在组件中,通过
this.$store.commit('mutations中定义的函数名')
调用 mutation 方法示例:Left.vue
add(n) { // 在组件中,调用全局的mutation,来修改全局数据 // 调用全局的 mutation 里面的方法来修改全局的数据 // 语法:this.$store.commit('mutation函数的名字') this.$store.commit('addCount', n) }
注意:
- 调用全局的 mutation 里面的方法来修改全局的数据
- 语法:
this.$store.commit('mutation函数的名字')
- 通过传参可以提高 mutation 方法的通用性。
- 语法:
this.$store.commit('mutation函数的名字', '参数')
-
调用方式二:
mapMutations
辅助函数Vuex 提供了 mapMutations 辅助函数,可以方便的把 store 中的 mutation 方法,映射到前组件的 methods 中
示例:Right.vue
import { mapMutations } from 'vuex' export default { methods: { // 使用 mapMutations 将全局的 mutation 函数,映射到当前组件的 methods 中去 ...mapMutations(['addCount']) }) } }
3.3.3 解决命名冲突
-
示例:
import { mapMutations } from 'vuex' export default { methods: { // 使用 mapMutations 中的对象格式的传参,解决命名冲突 ...mapMutations({ // 自定义的名称: '全局 mutation名称' addCount2: 'addCount' }) } }
4. Action 的基本使
注意:专门用来处理 Vuex 中的异步操作
4.1 action 的定义
- action 是什么
- action 本质上是 JavaScript 函数,专门用来处理 Vuex 中的异步操作
4.2 action 的使用步骤
4.2.1 定义 action 方法
-
定义:在 Vuex 中定义 action 方法
-
示例:store/index.vue
const store = new Vuex.Store({ strict: true, // 【在 actions 中,声明处理异步操作的方法】 actions: { addCountAsync(ctx, {n, time}) { // action 函数中,接收的第一个参数是 ctx,ctx 中有 commit 方法 setTimeout(() => { // 这里不可以直接修改全局数据,要通过调用 mutations 里的方法来修改数据 // 使用 ctx.commit 方法调用 mutation ctx.commit('addCount', n) }, time); } }, });
4.2.2 调用 action 中的异步方法
-
调用方式一:
dispatch
方法在组件中,通过
this.$store.dispatch('actions中定义的函数名')
调用 action 方法示例:Left.vue
export default { methods: { addAsync(n, time) { // 在组件中,调用全局的action,来实现异步处理函数,修改全局数据 // 在组件中调用全局的 action 里面的异步函数 // 语法:this.$store.dispatch('action函数名') // 第二个参数是 要传递的参数 this.$store.dispatch('addCountAsync', {n, time}) } } }
-
调用方式二:
mapActions
辅助函数Vuex 提供了 mapActions 辅助函数,可以方便的把 store 中的 action 方法,映射到当前组件的 methods 中
示例:Right.vue
import { mapActions } from 'vuex' export default { computed: { // 使用 mapAction 辅助函数,将全局的 action,映射到当前组件的 methods 中去 ...mapActions (['addCountAsync']) } }
4.2.3 解决命名冲突
-
示例:
import { mapActions } from 'vuex' export default { computed: { // 重新命名 ...mapActions({ addCountAsync2: 'addCountAsync' }) } }
5. Getter 的基本使用
5.1 getter 定义
-
getter 是什么
getter 可以理解为是 Vuex 中的计算属性,它内部依赖于 state 中的数据,state 中的值变化,getter 的值 会自动更新。
5.2 getter 的使用步骤
5.2.1 定义 getter 方法
-
定义:在 Vuex 中定义 action 方法
-
示例:store/index.js
const store = new Vuex.Store({ strict: true, state: { n1: 0, n2: 0 }, // 使用 geeter 定义全局的计算属性 // 某一个值,是依赖于全局的数据动态计算出来 // 这时就是使用 getter getters: { sum(state) { // getter 函数接收的第一个参数是 state 全局数据 return state.n1 + state.n2 } } });
5.2.2 访问 getter
-
访问方式一:
getters
属性在组件中,通过
this.$store.getters.getters中定义的函数名
,访问 gette示例:Left.vue
<template> <div class="left-container"> <!-- 在组件中,通过 this.$store.getters.getter函数名 --> <p>全局数据 n1 + n2 = {{ $store.getters.sum }}</p> </div> </template>
-
访问方式二:
mapGetters
辅助函数Vuex 提供了 mapGetters 辅助函数,可以方便的把 store 中的 getter,映射到当前组件的 computed 中
示例:Right.vue
import { mapGetters } from 'vuex' // mapGetters 要结合 computed 一起使用 export default { computed: { ...mapState(['count']) } }
5.2.3 解决命名冲突
-
示例:
import { mapGetters } from 'vuex' export default { computed: { // 重新命名 ...mapState({ sum2: 'count' }) } }
6. Module 的基本使
6.1 module 是什么
Vuex 中的 module 表示按照模块化的开发思想,把有业务关联的数据和方法封装在一起。module 就是 Vuex 中的模块化
6.2 module 的使用场景
当一个项目的页面数量很少,逻辑功能简单的情况下,是完全可以不使用 module 模块的。
但是当一个项目的页面数量很多,逻辑功能复杂的时候,所有的全局数据、方法都集中在了一起,会导致 Vuex 的结构混乱,不利于现阶段的开发和后期的维护,那么此时就需要使用模块来管理全局的数据和方法。
6.3 module 定义和注册模块
-
定义模块
-
每个模块都是彼此独立的,都可以拥有自己的 state、mutations、actions、getters 节点:
-
示例:store/count.js
// count 模块 export default { // 当前模块的数据 // 函数的形式为了解决 // 模块被多次注册,并且开启命名空间时,公用同一份数据 state: { num: 0 }, // 当前模块修改 state 数据的函数 mutations: { show() { console.log('调用了 count 模块下的 show 方法'); }, addNum(state, n = 1) { state.num += n } }, // 当前模块的异步操作 actions: { }, // 当前模块的计算属性 getters: { } }
-
-
注册模块
-
示例:store/index.js
// 1. 导入要注册的模块 import count from './count.js' import task from './task.js' const store = new Vuex.Store({ // 使用 modules 选项注册模块 modules: { // 2. 注册模块 // 语法: // 模块的注册名称:导入的模块 // count: count count, task } })
-
6.4 namespaced
(命名空间)
- namespaced(命名空间)可以解决不同模块之间成员名称冲突的问题。在实际项目开发中,
- 建议: 为每 个 module 模块都开启命名空间!
6.4.1 开启命名空间 - namespaced: true
-
在定义模块时,只需在模块中声明 namespaced: true 选项,即可为当前模块开启命名空间:
-
示例:store/count.js && store/task.js
// count 模块 export default { // 开启命名空间 - 用于隔离模块 namespaced:true, state: {}, mutations: {}, actions: {}, getters: {} }
// task 模块 export default { // 开启命名空间 - 用于隔离模块 namespaced:true, state: {}, mutations: {}, actions: {}, getters: {} }
6.4.2 通过模块注册名访问模块下成员
当模块启用了 namespaced: true
选项之后,模块就有了自己的命名空间。
-
注意:访问时要加上模块的注册名 称才能够访问到。
-
示例:Left.vue
add() { // 在组件中,通过 this.$store.commit('模块注册的名称/mutation函数名') this.$store.commit('count/addNum') }
6.4.3 在组件中访问模块内 state 的两种方式
-
方式一:通过
this.$store.state.模块注册名称.要访问的数据
示例:Left.vue
<template> <div class="left-container"> <!-- 在组件中,通过 this.$store.state.模块的注册名称.模块中的数据 --> <p>count 模块下的 num 的值:{{ $store.state.count.num }}</p> </div> </template>
-
方式二:通过
mapState
辅助函数示例:Right.vue
<template> <div class="right-container"> <h3>Right 组件</h3> <p>count 模块下的 num 的值:{{ num }}</p> <p>task 模块下的 num2 的值:{{ num2 }}</p> </div> </template> <script> import { mapState, mapMutations } from 'vuex' export default { computed: { // ...mapState('模块的注册名称', [‘模块中数据’]) ...mapState('count', ['num']), ...mapState('task', { num2: 'num' }) } } </script>
6.4.4 在组件中调用模块内 mutation 的两种方式
-
方式一:通过
this.$store.commit('组件的注册名称/要调用的mutation函数名称' , 参数)
示例:Left.vue
<template> <div class="right-container"> <h3>Right 组件</h3> <p>count 模块下的 num 的值:{{ num }}</p> <p>task 模块下的 num2 的值:{{ num2 }}</p> <hr> <button class="btn btn-warning" @click="addNum(-1)">-1</button> </div> </template>
-
方式二:通过
mapMutations
辅助函数示例:Right.vue
<template> <div class="left-container"> <button class="btn btn-primary" @click="add">+1</button> <hr> <button @click="show">打印 show</button> <hr> </div> </template> <script> export default { methods: { show() { this.$store.commit('count/show') }, add() { // 在组件中,通过 this.$store.commit('模块注册的名称/mutation函数名') this.$store.commit('count/addNum') } } } </script>
6.4.5 在组件中调用模块内 action 的两种方式
-
方式一:通过
this.$store.dispatch('组件的注册名称/要调用的action函数名称' , 参数)
示例:Left.vue
<template> <div> <!-- this.$store.dispatch('组建的注册名称/要调用的action函数名称', 参数) --> <button class="btn btn-info" @click="$store.dispatch('count/addAsync', -1)">1秒后 +1</button> </div> </template>
-
方式二:通过
mapActions
辅助函数示例:Right.vue
<template> <div class="right-container"> <button class="btn btn-warning" @click="addAsync(-1)">-1</button> </div> </template> <script> import { mapActions } from 'vuex' export default { methods: { ...mapMutations('count', ['addAsync']) } } </script>
6.4.6 在组件中访问模块内 getter 的两种方式
-
方式一:通过
this.$store.getters['模块的注册名称/要访问的getter名称']
示例:Left.vue
<template> <div class="left-container"> <h3>Left 组件</h3> <p>count 值:{{ this.$store.getters['count/numPlus'] }}</p> </div> </template>
-
方式二:通过
mapGetters
辅助函数示例:Right.vue
<template> <div class="right-container"> <p>count 值: {{ numPlus }}</p> </div> </template> <script> import { mapGetters } from 'vuex' export default { computed: { ...mapGetters('count', ['numPlus']) } } </script>