将脚本标签添加到React / JSX


264

我有一个相对简单的问题,试图将内联脚本添加到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正在使用,而我不想共享。


5
是否有特定的动机通过React加载而不是将其包含在基本HTML中?即使这样做确实可行,这也意味着您将在每次安装组件时重新插入脚本。
loganfsmyth,2015年

是这样吗 我认为DOM差异并非如此,但我承认这将取决于的实现DocumentTitle
loganfsmyth

8
正确@loganfsmyth,如果下一个状态也有脚本,React将不会在重新渲染时重新加载脚本。
Max

Answers:


403

编辑:事情变化很快,这已经过时了-查看更新


您是否想在每次渲染此组件时一次或一次将该组件安装到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';

这可能是您安装软件包的方式react以及react-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
}

谢谢。我在想这个错误。我继续执行此实现。只是try{Typekit.load({ async: true });}catch(e){}appendChild
ArrayKnight

2
我认为TypeKit的“高级”实现更适合这种方法。
ArrayKnight

10
这确实可以工作-加载脚本,但是如何获得脚本中的代码。例如,我想调用一个驻留在脚本内的函数,但无法在加载脚本的组件内调用它。
zero_cool,2016年

2
当脚本附加到页面后,它将照常执行。例如,如果您使用此方法从CDN下载jQuery,则在componentDidMount函数下载脚本并将其附加到页面后,您将拥有jQuery$对象全局可用(即:on window)。
Alex McMillan

1
我在使用身份验证脚本时遇到了类似的问题,结果发现将它包含在您的React App.js上方一层的根html文件中可能会更好。万一有人觉得有用。正如@loganfsmith提到的…
devssh

57

除了上述答案,您还可以执行以下操作:

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找到


5
this.instance不适用于我,但是document.body.appendChild从Alex McMillan的答案中获取了。
什么会酷

6
您可能没有绑定this.instance到render方法中的ref。我添加了一个演示链接来演示它的工作
原理

1
@ShubhamKushwah您必须执行服务器端渲染吗?
ArrayKnight,

@ArrayKnight是的,我后来发现服务器上这些对象不存在:documentwindow。因此,我更喜欢使用npm global软件包
Shubham Kushwah,

s.async = true有什么需要,我找不到关于它的任何参考,知道它的目的,您能解释一下吗
武罗曼诺夫

50

我最喜欢的方法是使用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


5
到目前为止,这是最好的解决方案。
paqash

4
不幸的是,它无法正常工作……请参阅codeandbox.io/s/l9qmrwxqzq
Darkowic

2
@Darkowic,通过添加将jQuery 添加async="true"<script>代码中的标签,我使您的代码可以工作。
Soma Mbadiwe

@SomaMbadiwe为什么可以使用它,async=true而没有它会失败?
Webwoman

3
试过这个,对我不起作用。我不建议使用react-helmet,其唯一原因是它向无法删除的脚本中注入了额外的属性。这实际上破坏了某些脚本,并且维护者多年未对其进行修复,并拒绝github.com/nfl/react-helmet/issues/79
Philberg,

16

如果您需要<script>在SSR(服务器端渲染)中使用阻止功能,可以使用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>

12

答案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);
  }

7
我不明白,如果只是将内联JS转储到页面上,为什么根本不使用React?
亚历克斯·麦克米伦

2
您需要添加document.head.removeChild(script);代码,或者只要用户访问此页面路由
便会


3

有一个非常好的解决方法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


您能否与使用示例合作,希望它如何工作?对我来说,它不能与传递脚本和正文一起使用
。–

3

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

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

1
解决了我的问题
RPichioli

1

您还可以使用反应头盔

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初学者很友好。


0

对于多个脚本,请使用此

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')

0
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);
}

0

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

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();
};

0

根据Alex McMillan的解决方案,我进行了以下调整。
我自己的环境:React 16.8+,下一个v9 +

//添加一个名为Script的自定义组件
// hooks / Script.js

import { useEffect } from 'react'

const useScript = (url, async) => {
  useEffect(() => {
    const script = document.createElement('script')

    script.src = url
    script.async = (typeof async === 'undefined' ? true : async )

    document.body.appendChild(script)

    return () => {
      document.body.removeChild(script)
    }
  }, [url])
}

export default function Script({ src, async=true}) {

  useScript(src, async)

  return null  // Return null is necessary for the moment.
}

//使用自定义组件,只需将其导入并<script>用自定义驼峰大写<Script>标签替换旧的小写标签即可。
// index.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 src='http://maps.google.com/maps/api/js' async={false} />
</Fragment>

该组件有一个警告:该脚本组件只能保证其自身同级的顺序。如果您在同一页面的多个组件中多次使用此组件,则脚本块可能会乱序。原因是所有脚本都以编程方式而不是声明方式由document.body.appendChild插入。头盔将所有脚本标签移动到head标签中,这不是我们想要的。
苏利

嘿,@ sully,我的问题是将脚本分别添加到DOM中,到目前为止,我看到的最好的解决方案是在Component Unmounting期间,从DOM中删除子元素(即<script>),然后当组件安装在DOM上时再次添加(我正在使用react-router-dom,只有一个组件需要我所有组件的脚本)
Eazy

0

晚了一点,但是我决定在看完@Alex Macmillan答案后创建自己的一个,那就是通过传递两个额外的参数;放置脚本(例如或)并将异步设置为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);

-3

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

Calendly寻找一个div并从其data-url属性读取并在所述div中加载一个iframe。

第一次加载页面时,一切都很好:首先,data-url呈现div with 。然后将日历脚本添加到正文中。浏览器下载并评估它,我们都很高兴回家。

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

固定:

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

2
添加jQuery来做到这一点不是一个好主意,再加上您的案例对您来说非常具体。实际上,一次添加Calendly脚本并没有错,因为我确定API会重新检测调用。一遍又一遍地删除和添加脚本是不正确的。
sidonaldson

@sidonaldson jQuery不是不好的做法,如果您要维护一个项目,它的不同框架(和libs)的体系结构复合不仅会做出反应,否则我们需要使用本机js来访问组件
AlexNikonov
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.