添加脚本标签到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,我有一个真正的id实际上在使用,我不想分享。

763971 次浏览

编辑:事情变化很快,这是过时的-见更新


您是希望在每次呈现该组件时,还是在将该组件挂载到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
}

我为这个特定的情况创建了一个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;

除了上面的答案,你还可以这样做:

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,脚本被注入其中。

Demo可以在codesandbox.io上找到

解决方案视场景而定。就像在我的情况下,我必须加载一个日历嵌入在一个react组件。

Calendly查找一个div并读取它的data-url属性,并在该div中加载一个iframe。

当你第一次加载页面时,一切都很好:首先,呈现带有data-url的div。然后日历脚本添加到主体。浏览器下载并评估它,我们都高兴地回家了。

问题来了,当你导航离开,然后回到页面。这一次脚本仍然在正文中,浏览器不会重新下载&重新评估。

解决办法:

  1. componentWillUnmount上找到并删除script元素。然后重新安装,重复上述步骤。
  2. 输入$.getScript。它是一个漂亮的jquery助手,接受一个脚本URI和一个成功回调。一旦加载了脚本,它就计算它并触发你的success回调。我所要做的就是在我的componentDidMount $.getScript(url)中。我的render方法已经有了calendly div。

亚历克斯·麦克米兰提供的答案对我帮助最大,但对于更复杂的脚本标记不太适用。

我稍微调整了他的答案,提出了一个解决方案的长标签与各种功能,另外已经设置“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);
}

我最喜欢的方法是使用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

如果你需要在SSR(服务器端渲染)中有<script>块,使用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>

有一个非常好的解决方法使用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

你可以使用npm postscribe在react组件中加载脚本

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

对于多个脚本,使用这个

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

你可以在以下链接找到最佳答案:

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的自定义组件 / /钩/ Script.js < / p >

import { useEffect } from 'react'




// react-helmet don't guarantee the scripts execution order
export default function Script(props) {


// Ruels: alwasy use effect at the top level and from React Functions
useEffect(() => {
const script = document.createElement('script')


// src, async, onload
Object.assign(script, props)


let { parent='body' } = props


let parentNode = document.querySelector(parent)
parentNode.appendChild(script)


return () => {
parentNode.removeChild(script)
}
} )


return null  // Return null is necessary for the moment.
}

//使用自定义组件,只需导入它并用自定义驼色标记<Script>替换旧的小写<script>标记就足够了。
/ / index.js < / p >

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 async={false} type="text/javascript" src='http://maps.google.com/maps/api/js' />
</Fragment>

有点晚了,但我决定创建我自己的@Alex Macmillan的答案后,这是通过传递两个额外的参数;放置脚本的位置,如或和设置async为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);

你也可以使用反应头盔

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初学者友好。

要在头部标记<head>中添加脚本标记或代码,请使用react-helmet包。它很轻,有很好的文档。

在script标签中添加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)测试

这个答案解释了此行为背后的为什么

任何渲染 (script标记)的方法都不能正常工作:

  1. 为外部脚本使用script标记
  2. 使用dangerouslySetInnerHTML

为什么

React DOM (web上的React渲染器)使用createElement调用将JSX渲染为DOM元素。

createElement使用innerHTML DOM API最终将这些添加到DOM (参见React源代码中的代码)。innerHTML 不执行script标签作为安全考虑添加。这就是为什么在React中渲染script标签不能按预期工作的原因。

关于如何在React中使用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')
}

< em >注意:< / em >将库保存在对象中,因为通常情况下库是一个函数,React将在存储状态时执行函数来检查更改——这将破坏库(如Stripe),期望使用特定的参数调用——所以我们将其存储在对象中以对React隐藏它,并保护库函数不被调用。

以下是我如何最终能够在React JS代码中添加两个外部JavaScript文件:

以下是我遵循的步骤。

<强>步骤1: 我在react-app文件夹路径中使用npm i react-helmet终端安装了React-Helmet

<强>步骤2: 然后,我在代码中添加了import {Helmet} from "react-helmet";报头

<强>步骤3: 最后,在我的代码中这是 我如何使用Helment

添加外部JS文件
<Helmet>
<script src = "path/to/my/js/file1.js" type = "text/javascript" />
<script src = "path/to/my/js/file2.js" type = "text/javascript" />
</Helmet>

只需在HTML文件中添加主体

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

你可以在react被调用之前把你的脚本放在Html文件中。

老实说,对于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备份中将脚本加载的redux值设置为false。

我看到了同样的问题,直到我发现这个包,很容易实现,我希望它能像它为我工作:)

https://github.com/gumgum/react-script-tag

import React from 'react';
import Script from '@gumgum/react-script-tag';


import './App.css';


function App() {
return (
<div >


<h1> Graphs</h1>
<div class="flourish-embed flourish-network" data-src="visualisation/8262420">
<Script  src"your script"
</Script>
</div>
</div>
);
}


export default App;
我最近遇到了一个问题, 尝试了这里给出的多种解决方案,最终满足于iframe, 如果你试图在特定屏幕上集成js插件,Iframe似乎可以无缝地工作

    <iframe
id="xxx"
title="xxx"
width="xxx"
height="xxx"
frameBorder="value"
allowTransparency
srcDoc={`
<!doctype html>
<html>
<head>
<title>Chat bot</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
</head>
<body style="width:100%">
<script type="text/javascript">
......
</script>
</body>
</html>
`}
/>

与其他答案非常相似,只是使用默认值来清理未定义的检查

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

对于支持加载状态和错误处理的更完整的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;
}

我有原始html字符串与javascript/Jquery 我安装了NPM库dangerous -set-html-content npm i dangerously-set-html-content < / p >

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>

你必须为这个脚本创建一个组件,你调用这个组件作为一个标准的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){}'}}/>
}
}
};