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

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

32 ответа

Решение

Вы хотите получать и выполнять скрипт снова и снова, каждый раз, когда этот компонент отображается, или только один раз, когда этот компонент монтируется в 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.

Мой любимый способ - использовать 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

В дополнение к ответам выше вы можете сделать это:

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

Этот ответ объясняет причину такого поведения.

Любой подход к визуализацииscript тег не работает должным образом:

  1. Используя script тег для внешних скриптов
  2. С помощью dangerouslySetInnerHTML

Почему

React DOM (средство визуализации для реагирования в сети) использует createElement вызовы для рендеринга JSX в элементы DOM.

createElement использует innerHTML DOM API, чтобы наконец добавить их в DOM (см. Код в исходном коде React). innerHTML не выполняет script тег добавлен как соображение безопасности. И это причина того, что, в свою очередь, рендеринг script Теги в React не работают должным образом.

Для того, как использовать script Теги в React проверяют некоторые другие ответы на этой странице.

Если вам нужно иметь <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>

Ответ, Alex McMillan предоставил Alex McMillan, помог мне больше всего, но не совсем 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);
  }

Я попытался отредактировать принятый ответ @Alex McMillan, но он не позволил мне, поэтому есть отдельный ответ, в котором вы можете получить значение библиотеки, в которую загружаете. Очень важное различие, о котором просили люди, и которое мне нужно для моего реализация с помощью stripe.js.

useScript.js

      import { useState, useEffect } from 'react'

export const useScript = (url, name) => {

  const [lib, setLib] = useState({})

  useEffect(() => {
    const script = document.createElement('script')

    script.src = url
    script.async = true
    script.onload = () => setLib({ [name]: window[name] })

    document.body.appendChild(script)

    return () => {
      document.body.removeChild(script)
    }
  }, [url])

  return lib

}

использование выглядит как

      const PaymentCard = (props) => {
  const { Stripe } = useScript('https://js.stripe.com/v2/', 'Stripe')
}

ПРИМЕЧАНИЕ. Сохранение библиотеки внутри объекта, потому что часто библиотека является функцией, и React будет выполнять функцию при сохранении в состоянии для проверки изменений, что приведет к поломке библиотек (например, Stripe), которые ожидают вызова с определенными аргументами - поэтому мы сохраняем это в объекте, чтобы скрыть это от React и защитить библиотечные функции от вызова.

Вы также можете использовать шлем React

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

Helmet принимает простые теги HTML и выводит простые теги HTML. Это очень просто, и React удобен для новичков.

Я создал компонент 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;

Существует очень хороший способ использования 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,

Вот как я наконец смог добавить два внешних файла JavaScript в свой код React JS:

Вот шаги, которым я следовал.

Шаг 1. Я установил React-Helmet, используя npm i react-helmetс терминала, находясь внутри моего пути к папке с реактивным приложением.

Шаг 2: затем я добавил import {Helmet} from "react-helmet"; заголовок в моем коде.

Шаг 3: Наконец, в моем коде я добавил внешние JS-файлы с помощью Helment.

      <Helmet>
    <script src = "path/to/my/js/file1.js" type = "text/javascript" />
    <script src = "path/to/my/js/file2.js" type = "text/javascript" />  
</Helmet>

Чтобы добавить тег скрипта или код в тег заголовка <head>, используйте пакет React-Helmet. он легкий и имеет хорошую документацию.

Чтобы добавить код Js в тег скрипта внутри тела,

    function htmlDecode(html) {
      return html.replace(/&([a-z]+);/ig, (match, entity) => {
        const entities = { amp: '&', apos: '\'', gt: '>', lt: '<', nbsp: '\xa0', quot: '"' };
        entity = entity.toLowerCase();
        if (entities.hasOwnProperty(entity)) {
          return entities[entity];
        }
        return match;
      });
    }
  render() {
    const scriptCode = `<script type="text/javascript">
          {(function() {
          window.hello={
            FIRST_NAME: 'firstName',
            LAST_NAME: 'lastName',
          };
          })()}
          </script>`
    return(
      <div dangerouslySetInnerHTML={{ __html: this.htmlDecode(scriptCode) }} />;
    );
  }

этот код можно проверить console.log(windows.hello)

Ты можешь использовать npm postscribe загрузить скрипт в реагирующий компонент

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

Очень похоже на другие ответы, просто используя значения по умолчанию для очистки неопределенных проверок.

      import { useEffect } from 'react'

const useScript = (url, selector = 'body', async = true) => {
  useEffect(() => {
    const element = document.querySelector(selector)
    const script = document.createElement('script')
    script.src = url
    script.async = async
    element.appendChild(script)
    return () => {
      element.removeChild(script)
    }
  }, [url])
}

export default useScript

Применение

      useScript('/path/to/local/script.js') // async on body
useScript('https://path/to/remote/script.js', 'html') // async on html 
useScript('/path/to/local/script.js', 'html', false) // not async on html.. e.g. this will block

Вы можете найти лучший ответ по следующей ссылке:

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

Согласно решению Алекса Макмиллана, у меня есть следующая адаптация.
Моя собственная среда: React 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>

Немного поздно для вечеринки, но я решил создать свой собственный, посмотрев на ответы @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);

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

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

если вы хотите импортировать его постоянно или не динамически
Просто добавьте его в файл index.html в тег body как обычно

      <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>

Для более полного useScriptреализация, которая поддерживает статус загрузки и обработку ошибок, проверьте это на useHooks.

Применение

      function App() {
  const status = useScript(
    "https://pm28k14qlj.codesandbox.io/test-external-script.js"
  );
  return (
    <div>
      <div>
        Script status: <b>{status}</b>
      </div>
      {status === "ready" && (
        <div>
          Script function call response: <b>{TEST_SCRIPT.start()}</b>
        </div>
      )}
    </div>
  );
}

Крюк

      function useScript(src) {
  // Keep track of script status ("idle", "loading", "ready", "error")
  const [status, setStatus] = useState(src ? "loading" : "idle");
  useEffect(
    () => {
      // Allow falsy src value if waiting on other data needed for
      // constructing the script URL passed to this hook.
      if (!src) {
        setStatus("idle");
        return;
      }
      // Fetch existing script element by src
      // It may have been added by another intance of this hook
      let script = document.querySelector(`script[src="${src}"]`);
      if (!script) {
        // Create script
        script = document.createElement("script");
        script.src = src;
        script.async = true;
        script.setAttribute("data-status", "loading");
        // Add script to document body
        document.body.appendChild(script);
        // Store status in attribute on script
        // This can be read by other instances of this hook
        const setAttributeFromEvent = (event) => {
          script.setAttribute(
            "data-status",
            event.type === "load" ? "ready" : "error"
          );
        };
        script.addEventListener("load", setAttributeFromEvent);
        script.addEventListener("error", setAttributeFromEvent);
      } else {
        // Grab existing script status from attribute and set to state.
        setStatus(script.getAttribute("data-status"));
      }
      // Script event handler to update status in state
      // Note: Even if the script already exists we still need to add
      // event handlers to update the state for *this* hook instance.
      const setStateFromEvent = (event) => {
        setStatus(event.type === "load" ? "ready" : "error");
      };
      // Add event listeners
      script.addEventListener("load", setStateFromEvent);
      script.addEventListener("error", setStateFromEvent);
      // Remove event listeners on cleanup
      return () => {
        if (script) {
          script.removeEventListener("load", setStateFromEvent);
          script.removeEventListener("error", setStateFromEvent);
        }
      };
    },
    [src] // Only re-run effect if script src changes
  );
  return status;
}

Честно говоря, для React - не беспокойтесь о добавлении <script>теги к вашему заголовку. Это головная боль - получить обратный вызов, когда они полностью загрузились. Вместо этого используйте такой пакет, как @charlietango / useScript, чтобы загрузить скрипт, когда он вам нужен, и получить обновление статуса, когда он будет завершен.

Пример использования:

      import React from 'react'
import useScript, { ScriptStatus } from '@charlietango/use-script'
 
const Component = () => {
  const [ready, status] = useScript('https://api.google.com/api.js')
 
  if (status === ScriptStatus.ERROR) {
    return <div>Failed to load Google API</div>
  }
 
  return <div>Google API Ready: {ready}</div>
}
 
export default Component

PS. Если вы используете redux, чтобы сообщить другим компонентам, когда ваш скрипт загружен, и вы используете redux-persist как и я, не забудьте включить модификатор в вашу настройку redux-persist, который всегда устанавливает для загруженного скриптом значения redux значение false в резервной копии redux.

У меня была необработанная строка html с javascript/Jquery, я установил библиотеку npm с опасным набором html-контента. npm i dangerously-set-html-content

      import InnerHTML from 'dangerously-set-html-content'
<div>
<InnerHTML html={html}/>
</div>

или же

      import InnerHTML from 'dangerously-set-html-content'
const renderhtml=`<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title> is not defined</title>$(document).ready(function(){    $("button").click(function(){        alert("jQuery is working perfectly.");    });      });</script></head><body>    <button type="button">Test jQuery Code</button></body></html>`

<div>
<InnerHTML html={renderhtml}/>
</div>

Убедитесь, что вы добавили jquery cdn в файл public/index.html.

       <script          src="https://code.jquery.com/jquery-3.3.1.min.js"          integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="          crossorigin="anonymous"          async="true"        ></script>

Вы можете попробовать использовать следующее:

Убедитесь, что вы доверяете скрипту

       <script>{`
            function myFunction(index, row) {
                return index;
            }
        `}
</script>

Я получил document "undefined" когда обновить. Я попробовал вот так

const MyComponent = () => {
   let script = React.createElement("script", {
    src: "https://.....js path",
    async: true,
    defer: true,
    crossorigin: "anonymous"
})

return (

        <div>
            {script}
        </div>)
}

Для нескольких сценариев используйте это

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

Вы должны создать компонент для этого сценария, вы вызываете этот компонент как стандартный тег сценария ES6.

      '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>
                </article>
            </DocumentTitle>
        );

        class Component extend Index.App {  
             <script src="https://use.typekit.net/foobar.js" />
             <script dangerouslySetInnerHTML={{__html: 'try{Typekit.load({ async: true });}catch(e){}'}}/>
        }
    }
};

Вы можете поместить свой скрипт в файл Html до вызова реакции.

Хук useScript.js - мне помог. Спасибо, user7547940

Мой вариант для WayForPay

      const {loaded,Wayforpay} = useScript('https://secure.wayforpay.com/server/pay-widget.js','Wayforpay');

обработчик нажатия кнопки

      const openPay = (Wayforpay) =>{
            axios.get('/site/paywfp',{params:{guid:pay.guid}})
                .then(({data}) =>{
    
                    const wfp = new Wayforpay();
    
                    wfp.run(
                        data.option,
                        () => console.log('done'),
                        () => console.log('err'),
                        () => console.log('wait')
                    );
                }
            );
        }

кнопка на загруженном наборе

      loaded?<button onClick={()=>openPay(Wayforpay)}>

Решение зависит от сценария. Как и в моем случае, мне пришлось загружать календари-вставку внутри компонента реакции.

Календарно ищет div и читает из него data-url атрибут и загружает фрейм внутри указанного div.

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

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

Fix:

  1. На componentWillUnmount найти и удалить элемент скрипта. Затем при повторной установке повторите описанные выше шаги.
  2. Войти $.getScript, Это отличный jquery помощник, который принимает URI сценария и обратный вызов. Как только скрипт загружен, он оценивает его и запускает ваш успешный обратный вызов. Все, что мне нужно сделать, это в моем componentDidMount$.getScript(url), мой render метод уже имеет календарный div. И это работает гладко.
Другие вопросы по тегам