Добавление тега скрипта в React / JSX

266

У меня относительно простая проблема - попытка добавить встроенные сценарии в компонент 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: не обращайте внимания на foobar, у меня есть настоящий идентификатор, которым я не хотел делиться.

ArrayKnight
источник
5
Есть ли конкретная мотивация для загрузки этого через React вместо включения его в HTML вашей базовой страницы? Даже если бы это сработало, это означало бы, что вы будете вставлять скрипт каждый раз при монтировании компонента.
loganfsmyth
Это тот случай? Я предполагал, что DOM diffing сделает это не так, но я признаю, что это будет зависеть от реализации DocumentTitle.
loganfsmyth
8
Исправьте @loganfsmyth, React не будет перезагружать скрипт при повторном рендеринге, если в следующем состоянии также есть скрипт.
Макс

Ответы:

405

Изменить: все меняется быстро, и это устарело - см. Обновление


Вы хотите загружать и выполнять скрипт снова и снова, каждый раз, когда этот компонент отображается, или только один раз, когда этот компонент монтируется в DOM?

Возможно, попробуйте что-то вроде этого:

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

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

    document.body.appendChild(script);
}

Однако это действительно полезно, только если скрипт, который вы хотите загрузить, не доступен в виде модуля / пакета. Во-первых, я бы всегда:

  • Ищите пакет на нпм
  • Скачайте и установите пакет в моем проекте ( 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и $глобально (т. Е. Включены 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)} />;
  }
}

DIV связан с, thisи сценарий вводится в него.

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

sidonaldson
источник
5
this.instance не работает для меня, но document.body.appendChild сделал из ответа Алекса Макмиллана.
Что было бы круто
6
Вы, вероятно, не связывались this.instanceс ref внутри вашего метода рендера. Я добавил демонстрационную ссылку, чтобы показать, как она работает
sidonaldson
1
@ShubhamKushwah Вы должны делать рендеринг на стороне сервера?
ArrayKnight
@ArrayKnight да я узнал позже , что на сервере эти объекты не существуют: document, window. Поэтому я предпочитаю использовать globalпакет npm
Shubham Kushwah
зачем нужен s.async = true, я не могу найти ссылку на это, узнать его цель, вы можете это объяснить
Саша Романов
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

AC Patrice
источник
5
Это, безусловно, лучшее решение.
Paqash
4
К сожалению, это не работает ... См. Codesandbox.io/s/l9qmrwxqzq
Darkowic
2
@Darkowic, я получил код для работы, добавив async="true"к <script>тегу , который добавил Jquery к коду.
Soma Mbadiwe
@ SomaMbadiwe, почему он работает async=trueи не работает без него?
Веб-леди
3
Попробовал это, не работает для меня. Я бы не рекомендовал использовать реактивный шлем по той единственной причине, что он добавляет в скрипт дополнительные свойства, которые нельзя удалить. Это на самом деле нарушает некоторые сценарии, и сопровождающие не исправляли его годами и отказываются от 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>
scabbiaza
источник
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);
  }
jake2620
источник
7
Я не понимаю, зачем вам вообще использовать React, если вы просто добавляете встроенный JS на страницу ...?
Алекс Макмиллан
2
Вы должны добавить document.head.removeChild(script);в свой код, или вы будете создавать бесконечное количество тегов сценария в вашем HTML, пока пользователь посещает эту страницу маршрута
Саша Романов
7

Я создал компонент React для этого конкретного случая: https://github.com/coreyleelarson/react-typekit

Просто нужно передать свой ID Kitkit 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>')
Ashh
источник
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);
}
Marlcg58
источник
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.
}

// Использовать пользовательский compoennt, просто импортировать его и заменить старый <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>
пятнать
источник
У этого компонента есть одна оговорка: этот компонент Script может гарантировать только порядок своих собственных братьев и сестер. Если вы используете этот компонент несколько раз на нескольких компонентах одной и той же страницы, блоки сценариев могут выйти из строя. Причина в том, что все сценарии вставляются в document.body.appendChild программно, а не декларативно. Ну и шлем перенесите все теги скрипта в заголовок тега, чего мы не хотим.
пачкать
Привет, @sully, моя проблема здесь в том, что скрипт был добавлен в DOM по отдельности, лучшее решение, которое я видел до сих пор, это во время размонтирования компонента, удаление дочернего элемента (то есть <script>) из DOM, а затем добавлен снова, когда компонент смонтирован на 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);
Kirasiris
источник
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.

Все хорошо, когда вы впервые загружаете страницу: сначала data-urlотображается div с . Затем календарный скрипт добавляется в тело. Браузер загружает и оценивает его, и мы все счастливы.

Проблема возникает, когда вы уходите, а затем возвращаетесь на страницу. На этот раз скрипт все еще находится в теле, и браузер не перезагружает и не переоценивает его.

Fix:

  1. На componentWillUnmountпоиск и удаление элемента сценария. Затем при повторной установке повторите вышеуказанные шаги.
  2. Введите $.getScript. Это отличный jquery помощник, который принимает URI сценария и обратный вызов. Как только скрипт загружен, он оценивает его и запускает ваш успешный обратный вызов. Все, что мне нужно сделать, это в моем componentDidMount $.getScript(url). Мой renderметод уже имеет календарный div. И это работает гладко.
Рохан Багчи
источник
2
Добавление jQuery для этого - плохая идея, плюс ваш случай очень специфичен для вас. На самом деле нет ничего плохого в том, чтобы добавить скрипт Calendly один раз, так как я уверен, что API имеет вызов повторного обнаружения. Удаление и добавление сценария снова и снова не является правильным.
Сидональдсон
@sidonaldson jQuery - неплохая практика, если вам нужно поддерживать проект, его архитектура, состоящая из разных структур (и библиотек), а не просто реагировать, иначе нам нужно использовать нативные js для доступа к компонентам
AlexNikonov