javascript - Lägga till script-tagg i React/JSX


Jag har ett relativt enkelt problem med att försöka lägga till inbäddad skriptning i en React-komponent. Det jag har hittills:

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

Jag har också försökt:

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

Ingen av metoderna verkar köra det önskade skriptet. Jag antar att det är en enkel sak som jag missar. Kan någon hjälpa till?

PS: Ignorera foobar, jag har faktiskt en riktig ID som jag faktiskt använder som jag inte kände för att dela.

Svar #1

Redigera: Saker förändras snabbt och detta är inaktuellt - se uppdatering


Vill du hämta och köra skriptet om och om igen, varje gång den här komponenten renderas, eller bara en gång när den här komponenten monteras i DOM?

Kanske prova något liknande detta:

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

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

    document.body.appendChild(script);
}

Men detta är egentligen bara användbart om skriptet du vill ladda inte finns tillgängligt som ett modul/paket. Först skulle jag alltid:

  • Sök efter paketet på npm
  • Ladda ner och installera paketet i mitt projekt (npm install typekit)
  • importera paketet där jag behöver det (importera Typekit från 'typekit';)

Detta är troligtvis hur du installerade paketen react och react-document-title från ditt exempel, och det finns ett Typekit-paket tillgängligt på npm.


Uppdatering :

Nu när vi har hooks, kan ett bättre tillvägagångssätt vara att använda useEffect på följande sätt:

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

Vilket gör det till en bra kandidat för en anpassad hook (t.ex.: 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;

Vilket kan användas på detta sätt:

import useScript from 'hooks/useScript';

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

  // rest of your component
}

Svar #2

Mitt favorit sätt är att använda React Helmet - det är en komponent som möjliggör enkel hantering av dokumenthuvudet på ett sätt som du förmodligen redan är van vid.

t.ex.

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

Svar #3

Ytterligare till svaren ovan kan du göra detta:

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-elementet är bundet till this och skriptet injiceras i det.

Demo kan hittas på codesandbox.io

Svar #4

Detta svar förklarar varför bakom detta beteende.

Något försök att rendera script-taggen fungerar inte som förväntat:

  1. Att använda script-taggen för externa skript
  2. Använda dangerouslySetInnerHTML

Varför

React DOM (renderaren för React på webben) använder createElement-anrop för att rendera JSX till DOM-element.

createElement använder innerHTML DOM-API:et för att slutligen lägga till dessa i DOM (se koden i React-källan). innerHTML kör inte script-taggen som läggs till av säkerhetsskäl. Och detta är anledningen till att renderingen av script-taggar i React inte fungerar som förväntat.

För hur man använder script-taggar i React, kolla några andra svar på den här sidan.

Svar #5

Om du behöver ha ett <script>-block i SSR (server-side rendering), kommer ett tillvägagångssätt med componentDidMount inte att fungera.

Du kan använda react-safe-biblioteket istället. Koden i React blir:

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>

Svar #6

Svaret Alex Mcmillan gav hjälpte mig mest men fungerade inte riktigt för en mer komplex skript-tagg.

Jag justerade hans svar lite för att komma fram till en lösning för en lång tagg med olika funktioner som dessutom redan satte "src".

(För mitt användningsfall behövde skriptet ligga i huvudet, vilket också återspeglas här):

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

Svar #7

Jag försökte redigera det accepterade svaret av @Alex McMillan men det låter mig inte, så här är ett separat svar där du kan få värdet av det bibliotek du laddar in. En mycket viktig skillnad som folk frågade om och som jag behövde för min implementation med 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

}

användning ser ut som

const PaymentCard = (props) => {
  const { Stripe } = useScript('https://js.stripe.com/v2/', 'Stripe')
}

OBS: Vi sparar biblioteket inuti ett objekt eftersom biblioteket ofta är en funktion och React kommer att köra funktionen när den lagras i state för att kontrollera ändringar -- vilket kommer att bryta bibliotek (som Stripe) som förväntar sig att kallas med specifika argument -- så vi lagrar det i ett objekt för att dölja det från React och skydda biblioteksfunktioner från att bli kallade.

Svar #8

Du kan också använda react helmet

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 tar vanliga HTML-taggar och ger ut vanliga HTML-taggar. Det är jättelätt och nybörjarvänligt för React.

Relaterade artiklar