# 一、vue 生命周期
# 1、概念认知
生命周期是指一个组件从创建 —> 运行 —> 销毁的整个阶段。它强调的是一个 时间段。
也可以说: 挂载 ---> 更新 ---> 销毁
生命周期函数是 vue 框架提供的内置函数,会伴随组件的生命周期,自动按次序执行。它强调的是一个 时间点。
# 2、生命周期示意图
- 示意图中的"template" option
即示意图的走左侧分支,整体替换 🤔
<div id="app">
<!-- <h3>title名称</h3>
<p>忽见面前一双玉色蝴蝶,大如团扇,一上一下,迎风翩跹,十分有趣。</p> -->
</div>
<script type="text/JavaScript">
new Vue({
el: '#app',
// 只能有一个🚩根元素
template:`
<div>
<h3>title名称</h3>
<p>忽见面前一双玉色蝴蝶,大如团扇,一上一下,迎风翩跹,十分有趣。</p>
</div>
`
})
</script>
# 3、重点关注 ✨
为了更好的演示 vue 的生命周期过程,我们可以使用debugger
进行断点调试。
以前我们还接触过一个东西:#region (代码折叠)
<script>
new Vue({
el: '#app',
mounted() {
console.log('mounted阶段的vue示例:' + this)
debugger // 进入页面后刷新一下即可
}
})
</script>
通常我们重点关注生命周期的几个重要阶段即可:
钩子 | 阶段 | 解释 |
---|---|---|
created | 怀孕 | (拥有模板解析的所要的东西) |
mounted | 出生 | (虚拟 DOM 变为 真实 DOM) |
beforeDestroy | 交代后事 | (vm.$destroy()触发,善后工作) |
# 二、$nextTick(cb) 钩子
$nextTick() 方法并不会触发 Vue.js 组件的生命周期钩子函数,它仅仅是在当前执行栈执行完毕之后,下一次 DOM 更新循环结束之后执行的回调函数。
提示
简单的说就是:我们可以使用$nextTick(cb)
钩子让cb
回调函数在生命周期触发 updated 函数后执行。
# 1、使用示例
组件的 $nextTick(cb) 方法,会把 cd 回调推迟到下一个 DOM 更新周期之后执行。
即:组件的 DOM 更新完成后,再执行 cb 回调函数。这样可以保证 cb 回调函数可以操作到最新的 DOM 元素。
位置:
它可以在组件的任何生命周期内(created、mounted、updated 和 destroyed 等)、任何组件的方法中使用
需求:
在组件的数据发生更改时,我们想要立即操作更新后的 DOM(然后等待 DOM 更新触发回调)
<template>
<h2 ref="title">当前计数:{{ counter }}</h2>
<button @click="increment">+1</button>
</template>
<script>
export default {
methods: {
increment() {
// 数据改变
this.counter += 20
// DOM操作
nextTick(() => {
const title = this.$refs[title].textContent
console.log(title)
})
}
}
}
</script>
# 2、原理分析 ✨
vue 为什么会给我们提供一个$nextTick(cb)
呢?
答案
首先我们要明白 Vue 中更改响应式状态时,DOM 更新 (opens new window)并不是同步生效的,而是异步执行的。
也就是说,它和 react 中的setState()
一样,会将更改状态的任务缓存在一个队列中,最后一次性更新 DOM,以此来减小 DOM 渲染开销。
而$nextTick(cb)
可以在状态改变后立即使用,以等待 DOM 更新完成。它是一个待执行的回调,可以保证其操作的是已经更新后的 DOM 。
简单的说,
$nextTick(cb)
的使用往往 DOM 操作
Vue 在内部会对异步队列尝试使用原生的 Promise.then
、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0)
代替。
实现原理
我们可以采用下面的两种方式来还原$nextTick(cb)的实现:
- Promise(微任务)
updateMessage() {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
// new Promise(resolve => resolve()).then(() => {
Promise.resolve().then(() => {
console.log(this.$el.textContent) // => '已更新'
})
}
- setTimeout(宏任务)
updateMessage() {
this.message = '已更新'
console.log(this.$el.textContent) // => '修改数据' '未更新'
setTimeout(() => {
console.log(this.$el.textContent) // => '修改数据' '已更新'
}, 0);
}
所以,其实$nextTick(cb)
有两种使用方式:
<script>
export default {
mounted() {
// 1、常规使用方式
this.$nextTick(() => {
// 在这里访问更新后的 DOM
const myDiv = this.$refs[xxx]
})
// 2、在浏览器支持 Promise 的前提下, $nextTick() 返回一个 Promise 对象,所以你还可以这样写
this.$nextTick().then(() => {
const myDiv = this.$refs[xxx]
})
// 或者使用async/await
}
}
</script>
思考
可以用周期函数 updated(),来代替 $nextTick() 吗?
# 3、应用:输入框
输入框的按需展示(焦点事件 focus、blur)
<div id="app">
<!-- 显示、隐藏 -->
<input type="text" v-if="isInput" @blur="buttonShow" ref="inputDOM" />
<button v-else @click="inputShow">展示输入框</button>
</div>
<script type="text/JavaScript">
new Vue({
el: '#app',
data() {
return {
isInput: false
}
},
methods: {
// 显示input框
inputShow() {
this.isInput = true
// input框显示后✨才能获取焦点
this.$nextTick(() => {
this.$refs.inputDOM.focus()
})
},
//显示button按钮
buttonShow() {
this.isInput = false
}
}
});
</script>
# 4、应用:弹窗
当我们使用 Dialog 对话框嵌套 Form 表单时,就会出现一个典型的应用:表单的数据回显:
el-from 与 el-dialog(编辑/修改) 的数据回显
# 注意事项 1:
resetFields() 方法用于 formData 重置为初始值并移除校验结果
clearValidate() 方法用于清除表单验证状态
- 如果在
<el-form>
标签上利用ref="dialogFrom"
属性使用了 resetFields()方法,那么和存在:rules="rules"
一样,每一个<el-form-item>
标签 ✍ 上都要加上 prop 属性
# 注意事项 2:
:before-close
是 Dialog 关闭前的回调函数,可以通过返回值控制 Dialog 是否关闭
@close
是 Dialog 关闭后的回调函数,它是在 Dialog 关闭之后执行
在处理 Dialog 对话框的关闭弹窗时的this.$refs['dialogFrom'].resetFields()
让表单回到原点的操作:
- 如果采用
:before-close
,它仅当用户通过点击关闭图标或遮罩关闭 Dialog 时起效 - 如果采用 ✍
@close
则对所有的关闭弹窗操作有效。
知识回顾
getFieldsValue() 方法在 Element UI 2.0 版本及之后的版本中已被删除(但我们可以访问表单组件实例的 model 属性代替this.$refs.dialogFrom.model
)
# 三、activated / deactivated 钩子
Vue.js 中组件的生命周期钩子函数 activated 和 deactivated ,处于 mounted 和 destroyed 钩子函数之间,用于处理组件被激活和停用时的逻辑。
为什么 activated 和 deactivated 没有在生命周期使示意图中显示?
答案
因为,这两个钩子函数只有在使用了 <keep-alive>
组件进行缓存时才会被调用,而普通的组件切换不会触发这两个钩子函数。
<script>
export default {
// 离开Cart组件时,它会被缓存(同时自动触发组件的 deactivated生命周期函数)
deactivated() {
console.log('Cart组件被缓存')
},
// 回到Cart组件时,它会被激活(同时自动触发组件的 activated生命周期函数)
activated() {
console.log('Cart组件被激活')
}
}
</script>