Dodanie znacznika skryptu do React / JSX

264

Mam stosunkowo prosty problem z dodaniem wbudowanego skryptu do komponentu React. Co mam do tej pory:

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

Próbowałem też:

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

Żadne z tych podejść nie wydaje się wykonywać żądanego skryptu. Zgaduję, że tęsknię za prostą rzeczą. Czy ktoś może pomóc?

PS: Zignoruj ​​foobar, mam naprawdę używany identyfikator, którego nie chciałem się dzielić.

ArrayKnight
źródło
5
Czy istnieje konkretna motywacja do załadowania tego za pomocą React zamiast umieszczania go w pliku HTML strony podstawowej? Nawet jeśli to zadziałałoby, oznaczałoby to, że wstawiałbyś skrypt za każdym razem, gdy montowany był komponent.
loganfsmyth
Czy tak jest w przypadku? Zakładałem, że DOM diffing sprawi, że tak nie będzie, ale przyznaję, że będzie to zależeć od implementacji DocumentTitle.
loganfsmyth
8
Prawidłowo @loganfsmyth, React nie przeładuje skryptu przy ponownym renderowaniu, jeśli następny stan również zawiera skrypt.
Maks

Odpowiedzi:

403

Edycja: Rzeczy zmieniają się szybko, a to jest przestarzałe - patrz aktualizacja


Czy chcesz pobierać i uruchamiać skrypt za każdym razem, gdy ten komponent jest renderowany, czy tylko raz, gdy ten komponent jest montowany w DOM?

Być może spróbuj czegoś takiego:

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

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

    document.body.appendChild(script);
}

Jest to jednak bardzo pomocne tylko wtedy, gdy skrypt, który chcesz załadować, nie jest dostępny jako moduł / pakiet. Po pierwsze, zawsze:

  • Poszukaj pakietu na npm
  • Pobierz i zainstaluj pakiet w moim projekcie ( npm install typekit)
  • importpakiet, w którym go potrzebuję ( import Typekit from 'typekit';)

Jest to prawdopodobnie sposób, w jaki zainstalowałeś pakiety reacti react-document-titlez twojego przykładu, a na przykład jest dostępny pakiet Typekit .


Aktualizacja:

Teraz, gdy mamy haczyki, lepszym rozwiązaniem może być użycie useEffecttakiego:

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);
  }
}, []);

Co czyni go doskonałym kandydatem na niestandardowy hak (np . 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;

Które można wykorzystać tak:

import useScript from 'hooks/useScript';

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

  // rest of your component
}
Alex McMillan
źródło
Dziękuję Ci. Myślałem o tym źle. Poszedłem z tym wdrożeniem. Właśnie try{Typekit.load({ async: true });}catch(e){}appendChild
dodałem
2
Zdecydowałem, że „zaawansowane” wdrożenie TypeKit jest bardziej odpowiednie dla tego podejścia.
ArrayKnight
10
To działa - aby załadować skrypt, ale jak mogę uzyskać dostęp do kodu w skrypcie. Na przykład chciałbym wywołać funkcję, która znajduje się w skrypcie, ale nie jestem w stanie wywołać jej w komponencie, w którym skrypt jest załadowany.
zero_cool
2
Po dodaniu skryptu do strony zostanie on wykonany normalnie. Na przykład, jeśli użyłeś tej metody do pobrania jQuery z CDN, to po componentDidMountpobraniu i dołączeniu skryptu przez funkcję do strony będziesz mieć globalnie dostępne obiekty jQueryi $(tj. Włączone window).
Alex McMillan,
1
Miałem podobny problem przy użyciu skryptu uwierzytelniającego i okazało się, że lepiej byłoby dołączyć go do pliku HTML w katalogu głównym warstwy powyżej Twojej aplikacji App.js. Jeśli ktoś uzna to za przydatne. Jak wspomniał @loganfsmith ...
devssh,
57

W odpowiedzi na powyższe odpowiedzi możesz to zrobić:

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 jest związany thisi skrypt jest do niego wstrzykiwany.

Demo można znaleźć na codeandbox.io

Sidonaldson
źródło
5
this.instance nie działało dla mnie, ale document.body.append Dziecko zrobiło to z odpowiedzi Alexa McMillana.
What Will Be Cool
6
Prawdopodobnie nie wiązałeś this.instancesię z ref w swojej metodzie renderowania. Dodałem link demonstracyjny, aby pokazać, że działa
sidonaldson,
1
@ShubhamKushwah Musisz renderować po stronie serwera?
ArrayKnight
@ArrayKnight tak Dowiedziałem się później, że na serwerze te obiekty nie istnieją: document, window. Wolę więc używać globalpakietu npm
Shubham Kushwah
co jest potrzeba s.async = prawda, nie mogę znaleźć żadnego odniesienia o tym, aby wiedzieć, że to cel, można to wyjaśnić
Sasha Romanov
50

Moim ulubionym sposobem jest użycie React Helmet - jest to komponent, który pozwala na łatwą manipulację głowicą dokumentu w sposób, w jaki prawdopodobnie już jesteś przyzwyczajony.

na przykład

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

AC Patrice
źródło
5
To zdecydowanie najlepsze rozwiązanie.
paqash
4
Niestety to nie działa ... Zobacz kody ibox.io/s/l9qmrwxqzq
Darkowic
2
@Darkowic, mam twój kod do pracy, dodając async="true"do <script>znacznika, który dodał jQuery do kodu.
Soma Mbadiwe
@SomaMbadiwe, dlaczego działa async=truebez niego i bez niego?
Webwoman
3
Próbowałem tego, nie działa dla mnie. Nie zalecałbym używania hełmu reakcyjnego tylko dlatego, że wprowadza on do skryptu dodatkowe właściwości, których nie można usunąć. To faktycznie łamie niektóre skrypty, a opiekunowie nie naprawili go od lat i odmawiają dostępu
Philberg
16

Jeśli potrzebujesz mieć <script>blok w SSR (renderowanie po stronie serwera), podejście z componentDidMountnie będzie działać.

Zamiast tego możesz użyć react-safebiblioteki. Kod w React będzie:

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>
scabbiaza
źródło
12

Odpowiedź udzielona przez Alexa Mcmillana najbardziej pomogła mi, ale nie całkiem działała w przypadku bardziej złożonego skryptu.

Lekko poprawiłem jego odpowiedź, aby znaleźć rozwiązanie dla długiego tagu z różnymi funkcjami, które dodatkowo ustawiały już „src”.

(W moim przypadku użycia skrypt musiał żyć w głowie, co również zostało odzwierciedlone tutaj):

  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);
  }
jake2620
źródło
7
Nie rozumiem, dlaczego w ogóle używałbyś React, jeśli po prostu zrzucasz wbudowany JS na stronę ...?
Alex McMillan
2
trzeba dodać document.head.removeChild(script);w kodzie, czy będzie tworzyć nieskończoną liczbę tagu skrypt na swojej html Dopóki wizyt użytkowników tej strony trasy
Sasha Romanov
7

Utworzyłem komponent React dla tego konkretnego przypadku: https://github.com/coreyleelarson/react-typekit

Wystarczy podać identyfikator zestawu Typekit jako rekwizyt i możesz już iść.

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;
Corey Larson
źródło
3

Istnieje bardzo przyjemne obejście 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));
}

Działa to w przypadku dowolnego kodu HTML, a także zachowuje informacje kontekstowe, takie jak document.currentScript.

Maksymilian Hils
źródło
Czy mógłbyś współpracować, jak powinien działać, z próbką użycia? Dla mnie nie działa to na przykład z przekazywaniem skryptu i treści.
Alex Efimov
3

Możesz użyć npm postscribedo załadowania skryptu w komponencie reagującym

postscribe('#mydiv', '<script src="https://use.typekit.net/foobar.js"></script>')
Ashh
źródło
1
Rozwiązuje mój problem
RPichioli,
1

Możesz także użyć kasku reagującego

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

Hełm pobiera zwykłe tagi HTML i generuje zwykłe tagi HTML. To bardzo proste i React przyjazne dla początkujących.

Muhammad Usama Rabani
źródło
0

w przypadku wielu skryptów użyj tego

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')
ben
źródło
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);
}
Marlcg58
źródło
0

Najlepszą odpowiedź znajdziesz pod następującym linkiem:

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();
};
Prajesh Mishrikotkar
źródło
0

Według rozwiązania Alexa McMillana mam następującą adaptację.
Moje własne środowisko: Reakcja 16.8+, następne v9 +

// dodaj niestandardowy komponent o nazwie 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.
}

// Użyj niestandardowego komponentu, wystarczy go zaimportować i zastąpić stary <script>znacznik małymi literami niestandardowym <Script>znacznikiem wielbłąda .
// 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>
kalać
źródło
Jest jedno zastrzeżenie dla tego komponentu: ten komponent Script może zagwarantować jedynie porządek własnego rodzeństwa. Jeśli użyjesz tego komponentu wiele razy w wielu komponentach tej samej strony, bloki skryptów mogą być niesprawne. Powodem jest to, że wszystkie skrypty są wstawiane przez document.body.appendChild programowo, a nie deklaratywnie. Hełm przenosi wszystkie znaczniki skryptu do znacznika głowy, czego nie chcemy.
sully
Hej @sully, moim problemem jest oddzielne dodawanie skryptu do DOM, najlepszym rozwiązaniem, jakie do tej pory widziałem, jest odmontowanie komponentu, usunięcie elementu potomnego (tj. <script>) z DOM, a następnie dodane ponownie, gdy komponent jest zamontowany w DOM (używam reag-router-dom i tylko jeden komponent potrzebuje tego skryptu ze wszystkich moich komponentów)
Eazy
0

Trochę późno na imprezę, ale postanowiłem stworzyć swój własny, po zapoznaniu się z odpowiedziami na @Alex Macmillan i to przez przekazanie dwóch dodatkowych parametrów; pozycja, w której należy umieścić skrypty takie jak lub i ustawić asynchronię na true / false, tutaj jest:

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;

Sposób wywołania jest dokładnie taki sam, jak pokazano w zaakceptowanej odpowiedzi tego postu, ale z dwoma dodatkowymi (ponownie) parametrami:

// First string is your URL
// Second string can be head or body
// Third parameter is true or false.
useScript("string", "string", bool);
Kirasiris
źródło
0

Aby zaimportować pliki JS w projekcie Reaguj, użyj tego polecenia

  1. Przenieś plik JS do projektu.
  2. zaimportuj plik js na stronę za pomocą następującego polecenia

To proste i łatwe.

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

W moim przypadku chciałbym zaimportować te pliki JS do mojego projektu reagowania.

Bathri Nathan
źródło
-3

Rozwiązanie zależy od scenariusza. Tak jak w moim przypadku musiałem załadować kalendarz osadzony w elemencie reagującym.

Calendly szuka div i czyta z jego data-urlatrybutu i ładuje iframe wewnątrz tego div.

Wszystko jest dobrze, gdy ładujesz stronę po raz pierwszy: po pierwsze data-urlrenderowany jest div z . Następnie skrypt dodaje kalendarz do treści. Przeglądarka pobiera ją i ocenia, a my wszyscy wracamy do domu zadowoleni.

Problem pojawia się, gdy opuścisz stronę, a następnie wrócisz na stronę. Tym razem skrypt jest nadal w treści, a przeglądarka go nie pobiera i nie ocenia ponownie.

Naprawić:

  1. Przy componentWillUnmountznajdowaniu i usuwaniu elementu skryptu. Następnie przy ponownym montażu powtórz powyższe kroki.
  2. Enter $.getScript. To sprytny pomocnik jquery, który pobiera identyfikator URI skryptu i oddzwanianie. Po załadowaniu skryptu ocenia go i uruchamia wywołanie zwrotne powodzenia. Wszystko co muszę zrobić, to w moim componentDidMount $.getScript(url). Moja rendermetoda ma już div kalendarza. I działa płynnie.
Rohan Bagchi
źródło
2
Dodanie jQuery do tego jest złym pomysłem, a twoja sprawa jest bardzo specyficzna dla ciebie. W rzeczywistości nie ma nic złego w dodaniu skryptu Calendly raz, ponieważ jestem pewien, że API ma wywołanie ponownego wykrycia. Ciągłe usuwanie i dodawanie skryptu jest nieprawidłowe.
sidonaldson,
@sidonaldson jQuery nie jest złą praktyką, jeśli musisz utrzymywać projekt, jego architektura złożona z różnych frameworków (i bibliotek) nie tylko reaguje, w przeciwnym razie potrzebujemy użyć natywnego js, ​​aby dotrzeć do komponentów
AlexNikonov