React / JSXへのスクリプトタグの追加


266

私は、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
ベースページのHTMLに含める代わりに、Reactを介してこれをロードする特定の動機はありますか?これが機能しても、コンポーネントがマウントされるたびにスクリプトを再挿入することになります。
loganfsmyth 2015

それは事実ですか?私はDOMの相違がそうではないと想定しましたが、それはの実装に依存することを認めDocumentTitleます。
loganfsmyth

8
@loganfsmythを修正し、次の状態にもスクリプトがある場合、Reactは再レンダリング時にスクリプトをリロードしません。
マックス・

回答:


405

編集:物事は急速に変化し、これは時代遅れです-アップデートを参照してください


このコンポーネントがレンダリングされるたびに、またはこのコンポーネントがDOMにマウントされたときに1回だけ、スクリプトを何度もフェッチして実行しますか?

おそらく次のようなことを試してください:

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
私は認証スクリプトを使用して同様の問題を抱えており、それをルートのhtmlファイルに含める方が、react App.jsの上のレイヤーに置く方がよい場合があることがわかりました。誰かがこれが便利だと思った場合。@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あなたのレンダーメソッドの中でrefにバインドしませんでした。私はそれが機能していることを示すためにデモリンクを追加しました
sidonaldson 2017

1
@ShubhamKushwahサーバーサイドレンダリングを実行している必要がありますか?
ArrayKnight、

@ArrayKnightはい私は後でサーバー上にこれらのオブジェクトが存在しないことを知りました:documentwindow。したがって、私はnpm globalパッケージの使用を好みます
Shubham Kushwah

s.async = trueの必要性とは何ですか、それについての参照が見つかりません。目的を知ることができます。説明できますか
sasha romanov

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
残念ながら、機能していません... codesandbox.io/s/l9qmrwxqzqを
Darkowic

2
@ Darkowic、jQueryをコードに追加async="true"した<script>タグに追加することで、コードを機能させました。
ソマムバディウェ

@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を使用するのかわからない...?
Alex McMillan

2
document.head.removeChild(script);コードを追加する必要があります。そうしないと、ユーザーがこのページルートに
アクセスし

7

この特定のケースのためにReactコンポーネントを作成しました:https : //github.com/coreyleelarson/react-typekit

タイプキットキット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;

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


使用サンプルを使用して、どのように機能することが期待されますか?私にとっては、例えば、スクリプトや体を渡すと協力していません...
アレックスEfimov

3

を使用npm postscribeして、スクリプトを反応コンポーネントにロードできます

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>

このコンポーネントには注意点が1つあります。このスクリプトコンポーネントは、自身の兄弟の順序のみを保証できます。同じページの複数のコンポーネントでこのコンポーネントを複数回使用すると、スクリプトブロックが故障する可能性があります。その理由は、すべてのスクリプトが、宣言的にではなく、プログラムによってdocument.body.appendChildによって挿入されるためです。ヘルメットはヘッドタグ内のすべてのスクリプトタグを移動しますが、これは望ましくありません。
sully

@sullyさん、私の問題は、スクリプトをDOMにいくつか追加することです。これまでに見た最善の解決策は、コンポーネントのアンマウント中に、子要素(つまり、<script>)をDOMから削除してから、コンポーネントがDOMにマウントされたときに再度追加されました(react-router-domを使用しており、1つのコンポーネントのみがすべてのコンポーネントのこのスクリプトを必要とします)
Eazy

0

パーティーには少し遅れましたが、@ Alex Macmillanの回答を見て、独自のパラメーターを作成することにしました。またはなどのスクリプトを配置し、asyncを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;

それを呼び出す方法は、この投稿の受け入れられた回答に示されているものとまったく同じですが、2つの追加の(再度)パラメータがあります。

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

0

反応プロジェクトでJSファイルをインポートするには、このコマンドを使用します

  1. JSファイルをプロジェクトに移動します。
  2. 次のコマンドを使用してjsをページにインポートします

シンプルで簡単です。

import  '../assets/js/jquery.min';
import  '../assets/js/popper.min';
import  '../assets/js/bootstrap.min';

私の場合、これらのJSファイルをreactプロジェクトにインポートします。


-3

解決策はシナリオによって異なります。私の場合と同様に、reactコンポーネント内に組み込みの埋め込みをロードする必要がありました。

Calendlyはdivを探し、そこから読み取ります data-url属性 div内にiframeを読み込みます。

最初にページをロードするときに問題はありません。最初に、div data-urlがレンダリングされます。次に、大胆にスクリプトが本体に追加されます。ブラウザーはそれをダウンロードして評価し、私たちはみんな幸せに帰ります。

ナビゲートしてページに戻ったときに問題が発生します。今回はスクリプトがまだ本体内にあり、ブラウザはそれを再ダウンロードおよび再評価しません。

修正:

  1. 上のcomponentWillUnmountスクリプト要素を検索し、削除します。次に、再マウント時に、上記の手順を繰り返します。
  2. と入力し$.getScriptます。スクリプトURIと成功コールバックを受け取る気の利いたjqueryヘルパーです。スクリプトがロードされると、スクリプトが評価され、成功コールバックが発生します。私がしなければならないすべては私の中にありcomponentDidMount $.getScript(url)ます。私のrenderメソッドにはすでにcalendly divがあります。そして、それはスムーズに動作します。

2
これを行うためにjQueryを追加することは悪い考えです。また、ケースは非常に固有です。実際には、APIが再検出呼び出しを持っていると確信しているので、Calendlyスクリプトを一度追加しても問題はありません。スクリプトを何度も削除して追加することは正しくありません。
sidonaldson

@sidonaldson jQueryは、異なるフレームワーク(およびライブラリ)のアーキテクチャコンパウンドを単に反応させるだけでなくプロジェクトを維持しなければならない場合、悪い習慣ではありません。そうでなければ、コンポーネントに到達するためにネイティブjsを使用する必要があります
AlexNikonov
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.