钩子Hook

通俗来说,“钩子”(Hook)就是软件官方在流水线上故意留的一个“插手机会”。

生活中的类比:快递打包流水线

想象你把寄快递的整个流程委托给了一个自动化打包系统:

  1. 系统拿取货物(编译代码)
  2. 塞进内包装盒(打包成文件夹)
  3. 【系统停顿 3 秒并喊一句:“谁要往盒子里放东西?”】 $\leftarrow$ 这就是钩子!
  4. 封箱并打打包带(压缩成最终的 .exe 安装包)

如果没有这个钩子,自动化流水线会一气呵成从第 1 步跑到第 4 步。如果你想在盒子封死前塞一张手写感谢信,或者拿剪刀把多余的泡沫塑料剪掉,你就只能把最终打好包的箱子暴力拆开,改完再重新粘上。

而官方在这里挂了一个“钩子”,意思就是:

“我知道我的自动化脚本不可能满足所有人千奇百怪的需求。所以在这个关键节点,我预先留一个空位(钩子)。如果你挂上了你的脚本,我就先暂停,执行你的代码;执行完了,我再继续往下走。”

在编程与工程中,它长什么样

钩子的本质,就是事件驱动的回调函数(Callback)或拦截器。

  • 没有钩子时的死板程序:
1
开始打包 ──> 拷贝文件 ──> 压缩成安装包 ──> 完成
  • 留了“钩子”的开放程序:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
开始打包 ──> 拷贝文件 ──> [ 检查有没有 afterPack 钩子? ]
				│
		  ┌───────────┴───────────┐
		 没有                    有!
		  │                       │
		  │                 执行你写的清理脚本
		  │                 (比如删掉 100MB 冗余文件)
		  │                       │
		  └───────────┬───────────┘
				▼
		      压缩生成安装包 ──> 完成

常见的几种“钩子”形态

钩子这个概念在整个软件开发中无处不在,叫法不同,底层逻辑完全一致:

  1. 构建工具的生命周期钩子(如 afterPack、Webpack 的 plugins):
    • 在“编译前”、“编译后”、“打包前”给你插手修改产物文件的机会。
  2. Git 钩子(Git Hooks):
    • 在 git commit(提交代码)的瞬间自动拦截,先运行代码格式检查;格式不合格直接拒绝提交。
  3. 前端框架生命周期钩子(如 Vue 的 mounted / React 的 useEffect):
    • 页面在“组件刚创建”、“DOM 渲染完成”、“组件销毁前”给你插手发起网络请求或清理内存的机会。
  4. 操作系统/底层钩子(Windows Hook):
    • 当用户按下键盘某个按键时,系统先不传给普通软件,而是先让钩子函数“偷看”或拦截(例如全局截屏快捷键、外设按键捕获)。

一句话总结:“主流程你来跑,关键节点等我插一手”,这个留给你插手的地方,就叫钩子。

工业与实体制造中的类似映射

软件里的架构思想,很多本就是现实工业流水线工程经验的代码化:

实体工程操作 现实场景 对应的软件概念
流水线预留工位 汽车装配线上留出“选装工位”,标准车架直接滑过,加装天窗或四驱套件的车型在此停留作业 构建工具生命周期钩子(如 afterPack)
出厂检验卡点(QC Gate) 钣金件冲压完成进入烤漆前,必须先经过三坐标尺寸扫描;超差直接停线报警打回 pre-commit Git 钩子、构建前校验
旁路阀与取样口(Bypass / Tap) 工业流体管路主管道上预留三通球阀与取样接头,平时封闭,需要校验或外接分析仪器时接管引流 操作系统拦截器、Packet Filter / Tap 钩子
急停安全联锁(Interlock) 防护光栅被遮挡或急停按钮按下时,硬接线电路优先于 PLC 主程序切断驱动电源 操作系统底层硬件中断与全局拦截钩子

顶层抽象:它究竟是什么

1. 控制反转(Inversion of Control, IoC)与“好莱坞原则”

“Don’t call us, we’ll call you.”(别找我们,我们会找你。)

传统编程是你调用库函数;而在“钩子/框架”模式下,是主流程调度你的代码。主流程掌握控制权,你只需要提供在特定时机被唤起的逻辑。

2. 开闭原则(Open-Closed Principle, OCP)

对扩展开放,对修改关闭。

当你想增加新功能时,不需要把官方封装好的底层源码撕开大改;官方通过暴露钩子,让系统在不需要动核心代码的前提下,无限支持未来未知的扩展。

3. 关注点分离(Separation of Concerns)

将全局流程编排(怎么打包、怎么运行生命周期、怎么调度请求)与局部业务细节(删哪个冗余文件、弹什么感谢信、加什么鉴权校验)彻底解耦,互不污染。

4. 协作契约(Contract-based Extension)

钩子不是随意的入侵,而是一份严格的“协议”。框架承诺:“在 X 时机给你传入 Y 数据,并等待你完成”;你承诺:“在钩子内按约定格式处理数据,不破坏主流水线的生命周期约束”。

举例

例如在安装程序的时候,会有一个选择安装目录的选择,这是不是就是一个钩子

严格来说,不是。

它更准确的定性是“参数配置”(Parameter / Configuration),而不是“钩子”(Hook)。虽然它们在表面上很像——都是系统暂停下来让你插手——但它们的“抽象层级”和“插手性质”有本质区别。

核心区别:传的是“数据”还是“逻辑”?

  • 选择安装目录(传数据 / 配置): 安装程序已经内置好了解压、写入文件的固定逻辑,它只是向你索取一个字符串变量(比如 D:\Software\App)。你没有注入任何新的代码或流程,你只是在填空。
  • 钩子(传逻辑 / 代码): 钩子不是给你填参数的,而是让你把一段自定义的代码/脚本挂上去执行。官方原本没有这个逻辑,是你临时生造了一段行为塞进流程里。

对比维度

维度 选择安装目录(配置参数) 真正的钩子(Hook)
输入内容 纯数据(路径字符串、布尔开关) 可执行逻辑(函数、回调、外部脚本)
执行者 安装程序原有的内置代码 开发者或用户自己编写的扩展逻辑
功能上限 无论选哪个盘,执行的动作都是 WriteFiles(path) 拥有无限可能(比如“安装前扫描竞品并杀掉进程”、“解压后自动配置环境变量并向企业微信发报警通知”)
抽象概念 配置驱动(Config-driven) / 依赖注入数据 控制反转(IoC) / 回调(Callback)

如果在安装程序里,什么才算“钩子”?

如果你用过专业安装包制作工具(如 Inno Setup、NSIS、WiX):

  • 普通配置:勾选“创建桌面快捷方式”、输入“安装路径 C:\Program Files” $\rightarrow$ 配置参数。
  • 钩子实例:
    • BeforeInstall 钩子:解压前执行一段 Pascal/C++ 脚本,检测当前机器的 VC++ 运行库版本,如果低于要求则自动下载安装。
    • AfterInstall 钩子:文件全部拷贝完毕后、写注册表之前,调用你的自定义脚本,静默修改某个 .ini 文件的特定字段,或向服务器发起激活请求。
Licensed under CC BY-NC-SA 4.0