Добавление тега скрипта в 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>
);
}
};
В дополнение к ответам выше вы можете сделать это:
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
тег не работает должным образом:
- Используя
script
тег для внешних скриптов - С помощью
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
Вы можете найти лучший ответ по следующей ссылке:
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:
- На
componentWillUnmount
найти и удалить элемент скрипта. Затем при повторной установке повторите описанные выше шаги. - Войти
$.getScript
, Это отличный jquery помощник, который принимает URI сценария и обратный вызов. Как только скрипт загружен, он оценивает его и запускает ваш успешный обратный вызов. Все, что мне нужно сделать, это в моемcomponentDidMount
$.getScript(url)
, мойrender
метод уже имеет календарный div. И это работает гладко.