Hinzufügen eines Skript-Tags zu React / JSX


265

Ich habe ein relativ einfaches Problem beim Versuch, einer React-Komponente Inline-Skripte hinzuzufügen. Was ich bisher habe:

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

Ich habe auch versucht:

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

Keiner der beiden Ansätze scheint das gewünschte Skript auszuführen. Ich vermute, es ist eine einfache Sache, die mir fehlt. Kann jemand helfen?

PS: Ignoriere die Foobar, ich habe einen echten Ausweis, den ich nicht teilen wollte.


5
Gibt es eine bestimmte Motivation, dies über React zu laden, anstatt es in das HTML Ihrer Basisseite aufzunehmen? Selbst wenn dies funktionieren würde, würde dies bedeuten, dass Sie jedes Mal, wenn die Komponente bereitgestellt wird, ein Skript erneut einfügen würden.
Loganfsmyth

Ist das der Fall? Ich nahm an, dass DOM-Unterschiede dies nicht der Fall machen würden, aber ich gebe zu, dass dies von der Implementierung von abhängen würde DocumentTitle.
Loganfsmyth

8
Richtig @loganfsmyth, React lädt das Skript beim erneuten Rendern nicht neu, wenn der nächste Status auch das Skript enthält.
Max

Antworten:


405

Bearbeiten: Dinge ändern sich schnell und dies ist veraltet - siehe Update


Möchten Sie das Skript jedes Mal abrufen und ausführen, wenn diese Komponente gerendert wird, oder nur einmal, wenn diese Komponente in das DOM eingebunden wird?

Vielleicht versuchen Sie so etwas:

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

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

    document.body.appendChild(script);
}

Dies ist jedoch nur dann wirklich hilfreich, wenn das zu ladende Skript nicht als Modul / Paket verfügbar ist. Erstens würde ich immer:

  • Suchen Sie nach dem Paket auf npm
  • Laden Sie das Paket herunter und installieren Sie es in meinem Projekt ( npm install typekit)
  • importdas Paket, wo ich es brauche ( import Typekit from 'typekit';)

Auf diese Weise haben Sie wahrscheinlich die Pakete installiert reactund anhandreact-document-title Ihres Beispiels, und auf npm ist ein Typekit-Paket verfügbar .


Aktualisieren:

Jetzt, wo wir Haken haben, könnte ein besserer Ansatz darin bestehen, Folgendes zu verwenden 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);
  }
}, []);

Das macht es ein großer Kandidat für einen benutzerdefinierten Haken (zB: 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;

Welches kann so verwendet werden:

import useScript from 'hooks/useScript';

const MyComponent = props => {
  useScript('https://use.typekit.net/foobar.js');

  // rest of your component
}

Danke dir. Ich habe falsch darüber nachgedacht. Ich habe diese Implementierung fortgesetzt. try{Typekit.load({ async: true });}catch(e){}appendChild
Fügen Sie

2
Ich entschied, dass die "erweiterte" Implementierung von TypeKit für diesen Ansatz besser geeignet ist.
ArrayKnight

10
Dies funktioniert - um das Skript zu laden, aber wie kann ich auf den Code im Skript zugreifen? Ich möchte beispielsweise eine Funktion aufrufen, die sich im Skript befindet, kann sie jedoch nicht in der Komponente aufrufen, in die das Skript geladen wird.
zero_cool

2
Wenn das Skript an die Seite angehängt wird, wird es wie gewohnt ausgeführt. Wenn Sie diese Methode zum Beispiel verwenden jQuery von einem CDN zum Download, dann , nachdem die componentDidMountFunktion heruntergeladen hatte und das Skript auf der Seite angehängt, haben Sie die jQueryund $Objekte global verfügbar (zB: auf window).
Alex McMillan

1
Ich hatte ein ähnliches Problem bei der Verwendung eines Authentifizierungsskripts und stellte fest, dass es möglicherweise besser ist, es in die HTML-Datei des Stammverzeichnisses eine Ebene über Ihrer reaktionsfähigen App.js aufzunehmen. Für den Fall, dass jemand dies nützlich findet. Wie @loganfsmith erwähnte ...
devssh

57

Nach den obigen Antworten können Sie Folgendes tun:

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

Das div ist gebunden thisund das Skript wird darin injiziert.

Demo finden Sie auf Codesandbox.io


5
Diese Instanz hat bei mir nicht funktioniert, aber document.body.appendChild nach Alex McMillans Antwort.
Was wäre cool

6
Sie haben sich wahrscheinlich nicht this.instancean die Referenz in Ihrer Rendermethode gebunden. Ich habe einen Demo-Link hinzugefügt, um zu zeigen, dass es funktioniert
Sidonaldson

1
@ShubhamKushwah Sie müssen serverseitiges Rendern durchführen?
ArrayKnight

@ArrayKnight ja Ich habe später herausgefunden, dass diese Objekte auf dem Server nicht existieren: document, window. Also bevorzuge ich das npm- globalPaket
Shubham Kushwah

Was ist die Notwendigkeit für s.async = wahr, ich kann keinen Hinweis darauf finden, um seinen Zweck zu kennen, können Sie es erklären
Sasha Romanov

50

Am liebsten verwende ich React Helmet - eine Komponente, mit der Sie den Dokumentenkopf auf eine Weise bearbeiten können, wie Sie es wahrscheinlich schon gewohnt sind.

z.B

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
Dies ist bei weitem die beste Lösung.
Paqash

4
Leider funktioniert es nicht ... Siehe Codesandbox.io/s/l9qmrwxqzq
Darkowic

2
@Darkowic, ich habe Ihren Code zum Laufen gebracht, indem ich async="true"dem <script>Tag hinzugefügt habe, das dem Code jQuery hinzugefügt hat.
Soma Mbadiwe

@SomaMbadiwe warum funktioniert es mit async=trueund scheitert ohne es?
Webwoman

3
Versuchte das, funktioniert bei mir nicht. Ich würde die Verwendung von React-Helm nicht empfehlen, nur weil dadurch zusätzliche Eigenschaften in das Skript eingefügt werden, die nicht entfernt werden können. Dies bricht tatsächlich bestimmte Skripte, und die Betreuer haben es seit Jahren nicht mehr
repariert

16

Wenn Sie einen <script>Block in SSR (serverseitiges Rendern) benötigen, componentDidMountfunktioniert ein Ansatz mit nicht.

Sie können react-safestattdessen die Bibliothek verwenden. Der Code in React lautet:

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

Die Antwort, die Alex Mcmillan gab, half mir am meisten, funktionierte aber für ein komplexeres Skript-Tag nicht ganz.

Ich habe seine Antwort leicht optimiert, um eine Lösung für ein langes Tag mit verschiedenen Funktionen zu finden, die zusätzlich bereits "src" gesetzt hat.

(Für meinen Anwendungsfall musste das Skript im Kopf leben, was sich auch hier widerspiegelt):

  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
Ich verstehe nicht, warum Sie React überhaupt verwenden würden, wenn Sie nur Inline-JS auf die Seite werfen ...?
Alex McMillan

2
Sie müssen document.head.removeChild(script);Ihren Code hinzufügen , oder Sie erstellen eine unendliche Anzahl von Skript-Tags in Ihrem HTML, solange der Benutzer diese
Seitenroute

7

Ich habe eine React-Komponente für diesen speziellen Fall erstellt: https://github.com/coreyleelarson/react-typekit

Sie müssen nur Ihre Typekit Kit ID als Requisite eingeben und können loslegen.

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

Es gibt eine sehr schöne Problemumgehung mit 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));
}

Dies funktioniert für beliebiges HTML und behält auch Kontextinformationen wie z document.currentScript.


Könnten Sie mit dem Verwendungsbeispiel zusammenarbeiten, wie es voraussichtlich funktionieren wird? Für mich funktioniert es zum Beispiel nicht mit der Übergabe von Skript und
Alex Efimov

3

Sie können das npm postscribeSkript in die Reaktionskomponente laden

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

1
Löst mein Problem
RPichioli

1

Sie können auch einen Reaktionshelm verwenden

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

Der Helm verwendet einfache HTML-Tags und gibt einfache HTML-Tags aus. Es ist ganz einfach und Anfängerfreundlich reagieren.


0

Verwenden Sie dies für mehrere Skripte

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

Die beste Antwort finden Sie unter folgendem Link:

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

Nach der Lösung von Alex McMillan habe ich die folgende Anpassung.
Meine eigene Umgebung: Reagiere 16.8+, nächstes v9 +

// füge eine benutzerdefinierte Komponente mit dem Namen Script
// hooks / Script.js hinzu

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.
}

// Verwenden Sie das benutzerdefinierte Compoennt, importieren Sie es einfach und ersetzen Sie das alte Kleinbuchstaben- <script>Tag durch das benutzerdefinierte Camel-Case- <Script>Tag.
// 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>

Für diese Komponente gibt es eine Einschränkung: Diese Skriptkomponente kann nur die Reihenfolge ihrer eigenen Geschwister garantieren. Wenn Sie diese Komponente mehrmals in mehreren Komponenten derselben Seite verwenden, sind die Skriptblöcke möglicherweise nicht in Ordnung. Der Grund dafür ist, dass alle Skripte von document.body.appendChild programmgesteuert und nicht deklarativ eingefügt werden. Nun, Helm verschiebt alle Skript-Tags im Head-Tag, was wir nicht wollen.
Sully

Hey @sully, mein Problem hier ist, dass das Skript einzeln zum DOM hinzugefügt wird. Die beste Lösung, die ich bisher gesehen habe, ist das Aufheben der Bereitstellung von Komponenten, das Entfernen des untergeordneten Elements (dh des <Skripts>) aus dem DOM und dann ist es Wird erneut hinzugefügt, wenn die Komponente im DOM bereitgestellt wird (ich verwende React-Router-Dom und nur eine Komponente benötigt dieses Skript aller meiner Komponenten)
Eazy

0

Ein bisschen spät zur Party, aber ich entschied mich, meine eigene zu erstellen, nachdem ich mir die Antworten von @Alex Macmillan angesehen hatte, und zwar durch Übergabe von zwei zusätzlichen Parametern. Die Position, an der die Skripte wie oder platziert werden sollen, und das Einrichten der Asynchronisierung auf true / false ist hier:

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;

Die Art und Weise, wie Sie es aufrufen, ist genau die gleiche wie in der akzeptierten Antwort dieses Beitrags, jedoch mit zwei zusätzlichen (erneut) Parametern:

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

0

Verwenden Sie diesen Befehl, um JS-Dateien in ein Reaktionsprojekt zu importieren

  1. Verschieben Sie die JS-Datei in das Projekt.
  2. Importieren Sie die js mit dem folgenden Befehl in die Seite

Es ist einfach und leicht.

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

In meinem Fall möchte ich diese JS-Dateien in mein Reaktionsprojekt importieren.


-3

Die Lösung hängt vom Szenario ab. Wie in meinem Fall musste ich eine kalendarische Einbettung in eine Reaktionskomponente laden.

Sucht Calendly nach einem Div und liest aus seinem data-urlAttribut und lädt einen Iframe in das Div.

Es ist alles gut, wenn Sie die Seite zum ersten Mal laden: Zuerst wird div with data-urlgerendert. Dann wird dem Körper ein kalendarisches Skript hinzugefügt. Der Browser lädt es herunter und wertet es aus, und wir gehen alle glücklich nach Hause.

Das Problem tritt auf, wenn Sie weg navigieren und dann wieder auf die Seite zurückkehren. Dieses Mal befindet sich das Skript noch im Hauptteil und der Browser lädt es nicht erneut herunter und bewertet es nicht neu.

Fix:

  1. Beim componentWillUnmountSuchen und Entfernen des Skriptelements. Wiederholen Sie bei der erneuten Montage die obigen Schritte.
  2. Geben Sie ein $.getScript. Es ist ein geschickter JQuery-Helfer, der eine Skript-URI und einen erfolgreichen Rückruf verwendet. Sobald das Skript geladen wurde, wertet es es aus und löst Ihren Erfolgsrückruf aus. Alles was ich tun muss ist in meinem componentDidMount $.getScript(url). Meine renderMethode hat bereits die kalendarische Div. Und es funktioniert reibungslos.

2
Das Hinzufügen von jQuery, um dies zu tun, ist eine schlechte Idee, und Ihr Fall ist sehr spezifisch für Sie. In Wirklichkeit ist es nichts Falsches, das Calendly-Skript einmal hinzuzufügen, da ich sicher bin, dass die API einen erneuten Erkennungsaufruf hat. Das wiederholte Entfernen und Hinzufügen eines Skripts ist nicht korrekt.
Sidonaldson

@sidonaldson jQuery ist keine schlechte Praxis, wenn Sie ein Projekt pflegen müssen, dessen Architektur aus verschiedenen Frameworks (und Bibliotheken) nicht nur reagiert, sondern auch native js verwenden müssen, um Komponenten zu erreichen
AlexNikonov
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.