Aggiunta tag script a React / JSX


266

Ho un problema relativamente semplice di provare ad aggiungere script inline a un componente React. Quello che ho finora:

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

Ho anche provato:

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

Nessuno dei due approcci sembra eseguire lo script desiderato. Immagino sia una cosa semplice che mi manca. Qualcuno può dare una mano?

PS: Ignora il foobar, ho un vero id in uso che non mi andava di condividere.


5
C'è una motivazione specifica per caricare questo tramite React invece di includerlo nell'HTML della tua pagina di base? Anche se funzionasse, ciò significherebbe che dovrai reinserire uno script ogni volta che il componente viene montato.
loganfsmyth,

È così? Supponevo che la difformità del DOM avrebbe fatto diversamente, ma ammetto che dipenderà dall'implementazione di DocumentTitle.
loganfsmyth,

8
Corretto @loganfsmyth, React non ricaricherà lo script al momento del nuovo rendering se lo stato successivo ha anche lo script.
Max

Risposte:


405

Modifica: le cose cambiano rapidamente e questo è obsoleto - vedi aggiornamento


Vuoi recuperare ed eseguire lo script ancora e ancora, ogni volta che viene eseguito il rendering di questo componente o solo una volta quando questo componente viene montato nel DOM?

Forse prova qualcosa del genere:

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

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

    document.body.appendChild(script);
}

Tuttavia, questo è davvero utile solo se lo script che vuoi caricare non è disponibile come modulo / pacchetto. Innanzitutto, vorrei sempre:

  • Cerca il pacchetto su npm
  • Scarica e installa il pacchetto nel mio progetto ( npm install typekit)
  • importil pacchetto dove ne ho bisogno ( import Typekit from 'typekit';)

Questo è probabilmente il modo in cui hai installato i pacchetti reacte react-document-titledal tuo esempio, e c'è un pacchetto Typekit disponibile su npm .


Aggiornare:

Ora che abbiamo hook, un approccio migliore potrebbe essere quello di utilizzare in questo useEffectmodo:

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

Il che lo rende un ottimo candidato per un hook personalizzato (ad esempio:) 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;

Quale può essere usato così:

import useScript from 'hooks/useScript';

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

  // rest of your component
}

Grazie. Stavo pensando a questo male. Sono andato avanti con questa implementazione. Ho appena aggiunto il try{Typekit.load({ async: true });}catch(e){}dopoappendChild
ArrayKnight il

2
Ho deciso che l'implementazione "avanzata" di TypeKit era più adatta a questo approccio.
ArrayKnight

10
Questo funziona - per caricare lo script, ma come posso ottenere l'accesso al codice nello script. Ad esempio, vorrei chiamare una funzione che vive all'interno dello script, ma non sono in grado di invocarla all'interno del componente in cui è caricato lo script.
zero_cool

2
Quando lo script viene aggiunto alla pagina, verrà eseguito normalmente. Ad esempio, se hai utilizzato questo metodo per scaricare jQuery da una rete CDN, dopo che la componentDidMountfunzione ha scaricato e aggiunto lo script alla pagina, avrai gli oggetti jQuerye $disponibili a livello globale (ad esempio: on window).
Alex McMillan,

1
Ho avuto un problema simile usando uno script di autenticazione e ho scoperto che potrebbe essere meglio includerlo nel file html della radice un livello sopra App.js. di reazione Nel caso qualcuno lo trovi utile. Come menzionato da @loganfsmith ...
devssh

57

Oltre alle risposte sopra puoi farlo:

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

Il div è associato thise lo script viene iniettato in esso.

La demo è disponibile su codesandbox.io


5
this.instance non ha funzionato per me, ma document.body.appendChild ha fatto dalla risposta di Alex McMillan.
Cosa sarebbe bello il

6
Probabilmente non ti sei associato this.instanceal riferimento all'interno del tuo metodo di rendering. Ho aggiunto un link demo per dimostrarlo funzionante
sidonaldson,

1
@ShubhamKushwah Devi eseguire il rendering lato server?
ArrayKnight

@ArrayKnight sì, ho scoperto in seguito che sul server non esistono questi oggetti: document, window. Quindi preferisco usare il globalpacchetto
npm

qual è la necessità di s.async = true, non riesco a trovare alcun riferimento a riguardo, per conoscerne lo scopo, puoi spiegarlo
sasha romanov

50

Il mio modo preferito è usare React Helmet - è un componente che consente una facile manipolazione della testina del documento in un modo a cui probabilmente sei già abituato.

per esempio

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
Questa è di gran lunga la soluzione migliore.
Paqash,

4
Sfortunatamente, non funziona ... Vedi codesandbox.io/s/l9qmrwxqzq
Darkowic,

2
@Darkowic, ho fatto funzionare il tuo codice aggiungendolo async="true"al <script>tag che ha aggiunto jQuery al codice.
Soma Mbadiwe,

@SomaMbadiwe perché funziona async=truee fallisce senza di essa?
Webwoman il

3
Ho provato questo, non funziona per me. Non consiglierei di utilizzareasco-casco per la sola ragione che inietta proprietà aggiuntive nello script che non possono essere rimosse. Questo in realtà rompe alcuni script e i manutentori non lo
riparano da

16

Se è necessario disporre di un <script>blocco in SSR (rendering lato server), un approccio con componentDidMountnon funzionerà.

Puoi react-safeinvece usare la libreria. Il codice in React sarà:

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

La risposta fornita da Alex Mcmillan mi ha aiutato di più, ma non ha funzionato del tutto per un tag di script più complesso.

Ho leggermente modificato la sua risposta per trovare una soluzione per un tag lungo con varie funzioni che stava già impostando "src".

(Nel mio caso d'uso la sceneggiatura doveva vivere in testa, che si riflette anche qui):

  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
Non capisco perché dovresti usare React se scarichi JS in linea sulla pagina ...?
Alex McMillan,

2
devi aggiungere il document.head.removeChild(script);tuo codice, altrimenti creerai un numero infinito di tag di script nel tuo html purché l'utente visiti questo percorso di pagina
sasha romanov

7

Ho creato un componente React per questo caso specifico: https://github.com/coreyleelarson/react-typekit

Ho solo bisogno di passare il tuo ID Kit di Typekit come supporto e sei a posto.

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

C'è una soluzione molto bella usando 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));
}

Funziona con HTML arbitrario e conserva anche informazioni di contesto come document.currentScript.


Potresti collaborare come dovrebbe funzionare per favore, con un campione di utilizzo? Per me non funziona con il passaggio di sceneggiatura e body per esempio ..
Alex Efimov

3

È possibile utilizzare npm postscribeper caricare script nel componente reagire

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

1
Risolve il mio problema
RPichioli,

1

Puoi anche usare il casco reattivo

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

Helmet accetta semplici tag HTML e genera semplici tag HTML. È semplicissimo e React amichevole per i principianti.


0

per più script, utilizzare questo

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

Puoi trovare la migliore risposta al seguente 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

Secondo la soluzione di Alex McMillan , ho il seguente adattamento.
Il mio ambiente: React 16.8+, next v9 +

// aggiungi un componente personalizzato chiamato 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.
}

// Usa il compoennt personalizzato, basta importarlo e sostituire il vecchio <script>tag minuscolo con il tag personalizzato camel case <Script>sarebbe sufficiente.
// 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>

C'è un avvertimento per questo componente: questo componente Script può solo garantire l'ordine dei suoi fratelli. Se si utilizza questo componente più volte in più componenti della stessa pagina, i blocchi di script potrebbero non funzionare. Il motivo è che tutti gli script sono inseriti da document.body.appendChild a livello di codice, anziché in modo dichiarativo. Bene, il casco sposta tutti i tag di script nel tag head, cosa che non vogliamo.
Sully

Ehi @sully, il mio problema qui è avere lo script aggiunto al DOM severamente, la migliore soluzione che ho visto finora è durante lo smontaggio dei componenti, rimuovendo l'elemento figlio (cioè <script>) dal DOM, e quindi viene aggiunto di nuovo quando il componente è montato sul DOM (sto usando reag-router-dom e solo un componente ha bisogno di questo script di tutti i miei componenti)
Eazy

0

Un po 'in ritardo alla festa, ma ho deciso di crearne uno mio dopo aver esaminato le risposte di @Alex Macmillan e questo è stato passando due parametri extra; la posizione in cui posizionare gli script come o e impostare l'asincrono su vero / falso, eccolo qui:

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;

Il modo di chiamarlo è esattamente lo stesso mostrato nella risposta accettata di questo post ma con due parametri (di nuovo) extra:

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

0

Per importare file JS nel progetto di reazione utilizzare questo comando

  1. Sposta il file JS nel progetto.
  2. importare js nella pagina usando il seguente comando

È semplice e facile

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

Nel mio caso, vorrei importare questi file JS nel mio progetto di reazione.


-3

La soluzione dipende dallo scenario. Come nel mio caso, ho dovuto caricare un incorporamento del calendario all'interno di un componente di reazione.

Calendly cerca un div e legge dal suo data-urlattributo e carica un iframe all'interno di detto div.

Va tutto bene quando carichi la pagina per la prima volta: prima, div con data-url viene visualizzato. Quindi lo script del calendario viene aggiunto al corpo. Il browser lo scarica e lo valuta e torniamo tutti a casa felici.

Il problema si presenta quando navighi via e poi ritorni nella pagina. Questa volta lo script è ancora nel corpo e il browser non lo scarica e rivaluta.

fix:

  1. Alla componentWillUnmountricerca e rimozione dell'elemento script. Quindi su re mount, ripetere i passaggi precedenti.
  2. Enter $.getScript. È un elegante jquery helper che prende un URI di script e un callback di successo. Una volta caricato lo script, lo valuta e genera il callback di successo. Tutto quello che devo fare è nel mio componentDidMount $.getScript(url). Il mio rendermetodo ha già il div mensile. E funziona senza problemi.

2
Aggiungere jQuery per farlo è una cattiva idea, inoltre il tuo caso è molto specifico per te. In realtà non c'è nulla di sbagliato nell'aggiungere lo script di Calendly una volta poiché sono sicuro che l'API abbia una chiamata di nuova rilevazione. La rimozione e l'aggiunta ripetuta di uno script non è corretta.
Sidonaldson,

@sidonaldson jQuery non è una cattiva pratica se devi mantenere un progetto la sua architettura composta da diversi framework (e libs) non solo reagire, altrimenti abbiamo bisogno di usare js nativi per raggiungere i componenti
AlexNikonov
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.