การเพิ่มแท็กสคริปต์ใน React / JSX


264

ฉันมีปัญหาที่ค่อนข้างตรงไปตรงมาในการพยายามเพิ่มสคริปต์แบบอินไลน์ให้กับองค์ประกอบ 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 ฉันมีรหัสจริงที่ใช้งานอยู่ซึ่งฉันไม่รู้สึกอยากแบ่งปัน


5
มีแรงจูงใจที่เฉพาะเจาะจงสำหรับการโหลดผ่าน React แทนที่จะรวมไว้ใน HTML หน้าฐานของคุณหรือไม่? แม้ว่าสิ่งนี้จะได้ผลก็หมายความว่าคุณจะแทรกสคริปต์ใหม่ทุกครั้งที่ส่วนประกอบถูกเมาท์
loganfsmyth

เป็นอย่างนั้นเหรอ? ผมถือว่า DOM diffing จะทำให้ไม่กรณี DocumentTitleแต่ผมยอมรับว่ามันจะขึ้นอยู่กับการดำเนินงานของ
loganfsmyth

8
ถูกต้อง @loganfsmyth ปฏิกิริยาจะไม่โหลดสคริปต์ใหม่อีกครั้งหากสถานะถัดไปยังมีสคริปต์
Max

คำตอบ:


404

แก้ไข: สิ่งต่าง ๆ เปลี่ยนแปลงอย่างรวดเร็วและสิ่งนี้ล้าสมัย - ดูการอัปเดต


คุณต้องการดึงข้อมูลและเรียกใช้งานสคริปต์ซ้ำแล้วซ้ำอีกทุกครั้งที่มีการแสดงผลคอมโพเนนต์นี้หรือเพียงแค่ครั้งเดียวเมื่อติดตั้งคอมโพเนนต์นี้ลงใน DOM

อาจลองทำสิ่งนี้:

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

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

    document.body.appendChild(script);
}

อย่างไรก็ตามสิ่งนี้มีประโยชน์มากหากสคริปต์ที่คุณต้องการโหลดไม่สามารถใช้งานได้ในโมดูล / แพ็คเกจ ก่อนอื่นฉันจะ:

  • มองหาแพ็คเกจในเวลา 23.00 น
  • ดาวน์โหลดและติดตั้งแพ็คเกจในโครงการของฉัน ( npm install typekit)
  • importแพ็คเกจที่ฉันต้องการ ( import Typekit from 'typekit';)

นี่คือแนวโน้มว่าคุณติดตั้งแพคเกจreactและreact-document-titleจากตัวอย่างของคุณและมีความเป็นแพคเกจที่มีอยู่บน Typekit NPM


ปรับปรุง:

ตอนนี้เรามี hooks วิธีการที่ดีกว่าอาจใช้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){}afterappendChild
ArrayKnight

2
ฉันตัดสินใจว่าการใช้งาน "ขั้นสูง" จาก TypeKit เหมาะสมกับวิธีนี้มากกว่า
ArrayKnight

10
สิ่งนี้ใช้งานได้ - เพื่อโหลดสคริปต์ แต่ฉันจะเข้าถึงรหัสในสคริปต์ได้อย่างไร ตัวอย่างเช่นฉันต้องการเรียกใช้ฟังก์ชันที่อาศัยอยู่ในสคริปต์ แต่ฉันไม่สามารถเรียกใช้ภายในองค์ประกอบที่สคริปต์โหลด
zero_cool

2
เมื่อสคริปต์ถูกผนวกเข้ากับหน้ามันจะถูกดำเนินการตามปกติ ตัวอย่างเช่นหากคุณใช้วิธีนี้ในการดาวน์โหลด jQuery จาก CDN จากนั้นหลังจากที่componentDidMountฟังก์ชั่นดาวน์โหลดและต่อท้ายสคริปต์ไปยังหน้าคุณจะมีjQueryและ$วัตถุพร้อมใช้งานทั่วโลก (เช่น: เปิดwindow)
Alex McMillan

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


5
สิ่งนี้ไม่ได้ผลสำหรับฉัน แต่ document.body.appendChild ทำจากคำตอบของ Alex McMillan
อะไรจะเจ๋ง

6
คุณอาจไม่ได้ผูกthis.instanceกับผู้อ้างอิงในวิธีการสร้างภาพของคุณ ฉันได้เพิ่มลิงค์สาธิตเพื่อแสดงว่ามันใช้งานได้
sidonaldson

1
@ShubhamKushwah คุณต้องทำการเรนเดอร์ฝั่งเซิร์ฟเวอร์หรือไม่
ArrayKnight

@ArrayKnight ใช่ฉันค้นพบในภายหลังว่าบนเซิร์ฟเวอร์วัตถุเหล่านี้ไม่มีอยู่: document, window. ดังนั้นฉันจึงชอบใช้globalแพคเกจnpm
Shubham Kushwah

อะไรคือสิ่งที่จำเป็นสำหรับ s.async = จริงฉันไม่สามารถหาข้อมูลอ้างอิงเกี่ยวกับมันเพื่อทราบว่ามันมีจุดประสงค์คุณสามารถอธิบายได้อย่างไร
sasha romanov

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


5
นี่คือทางออกที่ดีที่สุด
paqash

4
น่าเสียดายที่มันไม่ทำงาน ... ดูcodesandbox.io/s/l9qmrwxqzq
Darkowic

2
@Darkowic ผมได้รหัสของคุณในการทำงานโดยการเพิ่มasync="true"ไปยัง<script>แท็กที่เพิ่ม jQuery รหัส
Soma Mbadiwe

@SomaMbadiwe ทำไมมันทำงานกับasync=trueและล้มเหลวโดยไม่ได้หรือไม่
Webwoman

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>

12

คำตอบที่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);
  }

7
ฉันไม่เข้าใจว่าทำไมคุณถึงใช้ React เลยถ้าคุณแค่ทิ้งอินไลน์ JS ลงบนหน้า ... ?
Alex McMillan

2
คุณจะต้องเพิ่มdocument.head.removeChild(script);ในรหัสของคุณหรือคุณจะสร้างจำนวนอนันต์ของแท็กสคริปต์ที่ html ของคุณตราบเท่าที่ผู้ใช้เข้าชมนี้เส้นทางหน้า
sasha โรมา

7

ฉันสร้างองค์ประกอบ 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;

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

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

Helmet ใช้แท็ก HTML ธรรมดาและส่งแท็ก HTML ธรรมดา มันง่ายมากและตอบโต้ผู้เริ่มต้นเป็นมิตร


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

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

ตามวิธีการแก้ปัญหาของAlex McMillanฉันมีการปรับตัวต่อไปนี้
สภาพแวดล้อมของฉัน: ทำปฏิกิริยา 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>

มีข้อแม้หนึ่งข้อสำหรับคอมโพเนนต์นี้: คอมโพเนนต์สคริปต์นี้สามารถรับประกันการเรียงลำดับของพี่น้องของตัวเองเท่านั้น หากคุณใช้ส่วนประกอบนี้หลายครั้งในหลาย ๆ องค์ประกอบของหน้าเดียวกันบล็อกสคริปต์อาจไม่ทำงาน เหตุผลก็คือสคริปต์ทั้งหมดจะถูกแทรกโดย document.body.appendChild โดยทางโปรแกรมแทนที่จะเป็นการประกาศ หมวกกันน็อกย้ายแท็กสคริปต์ทั้งหมดในแท็กหัวซึ่งเราไม่ต้องการ
sully

สวัสดี @sull ปัญหาของฉันที่นี่คือการเพิ่มสคริปต์ใน DOM อย่างรุนแรงทางออกที่ดีที่สุดที่ฉันเคยเห็นคือในระหว่างการถอนการติดตั้งคอมโพเนนต์การลบองค์ประกอบลูก (เช่น <script>) ออกจาก DOM จากนั้นก็เป็น เพิ่มอีกครั้งเมื่อติดตั้งคอมโพเนนต์ใน DOM (ฉันใช้ react-router-dom และมีเพียงหนึ่งคอมโพเนนต์เท่านั้นที่ต้องการสคริปต์นี้สำหรับส่วนประกอบทั้งหมดของฉัน)
Eazy

0

สายไปงานเลี้ยง แต่ฉันตัดสินใจที่จะสร้างของตัวเองหลังจากดูที่ @Alex Macmillan คำตอบและนั่นคือโดยการผ่านสองพารามิเตอร์พิเศษ; ตำแหน่งที่จะวางสคริปต์เช่นหรือและการตั้งค่า async ให้เป็นจริง / เท็จนี่คือ:

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

0

ในการนำเข้าไฟล์ JS ในโครงการตอบสนองให้ใช้คำสั่งนี้

  1. ย้ายไฟล์ JS ไปที่โครงการ
  2. อิมพอร์ต js ไปยังหน้าโดยใช้คำสั่งต่อไปนี้

มันง่ายและสะดวก

import  '../assets/js/jquery.min';
import  '../assets/js/popper.min';
import  '../assets/js/bootstrap.min';

ในกรณีของฉันฉันต้องการนำเข้าไฟล์ JS เหล่านี้ในโครงการตอบสนองของฉัน


-3

การแก้ปัญหาขึ้นอยู่กับสถานการณ์ เช่นเดียวกับในกรณีของฉันฉันต้องโหลดสิ่งที่ฝังลงไปในส่วนทำปฏิกิริยา

มองหา div และอ่านจากมันอย่างตั้งใจ data-urlแอตทริบิวต์และโหลด iframe ภายใน div ดังกล่าว

ทุกอย่างจะดีเมื่อคุณโหลดหน้าเว็บ: ก่อนอื่นให้แบ่งด้วย data-urlจะแสดงผล จากนั้นเพิ่มสคริปต์ calendly ให้กับเนื้อหา ดาวน์โหลดเบราว์เซอร์และประเมินมันและเราทุกคนกลับบ้านมีความสุข

ปัญหาเกิดขึ้นเมื่อคุณนำทางไปจากนั้นกลับมาที่หน้า เวลานี้สคริปต์ยังคงอยู่ในเนื้อความและเบราว์เซอร์จะไม่ดาวน์โหลดและประเมินใหม่อีกครั้ง

การแก้ไข:

  1. เมื่อcomponentWillUnmountค้นหาและลบองค์ประกอบสคริปต์ จากนั้นติดอีกครั้งทำซ้ำขั้นตอนข้างต้น
  2. $.getScriptเข้าสู่ มันเป็นตัวช่วย jquery ที่ดีที่ใช้ URI ของสคริปต์และการเรียกกลับสำเร็จ เมื่อสคริปต์โหลดแล้วสคริปต์จะประเมินและเริ่มการติดต่อกลับที่สำเร็จของคุณ componentDidMount $.getScript(url)ทั้งหมดที่ฉันต้องทำคือในของฉัน renderวิธีการของฉันมี div calendly อยู่แล้ว และทำงานได้อย่างราบรื่น

2
การเพิ่ม jQuery เพื่อทำสิ่งนี้เป็นความคิดที่ไม่ดีรวมถึงกรณีของคุณนั้นเฉพาะเจาะจงกับคุณมาก ในความเป็นจริงไม่มีอะไรผิดปกติกับการเพิ่มสคริปต์ Calendly หนึ่งครั้งเพราะฉันแน่ใจว่า API มีการตรวจพบการโทรอีกครั้ง การลบและเพิ่มสคริปต์ซ้ำแล้วซ้ำอีกไม่ถูกต้อง
sidonaldson

@sidonaldson jQuery ไม่ใช่วิธีปฏิบัติที่ไม่ถูกต้องหากคุณต้องรักษาโครงงานสถาปัตยกรรมของเฟรมเวิร์กที่แตกต่างกัน (และ libs) ไม่เพียงแค่ตอบสนองอื่น ๆ ที่เราต้องการใช้ js ดั้งเดิมเพื่อเข้าถึงส่วนประกอบ
AlexNikonov
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.