Vue 系列笔记第四篇。本文参考:>> 黑马程序员 Vue 全套视频教程
🐾 文章内容预览
一、组件的生命周期
>> 两个概念:生命周期 和 生命周期函数
生命周期:是指一个组件从 创建 => 运行 => 销毁 的整个阶段,强调的是一个时间段。
生命周期函数:是由 vue 框架提供的 内置函数,会伴随着组件的生命周期,自动按次序执行。生命周期函数所强调的是时间点,也就是说如果你想在某个特定的时间点去执行某些事情,那么你就将代码写在相应的生命周期函数中即可。
>> 组件生命周期函数的分类
可以参考文末给出的 >> 生命周期图示及说明 ,进一步理解组件生命周期执行的过程。
>> 组件创建阶段的生命周期函数
beforeCreate()
创建组件实例化对象后的第一个周期函数是 beforeCreate()
,这时组件的 props、data、methods 尚未被创建,都处于 不可用状态 。因此, beforeCreate()
基本上没有什么用武之地,因此也比较少被用到。
created()
在初始化 props、data、methods 后(这时它们处于 可用状态 ),迎来了第二个周期函数的节点,也就是 created()
函数。 created()
函数是特别常用的,一般用来发起 AJAX 请求来请求数据,并且将请求到的数据转存在 data 中,供 template 模板渲染的时候使用。
🎈🎈 注意:这时组件的模板结构尚未生成,不能操作 DOM。
beforeMount()
内存中 的模板编译生成 HTML 结构后,迎来第三个生命周期函数 beforeMount()
的节点。因为此时只是在内存中生成了 HTML 结构,还没有被渲染到页面中,因此此时仍不能操作 DOM 结构。由上述原因,这个生命周期函数也很少在开发中被用到。
🎈🎈 注意:此时只是在内存中生成 HTML 结构,并没有渲染到浏览器中。
mounted()
内存中的 HTML 结构成功渲染到了浏览器之后,迎来第四个生命周期函数 mounted()
。因为此时浏览器中已经包含 DOM 结构,因此此时可以获取和操作当前组件中的 DOM 元素。
🎈🎈 注意:最早在此时可以操作浏览器中 DOM 元素。
>> 组件运行阶段的生命周期函数
beforeUpdate()
当修改 data 中数据时,将要 根据更改后的数据重新渲染组件的模板结构时,迎来了周期函数 beforeUpdate()
的节点。此时刚刚修改完 data 数据,但是还没有重新渲染 UI 结构。
🎈🎈 注意:数据是新数据,结构是旧结构。数据变化时才执行,最少执行 0 次。
updated()
已经根据最新的数据,重新渲染了组件的 DOM 结构,此时迎来了周期函数 updated()
的节点。这时可以操作最新的 DOM 元素。
🎈🎈 注意:数据是新数据,结构是新结构。
>> 组件销毁阶段的生命周期函数
beforeDestrory()
将要销毁此组件,但是还尚未销毁,组件仍处于正常工作的状态,迎来 beforeDestrory()
函数的节点。
destroyed()
组件已经被销毁,同时,此组件在浏览器中的 DOM 结构已经被完全移除,迎来 destroryed()
函数的节点。
二、组件之间的数据共享
在项目开发中,组件之间的最常见的关系分为如下两种:父子关系 和 兄弟关系 。下面我们分别来介绍 父子组件间如何数据共享 和 兄弟组件间如何数据共享 。
1. 父向子共享数据
父组件向子组件共享数据需要使用 自定义属性 。这里的自定义属性定义在子组件内,父组件只是负责提供数据传递给子组件。参考下面代码:
// 父组件
<Son :msg="message" :user="userinfo"></Son>
data() {
return {
message: 'hello vue.js',
userinfo: { name: 'zs', age: 20 }
}
}
// 子组件
<template>
<div class="app-container">
<h5>Son 组件</h5>、
<p>父组件传递来的 msg 值: {{ msg }}</p>
<p>父组件传递来的 user 值: {{ user }}</p>
</div>
</template>
props: ['msg', 'user']
2. 子向父共享数据
子组件向父组件共享数据使用 自定义事件 ,子组件可以使用 $emit
触发父组件的自定义事件,通过 传递参数 ,将子组件中数据传递给父组件的事件处理函数。
在父组件事件处理函数中,将从子组件获取到的数据 转存 给到父组件自身使用,这就完成了子组件向父组件的数据共享。
// 父组件
<Son @numChange="getNewCount"></Son>
data() {
return {
// 接收子组件传递的数据
countFromSon: 0,
};
},
methods: {
getNewCount(val) {
this.countFromSon = val; // val 是子组件中传递来的参数
},
},
// 子组件
<button @click="add">+1</button>
data() {
return {
// 希望将 count 值传递给父组件
count: 0,
};
},
methods: {
add() {
// 让子组件的 count 值自增
this.count += 1;
// 将结果传递给父组件
this.$emit("numChange", this.count);
},
},
3. 兄弟之间的共享数据
在 vue2.x 中,兄弟组件之间数据共享的方案是 EventBus 。具体使用步骤如下:
- 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
- 在数据发送方,调用 bus.$emit 方法触发自定义事件
- 在数据接收方,调用 bus.$on 方法注册一个自定义事件
// 发送方
const obj = { id: this.id, value: this.num - 1 };
// 要做的事情:通过 EventBus 把 obj 对象,发送给 App.vue 组件
bus.$emit('share', obj);
// 中间件
import Vue from 'vue'
export default new Vue()
// 接收方
created() {
bus.$on("share", (val) => {
this.list[val.id - 1].goods_count = val.value;
});
},
三、ref 引用
1. 何为 ref 引用
ref 用来辅助开发者在 不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。在每个 vue 的组件实例上,都包含一个 $refs 对象 ,里面存储着对应的 DOM 元素或组件的引用。
默认情况下,组件的 $refs 指向一个空对象 。
如下图,如果直接打印 vue 实例对象,可以发现其含有一个空的 $refs 对象。
>> 使用 ref 引用 DOM 元素
如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作:
<h1 ref="myref">ref 引用 DOM 元素</h1>
再次打印 vue 实例,可以发现 &refs 对象中有了属性 myref,通过 this.&refs.myref
即可获取到该 DOM 元素的引用。
>> 使用 ref 引用组件
// 渲染 Left 组件 添加 ref 引用
<Left ref="comRef"></Left>
// 调用 Left 组件中的方法 resetCount
this.$refs.comRef.resetCount();
2. this.$nextTick() 方法
组件的 $nextTick(callback)
方法,会把 cb 回调 推迟到下一个 DOM 更新周期之后 执行。
通俗的理解是:等组件的 DOM 更新完成之后,再执行 cb 回调函数。从而能保证 callback 回调函数可以操作到最新的 DOM 元素。
>> 来看一个 this.$nextTick()
的应用场景
点击按钮后,显示文本框,并且自动聚焦到文本框中。我们可以使用一个开关变量,结合 v-if
v-else
进行判断,显示文本框时,根据文本框引用来使其获取焦点。
实现效果:点击显示输入框,并且自动聚焦
<input type="text" v-if="inputVisible" @blur="showButton" ref="iptRef" />
<button v-else @click="showInput">展示输入框</button>
data() {
return {
// 控制输入框和按钮的按需切换 默认 false 展示按钮 隐藏输入框
inputVisible: false,
};
},
methods: {
showInput() {
this.inputVisible = true;
// 将对 input 文本框的操作,推迟到下次 DOM 更新之后。否则页面上没有文本框。
this.$nextTick(() => {
this.$refs.iptRef.focus();
});
},
showButton() {
this.inputVisible = false;
},
},
>> 问题一:如果不使用 this.$nextTick()
会怎么样呢?
这里来说明一下,当更改完 data 中数据 this.inputVisible = true;
,页面还未来得及渲染,就获取了新 DOM 元素的引用 this.$refs.iptRef
。此时该引用为空,因此无法进行文本框聚焦。而使用 this.$nextTick()
,将其推迟到了 DOM 渲染完后执行,此时可以获取到最新 DOM 元素的引用,因此可以完美解决。
>> 问题二:为什么不能写在 updated() 中呢?
updated() {
this.$refs.iptRef.focus();
}
因为,更改数据 this.inputVisible = true;
后文本框显示,DOM 成功渲染后执行 updated
文本框获得焦点,这是没有问题的。但是当失去焦点时,data 数据会被更改 this.inputVisible = false;
,DOM 重新渲染文本框消失、按钮显示,之后再次 updated
获取文本框引用,发现其引用为空,会导致报错。
四、购物车案例
>> 效果展示
>> 实现步骤
- 初始化项目基本结构
- 封装 Header 组件
- 基于 axios 请求商品列表数据( GET 请求,地址为 https://www.escook.cn/api/cart )
- 封装 Footer 组件
- 封装 Goods 组件
- 封装 Counter 组件
代码过长不再给出,如果需要可以私信博主。
>> 案例总结
Ⅰ. 此案例主要是对前面所学知识的综合练习,重点利用了组件间的数据共享:父组件向子组件传值用 props
自定义属性,子组件向父组件传值要用 $emit
结合父组件事件绑定,而兄弟组件传值,需要利用 eventBue.js 作为中间件过渡。
Ⅱ. 此外还需注意计算属性的使用,计算属性需要使用 return 将值返回。虽然在 computed
中计算属性被定义成了函数,但是在使用时它当作属性值直接使用。
Ⅲ. 在利用 Ajax 请求页面数据时,必须将请求来的数据转存到组件的 data
中,否则该数据在页面中无法被访问到。此外,Ajax 请求时,要熟练使用 async
和 await
。const { data: res }
是利用解构赋值的方式,将请求到的 data 数据赋给 data,因为重名问题所以用 :res
将其重命名为 res。
// 封装请求列表数据方法
async initCarList() {
// 调用 axios 的 get 方法,请求列表数据
const { data: res } = await axios.get("https://www.escook.cn/api/cart");
// console.log(res);
// 请求回来的数据,在页面渲染期间要用到,必须转存到 data 中
if (res.status === 200) {
this.list = res.list;
}
},
Ⅳ. 编码规范上,在渲染组件时,按照先 vue指令 => 属性绑定 => 事件绑定 的顺序:
<Goods
v-for="item in list"
:key="item.id"
:id="item.id"
:title="item.goods_name"
:pic="item.goods_img"
:price="item.goods_price"
:state="item.goods_state"
:count="item.goods_count"
@state-change="getNewState"
></Goods>
Ⅴ. 再说一下之前没怎么使用过的数组中 reduce()
方法。
array.reduce(function(返回值, 当前元素), 传递给函数的初始值)
本例中用于获取勾选商品的总价格和总数量,实现方法是先利用 filter()
进行数组筛选,接着配合 reduce()
方法实现累加,这里重点需要理解其简写格式:
// 勾选商品总价格
amount() {
return this.list
.filter((item) => item.goods_state)
.reduce((total, item) => (total += item.goods_price * item.goods_count), 0);
},
// 获取已勾选商品的数量
total() {
return this.list
.filter((item) => item.goods_state)
.reduce((t, item) => (t += item.goods_count), 0);
},
附:生命周期图示及说明