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.


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
}

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


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


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>

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

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;

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.


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

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.


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

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

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

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>

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

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.


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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.