如何让自定义Hook全局仅执行一次,避免重复添加脚本标签
现有方案潜在问题
- 方案0(Provider替代):除了你提到的灵活度不足问题,额外存在提前加载脚本的缺陷:Provider需要在上层组件初始化时引入,会导致无需使用脚本的场景也会触发脚本加载逻辑,违背按需加载的需求。
- 方案1(函数标记调用状态):存在两个明显隐患:第一如果Hook被多个独立打包的chunk引用,会生成多个
addHeaderTag函数实例,实例之间的标记互不共享,重复添加的问题仍然会出现;第二如果标签被其他业务逻辑意外删除,函数标记会判定已完成加载,不会触发重新添加,导致依赖脚本的功能失效。 - 方案2(DOM id校验):你担心的频繁访问DOM问题实际影响极小,
document.getElementById的性能损耗可以忽略,该方案唯一的隐患是id命名冲突,如果其他业务代码使用了相同的id属性,会导致校验逻辑失效。
更优解决方案
推荐使用模块单例计数方案,兼具可靠性和灵活性,实现逻辑如下:
在Hook所在的模块顶层声明两个模块作用域变量:
// Hook 同模块顶层 let tagRefCount = 0 let loadedTag = null
Hook内部逻辑参考:
function useExternalScript(src) { useEffect(() => { // 引用计数为0时才添加标签 if (tagRefCount === 0) { const tag = document.createElement('script') tag.src = src document.head.appendChild(tag) loadedTag = tag } // 计数加1 tagRefCount += 1 // 清理逻辑 return () => { tagRefCount -= 1 // 所有引用组件都卸载时移除标签 if (tagRefCount === 0) { loadedTag?.remove() loadedTag = null } } }, [src]) }
该方案的优势:
- 基于ES模块单例特性,同一模块导出的Hook共享计数变量,不存在多实例失效问题,比方案1更可靠
- 仅在计数从0变为1时操作一次DOM,不会触发频繁DOM访问,满足你的性能要求
- 自带自动清理能力,当所有调用Hook的组件都卸载后,会自动移除header中的标签,不会残留无用节点
- 完全保留Hook的调用灵活性,无需调整上层组件结构
内容的提问来源于stack exchange,提问作者Bertuz
相关产品推荐
相关产品推荐

