SPAで外部スクリプトを動的にロードする方法


何をするか

SPAで外部から読み込むスクリプトのコード<script>~~~</sccript>をページによって切り替えれるかを調べたのでまとめます。

↓このような感じです。

react-headerでタイトルを変更する要領で動的にロードさせようとしましたが、発火させることができなかったため以下のような方法を取りました。
サイト全体で使う時はグローバルにおけばいいのですが、使用箇所が限定される場合はこのような使い方をしてみてもいいのかもと思います

構成

PageAとPageBがあり、PageBでのみ外部スクリプトを読み込むこととします。
(外部スクリプトはalert.jsというalertを出すだけのjsを用意しました)

.
// htmlにrender
├── index.js
└── modules
       //↓react-routerで出しわけ
    ├── App.js
    | // ↓PageAのコンポーネント(外部スクリプトの読み込み無し)
    ├── PageA.js
    | // ↓PageBのコンポーネント(外部スクリプトの読み込みあり)
    ├── PageB.js
    | // ↓別コンポーネントで読み込んだ外部スクリプトを削除する関数
    ├── clearScript.js
    | // ↓scriptタグを動的に作成する関数
    └── loadDynamicScript.js

App.js

react-routerで出しわけのみを記述しています。

app.js
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";

import PageA from "./PageA";
import PageB from "./PageB";

// React-Routerのルーティングのみ
const App = () => {
  return (
    <Router>
      <Switch>
        <Route path="/" exact>
          <PageA />
        </Route>
        <Route path="/B" exact>
          <PageB />
        </Route>
      </Switch>
    </Router>
  );
};

export default App;


PageA.js

PageAのコンポーネント *外部スクリプトの読み込み無し

PageA.js
import React, { useEffect } from "react";
import { BrowserRouter as Router, useHistory } from "react-router-dom";

import { clearScript } from "./clearScript";

// 外部スクリプトの読み込み無し
const PageA = () => {
  const history = useHistory();

  // 別ページで外部スクリプトが読み込まれている場合削除する
  useEffect(() => {
    clearScript();
  }, []);

  return (
    <div>
      <p>this is PageA</p>
      <button onClick={() => history.push("/B")}>Next</button>
    </div>
  );
};

export default PageA;

PageB.js

PageBのコンポーネント *外部スクリプトの読み込みあり
今回はreact-hooksを使いましたが、componentWillMountで呼び出せばOKです!

PageB.js
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, useHistory } from "react-router-dom";

import { loadDynamicScript } from "./loadDynamicScript";

const PageB = () => {
  const [hasScript, setHasScript] = useState(false);

  // 外部スクリプトを動的に読み込む
  useEffect(() => {
    loadDynamicScript(() => {
      setHasScript(true);
    });
  }, [hasScript]);

  const history = useHistory();
  return (
    <div>
      {/* 外部スクリプトの読み込み完了後に表示 */}
      {hasScript ? (
        <div>
          <p>this is PageB</p>
          <button onClick={() => history.goBack()}>Go Back</button>
        </div>
      ) : (
        ""
      )}
    </div>
  );
};

export default PageB;

clearScript.js

別コンポーネントで読み込んだ外部スクリプトを削除する関数です。
PageAに遷移した場合PageBでappendされたスクリプトタグが残るためそちらを削除します。

clearScript.js
// 作成したscriptタグを削除する
export const clearScript = () => {
  const externalScript = document.getElementsByClassName("externalScript");

  if (externalScript) {
    console.log(externalScript);
    while (externalScript.length) {
      externalScript.item(0).remove();
    }
  }
};

loadDynamicScript.js

外部スクリプト(<script></script>タグ)を動的にを作成し、<body></body>の中に挿入する関数を作成します。

loadDynamicScript.js
export const loadDynamicScript = callback => {
  // スクリプトに付与するIDを指定
  const scriptId = "alert";
  const existingScript = document.getElementById(scriptId);

  // scriptががなければ新しく作成する
  if (!existingScript) {
    const script = document.createElement("script");
    script.id = scriptId;
    script.type = "text/javascript";
    // 外部スクリプトのURLはコチラに
    script.src = "./alert.js";
    // 削除するためのtargetとしてclassNameを付与
    script.className = "externalScript";
    // 作成したtagをappend
    document.body.appendChild(script);

    script.onload = () => {
      if (callback) callback();
    };
  }

  if (existingScript && callback) callback();
};

もっといい方法があるのでは??と思いながら書いていました。。
何か良き方法があればご教示頂きたいです

参考記事