将脚本标签添加到 React/JSX

Adding script tag to React/JSX

本文关键字:React JSX 添加 脚本 标签      更新时间:2023-09-26

我有一个相对简单的问题,即尝试将内联脚本添加到 React 组件。到目前为止,我拥有的:

'use strict';
import '../../styles/pages/people.scss';
import React, { Component } from 'react';
import DocumentTitle from 'react-document-title';
import { prefix } from '../../core/util';
export default class extends Component {
    render() {
        return (
            <DocumentTitle title="People">
                <article className={[prefix('people'), prefix('people', 'index')].join(' ')}>
                    <h1 className="tk-brandon-grotesque">People</h1>
                    
                    <script src="https://use.typekit.net/foobar.js"></script>
                    <script dangerouslySetInnerHTML={{__html: 'try{Typekit.load({ async: true });}catch(e){}'}}></script>
                </article>
            </DocumentTitle>
        );
    }
};

我也尝试过:

<script src="https://use.typekit.net/foobar.js"></script>
<script>try{Typekit.load({ async: true });}catch(e){}</script>

这两种方法似乎都没有执行所需的脚本。我猜这是我错过的一件简单的事情。有人可以帮忙吗?

PS:忽略foobar,我有一个实际使用的真实ID,我不想分享。

编辑:事情变化很快,这已经过时了 - 请参阅更新

<小时 />

是要在每次呈现此组件时一次又一次地获取和执行脚本,还是在将此组件挂载到 DOM 中时仅获取和执行一次脚本?

也许尝试这样的事情:

componentDidMount () {
    const script = document.createElement("script");
    script.src = "https://use.typekit.net/foobar.js";
    script.async = true;
    document.body.appendChild(script);
}

但是,仅当您要加载的脚本不能作为模块/包提供时,这才真正有用。 首先,我总是:

  • 在 npm 上查找包
  • 在我的项目中下载并安装软件包 ( npm install typekit (
  • import我需要的包裹 ( import Typekit from 'typekit'; (

这可能是您从示例中reactreact-document-title安装包的方式,并且 npm 上有一个 Typekit 包可用。

<小时 />

更新:

现在我们有了钩子,更好的方法可能是使用这样的useEffect

useEffect(() => {
  const script = document.createElement('script');
  script.src = "https://use.typekit.net/foobar.js";
  script.async = true;
  document.body.appendChild(script);
  return () => {
    document.body.removeChild(script);
  }
}, []);

这使它成为自定义钩子的绝佳候选者(例如:hooks/useScript.js(:

import { useEffect } from 'react';
const useScript = url => {
  useEffect(() => {
    const script = document.createElement('script');
    script.src = url;
    script.async = true;
    document.body.appendChild(script);
    return () => {
      document.body.removeChild(script);
    }
  }, [url]);
};
export default useScript;

可以这样使用:

import useScript from 'hooks/useScript';
const MyComponent = props => {
  useScript('https://use.typekit.net/foobar.js');
  // rest of your component
}

我最喜欢的方法是使用 React Helmet——它是一个组件,允许以您可能已经习惯的方式轻松操作文档头。

例如

import React from "react";
import {Helmet} from "react-helmet";
class Application extends React.Component {
  render () {
    return (
        <div className="application">
            <Helmet>
                <script src="https://use.typekit.net/foobar.js"></script>
                <script>try{Typekit.load({ async: true });}catch(e){}</script>
            </Helmet>
            ...
        </div>
    );
  }
};

https://github.com/nfl/react-helmet

除了上面的答案,你可以这样做:

import React from 'react';
export default class Test extends React.Component {
  constructor(props) {
    super(props);
  }
  componentDidMount() {
    const s = document.createElement('script');
    s.type = 'text/javascript';
    s.async = true;
    s.innerHTML = "document.write('This is output by document.write()!')";
    this.instance.appendChild(s);
  }
  render() {
    return <div ref={el => (this.instance = el)} />;
  }
}

div 绑定到this,脚本注入其中。

演示可以在 codesandbox.io 上找到

这个答案解释了这种行为背后的原因

任何呈现script标记的方法都无法按预期工作:

  1. 对外部脚本使用 script 标记
  2. 使用dangerouslySetInnerHTML

为什么

React DOM(Web 上的 react 渲染器(使用createElement调用将 JSX 渲染为 DOM 元素。

createElement使用 innerHTML DOM API 最终将这些添加到 DOM 中(参见 React 源代码中的代码(。 innerHTML不会执行作为安全考虑添加script标记。这就是为什么反过来在 React 中渲染script标签无法按预期工作的原因。

有关如何在 React 中使用 script 标签,请查看此页面上的其他一些答案。

如果需要在 SSR(服务器端渲染(中使用<script>块,则使用 componentDidMount 的方法将不起作用。

您可以改用react-safe库。React 中的代码将是:

import Safe from "react-safe"
// in render 
<Safe.script src="https://use.typekit.net/foobar.js"></Safe.script>
<Safe.script>{
  `try{Typekit.load({ async: true });}catch(e){}`
}
</Safe.script>

Alex Mcmillan 提供的答案对我帮助最大,但对于更复杂的脚本标签并不完全有效。

我稍微调整了他的答案,为具有各种功能的长标签提出了一个解决方案,该标签已经设置了"src"。

(对于我的用例,脚本需要存在于头部,这也反映在这里(:

  componentWillMount () {
      const script = document.createElement("script");
      const scriptText = document.createTextNode("complex script with functions i.e. everything that would go inside the script tags");
      script.appendChild(scriptText);
      document.head.appendChild(script);
  }

试图编辑 @Alex McMillan 接受的答案,但它不会让我这样做,所以这里有一个单独的答案,您可以在其中获得加载的库的值。人们要求的一个非常重要的区别,我需要用 stripe.js 来实现。

使用脚本.js

import { useState, useEffect } from 'react'
export const useScript = (url, name) => {
  const [lib, setLib] = useState({})
  useEffect(() => {
    const script = document.createElement('script')
    script.src = url
    script.async = true
    script.onload = () => setLib({ [name]: window[name] })
    document.body.appendChild(script)
    return () => {
      document.body.removeChild(script)
    }
  }, [url])
  return lib
}

用法看起来像

const PaymentCard = (props) => {
  const { Stripe } = useScript('https://js.stripe.com/v2/', 'Stripe')
}

注意:将库保存在对象中,因为通常库是一个函数,React 会在存储状态时执行该函数以检查更改 - 这将破坏期望使用特定参数调用的库(如 Stripe(——因此我们将其存储在对象中以隐藏它对 React 并保护库函数不被调用。

你也可以使用反应头盔

import React from "react";
import {Helmet} from "react-helmet";
class Application extends React.Component {
  render () {
    return (
        <div className="application">
            <Helmet>
                <meta charSet="utf-8" />
                <title>My Title</title>
                <link rel="canonical" href="http://example.com/example" />
                <script src="/path/to/resource.js" type="text/javascript" />
            </Helmet>
            ...
        </div>
    );
  }
};

头盔采用纯 HTML 标签并输出纯 HTML 标签。它非常简单,并且 React 对初学者友好。

我为这个特定情况创建了一个 React 组件:https://github.com/coreyleelarson/react-typekit

只需要将您的Typekit Kit ID作为道具传递即可。

import React from 'react';
import Typekit from 'react-typekit';
const HtmlLayout = () => (
  <html>
    <body>
      <h1>My Example React Component</h1>
      <Typekit kitId="abc123" />
    </body>
  </html>
);
export default HtmlLayout;

使用 Range.createContextualFragment 有一个非常好的解决方法。

/**
 * Like React's dangerouslySetInnerHTML, but also with JS evaluation.
 * Usage:
 *   <div ref={setDangerousHtml.bind(null, html)}/>
 */
function setDangerousHtml(html, el) {
    if(el === null) return;
    const range = document.createRange();
    range.selectNodeContents(el);
    range.deleteContents();
    el.appendChild(range.createContextualFragment(html));
}

这适用于任意 HTML,并保留上下文信息,例如 document.currentScript

以下是我最终能够在 React JS 代码中添加两个外部 JavaScript 文件的方式:

这些是我遵循的步骤。

第 1 步:我在 react-app 文件夹路径内使用终端中的npm i react-helmet安装了 React-Helmet

第 2 步:然后,我在代码中添加了import {Helmet} from "react-helmet";标头。

第 3 步:最后,在我的代码中,这是我如何使用 Helment 添加外部 JS 文件

<Helmet>
    <script src = "path/to/my/js/file1.js" type = "text/javascript" />
    <script src = "path/to/my/js/file2.js" type = "text/javascript" />  
</Helmet>

您可以使用npm postscribe在 react 组件中加载脚本

postscribe('#mydiv', '<script src="https://use.typekit.net/foobar.js"></script>')

您可以在以下链接中找到最佳答案:

https://cleverbeagle.com/blog/articles/tutorial-how-to-load-third-party-scripts-dynamically-in-javascript

const loadDynamicScript = (callback) => {
const existingScript = document.getElementById('scriptId');
if (!existingScript) {
    const script = document.createElement('script');
    script.src = 'url'; // URL for the third-party library being loaded.
    script.id = 'libraryName'; // e.g., googleMaps or stripe
    document.body.appendChild(script);
    script.onload = () => {
      if (callback) callback();
    };
  }
  if (existingScript && callback) callback();
};

要在 head tag <head> 中添加脚本标签或代码,请使用 react-helmet 包。 它很轻,有很好的文档。

要在正文内的脚本标记中添加 Js 代码,

    function htmlDecode(html) {
      return html.replace(/&([a-z]+);/ig, (match, entity) => {
        const entities = { amp: '&', apos: '''', gt: '>', lt: '<', nbsp: ''xa0', quot: '"' };
        entity = entity.toLowerCase();
        if (entities.hasOwnProperty(entity)) {
          return entities[entity];
        }
        return match;
      });
    }
  render() {
    const scriptCode = `<script type="text/javascript">
          {(function() {
          window.hello={
            FIRST_NAME: 'firstName',
            LAST_NAME: 'lastName',
          };
          })()}
          </script>`
    return(
      <div dangerouslySetInnerHTML={{ __html: this.htmlDecode(scriptCode) }} />;
    );
  }

此代码可以通过console.log(windows.hello)进行测试

与其他答案非常相似,只是使用默认值来清理未定义的检查

import { useEffect } from 'react'
const useScript = (url, selector = 'body', async = true) => {
  useEffect(() => {
    const element = document.querySelector(selector)
    const script = document.createElement('script')
    script.src = url
    script.async = async
    element.appendChild(script)
    return () => {
      element.removeChild(script)
    }
  }, [url])
}
export default useScript

用法

useScript('/path/to/local/script.js') // async on body
useScript('https://path/to/remote/script.js', 'html') // async on html 
useScript('/path/to/local/script.js', 'html', false) // not async on html.. e.g. this will block

根据Alex McMillan的解决方案,我有以下改编。
我自己的环境:React 16.8+,下一个 v9+

添加一个名为 Script
的自定义组件钩子/脚本.js

import { useEffect } from 'react'

// react-helmet don't guarantee the scripts execution order
export default function Script(props) {
  // Ruels: alwasy use effect at the top level and from React Functions
  useEffect(() => {
    const script = document.createElement('script')
    // src, async, onload
    Object.assign(script, props)
    let { parent='body' } = props
    let parentNode = document.querySelector(parent)
    parentNode.appendChild(script)
    return () => {
      parentNode.removeChild(script)
    }
  } )
  return null  // Return null is necessary for the moment.
}

使用自定义组合,只需导入它并用自定义驼峰大小写替换旧的小写<script>标签<Script>标签就足够了.
索引.js

import Script from "../hooks/Script";
    
<Fragment>
  {/* Google Map */}
  <div ref={el => this.el = el} className="gmap"></div>
  {/* Old html script */}
  {/*<script type="text/javascript" src="http://maps.google.com/maps/api/js"></script>*/}
  {/* new custom Script component */}
  <Script async={false} type="text/javascript" src='http://maps.google.com/maps/api/js' />
</Fragment>

派对有点晚了,但我在查看了麦克米伦@Alex答案后决定创建自己的答案,这是通过传递两个额外的参数; 放置脚本(如 or 并将异步设置为 true/false(的位置,这里是:

import { useEffect } from 'react';
const useScript = (url, position, async) => {
  useEffect(() => {
    const placement = document.querySelector(position);
    const script = document.createElement('script');
    script.src = url;
    script.async = typeof async === 'undefined' ? true : async;
    placement.appendChild(script);
    return () => {
      placement.removeChild(script);
    };
  }, [url]);
};
export default useScript;

调用它的方式与本文接受的答案中显示的完全相同,但有两个额外的(再次(参数:

// First string is your URL
// Second string can be head or body
// Third parameter is true or false.
useScript("string", "string", bool);

我最近遇到了这个问题,尝试了这里给出的多种解决方案,最后用 iframe 满意,如果您尝试在特定屏幕上集成js插件,则Iframe似乎可以无缝工作

    <iframe
      id="xxx"
      title="xxx"
      width="xxx"
      height="xxx"
      frameBorder="value"
      allowTransparency
      srcDoc={`
          <!doctype html>
          <html>
          <head>
              <title>Chat bot</title>
              <meta charset="utf-8">
              <meta name="viewport" content="width=device-width">
          </head>
          <body style="width:100%">
              <script type="text/javascript"> 
                ......
                            </script>  
          </body>
          </html>
          `}
 />

componentDidMount() {
  const head = document.querySelector("head");
  const script = document.createElement("script");
  script.setAttribute(
    "src",
    "https://assets.calendly.com/assets/external/widget.js"
  );
  head.appendChild(script);
}

只需在 html 文件中添加正文

<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>

老实说,对于 React - 不要费心在标题中添加<script>标签。当他们完全加载时收到回调是一种痛苦。相反,请使用像 @charlietango/useScript 这样的包在需要时加载脚本,并在完成后获取状态更新。

用法示例:

import React from 'react'
import useScript, { ScriptStatus } from '@charlietango/use-script'
 
const Component = () => {
  const [ready, status] = useScript('https://api.google.com/api.js')
 
  if (status === ScriptStatus.ERROR) {
    return <div>Failed to load Google API</div>
  }
 
  return <div>Google API Ready: {ready}</div>
}
 
export default Component

附言。如果您使用 redux 来告诉其他组件脚本何时加载,并且像我一样使用redux-persist,请不要忘记在您的 redux-persist 设置中包含修饰符,该修饰符始终在 redux 备份中将脚本加载的 redux 值设置为 false。

有关支持加载状态和错误处理的更完整的useScript实现,请查看 useHooks。

用法

function App() {
  const status = useScript(
    "https://pm28k14qlj.codesandbox.io/test-external-script.js"
  );
  return (
    <div>
      <div>
        Script status: <b>{status}</b>
      </div>
      {status === "ready" && (
        <div>
          Script function call response: <b>{TEST_SCRIPT.start()}</b>
        </div>
      )}
    </div>
  );
}

function useScript(src) {
  // Keep track of script status ("idle", "loading", "ready", "error")
  const [status, setStatus] = useState(src ? "loading" : "idle");
  useEffect(
    () => {
      // Allow falsy src value if waiting on other data needed for
      // constructing the script URL passed to this hook.
      if (!src) {
        setStatus("idle");
        return;
      }
      // Fetch existing script element by src
      // It may have been added by another intance of this hook
      let script = document.querySelector(`script[src="${src}"]`);
      if (!script) {
        // Create script
        script = document.createElement("script");
        script.src = src;
        script.async = true;
        script.setAttribute("data-status", "loading");
        // Add script to document body
        document.body.appendChild(script);
        // Store status in attribute on script
        // This can be read by other instances of this hook
        const setAttributeFromEvent = (event) => {
          script.setAttribute(
            "data-status",
            event.type === "load" ? "ready" : "error"
          );
        };
        script.addEventListener("load", setAttributeFromEvent);
        script.addEventListener("error", setAttributeFromEvent);
      } else {
        // Grab existing script status from attribute and set to state.
        setStatus(script.getAttribute("data-status"));
      }
      // Script event handler to update status in state
      // Note: Even if the script already exists we still need to add
      // event handlers to update the state for *this* hook instance.
      const setStateFromEvent = (event) => {
        setStatus(event.type === "load" ? "ready" : "error");
      };
      // Add event listeners
      script.addEventListener("load", setStateFromEvent);
      script.addEventListener("error", setStateFromEvent);
      // Remove event listeners on cleanup
      return () => {
        if (script) {
          script.removeEventListener("load", setStateFromEvent);
          script.removeEventListener("error", setStateFromEvent);
        }
      };
    },
    [src] // Only re-run effect if script src changes
  );
  return status;
}

对于多个脚本,请使用这个

var loadScript = function(src) {
  var tag = document.createElement('script');
  tag.async = false;
  tag.src = src;
  document.getElementsByTagName('body').appendChild(tag);
}
loadScript('//cdnjs.com/some/library.js')
loadScript('//cdnjs.com/some/other/library.js')

我有带有javascript/Jquery的原始html字符串我安装了 npm 库危险地设置-html-内容 npm i dangerously-set-html-content

import InnerHTML from 'dangerously-set-html-content'
<div>
<InnerHTML html={html}/>
</div>

import InnerHTML from 'dangerously-set-html-content'
const renderhtml=`<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title> is not defined</title>$(document).ready(function(){    $("button").click(function(){        alert("jQuery is working perfectly.");    });      });</script></head><body>    <button type="button">Test jQuery Code</button></body></html>`
<div>
<InnerHTML html={renderhtml}/>
</div>

确保将 jquery cdn 添加到 public/index.html 文件

 <script          src="https://code.jquery.com/jquery-3.3.1.min.js"          integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="          crossorigin="anonymous"          async="true"        ></script>

您可以尝试使用以下方法:

确保您信任脚本

 <script>{`
            function myFunction(index, row) {
                return index;
            }
        `}
</script>

您必须为此脚本创建一个组件,将此组件称为标准 ES6 脚本标记

'use strict';
import '../../styles/pages/people.scss';
import React, { Component } from 'react';
import DocumentTitle from 'react-document-title';
import { prefix } from '../../core/util';
export default class extends Component {
    render() {
        return (
            <DocumentTitle title="People">
                <article className={[prefix('people'), prefix('people', 'index')].join(' ')}>
                    <h1 className="tk-brandon-grotesque">People</h1>
                </article>
            </DocumentTitle>
        );
        class Component extend Index.App {  
             <script src="https://use.typekit.net/foobar.js" />
             <script dangerouslySetInnerHTML={{__html: 'try{Typekit.load({ async: true });}catch(e){}'}}/>
        }
    }
};

useScript.js hook - 帮助了我。谢谢拉耶普斯

我的变种方式支付

const {loaded,Wayforpay} = useScript('https://secure.wayforpay.com/server/pay-widget.js','Wayforpay');

单击按钮处理程序

const openPay = (Wayforpay) =>{
            axios.get('/site/paywfp',{params:{guid:pay.guid}})
                .then(({data}) =>{
    
                    const wfp = new Wayforpay();
    
                    wfp.run(
                        data.option,
                        () => console.log('done'),
                        () => console.log('err'),
                        () => console.log('wait')
                    );
                }
            );
        }

加载集上的按钮

loaded?<button onClick={()=>openPay(Wayforpay)}>

解决方案取决于场景。就像我的情况一样,我必须在 react 组件中加载一个日历嵌入。

Calendly 查找一个div 并从它的 data-url 属性中读取,并在所述div 中加载一个 iframe。

当您第一次加载页面时,一切都很好:首先,渲染带有data-url的div。然后将日历脚本添加到正文中。浏览器下载并评估它,我们都高高兴兴地回家了。

当您离开然后返回页面时,问题就来了。这次脚本仍在正文中,浏览器不会重新下载和重新评估它。

修复:

  1. componentWillUnmount找到并删除脚本元素。然后在重新装载时,重复上述步骤。
  2. 输入 $.getScript 。它是一个漂亮的jquery助手,它接受脚本URI和成功回调。加载脚本后,它会对其进行评估并触发成功回调。我所要做的就是在我的componentDidMount $.getScript(url).我的render方法已经有日历div。而且效果很顺利。

我看到了同样的问题,直到我发现这个包很容易实现,我希望它像对我一样工作:)

https://github.com/gumgum/react-script-tag

import React from 'react';
import Script from '@gumgum/react-script-tag';
import './App.css';
function App() {
  return (
    <div >
      <h1> Graphs</h1>
      <div class="flourish-embed flourish-network" data-src="visualisation/8262420">
        <Script  src"your script"
        </Script>
      </div>
    </div>
  );
}
export default App;

您可以在调用 react 之前将脚本放在 HTML 文件中。