Gatsby中如何为指定页面按依赖顺序加载多个script脚本
Gatsby指定路由下按顺序加载存在依赖关系的脚本方案
你的实现失效核心是两点:
- 所用的自定义
useScript钩子默认给动态创建的脚本设置了async=true,异步脚本本身不保证执行顺序 react-helmet注入脚本时默认采用异步加载逻辑,哪怕你通过状态判断控制脚本标签渲染时机,也无法保证插入DOM后的执行顺序,才会出现第二个脚本提前执行的报错。
下面给出两种可直接落地的实现方案,都只会在/apply路由加载对应脚本,不会全局引入。
方案1:使用Gatsby内置Script组件(推荐,适配Gatsby 4.15及以上版本)
Gatsby官方提供的Script组件原生支持脚本依赖顺序控制,不需要手写状态判断、不需要额外处理加载时序,是最稳定的方案。
- 4.15以下版本先安装对应插件,高版本可跳过此步:
npm install gatsby-plugin-script
- 在项目根目录的
gatsby-config.js中添加插件配置:
module.exports = { plugins: [ // 其他已有插件配置 `gatsby-plugin-script` ] }
- 在
/apply路由对应的页面文件中直接引入使用,通过dependencies参数绑定依赖关系:
import { Script } from "gatsby" const Apply = () => { return ( <> {/* 第一个前置脚本 */} <Script src="https://myscripturl/script1.js" strategy="beforeInteractive" id="dep-script-1" /> {/* 第二个脚本绑定前置脚本id,保证前置脚本加载执行完成后才会加载执行 */} <Script src="https://myscripturl/script2.js" strategy="afterInteractive" id="dep-script-2" dependencies={["dep-script-1"]} /> {/* 页面其余业务内容 */} </> ) } export default Apply
这个方案的优势:
- 脚本仅在当前路由加载,不会在其他页面引入冗余资源
- 框架底层自动处理加载时序,不需要手动维护加载状态
- 支持加载成功、失败回调,方便做异常兜底
- 不会出现
react-helmet注入脚本的异步时序问题
方案2:自定义Hook串行加载(适配低版本Gatsby)
如果你的项目Gatsby版本较低无法使用内置Script组件,可以放弃用react-helmet注入依赖脚本,全部通过动态创建script标签的方式串行加载,从根源避免异步注入的时序问题。
- 改造原有
useScript钩子,支持控制脚本加载属性,修复默认async导致的顺序问题:
import { useState, useEffect } from 'react' function useScript(src, config = { async: false, defer: false }) { // 维护脚本加载状态:idle/loading/ready/error const [status, setStatus] = useState(src ? "loading" : "idle"); useEffect(() => { if (!src) { setStatus("idle"); return; } // 避免重复加载同地址脚本 const existScript = document.querySelector(`script[src="${src}"]`); if (existScript) { setStatus(existScript.getAttribute("data-status") || "ready"); return; } // 动态创建脚本标签 const script = document.createElement("script"); script.src = src; script.async = config.async; script.defer = config.defer; script.setAttribute("data-status", "loading"); document.body.appendChild(script); // 加载状态回调 const onLoad = () => { script.setAttribute("data-status", "ready"); setStatus("ready"); }; const onError = () => { script.setAttribute("data-status", "error"); setStatus("error"); }; script.addEventListener("load", onLoad); script.addEventListener("error", onError); // 组件卸载时移除事件监听 return () => { script.removeEventListener("load", onLoad); script.removeEventListener("error", onError); }; }, [src, config.async, config.defer]); return status; } export default useScript
- 在页面组件中串行触发两个脚本的加载,等第一个脚本加载完成后再加载第二个:
import { useState, useEffect } from 'react' import useScript from './useScript' const Apply = () => { // 先加载第一个前置脚本,关闭async模式 const script1Status = useScript("https://myscripturl/script1.js", { async: false }); const [canLoadScript2, setCanLoadScript2] = useState(false); // 监听第一个脚本加载状态,加载完成后再触发第二个脚本加载 useEffect(() => { if (script1Status === "ready") setCanLoadScript2(true); }, [script1Status]); const script2Status = useScript( canLoadScript2 ? "https://myscripturl/script2.js" : null, { async: false } ); // 两个脚本都加载完成前可以展示加载态,避免业务逻辑调用未加载的脚本方法报错 if (script1Status !== "ready" || script2Status !== "ready") { return <div>页面加载中...</div> } return ( <> {/* 页面业务内容,不需要再通过Helmet引入脚本 */} </> ) } export default Apply
注意:存在依赖关系的脚本不要通过
react-helmet注入,该组件插入脚本标签时默认采用异步加载逻辑,不会保证多个脚本的执行顺序,很容易出现依赖报错。
内容的提问来源于stack exchange,提问作者zyfygi
相关产品推荐
相关产品推荐

