Vue 源码解读(7)—— Hook Event
前言
Hook Event
(钩子事件)相信很多 Vue 开发者都没有使用过,甚至没听过,毕竟 Vue 官方文档中也没有提及。
Vue 提供了一些生命周期钩子函数,供开发者在特定的逻辑点添加额外的处理逻辑,比如:在组件挂载阶段提供了 beforeMount
和 mounted
两个生命周期钩子,供开发者在组件挂载阶段执行额外的逻辑处理,比如为组件准备渲染所需的数据。
那这个 Hook Event —— 钩子事件,其中也有钩子的意思,和 Vue 的生命周期钩子函数有什么关系呢?它又有什么用呢?这就是这边文章要解答的问题。
目标
理解什么是 Hook Event ?明白其使用场景
深入理解 Hook Event 的实现原理
什么是 Hook Event ?
Hook Event 是 Vue 的自定义事件结合生命周期钩子实现的一种从组件外部为组件注入额外生命周期方法的功能。
使用场景
假设现在有这么一个第三方的业务组件,逻辑很简单,就在 mounted 生命周期中调用接口获取数据,然后将数据渲染到页面上。
<template>
<div class="wrapper">
<ul>
<li v-for="item in arr" :key="JSON.stringify(item)">
{{ item }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
arr: []
}
},
async mounted() {
// 调用接口获取组件渲染的数据
const { data: { data } } = await this.$axios.get('/api/getList')
this.arr.push(...data)
}
}
</script>
然后在使用的发现这个组件有些瑕疵,比如最简单的,接口等待时间可能比较长,我想在 mounted 生命周期开始执行的时候在控制台输出一个 loading ...
字符串,增强用户体验。
这个需求该怎么实现呢?
有两个办法:第一个比较麻烦,修改源码;而第二种方式则简单多了,就是我们今天介绍的 Hook Event,从组件外面为组件注入额外的生命周期方法。
<template>
<div class="wrapper">
<comp @hook:mounted="hookMounted" />
</div>
</template>
<script>
// 这就是上面的那个第三方业务组件
import Comp from '@/components/Comp.vue'
export default {
components: {
Comp
},
methods: {
hookMounted() {
console.log('loading ...')
}
}
}
</script>
这时候你再刷新页面就会发现业务组件在请求数据的时候,会在控制台输出一个 loading ...
字符串。
作用
Hook Event 有什么作用?
通过 Hook Event 可以从组件外部为组件注入额外的生命周期方法。
实现原理
知道了 Hook Event 的使用场景和作用,接下来就从源码去找它的实现原理,做到 “知其然,亦知其所以然”。
前面说过,Hook Event 是 Vue 的自定义事件结合生命周期钩子函数实现的一种功能,所以我们就去看生命周期相关的代码,比如:我们知道,Vue 的生命周期函数是通过一个叫 callHook
的方法来执行的
callHook
/src/core/instance/lifecycle.js
/**
* callHook(vm, 'mounted')
* 执行实例指定的生命周期钩子函数
* 如果实例设置有对应的 Hook Event,比如:<comp @hook:mounted="method" />,执行完生命周期函数之后,触发该事件的执行
* @param {*} vm 组件实例
* @param {*} hook 生命周期钩子函数
*/
export function callHook (vm: Component, hook: string) {
// 在执行生命周期钩子函数期间禁止依赖收集
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget()
// 从实例配置对象中获取指定钩子函数,比如 mounted
const handlers = vm.$options[hook]
// mounted hook
const info = `${hook} hook`
if (handlers) {
// 通过 invokeWithErrorHandler 执行生命周期钩子
for (let i = 0, j = handlers.length; i < j; i++) {
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
// Hook Event,如果设置了 Hook Event,比如 <comp @hook:mounted="method" />,则通过 $emit 触发该事件
// vm._hasHookEvent 标识组件是否有 hook event,这是在 vm.$on 中处理组件自定义事件时设置的
if (vm._hasHookEvent) {
// vm.$emit('hook:mounted')
vm.$emit('hook:' + hook)
}
// 关闭依赖收集
popTarget()
}
invokeWithErrorHandling
/src/core/util/error.js
/**
* 通用函数,执行指定函数 handler
* 传递进来的函数会被用 try catch 包裹,进行异常捕获处理
*/
export function invokeWithErrorHandling (
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res
try {
// 执行传递进来的函数 handler,并将执行结果返回
res = args ? handler.apply(context, args) : handler.call(context)
if (res && !res._isVue && isPromise(res) && !res._handled) {
res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
// issue #9511
// avoid catch triggering multiple times when nested calls
res._handled = true
}
} catch (e) {
handleError(e, vm, info)
}
return res
}
vm.$on
/src/core/instance/events.js
/**
* 监听实例上的自定义事件,vm._event = { eventName: [fn1, ...], ... }
* @param {*} event 单个的事件名称或者有多个事件名组成的数组
* @param {*} fn 当 event 被触发时执行的回调函数
* @returns
*/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
// event 是有多个事件名组成的数组,则遍历这些事件,依次递归调用 $on
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
// 将注册的事件和回调以键值对的形式存储到 vm._event 对象中 vm._event = { eventName: [fn1, ...] }
(vm._events[event] || (vm._events[event] = [])).push(fn)
// hookEvent,提供从外部为组件实例注入声明周期方法的机会
// 比如从组件外部为组件的 mounted 方法注入额外的逻辑
// 该能力是结合 callhook 方法实现的
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
总结
面试官 问:什么是 Hook Event?
答:
Hook Event 是 Vue 的自定义事件结合生命周期钩子实现的一种从组件外部为组件注入额外生命周期方法的功能。
面试官 问:Hook Event 是如果实现的?
答:
<comp @hook:lifecycleMethod="method" />
处理组件自定义事件的时候(vm.$on) 如果发现组件有
hook:xx
格式的事件(xx 为 Vue 的生命周期函数),则将vm._hasHookEvent
置为true
,表示该组件有 Hook Event在组件生命周期方法被触发的时候,内部会通过
callHook
方法来执行这些生命周期函数,在生命周期函数执行之后,如果发现vm._hasHookEvent
为 true,则表示当前组件有 Hook Event,通过vm.$emit('hook:xx')
触发 Hook Event 的执行
这就是 Hook Event 的实现原理。
链接
- 配套视频,微信公众号回复:"精通 Vue 技术栈源码原理视频版" 获取
- 精通 Vue 技术栈源码原理 专栏
- github 仓库 liyongning/Vue 欢迎 Star
感谢各位的:关注、点赞、收藏和评论,我们下期见。
当学习成为了习惯,知识也就变成了常识。 感谢各位的 关注、 点赞、收藏和评论。
新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn
文章已收录到 github 仓库 liyongning/blog,欢迎 Watch 和 Star。
Vue 源码解读(7)—— Hook Event的更多相关文章
- Vue 源码解读(6)—— 实例方法
前言 上一篇文章 Vue 源码解读(5)-- 全局 API 详细介绍了 Vue 的各个全局 API 的实现原理,本篇文章将会详细介绍各个实例方法的实现原理. 目标 深入理解以下实例方法的实现原理. v ...
- Vue 源码解读(11)—— render helper
前言 上一篇文章 Vue 源码解读(10)-- 编译器 之 生成渲染函数 最后讲到组件更新时,需要先执行编译器生成的渲染函数得到组件的 vnode. 渲染函数之所以能生成 vnode 是通过其中的 _ ...
- Vue 源码解读(12)—— patch
前言 前面我们说到,当组件更新时,实例化渲染 watcher 时传递的 updateComponent 方法会被执行: const updateComponent = () => { // 执行 ...
- Vue 源码解读(4)—— 异步更新
前言 上一篇的 Vue 源码解读(3)-- 响应式原理 说到通过 Object.defineProperty 为对象的每个 key 设置 getter.setter,从而拦截对数据的访问和设置. 当对 ...
- Vue 源码解读(8)—— 编译器 之 解析(下)
特殊说明 由于文章篇幅限制,所以将 Vue 源码解读(8)-- 编译器 之 解析 拆成了两篇文章,本篇是对 Vue 源码解读(8)-- 编译器 之 解析(上) 的一个补充,所以在阅读时请同时打开 Vu ...
- Vue 源码解读(10)—— 编译器 之 生成渲染函数
前言 这篇文章是 Vue 编译器的最后一部分,前两部分分别是:Vue 源码解读(8)-- 编译器 之 解析.Vue 源码解读(9)-- 编译器 之 优化. 从 HTML 模版字符串开始,解析所有标签以 ...
- Vue 源码解读(1)—— 前言
当学习成为了习惯,知识也就变成了常识. 感谢各位的 点赞.收藏和评论. 新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog ...
- Vue 源码解读(2)—— Vue 初始化过程
当学习成为了习惯,知识也就变成了常识. 感谢各位的 点赞.收藏和评论. 新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn 文章已收录到 github 仓库 liyongning/blog ...
- Vue 源码解读(3)—— 响应式原理
前言 上一篇文章 Vue 源码解读(2)-- Vue 初始化过程 详细讲解了 Vue 的初始化过程,明白了 new Vue(options) 都做了什么,其中关于 数据响应式 的实现用一句话简单的带过 ...
- Vue 源码解读(5)—— 全局 API
目标 深入理解以下全局 API 的实现原理. Vue.use Vue.mixin Vue.component Vue.filter Vue.directive Vue.extend Vue.set V ...
随机推荐
- .NET 云原生架构师训练营(KestrelServer源码分析)--学习笔记
目录 目标 源码 目标 理解 KestrelServer 如何接收网络请求,网络请求如何转换成 http request context(C# 可识别) 源码 https://github.com/d ...
- cesium结合geoserver利用WFS服务实现图层删除(附源码下载)
前言 cesium 官网的api文档介绍地址cesium官网api,里面详细的介绍 cesium 各个类的介绍,还有就是在线例子:cesium 官网在线例子,这个也是学习 cesium 的好素材. 内 ...
- 学习AJAX必知必会(4)~同源策略、解决跨域问题(JSONP、CORS)
一.同源策略(Same-Origin Policy),是浏览器的一种安全策略. 1.同源(即url相同):协议.域名.端口号 必须完全相同.(请求是来自同一个服务) 2.跨域:违背了同源策略,即跨域. ...
- AOP-底层原理
AOP(底层原理) 1,AOP底层使用动态代理 (1)有两种情况动态代理 第一种 有接口情况,使用JDK动态代理 *创建接口实现类代理对象,增强类的方法 第二种 无接口情况,使用CGLIB动态代理 * ...
- 基于 SSR 的预渲染首屏直出方案
基于 SSR 的预渲染首屏直出方案 Create React Doc 是一个使用 React 的 markdown 文档站点生成工具.此前在 Create React Doc 中引入了预渲染技术来预先 ...
- 请解释final finally finalize的区别
final 关键字 ,可以定义不能被继承的父类.定义不能被重写的方法,常量 finally 关键字, 异常处理的统一出口 不管是否有异常都执行 finalize 方法(protected ...
- html+css+js(登录页)
直接上代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...
- kindle序列号对应版本
序列号前缀 型号全称 型号简称 支持越狱 B001, Kindle 1 K1 - B101 B002 Kindle 2 U.S. (Sprint) K2 - B003 Kindle 2 Interna ...
- SpringCloud整合Hystrix
1.Hystrix简介 Hystrix是由Nefflix开源的一个延迟和容错库,用于隔离访问远程系统.服务或第三方库,防止级联失败,从而提升系统的可用性.容错性与局部应用的弹性,是一个实现了超时机制和 ...
- SP5971 LCMSUM - LCM Sum
一个基于观察不依赖于反演的做法. 首先 \(\rm lcm\) 是不好算的,转化为计算 \(\rm gcd\) 的问题,求: \[\sum\limits_{i = 1} ^ n \frac{in}{\ ...