web-dev-qa-db-fra.com

React: la balise de script ne fonctionne pas lorsqu'elle est insérée à l'aide de dangerouslySetInnerHTML

J'essaie de définir le html envoyé depuis mon serveur pour qu'il s'affiche à l'intérieur d'une div en utilisant la propriété dangerouslySetInnerHTML dans React. J'ai également une balise de script à l'intérieur et j'utilise des fonctions définies de la même manière dans ce html. J'ai fait un exemple d'erreur dans JSFiddle ici .

Voici le code de test:

var x = '<html><scr'+'ipt>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</scr'+'ipt><body><p onClick="pClicked()">Hello</p></body></html>';

var Hello = React.createClass({
  displayName: 'Hello',
  render: function() {
    return (<div dangerouslySetInnerHTML={{__html: x}} />);
  }
});

J'ai vérifié et la balise de script est ajoutée au DOM, mais je ne peux pas appeler les fonctions définies dans cette balise de script. Si ce n'est pas la bonne façon, y a-t-il une autre manière par laquelle je peux injecter le contenu de la balise de script.

17
meteors

Voici un peu une sale façon de le faire, un peu d'explication sur ce qui se passe ici, vous extrayez le contenu du script via une expression régulière et ne restituez le html qu'en utilisant react, puis une fois le composant monté, le contenu dans la balise de script est exécuté sur une portée globale.

var x = '<html><scr'+'ipt>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</scr'+'ipt><body><p onClick="pClicked()">Hello</p></body></html>';

var extractscript=/<script>(.+)<\/script>/gi.exec(x);
x=x.replace(extractscript[0],"");

var Hello = React.createClass({
  displayName: 'Hello',
  componentDidMount: function() {
    // this runs the contents in script tag on a window/global scope
    window.eval(extractscript[1]);

  },
  render: function() {
    return (<div dangerouslySetInnerHTML={{__html: x}} />);
  }
});

ReactDOM.render(
  React.createElement(Hello),
  document.getElementById('container')
);
14
Dasith

Je ne pense pas que vous ayez besoin d'utiliser la concaténation (+) ici.

var x = '<html><scr'+'ipt>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</scr'+'ipt><body><p onClick="pClicked()">Hello</p></body></html>';

Je pense que vous pouvez simplement faire:

var x = '<html><script>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</script><body><p onClick="pClicked()">Hello</p></body></html>';

Puisqu'il est passé à dangerouslySetInnerHTML de toute façon.

Mais revenons à la question. Vous n'avez pas besoin d'utiliser l'expression régulière pour accéder au contenu de la balise de script. Si vous ajoutez l'attribut id, par exemple <script id="myId">...</script>, vous pouvez facilement accéder à l'élément.

Voyons un exemple d'une telle implémentation.

const x = `
  <html>
    <script id="myScript">
      alert("this.is.sparta");
      function pClicked() {console.log("p is clicked");}
    </script>
    <body>
      <p onClick="pClicked()">Hello</p>
    </body>
  </html>
`;

const Hello = React.createClass({

  displayName: 'Hello',

  componentDidMount() {
    const script = document.getElementById('myScript').innerHTML;
    window.eval(script);
  }

  render() {
    return <div dangerouslySetInnerHTML={{__html: x}} />;
  }

});

Si vous disposez de plusieurs scripts, vous pouvez ajouter un attribut de données [data-my-script] par exemple, puis accédez-y à l'aide de jQuery:

const x = `
  <html>
    <script data-my-script="">
      alert("this.is.sparta");
      function pClicked() {console.log("p is clicked");}
    </script>
    <script data-my-script="">
      alert("another script");
    </script>
    <body>
      <p onClick="pClicked()">Hello</p>
    </body>
  </html>
`;

const Hello = React.createClass({

  constructor(props) {
    super(props);

    this.helloElement = null;
  }

  displayName: 'Hello',

  componentDidMount() {
    $(this.helloElement).find('[data-my-script]').each(function forEachScript() {
      const script = $(this).text();
      window.eval(script);
    });
  }

  render() {
    return (
      <div
        ref={helloElement => (this.helloElement = helloElement)} 
        dangerouslySetInnerHTML={{__html: x}} 
      />
    );
  }

});

Dans tous les cas, il est toujours bon d'éviter d'utiliser eval, donc une autre option est d'obtenir le texte et d'ajouter une nouvelle balise de script avec le contenu du script d'origine au lieu d'appeler eval. Cette réponse suggère une telle approche

5
emil.c

une petite extension pour la réponse de Dasith pour les vues futures ...

J'ai eu un problème très similaire mais dans mon cas, j'ai obtenu le code HTML côté serveur et cela a pris un certain temps (partie de la solution de génération de rapports où le backend rendra le rapport en html)

donc ce que j'ai fait était très similaire mais j'ai géré le script exécuté dans la fonction componentWillMount ():

import React from 'react';
import jsreport from 'jsreport-browser-client-dist'
import logo from './logo.svg';
import './App.css';

class App extends React.Component {
    constructor() {
        super()
        this.state = {
            report: "",
            reportScript: ""
        }
    }

    componentWillMount() {
        jsreport.serverUrl = 'http://localhost:5488';
        let reportRequest = {template: {shortid: 'HJH11D83ce'}}
        // let temp = "this is temp"
        jsreport.renderAsync(reportRequest)
            .then(res => {
                let htmlResponse = res.toString()
                let extractedScript = /<script>[\s\S]*<\/script>/g.exec(htmlResponse)[0];
                // console.log('html is: ',htmlResponse)
                // console.log('script is: ',extractedScript)
                this.setState({report: htmlResponse})
                this.setState({reportScript: extractedScript})
            })
    }

    render() {
        let report = this.state.report
        return (
            <div className="App">
                <div className="App-header">
                    <img src={logo} className="App-logo" alt="logo"/>
                    <h2>Welcome to React</h2>
                </div>
                <div id="reportPlaceholder">
                    <div dangerouslySetInnerHTML={{__html: report}}/>

                </div>
            </div>
        );
    }

    componentDidUpdate() {
        // this runs the contents in script tag on a window/global scope
        let scriptToRun = this.state.reportScript
        if (scriptToRun !== undefined) {
            //remove <script> and </script> tags since eval expects only code without html tags
            let scriptLines = scriptToRun.split("\n")
            scriptLines.pop()
            scriptLines.shift()
            let cleanScript = scriptLines.join("\n")
            console.log('running script ',cleanScript)
            window.eval(cleanScript)
        }

    }
}

export default App;

j'espère que cela vous sera utile ...

4
Tal Joffe