页面一刷新就发两次请求,接口日志里躺着两条一模一样的记录。代码里 useEffect 只写了一次,依赖数组也写了。查下去发现数组里放了个每次渲染都新建的对象,等于没写依赖。
钩子函数的本质很朴素:框架或者系统在某个固定时机,主动回调你注册的函数。你不需要知道它什么时候执行,只要把代码挂在那个点上。不同领域的钩子长得完全不一样,混着聊容易乱。
前端的生命周期钩子
Vue 有 onMounted、onUpdated、onUnmounted,React 用 useEffect 的返回函数模拟清理。React 的依赖数组是核心:空数组只跑一次,不写数组每次渲染都跑,写了变量就跟着变量变。对象和函数每次渲染都是新引用,放进依赖必然重复触发,用 useMemo 包一层或者移到组件外定义。
useEffect(()=>{ fetchList(); return ()=>{ controller.abort(); }; },[]);清理函数别省。定时器、事件监听、未完成的请求,都该在返回的函数里收掉,否则切页面之后还在后台跑,问题很难复现。
系统和工具里的钩子
- Windows:SetWindowsHookEx 挂键盘鼠标消息,做全局热键和输入监控,需要 DLL 注入
- Git:hooks 目录下的 pre-commit、pre-push 脚本,提交前跑 lint,不符合就返回非零码
- Webhook:它不是钩子函数,是反向通知,别人主动 POST 到你的地址,别混为一谈
Git 钩子默认不随仓库分发,团队要统一规范,得配 core.hooksPath 指向一个提交进仓库的目录,否则每个人本地都没有,检查形同虚设。
用钩子的前提是搞清楚触发时机和调用频率。生命周期钩子里做重活会拖慢渲染,Git 钩子里跑全量测试会让人想办法绕过它。挂之前先想清楚它会被调多少次,比写什么更重要。