Додавання тегу сценарію до 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>

Жоден підхід не здається виконати потрібний сценарій. Я здогадуюсь, що це проста річ, якої мені не вистачає. Хтось може допомогти?

PS: Ігноруйте фоабар, я справді використовую ідентифікатор, який мені не подобалося ділитися.


5
Чи є конкретна мотивація для завантаження цього через React, а не включати його у HTML базової сторінки? Навіть якби це спрацювало, це означатиме, що ви будете повторно вставляти сценарій кожного разу, коли компонент змонтований.
loganfsmyth

Це так? Я припускав, що DOM відрізняється, це зробить це не так, але я визнаю, це залежатиме від впровадження DocumentTitle.
loganfsmyth

8
Правильно @loganfsmyth, React не буде перезавантажувати сценарій при повторному відтворенні, якщо наступний стан також має сценарій.
Макс

Відповіді:


403

Редагувати: речі швидко змінюються, і це застаріло - див. Оновлення


Ви хочете отримувати та виконувати скрипт знову і знову, кожного разу, коли цей компонент відображається або лише один раз, коли цей компонент встановлюється в 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';)

Це, ймовірно, як ви встановили пакунки reactта react-document-titleз вашого прикладу, і є пакет Typekit, доступний у npm .


Оновлення:

Тепер, коли у нас є гачки, кращим підходом може бути таке використання 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
Коли сценарій додається до сторінки, він буде виконаний як звичайний. Наприклад, якщо ви використовували цей метод для завантаження jQuery з CDN, то після завантаження componentDidMountфункції та додавання сценарію до сторінки, у вас з'являться об’єкти jQueryта $доступні у всьому світі (тобто: on window).
Алекс Макміллан

1
У мене була подібна проблема із використанням скрипта аутентифікації, і виявилося, що, можливо, було б краще включити його у файл html-корінця шаром над вашою реакцією App.js. У випадку, якщо хтось вважає це корисним. Як згадував @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)} />;
  }
}

Діва зв'язаний thisі сценарій вводиться в нього.

Демо можна знайти на codeandbox.io


5
ця речовина не працювала для мене, але document.body.appendChild зробив відповідь Алекса Макміллана.
Що було б круто

6
Ви, ймовірно, не прив'язувались this.instanceдо посилання у вашому методі візуалізації. Я додав демо-посилання, щоб показати, що це працює
sidonaldson

1
@ShubhamKushwah Ви повинні робити рендеринг на стороні сервера?
ArrayKnight

@ArrayKnight так, я пізніше дізнався, що на сервері цих об'єктів не існує: document, window. Тому я вважаю за краще використовувати globalпакет npm
Shubham Kushwah,

у чому потреба s.async = правда, я не можу знайти посилання на це, щоб знати, що це, чи можете ви пояснити це
sasha

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
На жаль, це не працює ... Дивіться codeandbox.io/s/l9qmrwxqzq
Darkowic

2
@Darkowic, я змусив ваш код працювати, додавши async="true"до <script>тегу, який додав jQuery до коду.
Soma Mbadiwe

@SomaMbadiwe, чому вона працює async=trueі не спрацьовує без неї?
Веб-жінка

3
Спробував це, не працює для мене. Я б не рекомендував використовувати реактив-шолом з єдиної причини, що він вводить додаткові властивості в сценарій, який неможливо видалити. Це фактично порушує певні сценарії, і технічне
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

Відповідь, яку надав Алекс Макміллан, найбільше допомогла мені, але не працювала над складнішим тегом сценарію.

Я трохи підмітив його відповідь, щоб придумати рішення для довгого тегу з різними функціями, яке додатково вже було встановлено "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
Я не розумію, чому ти взагалі використовуєш React, якщо ти просто скидаєш вбудований JS на сторінку ...?
Алекс Макміллан

2
вам потрібно додати document.head.removeChild(script);свій код, інакше ви створите нескінченну кількість тегів скриптів у своєму HTML, поки користувач відвідує цей маршрут на сторінці
sasha

7

Я створив компонент React для цього конкретного випадку: https://github.com/coreyleelarson/react-typekit

Просто потрібно вказати свій ідентифікатор Typekit Kit як опору, і ви готові йти.

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.


Чи можете ви співпрацювати, як очікується, будь ласка, працювати з прикладом використання? Для мене це, наприклад, не працює з передачею сценарію та тілом.
Олексій Єфімов,

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. Це мертво просто, і реагує на початківців дружелюбно.


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

Відповідно до рішення Алекса Макміллана , у мене є така адаптація.
Моє власне оточення: Реагуйте 16.8+, наступний 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 програмно, а не декларативно. Добре шолом переміщуємо всі теги сценарію в тезі head, чого не хочеться.
бруднити

Привіт @sully, моя проблема тут полягає в тому, щоб сценарій було додано до DOM поодиноко, найкраще рішення, яке я бачив до цих пір, це під час демонтажу компонентів, видалення дочірнього елемента (тобто <script>) з DOM, а потім це відбувається додано знову, коли компонент встановлено на 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. імпортуйте js на сторінку за допомогою наступної команди

Це просто і легко.

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

У моєму випадку я хотів би імпортувати ці JS-файли у свій проект реагування.


-3

Рішення залежить від сценарію. Як і в моєму випадку, мені довелося завантажувати вбудований компонент всередину реагуючого компонента.

Calendly шукає div і читає з його data-urlатрибута і завантажує iframe всередині вказаного div.

Все добре, коли ви завантажуєте сторінку: спочатку виводиться div з data-url. Потім додано сценарій до тіла. Браузер завантажує та оцінює його, і всі ми раді додому.

Проблема виникає, коли ви перейдете далі, а потім повернетесь на сторінку. Цього разу сценарій все ще знаходиться в тілі, і браузер не повторно завантажує його та не оцінює його.

Виправити:

  1. На componentWillUnmountпошук і видалення елемента сценарію. Після повторного монтажу повторіть описані вище дії.
  2. Введіть $.getScript. Це вишуканий помічник jquery, який приймає URI сценарію та успішний зворотний дзвінок. Після завантаження скрипту він оцінює його і спрацьовує ваш зворотний дзвінок успіху. Все, що я повинен зробити, - це в своєму componentDidMount $.getScript(url). У моєму renderметоді вже є кавалентний дів. І це працює гладко.

2
Додавання jQuery для цього - погана ідея, плюс ваш випадок дуже конкретний для вас. Насправді немає нічого поганого в тому, щоб додати сценарій Calendly один раз, оскільки я впевнений, що API має виклик повторного виявлення. Видалення та додавання сценарію знову і знову не є правильним.
sidonaldson

@sidonaldson jQuery - це не погана практика, якщо вам доведеться підтримувати проект, його архітектурна сполука з різних фреймворків (і libs) не просто реагує, іншим нам потрібно використовувати нативний js, щоб дійти до компонентів
AlexNikonov
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.