如何在反应中加载自定义脚本?

Posted

技术标签:

【中文标题】如何在反应中加载自定义脚本?【英文标题】:How to load custom script in react? 【发布时间】:2021-05-15 02:33:13 【问题描述】:

这有点类似于这个问题:

Adding script tag to React/JSX

但就我而言,我正在加载这样的脚本:

<script>(function(w,d,s,l,i)w[l]=w[l]||[];w[l].push('gtm.start':
new Date().getTime(),event:'gtm.js');var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
)(window,document,'script','dataLayer','ID');</script>
<!-- End Google Tag Manager -->

现在我知道谷歌标签管理器有一个 npm 包,但我很好奇如果我想以自定义方式执行此操作,我该怎么做?

在上面的问题中,我看到了很多:

const script = document.createElement("script");

script.src = "https://use.typekit.net/foobar.js";
script.async = true;

document.body.appendChild(script);

这很好,但如果我在加载的脚本中有一个函数,我将如何正确执行它?

【问题讨论】:

将该函数设为全局变量,然后在任何地方使用它,如下所示:gloabl.functionFromScript。此外,您为什么要这样做,无论您的实际用例如何,都必须有更好的解决方案。 另外,如果您想始终加载自定义脚本,只需将其添加到您的 index.html,而不是通过所有 js 将脚本标签附加到正文 你是对的。上面的脚本将被移动到 index.html 但我有一个不同的场景,其中添加了类似的东西但它仅用于特定场景,因此为了不加载基本上只是坐在那里直到用户来到的全局脚本特定的场景并不理想,所以我想一旦用户触发所需的行为,我就会加载它。我想避免将其保存在全局变量中,因此情况相同。我有一个 HOOK,我想触发以与上面类似的方式编写的脚本,但无法弄清楚如何正确执行。 你不能将你的函数附加到一个对象上,然后将它暴露给其他脚本使用。是的,新对象最终将附加到全局/窗口,但还能做什么。 【参考方案1】:

要添加这样的随机脚本,您可以:

    将脚本添加到您的 index.html 将代码粘贴到文件中并使用导入语句。 一旦用户做某事,使用代码拆分动态加载脚本。

1.将脚本添加到您的 HTML

只需将脚本标签粘贴到您的 index.html 文件中,最好放在正文标签的末尾。如果使用 create-react-app,则 index.html 文件位于公共目录中:

<body>
  <div id="root"></div>
  <script>/* your script here */</script>
</body>

2。从文件导入

或者,您可以将脚本粘贴到 .js 文件中,然后从代码中的任何位置导入。导入通用脚本的好地方是您的 index.js 入口点文件。这种方法的好处是将脚本包含在您的 js 包的其余部分中,从而实现缩小和摇树。

// index.js
import "../path/to/your/script-file";

3.代码拆分 最后,如果您想在某个时间点动态加载一段 js 代码,同时确保它不是您的起始包的一部分,您可以使用动态导入进行代码拆分。 https://create-react-app.dev/docs/code-splitting

function App() 
  function handleLoadScript() 
    import('./your-script')
      .then(( functionFromModule ) => 
        // Use functionFromModule 
      )
      .catch(err => 
        // Handle failure
      );
  ;

  return <button onClick=handleLoadScript>Load</button>;

【讨论】:

【参考方案2】:

通常,可以使用 dangerouslySetInnerHTML 属性在 react 中更新 HTML 元素。 但是对于要执行的脚本,这将不起作用,正如其他SO question 中所讨论的那样。

实现此目的的一个选项是使用文档 Range API createContextualFragment 将元素附加到新的文档上下文中

下面的工作示例。 请注意,我对您的脚本进行了一些调整,以展示一些自定义方法。

const  useState, useRef, useEffect, memo  = React;

const MyCustomScriptComponent = () => 
  const [includeScript, setIncludeScript] = useState(false)

  // just some examples of customizing the literal script definition
  const labelName = 'dataLayer'
  const gtmId = 'ID' // your GTM id

  // declare the custom <script> literal string
  const scriptToInject = `
    <script>
(function(w,d,s,l,i)
const gtmStart = new Date().getTime();
w[l]=w[l]||[];w[l].push('gtm.start':
gtmStart,event:'gtm.js');var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
console.log("loaded at gtmStart:", gtmStart);
)(window,document,'script','$labelName','$gtmId');
console.log("fetching GTM using id '$gtmId'");
     </script>`

  const InjectScript = memo(( script ) => 
    const divRef = useRef(null);

    useEffect(() => 
      if (divRef.current === null) 
        return;
      
      // create a contextual fragment that will execute the script
      // beware of security concerns!!
      const doc = document
          .createRange()
          .createContextualFragment(script)
      
      // clear the div HTML, and append the doc fragment with the script 
      divRef.current.innerHTML = ''
      divRef.current.appendChild(doc)
    )

    return <div ref=divRef />
  )

  const toggleIncludeScript = () => setIncludeScript((include) => !include)

  return (
    <div>
      includeScript && <InjectScript script=scriptToInject />
      <p>Custom script includeScript ? 'loaded!' : 'not loaded.'</p>
      <button onClick=toggleIncludeScript>Click to load</button>
    </div>
  )


ReactDOM.render(<MyCustomScriptComponent />, document.getElementById('app'))

在codepen 上试用。

如需更多参考,您可以在此medium post 中找到更多注入脚本的替代方法。

【讨论】:

以上是关于如何在反应中加载自定义脚本?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Django 管理主页中加载自定义 JS 文件?

如何从资源中加载自定义字体? [C#]

如何从文件中加载自定义 Django 标签?

如何在 tensorflow 服务 (gpu) 中加载自定义动态库 (*.so)?

如何在Spring容器中加载自定义的配置文件

在 ScrollView 中加载自定义 UIView