Thêm thẻ script vào React / JSX


266

Tôi có một vấn đề tương đối đơn giản là cố gắng thêm tập lệnh nội tuyến vào thành phần React. Những gì tôi có cho đến nay:

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

Tôi cũng đã thử:

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

Không có cách tiếp cận dường như để thực hiện các kịch bản mong muốn. Tôi đoán đó là một điều đơn giản tôi đang thiếu. Ai có thể giúp đỡ?

PS: Bỏ qua foobar, tôi có một id thực sự đang sử dụng mà tôi không cảm thấy muốn chia sẻ.


5
Có động lực cụ thể nào để tải cái này qua React thay vì đưa nó vào HTML trang cơ sở của bạn không? Ngay cả khi điều này đã làm việc, điều đó có nghĩa là bạn sẽ chèn lại một tập lệnh mỗi khi thành phần được gắn kết.
loganfsmyth

Có phải vậy không? Tôi giả sử DOM diffing sẽ làm cho điều đó không đúng, nhưng tôi thừa nhận nó sẽ phụ thuộc vào việc thực hiện DocumentTitle.
loganfsmyth

8
Đúng @loganfsmyth, React sẽ không tải lại tập lệnh khi kết xuất lại nếu trạng thái tiếp theo cũng có tập lệnh.
Tối đa

Câu trả lời:


405

Chỉnh sửa: Mọi thứ thay đổi nhanh chóng và điều này đã lỗi thời - xem cập nhật


Bạn có muốn tìm nạp và thực thi tập lệnh nhiều lần, mỗi khi thành phần này được hiển thị hoặc chỉ một lần khi thành phần này được gắn vào DOM?

Có lẽ thử một cái gì đó như thế này:

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

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

    document.body.appendChild(script);
}

Tuy nhiên, điều này chỉ thực sự hữu ích nếu tập lệnh bạn muốn tải không có sẵn dưới dạng mô-đun / gói. Đầu tiên, tôi sẽ luôn luôn:

  • Tìm gói hàng vào npm
  • Tải xuống và cài đặt gói trong dự án của tôi ( npm install typekit)
  • importgói mà tôi cần nó ( import Typekit from 'typekit';)

Đây có thể là cách bạn đã cài đặt các gói reactreact-document-titletừ ví dụ của bạn và có một gói Typekit có sẵn trên npm .


Cập nhật:

Bây giờ chúng ta có các hook, một cách tiếp cận tốt hơn có thể là sử dụng useEffectnhư vậy:

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

Điều này làm cho nó trở thành một ứng cử viên tuyệt vời cho một hook tùy chỉnh (ví dụ 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;

Mà có thể được sử dụng như vậy:

import useScript from 'hooks/useScript';

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

  // rest of your component
}

Cảm ơn bạn. Tôi đã suy nghĩ về điều này sai. Tôi đã đi trước với việc thực hiện này. Chỉ cần thêm try{Typekit.load({ async: true });}catch(e){}sauappendChild
ArrayKnight

2
Tôi quyết định rằng việc triển khai "nâng cao" từ TypeKit phù hợp hơn với phương pháp này.
ArrayKnight

10
Điều này không hoạt động - để tải tập lệnh, nhưng làm thế nào tôi có thể truy cập vào mã trong tập lệnh. Ví dụ: tôi muốn gọi một hàm nằm bên trong tập lệnh, nhưng tôi không thể gọi nó bên trong thành phần nơi tập lệnh được tải.
zero_cool

2
Khi tập lệnh được thêm vào trang, nó sẽ được thực thi như bình thường. Ví dụ: nếu bạn đã sử dụng phương pháp này để tải xuống jQuery từ CDN, thì sau khi componentDidMounthàm đã tải xuống và nối thêm tập lệnh vào trang, bạn sẽ có jQuery$các đối tượng có sẵn trên toàn cầu (ví dụ: bật window).
Alex McMillan

1
Tôi đã gặp một vấn đề tương tự khi sử dụng tập lệnh xác thực và hóa ra rằng có thể tốt hơn nếu đưa nó vào tệp html của lớp gốc bên trên lớp App.js phản ứng của bạn. Trong trường hợp bất cứ ai tìm thấy điều này hữu ích. Như @loganfsmith đã đề cập ...
devssh

57

Hơn nữa với các câu trả lời ở trên, bạn có thể làm điều này:

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

Các div bị ràng buộc thisvà kịch bản được đưa vào nó.

Bản demo có thể được tìm thấy trên scriptsandbox.io


5
this.instance không làm việc cho tôi, nhưng document.body.appendChild đã làm từ câu trả lời của Alex McMillan.
Điều gì sẽ tuyệt vời

6
Bạn có thể đã không liên kết this.instancevới ref trong phương thức kết xuất của bạn. Tôi đã thêm một liên kết demo để hiển thị nó hoạt động
sidonaldson

1
@ShubhamKushwah Bạn phải thực hiện kết xuất phía máy chủ?
ArrayKnight

@ArrayKnight vâng tôi phát hiện ra sau đó trên máy chủ các đối tượng này không tồn tại: document, window. Vì vậy, tôi thích sử dụng globalgói
npm

nhu cầu của s.async = true là gì, tôi không thể tìm thấy bất kỳ tài liệu tham khảo nào về nó, để biết mục đích của nó, bạn có thể giải thích nó không
sasha romanov

50

Cách ưa thích của tôi là sử dụng React Mũ bảo hiểm - đó là một thành phần cho phép thao tác dễ dàng đầu tài liệu theo cách mà bạn có thể đã quen.

ví dụ

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-rcmet


5
Đây là giải pháp tốt nhất.
paqash

4
Thật không may, nó không hoạt động ... Xem mãandbox.io / s / l9qmrwxqzq
Darkowic

2
@Darkowic, tôi đã làm cho mã của bạn hoạt động bằng cách thêm async="true"vào <script>thẻ đã thêm jQuery vào mã.
Soma Mbadiwe

@SomaMbadiwe tại sao nó hoạt động async=truevà thất bại mà không có nó?
Webdess

3
Đã thử điều này, không làm việc cho tôi. Tôi không khuyên bạn nên sử dụng mũ bảo hiểm phản ứng vì lý do duy nhất là nó tiêm các thuộc tính bổ sung vào tập lệnh không thể xóa được. Điều này thực sự phá vỡ một số tập lệnh nhất định và các nhà bảo trì đã không sửa nó trong nhiều năm và từ chối github.com/nfl/react-helmet/issues/79
Philberg

16

Nếu bạn cần có <script>khối trong SSR (kết xuất phía máy chủ), một cách tiếp cận componentDidMountsẽ không hiệu quả.

Bạn có thể sử dụng react-safethư viện thay thế. Mã trong React sẽ là:

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

Câu trả lời Alex Mcmillan cung cấp đã giúp tôi nhiều nhất nhưng không hoàn toàn hiệu quả đối với thẻ script phức tạp hơn.

Tôi hơi điều chỉnh câu trả lời của anh ấy để đưa ra giải pháp cho một thẻ dài với nhiều chức năng khác nhau đã được đặt "src".

(Đối với trường hợp sử dụng của tôi, tập lệnh cần để sống trong đầu cũng được phản ánh ở đây):

  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
Tôi không hiểu tại sao bạn lại sử dụng React nếu bạn chỉ bỏ JS nội tuyến lên trang ...?
Alex McMillan

2
bạn cần thêm document.head.removeChild(script);mã của mình hoặc bạn sẽ tạo vô số thẻ script tại html của mình miễn là người dùng truy cập tuyến đường trang này
sasha romanov

7

Tôi đã tạo một thành phần React cho trường hợp cụ thể này: https://github.com/coreyleelarson/react-typekit

Chỉ cần chuyển ID ID Bộ công cụ của bạn làm chỗ dựa và bạn sẽ ổn.

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

Có một cách giải quyết rất tốt đẹp 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));
}

Điều này hoạt động cho HTML tùy ý và cũng giữ lại thông tin ngữ cảnh như document.currentScript.


Bạn có thể hợp tác làm thế nào nó được mong đợi để làm việc, với mẫu sử dụng? Đối với tôi nó không hoạt động với việc chuyển tập lệnh và cơ thể chẳng hạn ..
Alex Efimov

3

Bạn có thể sử dụng npm postscribeđể tải tập lệnh trong thành phần phản ứng

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

1
Giải quyết vấn đề của tôi
RPichioli

1

Bạn cũng có thể sử dụng mũ bảo hiểm phản ứng

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

Mũ bảo hiểm lấy các thẻ HTML đơn giản và xuất ra các thẻ HTML đơn giản. Thật đơn giản, và React thân thiện với người mới bắt đầu.


0

cho nhiều tập lệnh, sử dụng cái này

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

Bạn có thể tìm thấy câu trả lời tốt nhất tại liên kết sau:

https://cleverbeagle.com/blog/articles/tutorial-how-to-load-third-party-scripts-dynamical-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

Theo giải pháp của Alex McMillan , tôi có sự thích nghi sau đây.
Môi trường của riêng tôi: Phản ứng 16.8+, v9 + tiếp theo

// thêm một thành phần tùy chỉnh có tên Script
// hook / 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.
}

// Sử dụng compoennt tùy chỉnh, chỉ cần nhập nó và thay thế <script>thẻ chữ thường cũ bằng <Script>thẻ trường hợp lạc đà tùy chỉnh sẽ đủ.
// 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>

Có một cảnh báo cho thành phần này: thành phần Script này chỉ có thể đảm bảo thứ tự của anh chị em của nó. Nếu bạn sử dụng thành phần này nhiều lần trong nhiều thành phần của cùng một trang, các khối tập lệnh có thể bị lỗi. Lý do là tất cả các tập lệnh được chèn bởi document.body.appendChild theo chương trình, thay vì khai báo. Mũ bảo hiểm cũng di chuyển tất cả các thẻ script trong thẻ head, điều mà chúng tôi không muốn.
hờn dỗi

Xin chào @sully, vấn đề của tôi ở đây là có tập lệnh được thêm vào DOM một cách nghiêm túc, giải pháp tốt nhất tôi thấy cho đến nay là trong phần Unmounting, loại bỏ phần tử con (ví dụ: <script>) khỏi DOM, và sau đó là được thêm lại khi thành phần được gắn trên DOM (Tôi đang sử dụng Reac-router-dom và chỉ một thành phần cần tập lệnh này của tất cả các thành phần của tôi)
Eazy

0

Đến bữa tiệc muộn một chút nhưng tôi quyết định tự tạo một cái sau khi xem câu trả lời của @Alex Macmillan và đó là bằng cách chuyển hai tham số phụ; vị trí để đặt các tập lệnh như hoặc và thiết lập async thành true / false, đây là:

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;

Cách gọi nó hoàn toàn giống như trong câu trả lời được chấp nhận của bài đăng này nhưng có thêm hai tham số (một lần nữa):

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

0

Để nhập tệp JS trong dự án phản ứng, hãy sử dụng commange này

  1. Di chuyển tệp JS vào dự án.
  2. nhập js vào trang bằng lệnh sau

Thật đơn giản và dễ dàng.

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

Trong trường hợp của tôi, tôi muốn nhập các tệp JS này trong dự án phản ứng của mình.


-3

Giải pháp phụ thuộc vào kịch bản. Giống như trong trường hợp của tôi, tôi đã phải tải một cách lịch sự vào bên trong một thành phần phản ứng.

Thường xuyên tìm kiếm một div và đọc từ data-urlthuộc tính của nó và tải iframe bên trong div đã nói.

Tất cả đều tốt khi bạn tải trang đầu tiên: đầu tiên, div với data-urlđược hiển thị. Sau đó, kịch bản lịch được thêm vào cơ thể. Trình duyệt tải xuống và đánh giá nó và tất cả chúng tôi về nhà hạnh phúc.

Vấn đề xảy ra khi bạn điều hướng đi và sau đó quay trở lại trang. Lần này tập lệnh vẫn còn trong thân và trình duyệt không tải xuống lại và đánh giá lại nó.

Sửa chữa:

  1. Trên componentWillUnmounttìm và loại bỏ các yếu tố kịch bản. Sau đó trên re mount, lặp lại các bước trên.
  2. Nhập $.getScript. Nó là một trình trợ giúp jquery tiện lợi, lấy URI script và gọi lại thành công. Khi tập lệnh được tải, nó sẽ đánh giá nó và thực hiện cuộc gọi lại thành công của bạn. Tất cả tôi phải làm là trong tôi componentDidMount $.getScript(url). renderPhương pháp của tôi đã có div lịch. Và nó hoạt động trơn tru.

2
Thêm jQuery để làm điều này là một ý tưởng tồi, cộng với trường hợp của bạn là rất cụ thể đối với bạn. Trong thực tế, không có gì sai khi thêm tập lệnh Calendly một lần vì tôi chắc chắn API có cuộc gọi phát hiện lại. Loại bỏ và thêm một tập lệnh nhiều lần là không chính xác.
sidonaldson

@sidonaldson jQuery không phải là một thực tiễn tồi nếu bạn phải duy trì một dự án hợp chất kiến ​​trúc của nó với các khung khác nhau (và lib) không chỉ phản ứng, chúng ta cần sử dụng js gốc để tiếp cận các thành phần
AlexNikonov
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.