React / JSX에 스크립트 태그 추가


264

인라인 스크립팅을 React 구성 요소에 추가하려고하는 비교적 간단한 문제가 있습니다. 내가 지금까지 무엇을 :

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

나는 또한 시도했다 :

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

두 방법 모두 원하는 스크립트를 실행하지 않는 것 같습니다. 나는 그것이 누락 된 간단한 것 같아요. 아무도 도와 줄 수 있습니까?

추신 : foobar를 무시하고 실제로 공유하고 싶지 않은 실제 ID가 있습니다.


5
기본 페이지 HTML에 포함시키는 대신 React를 통해 이것을로드하려는 특별한 동기가 있습니까? 이것이 작동하더라도 구성 요소가 마운트 될 때마다 스크립트를 다시 삽입한다는 의미입니다.
loganfsmyth

그 경우입니까? DOM diffing이 사실이 아니라고 가정했지만의 구현에 달려 있다고 인정합니다 DocumentTitle.
loganfsmyth

8
올바른 @loganfsmyth, 다음 상태에도 스크립트가있는 경우 React는 다시 렌더링 할 때 스크립트를 다시로드하지 않습니다.
Max

답변:


404

편집 : 상황이 빠르게 변하고 구식입니다-업데이트 참조


이 컴포넌트가 렌더링 될 때마다 또는이 컴포넌트가 DOM에 마운트 될 때마다 스크립트를 반복해서 페치하고 실행 하시겠습니까?

아마도 다음과 같이 시도하십시오.

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

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

    document.body.appendChild(script);
}

그러나 이것은로드하려는 스크립트를 모듈 / 패키지로 사용할 수없는 경우에만 유용합니다. 먼저, 나는 항상 :

  • npm 에서 패키지를 찾으십시오.
  • 프로젝트에서 패키지를 다운로드하여 설치하십시오 ( npm install typekit).
  • import내가 필요한 패키지 ( import Typekit from 'typekit';)

이것은 패키지 reactreact-document-title예제 를 설치 한 방법 일 가능성이 있으며 npm에서 사용 가능한 Typekit 패키지가 있습니다 .


최신 정보:

이제 고리가 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);
  }
}, []);

다음은 커스텀 훅 (예 :)에 대한 훌륭한 후보입니다 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;

다음과 같이 사용할 수 있습니다.

import useScript from 'hooks/useScript';

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

  // rest of your component
}

감사합니다. 나는 이것에 대해 생각하고 있었다. 이 구현을 진행했습니다. 그냥 추가 try{Typekit.load({ async: true });}catch(e){}appendChild
ArrayKnight

2
TypeKit의 "고급"구현이이 방법에 더 적합하다고 결정했습니다.
ArrayKnight

10
이것은 작동합니다-스크립트를로드하지만 스크립트의 코드에 액세스하는 방법은 무엇입니까? 예를 들어, 스크립트 안에있는 함수를 호출하고 싶지만 스크립트가로드 된 구성 요소 안에서는 호출 할 수 없습니다.
zero_cool

2
스크립트가 페이지에 추가되면 정상적으로 실행됩니다. 예를 들어,이 방법을 사용하여 CDN에서 jQuery를 다운로드 한 경우 componentDidMount함수가 스크립트를 다운로드하여 페이지에 추가 한 후 jQuery$객체를 전역 적으로 사용할 수 있습니다 (예 : on window).
Alex McMillan

1
인증 스크립트를 사용하는 것과 비슷한 문제가 있었고 반응 App.js 위의 레이어를 루트의 html 파일에 포함하는 것이 더 낫습니다. 누구나 유용하다고 생각되는 경우. @loganfsmith가 언급했듯이 ...
devssh

57

위의 답변 외에도 다음을 수행 할 수 있습니다.

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가 바인딩되고 this스크립트가 삽입됩니다.

데모는 codesandbox.io 에서 찾을 수 있습니다


5
this.instance는 저에게 효과적이지 않지만 document.body.appendChild는 Alex McMillan의 답변에서했습니다.
멋진 것

6
this.instance렌더 메소드 내부의 참조에 바인딩하지 않았을 것입니다 . 데모 링크를 추가하여 작동하는지 확인
sidonaldson

1
@ShubhamKushwah 서버 측 렌더링을 수행해야합니까?
ArrayKnight

@ArrayKnight yes 나중에 서버에 이러한 객체가 존재하지 않는다는 것을 알았습니다. document, window. 따라서 저는 npm global패키지를 선호 합니다
Shubham Kushwah

s.async = true에 대한 필요성은 무엇인지, 그에 대한 참조를 찾을 수 없으며, 목적을 알기 위해 설명 할 수 있습니까?
sasha romanov

50

제가 가장 좋아하는 방법은 React Helmet을 사용하는 것입니다. 이미 사용했던 방식으로 문서 헤드를 쉽게 조작 할 수있는 구성 요소입니다.

예 :

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
이것이 지금까지 가장 좋은 솔루션입니다.
paqash

4
불행히도, 그것은 작동하지 않습니다 ... 참조 codesandbox.io/s/l9qmrwxqzq
Darkowic

2
@Darkowic, jQuery를 코드에 추가 async="true"<script>태그 에 추가하여 코드가 작동하도록했습니다 .
Soma Mbadiwe

@SomaMbadiwe 왜 작동 async=true하지 않고 실패합니까?
Webwoman

3
이것을 시도했지만 나를 위해 작동하지 않습니다. 제거 할 수없는 스크립트에 추가 속성을 주입한다는 유일한 이유로 react-helmet을 사용하지 않는 것이 좋습니다. 이것은 실제로 특정 스크립트를 깨뜨리고 관리자는 수년간 수정하지 않았 으며 github.com/nfl/react-helmet/issues/79
Philberg

16

<script>SSR (서버 측 렌더링)에서 차단 이 필요한 경우 에는 접근 방식 componentDidMount이 작동하지 않습니다.

react-safe대신 라이브러리를 사용할 수 있습니다 . React의 코드는 다음과 같습니다.

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

Alex Mcmillan이 제공 한 답변 은 가장 도움이되었지만 더 복잡한 스크립트 태그에는 효과가 없었습니다.

나는 이미 "src"를 설정 한 다양한 기능을 가진 긴 태그에 대한 해결책을 제시하기 위해 그의 대답을 약간 조정했습니다.

(사용 사례의 경우 스크립트가 여기에 반영되어 머리 속에 살 필요가있었습니다) :

  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
인라인 JS를 페이지에 덤프하는 경우 왜 React를 사용하는지 이해가되지 않습니다 ...?
Alex McMillan

2
document.head.removeChild(script);코드 를 추가 해야합니다. 그렇지 않으면 사용자가이 페이지를 방문하는 한 html에 무한한 수의 스크립트 태그를 만들 것입니다.
sasha romanov

7

이 특정 사례에 대한 React 구성 요소를 만들었습니다 : https://github.com/coreyleelarson/react-typekit

Typekit Kit ID를 소품으로 전달하면됩니다.

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

를 사용하는 매우 좋은 해결 방법이 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));
}

이것은 임의의 HTML에서 작동하며 같은 컨텍스트 정보를 유지합니다 document.currentScript.


사용 샘플을 사용하여 어떻게 작동하는지 예상 할 수 있습니까? 나를 위해 그것은 예를 들어 스크립트와 본문을 전달하는 작업하지 않습니다 ..
Alex Efimov

3

npm postscribe반응 구성 요소에서 스크립트를로드 하는 데 사용할 수 있습니다.

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

1
내 문제를 해결
RPichioli

1

반응 헬멧을 사용할 수도 있습니다

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

헬멧은 일반 HTML 태그를 사용하여 일반 HTML 태그를 출력합니다. 매우 간단하고, React 초보자에게 친숙합니다.


0

여러 스크립트의 경우 이것을 사용하십시오

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

다음 링크에서 최상의 답변을 찾을 수 있습니다.

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

Alex McMillan 의 솔루션 에 따르면 다음과 같은 적응이 있습니다.
내 자신의 환경 : React 16.8+, next v9 +

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

// 맞춤 구성 요소를 사용하고 가져 오기만하면 기존 소문자 <script>태그를 맞춤 낙타 케이스 <Script>태그로 대체하면 됩니다.
// 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>

이 구성 요소에는 한 가지주의 사항이 있습니다.이 스크립트 구성 요소는 자체 형제의 순서 만 보장 할 수 있습니다. 동일한 페이지의 여러 구성 요소에서이 구성 요소를 여러 번 사용하면 스크립트 블록의 순서가 잘못되었을 수 있습니다. 그 이유는 모든 스크립트가 선언적으로 대신 document.body.appendChild에 의해 프로그래밍 방식으로 삽입 되었기 때문입니다. 헬멧은 헤드 태그에서 모든 스크립트 태그를 움직입니다.
sully

@sully, 내 문제는 여기에 스크립트를 DOM에 여러 번 추가 한 것입니다. 지금까지 본 가장 좋은 해결책은 Component Unmounting 중에 DOM에서 자식 요소 (예 : <script>)를 제거하는 것입니다. 컴포넌트가 DOM에 마운트 될 때 다시 추가됨 (react-router-dom을 사용하고 있으며 하나의 컴포넌트 만 모든 컴포넌트의이 스크립트를 필요로 함)
Eazy

0

파티에 조금 늦었지만 @Alex Macmillan 답변을보고 나 자신의 것을 만들기로 결정했으며 두 개의 추가 매개 변수를 전달했습니다. 비동기를 true / false로 설정하는 등의 스크립트를 배치 할 위치는 다음과 같습니다.

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;

그것을 호출하는 방법은이 게시물의 허용 된 답변에 표시된 것과 정확히 동일하지만 두 가지 추가 매개 변수가 있습니다.

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

0

반응 프로젝트에서 JS 파일을 가져 오려면이 명령을 사용하십시오.

  1. JS 파일을 프로젝트로 이동하십시오.
  2. 다음 명령을 사용하여 j를 페이지로 가져옵니다.

간단하고 쉽습니다.

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

제 경우에는 이러한 JS 파일을 반응 프로젝트로 가져오고 싶습니다.


-3

솔루션은 시나리오에 따라 다릅니다. 필자의 경우와 마찬가지로 반응 구성 요소에 calendly embed를로드해야했습니다.

Calendly는 div를 찾아 해당 data-url속성 에서 읽고 해당 div 안에 iframe을로드합니다.

페이지를 처음로드 할 때 좋습니다. 먼저 div with data-url가 렌더링됩니다. 그런 다음 calendly 스크립트가 본문에 추가됩니다. 브라우저는 그것을 다운로드하고 평가하며 우리 모두는 행복합니다.

탐색 후 페이지로 돌아올 때 문제가 발생합니다. 이번에는 스크립트가 여전히 본문에 있으며 브라우저는 다시 다운로드 및 재평가하지 않습니다.

고치다:

  1. componentWillUnmount찾아 스크립트 요소를 제거합니다. 그런 다음 다시 마운트 할 때 위 단계를 반복하십시오.
  2. 를 입력하십시오 $.getScript. 스크립트 URI와 성공 콜백을 취하는 멋진 jquery 도우미입니다. 스크립트가로드되면 스크립트가 평가되고 성공 콜백이 실행됩니다. 내가해야 할 일은 내 안에 componentDidMount $.getScript(url)있습니다. 내 render방법에는 이미 calendly div가 있습니다. 그리고 그것은 매끄럽게 작동합니다.

2
이 작업을 수행하기 위해 jQuery를 추가하는 것은 나쁜 생각이며 귀하의 경우는 귀하에게 매우 다릅니다. 실제로 API에 재 감지 호출이 있다고 확신하므로 Calendly 스크립트를 한 번 추가해도 아무런 문제가 없습니다. 스크립트를 반복해서 제거하고 추가하는 것은 올바르지 않습니다.
sidonaldson

@sidonaldson jQuery는 프로젝트가 다른 프레임 워크 (및 libs)로 구성된 아키텍처를 단순히 반응하지 않고 유지 보수해야하는 경우 나쁜 습관이 아닙니다. 그렇지 않으면 네이티브 js를 사용하여 컴포넌트에 도달해야합니다.
AlexNikonov
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.