簡體   English   中英

向 React/JSX 添加腳本標簽

[英]Adding script tag to 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,我不想分享。

編輯:事情變化很快,這已經過時了 - 請參閱更新


你想一次又一次地獲取和執行腳本,每次渲染這個組件時,還是當這個組件被掛載到 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 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綁定,並將腳本注入其中。

可以在codeandbox.io上找到演示

如果您需要在 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>

這樣的回答解釋了為什么這種行為背后。

任何呈現script標記的方法都無法按預期工作:

  1. 對外部腳本使用script標簽
  2. 使用dangerouslySetInnerHTML

為什么

React DOM(react on web 的渲染器)使用createElement調用將 JSX 渲染為 DOM 元素。

createElement使用innerHTML DOM API 最終將這些添加到 DOM( 參見 React 源代碼中的代碼)。 innerHTML 不執行作為安全考慮添加的script標記。 這就是為什么在 React 中渲染script標簽沒有按預期工作的原因。

有關如何在 React 中使用script標簽,請查看此頁面上的其他一些答案。

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

您也可以使用反應頭盔

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

我試圖編輯@Alex McMillan 接受的答案,但它不會讓我如此這里有一個單獨的答案,您可以在其中獲得您加載的庫的價值。人們要求的一個非常重要的區別,我需要我的使用 stripe.js 實現。

使用腳本.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 組件: 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;

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

您可以在以下鏈接中找到最佳答案:

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'; // eg, googleMaps or stripe document.body.appendChild(script); script.onload = () => { if (callback) callback(); }; } if (existingScript && callback) callback(); };

要在 head 標簽<head>添加腳本標簽或代碼,請使用react-helmet package 它很輕,並且有很好的文檔。

要在 body 內的 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)測試

與其他答案非常相似,只是使用默認值來清理未定義的檢查

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

根據Alex McMillan的解決方案,我有以下改編。
我自己的環境:React 16.8+,next v9+

// 添加一個名為 Script 的自定義組件
// 鈎子/Script.js

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

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 的答案后創建自己的一個,這是通過傳遞兩個額外的參數; 放置腳本的位置,例如或並將異步設置為真/假,這里是:

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

只需在 html 文件中添加正文

<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></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')

嘗試了所有建議的解決方案,但沒有任何效果。 我試圖將其嵌入到組件中,但未加載。 這是來自bannersnack.com的橫幅。

<script type="text/javascript">
var bannersnack_embed = {"hash":"bxpien9pr","width":1190,"height":300,"t":1559889975,"userId":39254375,"responsive":true,"type":"html5"};
</script>
<script type="text/javascript" src="//cdn.bannersnack.com/iframe/embed.js"></script>
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);
}

如果您的腳本具有npm軟件包,請使用npm add進行安裝,只需導入即可。 例如“ chart.js”:

npm install chart.js --save

然后在您的組件中使用:

import Chart from "chart.js/dist/Chart.min.js"

要么

import "chart.js/dist/Chart.min.js"

這是我最終能夠在我的 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>

老實說,對於 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

我最近遇到了這個問題,嘗試了這里給出的多種解決方案,最后遇到了 iframe,如果您嘗試在特定屏幕上集成 js 插件,iframe 似乎可以無縫工作

有關支持加載狀態和錯誤處理的更完整的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;
}

我有帶有javascript / Jquery的原始html字符串我安裝了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>

你必須為這個腳本創建一個組件,你把這個組件稱為標准的 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){}'}}/>
        }
    }
};

我看到了同樣的問題,直到我找到這個包,很容易實現,我希望它對我有用:)

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

從“反應”導入反應; 從“@gumgum/react-script-tag”導入腳本;

導入'./App.css';

函數 App() { 返回 (

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

); }

導出默認應用程序;

很簡單!!

解決方案取決於場景。 就像在我的情況下一樣,我必須在 react 組件中加載一個日歷嵌入。

Calendly 查找一個 div 並從它的data-url屬性中讀取並在所述 div 內加載一個 iframe。

第一次加載頁面時一切都很好:首先,呈現帶有data-url div。 然后將日歷腳本添加到正文中。 瀏覽器下載並評估它,我們都高興地回家了。

當您離開然后返回頁面時,問題就出現了。 這次腳本仍在正文中,瀏覽器不會重新下載和重新評估它。

使固定:

  1. componentWillUnmount找到並刪除腳本元素。 然后重新掛載,重復上述步驟。
  2. 輸入$.getScript 它是一個漂亮的 jquery 助手,它接受一個腳本 URI 和一個成功回調。 一旦它加載了腳本,它就會對其進行評估並觸發您的成功回調。 我所要做的就是在我的componentDidMount $.getScript(url) 我的render方法已經有了日歷 div。 它工作順利。

您可以在調用 react 之前將腳本放在 Html 文件中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM