Menambahkan tag skrip ke React / JSX

266

Saya memiliki masalah yang relatif mudah mencoba menambahkan skrip sebaris ke komponen Bereaksi. Apa yang saya miliki sejauh ini:

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

Saya juga sudah mencoba:

<script src="https://use.typekit.net/foobar.js"></script>
<script>try{Typekit.load({ async: true });}catch(e){}</script>

Tidak ada pendekatan yang menjalankan skrip yang diinginkan. Saya kira itu hal sederhana yang saya lewatkan. Adakah yang bisa membantu?

PS: Abaikan foobar itu, sebenarnya saya punya id yang sebenarnya saya tidak suka berbagi.

ArrayKnight
sumber
5
Apakah ada motivasi khusus untuk memuat ini melalui Bereaksi alih-alih memasukkannya ke HTML halaman dasar Anda? Bahkan jika ini berhasil, itu berarti Anda akan memasukkan kembali skrip setiap kali komponen dipasang.
loganfsmyth
Apakah itu masalahnya? Saya berasumsi DOM diffing akan membuat itu tidak terjadi, tetapi saya akui itu akan tergantung pada implementasi DocumentTitle.
loganfsmyth
8
Benar @loganfsmyth, Bereaksi tidak akan memuat ulang skrip pada render ulang jika negara berikutnya juga memiliki skrip.
Maks

Jawaban:

405

Sunting: Hal berubah dengan cepat dan ini sudah ketinggalan zaman - lihat pembaruan


Apakah Anda ingin mengambil dan menjalankan skrip berulang kali, setiap kali komponen ini dirender, atau hanya sekali ketika komponen ini dipasang ke DOM?

Mungkin coba sesuatu seperti ini:

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

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

    document.body.appendChild(script);
}

Namun, ini hanya sangat membantu jika skrip yang ingin Anda muat tidak tersedia sebagai modul / paket. Pertama, saya akan selalu:

  • Cari paket di npm
  • Unduh dan instal paket di proyek saya ( npm install typekit)
  • importpaket tempat saya membutuhkannya ( import Typekit from 'typekit';)

Ini kemungkinan cara Anda menginstal paket reactdan react-document-titledari contoh Anda, dan ada paket Typekit yang tersedia di npm .


Memperbarui:

Sekarang kita memiliki kait, pendekatan yang lebih baik mungkin digunakan useEffectseperti:

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);
  }
}, []);

Yang menjadikannya kandidat yang tepat untuk pengait kustom (mis .:) 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;

Yang bisa digunakan seperti ini:

import useScript from 'hooks/useScript';

const MyComponent = props => {
  useScript('https://use.typekit.net/foobar.js');

  // rest of your component
}
Alex McMillan
sumber
Terima kasih. Saya memikirkan hal yang salah ini. Saya melanjutkan implementasi ini. Hanya menambahkan try{Typekit.load({ async: true });}catch(e){}setelahappendChild
ArrayKnight
2
Saya memutuskan bahwa implementasi "lanjutan" dari TypeKit lebih cocok untuk pendekatan ini.
ArrayKnight
10
Ini berfungsi - untuk memuat skrip, tetapi bagaimana saya bisa mendapatkan akses ke kode dalam skrip. Misalnya, saya ingin memanggil fungsi yang hidup di dalam skrip, tetapi saya tidak dapat memanggilnya di dalam komponen tempat skrip dimuat.
zero_cool
2
Ketika skrip ditambahkan ke halaman itu akan dieksekusi seperti biasa. Misalnya, jika Anda menggunakan metode ini untuk mengunduh jQuery dari CDN, maka setelah componentDidMountfungsi mengunduh dan menambahkan skrip ke halaman, Anda akan memiliki jQuerydan $objek tersedia secara global (mis .: aktif window).
Alex McMillan
1
Saya memiliki masalah yang sama menggunakan skrip otentikasi dan ternyata mungkin lebih baik untuk memasukkannya dalam file html dari root, layer di atas App.js. reaksi Anda. Jika ada yang menemukan ini berguna. Seperti @loganfsmith sebutkan ...
devssh
57

Lebih jauh ke jawaban di atas Anda dapat melakukan ini:

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 terikat thisdan skrip disuntikkan ke dalamnya.

Demo dapat ditemukan di codesandbox.io

sidonaldson
sumber
5
ini. contoh tidak bekerja untuk saya, tetapi document.body.appendChild melakukannya dari jawaban Alex McMillan.
What Would Be Cool
6
Anda mungkin tidak mengikat this.instanceref di dalam metode render Anda. Saya telah menambahkan tautan demo untuk menunjukkannya berfungsi
sidonaldson
1
@ShubhamKushwah Anda harus melakukan rendering sisi server?
ArrayKnight
@ArrayKnight ya saya tahu kemudian bahwa di server objek ini tidak ada: document, window. Jadi saya lebih suka menggunakan globalpaket npm
Shubham Kushwah
apa perlunya s.async = benar, saya tidak dapat menemukan referensi tentang itu, untuk mengetahui tujuannya, dapatkah Anda menjelaskannya
sasha romanov
50

Cara favorit saya adalah menggunakan React Helmet - ini adalah komponen yang memungkinkan manipulasi kepala dokumen dengan mudah dengan cara yang mungkin sudah biasa Anda lakukan.

misalnya

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

AC Patrice
sumber
5
Sejauh ini, ini adalah solusi terbaik.
paqash
4
Sayangnya, ini tidak berfungsi ... Lihat codesandbox.io/s/l9qmrwxqzq
Darkowic
2
@Darkowic, aku kode Anda bekerja dengan menambahkan async="true"ke <script>tag yang ditambahkan jQuery untuk kode.
Soma Mbadiwe
@SomaMbadiwe mengapa ini berhasil async=truedan gagal tanpanya?
Webwoman
3
Sudah mencoba ini, tidak berhasil untukku. Saya tidak akan merekomendasikan menggunakan helm reaksi karena satu-satunya alasan bahwa itu menyuntikkan properti tambahan ke dalam skrip yang tidak dapat dihapus. Ini benar-benar memecahkan skrip tertentu, dan pengelola belum memperbaikinya dalam beberapa tahun, dan menolak untuk github.com/nfl/react-helmet/issues/79
Philberg
16

Jika Anda perlu memiliki <script>blok di SSR (rendering sisi-server), pendekatan dengan componentDidMounttidak akan berfungsi.

Anda dapat menggunakan react-safeperpustakaan sebagai gantinya. Kode dalam Bereaksi adalah:

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>
scabbiaza
sumber
12

Jawabannya Alex Mcmillan membantu saya tetapi tidak cukup berhasil untuk tag skrip yang lebih kompleks.

Saya sedikit mengutak-atik jawabannya untuk menghasilkan solusi untuk tag panjang dengan berbagai fungsi yang juga sudah diatur "src".

(Untuk kasus penggunaan saya, skrip harus hidup dalam kepala yang tercermin di sini juga):

  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);
  }
jake2620
sumber
7
Saya tidak mengerti mengapa Anda akan menggunakan Bereaksi sama sekali jika Anda hanya membuang JS inline ke halaman ...?
Alex McMillan
2
Anda perlu menambahkan document.head.removeChild(script);kode Anda, atau Anda akan membuat jumlah tag script yang tak terbatas di html Anda selama pengguna mengunjungi rute halaman ini
sasha romanov
7

Saya membuat komponen Bereaksi untuk kasus khusus ini: https://github.com/coreyleelarson/react-typekit

Hanya perlu memasukkan ID Kit Typekit Anda sebagai alat peraga dan Anda siap melakukannya.

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;
Corey Larson
sumber
3

Ada solusi yang sangat bagus menggunakan 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));
}

Ini berfungsi untuk HTML sewenang-wenang dan juga menyimpan informasi konteks seperti document.currentScript.

Maximilian Hils
sumber
Bisakah Anda berkolaborasi bagaimana cara kerjanya diharapkan, dengan sampel penggunaan? Bagi saya itu tidak bekerja dengan melewati skrip dan tubuh misalnya ..
Alex Efimov
3

Anda dapat menggunakan npm postscribeuntuk memuat skrip di komponen reaksi

postscribe('#mydiv', '<script src="https://use.typekit.net/foobar.js"></script>')
Ashh
sumber
1
Memecahkan masalah saya
RPichioli
1

Anda juga dapat menggunakan helm reaksi

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

Helm mengambil tag HTML biasa dan menghasilkan tag HTML biasa. Ini sederhana, dan Bereaksi ramah pemula.

Muhammad Usama Rabani
sumber
0

untuk banyak skrip, gunakan ini

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')
ben
sumber
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);
}
Marlcg58
sumber
0

Anda dapat menemukan jawaban terbaik di tautan berikut:

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();
};
Prajesh Mishrikotkar
sumber
0

Menurut solusi Alex McMillan , saya memiliki adaptasi berikut.
Lingkungan saya sendiri: React 16.8+, v9 + berikutnya

// tambahkan komponen khusus bernama 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.
}

// Gunakan kompoennt khusus, cukup impor dan gantikan <script>tag huruf kecil lama dengan <Script>tag case unta kustom sudah cukup.
// 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>
menodai
sumber
Ada satu peringatan untuk komponen ini: komponen Script ini hanya dapat menjamin urutan saudara kandungnya sendiri. Jika Anda menggunakan komponen ini beberapa kali dalam beberapa komponen pada halaman yang sama, blok skrip mungkin rusak. Alasannya adalah bahwa semua skrip dimasukkan oleh document.body.appendChild secara terprogram, bukan secara deklaratif. Nah helm pindahkan semua tag script di head tag, yang tidak kita inginkan.
sully
Hei @ sepenuhnya, masalah saya di sini adalah memiliki skrip ditambahkan ke DOM beberapa kali, solusi terbaik yang saya lihat sejauh ini adalah selama Component Unmounting, menghapus elemen anak (yaitu <script>) dari DOM, dan kemudian menjadi ditambahkan lagi ketika komponen sudah terpasang pada DOM (Saya menggunakan react-router-dom dan hanya satu komponen yang membutuhkan skrip ini dari semua komponen saya)
Eazy
0

Sedikit terlambat ke pesta tetapi saya memutuskan untuk membuat yang saya sendiri setelah melihat jawaban @Alex Macmillan dan itu dengan melewati dua parameter tambahan; posisi di mana menempatkan skrip seperti atau dan mengatur async ke true / false, ini dia:

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;

Cara untuk memanggilnya persis sama dengan yang ditunjukkan pada jawaban yang diterima dari pos ini tetapi dengan dua parameter tambahan (lagi):

// First string is your URL
// Second string can be head or body
// Third parameter is true or false.
useScript("string", "string", bool);
Kirasiris
sumber
0

Untuk mengimpor file JS dalam proyek reaksi gunakan commange ini

  1. Pindahkan file JS ke proyek.
  2. impor js ke halaman menggunakan perintah berikut

Sederhana dan mudah.

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

Dalam kasus saya, saya ingin mengimpor file JS ini dalam proyek reaksi saya.

Bathri Nathan
sumber
-3

Solusi tergantung pada skenario. Seperti dalam kasus saya, saya harus memuat embed kalender di dalam komponen reaksi.

Calendly mencari div dan membaca dari data-urlatribut itu dan memuat iframe di dalam div tersebut.

Semuanya baik ketika Anda pertama kali memuat halaman: pertama, div dengan data-url diberikan. Kemudian skrip calendly ditambahkan ke tubuh. Browser mengunduh dan mengevaluasinya dan kami semua pulang dengan senang hati.

Masalah muncul ketika Anda menavigasi lalu kembali ke halaman. Kali ini skrip masih dalam tubuh dan browser tidak mengunduh ulang & mengevaluasi ulang itu.

Memperbaiki:

  1. Saat componentWillUnmountmenemukan dan menghapus elemen skrip. Kemudian saat dipasang kembali, ulangi langkah-langkah di atas.
  2. Masukkan $.getScript. Ini adalah penolong jquery yang bagus yang mengambil skrip URI dan panggilan balik sukses. Setelah skrip dimuat, ia mengevaluasi dan menjalankan panggilan balik sukses Anda. Yang harus saya lakukan adalah di componentDidMount $.getScript(url). renderMetode saya sudah memiliki div kalender. Dan itu bekerja dengan lancar.
Rohan Bagchi
sumber
2
Menambahkan jQuery untuk melakukan ini adalah ide yang buruk, ditambah kasus Anda sangat spesifik untuk Anda. Pada kenyataannya tidak ada yang salah dengan menambahkan skrip Calendly sekali karena saya yakin API memiliki panggilan deteksi ulang. Menghapus dan menambahkan skrip berulang kali tidak benar.
sidonaldson
@sidonaldson jQuery bukan praktik yang buruk jika Anda harus mempertahankan proyek senyawa arsitektur kerangka kerja yang berbeda (dan libs) tidak hanya bereaksi, selain itu kita perlu menggunakan js asli untuk mencapai komponen
AlexNikonov