Incluindo Tag de Script no React / JSX

266

Eu tenho um problema relativamente simples de tentar adicionar scripts em linha a um componente React. O que tenho até agora:

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

Eu também tentei:

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

Nenhuma das abordagens parece executar o script desejado. Acho que é uma coisa simples que estou perdendo. Alguém pode ajudar?

PS: Ignore o foobar, tenho uma identificação real realmente em uso que não me apetecia compartilhar.

ArrayKnight
fonte
5
Existe motivação específica para carregar isso via React em vez de incluí-lo no HTML da página base? Mesmo se isso funcionasse, isso significaria que você reinseria um script toda vez que o componente fosse montado.
loganfsmyth
É esse o caso? Eu assumi que o DOM diffing não faria esse caso, mas admito que isso dependeria da implementação de DocumentTitle.
loganfsmyth
8
Correto @loganfsmyth, o React não recarregará o script na nova renderização se o próximo estado também tiver o script.
Max

Respostas:

405

Edit: As coisas mudam rapidamente e isso está desatualizado - ver atualização


Deseja buscar e executar o script repetidamente, toda vez que esse componente é renderizado ou apenas uma vez quando esse componente é montado no DOM?

Talvez tente algo como isto:

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

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

    document.body.appendChild(script);
}

No entanto, isso só é realmente útil se o script que você deseja carregar não estiver disponível como módulo / pacote. Primeiro, eu sempre:

  • Procure o pacote no npm
  • Baixe e instale o pacote no meu projeto ( npm install typekit)
  • importo pacote onde eu preciso ( import Typekit from 'typekit';)

É provável como você instalou os pacotes reacte a react-document-titlepartir do seu exemplo, e há um pacote Typekit disponível no npm .


Atualizar:

Agora que temos ganchos, uma abordagem melhor pode ser a seguinte 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);
  }
}, []);

O que o torna um ótimo candidato para um gancho personalizado (por exemplo 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;

Que pode ser usado assim:

import useScript from 'hooks/useScript';

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

  // rest of your component
}
Alex McMillan
fonte
Obrigado. Eu estava pensando sobre isso errado. Eu fui adiante com esta implementação. Apenas adicionando o try{Typekit.load({ async: true });}catch(e){}após oappendChild
ArrayKnight
2
Decidi que a implementação "avançada" do TypeKit era mais adequada para essa abordagem.
ArrayKnight
10
Isso funciona - para carregar o script, mas como posso obter acesso ao código no script. Por exemplo, eu gostaria de chamar uma função que vive dentro do script, mas não posso invocá-la dentro do componente em que o script está carregado.
zero_cool
2
Quando o script é anexado à página, ele é executado normalmente. Por exemplo, se você usou esse método para baixar o jQuery de uma CDN, depois que a componentDidMountfunção tiver baixado e anexado o script à página, você terá os objetos jQuerye $disponíveis globalmente (por exemplo: ativado window).
Alex McMillan
1
Eu tive um problema semelhante ao usar um script de autenticação e constatei que seria melhor incluí-lo no arquivo html da raiz, uma camada acima do App.js. react. Caso alguém ache isso útil. Como @loganfsmith mencionado ...
devssh
57

Além das respostas acima, você pode fazer isso:

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

A div está vinculada thise o script é injetado nela.

A demonstração pode ser encontrada em codesandbox.io

sidonaldson
fonte
5
this.instance não funcionou para mim, mas document.body.appendChild funcionou com a resposta de Alex McMillan.
O que seria legal
6
Você provavelmente não ligou this.instanceà ref dentro do seu método de renderização. Eu adicionei um link de demonstração para mostrá-lo a trabalhar
sidonaldson
1
@ShubhamKushwah Você deve estar fazendo renderização no servidor?
ArrayKnight
@ArrayKnight sim, descobri mais tarde que no servidor esses objetos não existem: document, window. Então, eu prefiro usar o npm globalpacote
Shubham Kushwah
o que é a necessidade de s.async = verdadeiro, eu não consigo encontrar qualquer referência sobre o assunto, para saber a sua finalidade, você pode explicar isso
sasha Romanov
50

Minha maneira favorita é usar o React Helmet - é um componente que permite a fácil manipulação da cabeça do documento de uma maneira que você provavelmente já está acostumado.

por exemplo

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
fonte
5
Esta é de longe a melhor solução.
22418 paqash
4
Infelizmente, ele não está funcionando ... Veja codesandbox.io/s/l9qmrwxqzq
Darkowic
2
@ Darkowic, consegui que seu código funcionasse adicionando async="true"à <script>tag que adicionou jQuery ao código.
Soma Mbadiwe 01/02/19
@SomaMbadiwe por que funciona async=truee falha sem ele?
Webwoman
3
Tentei isso, não funciona para mim. Eu não recomendaria o uso do react-helmet pelo único motivo de que ele injeta propriedades extras no script que não podem ser removidas. Este realmente quebrar certos scripts e os mantenedores não ter corrigido isso em anos, e se recusam a github.com/nfl/react-helmet/issues/79
Philberg
16

Se você precisar ter um <script>bloqueio no SSR (renderização no servidor), uma abordagem com componentDidMountnão funcionará.

Você pode usar a react-safebiblioteca. O código em React será:

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
fonte
12

A resposta que Alex Mcmillan forneceu me ajudou mais, mas não funcionou muito para uma tag de script mais complexa.

Ajustei levemente a resposta dele para encontrar uma solução para uma etiqueta longa com várias funções que já estavam configurando "src".

(Para o meu caso de uso, o script precisava viver na cabeça, o que também é refletido aqui):

  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
fonte
7
Não entendo por que você usaria o React se você está apenas despejando JS embutido na página ...?
Alex McMillan
2
você precisa adicionar document.head.removeChild(script);em seu código, ou você vai criar um número infinito de tag de script em seu html, desde que o usuário visita essa página de rota
sasha Romanov
7

Criei um componente React para este caso específico: https://github.com/coreyleelarson/react-typekit

Só precisa passar seu ID do Kit Typekit como suporte e você estará pronto.

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
fonte
3

Existe uma solução muito boa 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));
}

Isso funciona para HTML arbitrário e também retém informações de contexto, como document.currentScript.

Maximilian Hils
fonte
Você poderia colaborar, como se espera que funcione, com amostra de uso? Para mim, não está funcionando com a passagem de script e corpo, por exemplo ..
Alex Efimov
3

Você pode usar npm postscribepara carregar o script no componente react

postscribe('#mydiv', '<script src="https://use.typekit.net/foobar.js"></script>')
Ashh
fonte
1
Resolve o meu problema
RPichioli
1

Você também pode usar capacete de reação

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

O capacete usa tags HTML simples e gera tags HTML simples. É simples, e Reage amigável para iniciantes.

Muhammad Usama Rabani
fonte
0

para vários scripts, use este

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
fonte
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
fonte
0

Você pode encontrar a melhor resposta no seguinte 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();
};
Prajesh Mishrikotkar
fonte
0

De acordo com a solução de Alex McMillan , tenho a seguinte adaptação.
Meu próprio ambiente: Reagir 16.8+, próxima v9 +

// adiciona um componente personalizado chamado 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.
}

// Use o componente personalizado, apenas importe-o e substitua a <script>tag antiga em minúscula pela tag custom camel case <Script>seria suficiente.
// 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>
manchar
fonte
Há uma ressalva para esse componente: esse componente de script pode garantir apenas a ordem de seus próprios irmãos. Se você usar esse componente várias vezes em vários componentes da mesma página, os blocos de scripts poderão estar com defeito. O motivo é que todos os scripts são inseridos por document.body.appendChild programaticamente, em vez de declarativamente. Bem, capacete mova todas as tags de script na tag head, o que não é o que queremos.
Sully
Ei @sully, meu problema aqui é ter o script adicionado ao DOM de várias maneiras, a melhor solução que eu vi até agora é durante a Desmontagem de Componentes, removendo o elemento filho (ou seja, o <script>) do DOM, e então ele está sendo adicionada novamente quando o componente é montado no DOM (estou usando reagem-router-dom e apenas uma necessidade componente esse script de todos os meus componentes)
Eazy
0

Um pouco atrasado para a festa, mas decidi criar o meu próprio depois de analisar as respostas de @Alex Macmillan e isso foi passando dois parâmetros extras; a posição na qual colocar os scripts como ou e configurar o assíncrono como verdadeiro / falso, aqui está:

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;

A maneira de chamá-lo é exatamente a mesma mostrada na resposta aceita deste post, mas com dois parâmetros extras (novamente):

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

Para importar arquivos JS no projeto react, use este comando

  1. Mova o arquivo JS para o projeto.
  2. importe os js para a página usando o seguinte comando

É simples e fácil.

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

No meu caso, eu gostaria de importar esses arquivos JS no meu projeto de reação.

Bathri Nathan
fonte
-3

A solução depende do cenário. Como no meu caso, eu tive que carregar uma incorporação calendamente dentro de um componente de reação.

Calendamente procura uma div e lê seu data-urlatributo e carrega um iframe dentro da referida div.

Tudo é bom quando você carrega a página: primeiro, div com data-urlé renderizado. Em seguida, calendamente, o script é adicionado ao corpo. O navegador faz o download e avalia e todos nós vamos para casa felizes.

O problema surge quando você sai e volta para a página. Dessa vez, o script ainda está no corpo e o navegador não faz o download e a reavalia.

Consertar:

  1. Em componentWillUnmountlocalizar e remover o elemento de script. Em seguida, monte novamente, repita as etapas acima.
  2. Enter $.getScript. É um auxiliar de jquery bacana que usa um URI de script e um retorno de chamada bem-sucedido. Depois que o script é carregado, ele é avaliado e dispara seu retorno de chamada bem-sucedido. Tudo o que tenho que fazer é no meu componentDidMount $.getScript(url). Meu rendermétodo já possui a div calendamente. E funciona sem problemas.
Rohan Bagchi
fonte
2
Adicionar o jQuery para fazer isso é uma má ideia, além do seu caso ser muito específico para você. Na realidade, não há nada errado em adicionar o script Calendly uma vez, pois tenho certeza de que a API tem uma nova detecção de chamada. Remover e adicionar um script repetidamente não está correto.
Sidneyson
@sidonaldson jQuery não é uma prática ruim se você tem que manter um projeto seu composto arquitetura de quadros diferentes (e libs) não apenas reagir, otherwize Precisamos usar js nativas para alcançar componentes
AlexNikonov