67182


React.js を用いて Web サイトを開発しています。

モジュール形式で提供されていない、`<script>`
タグで読みこんでグローバルオブジェクト（`window`）に追加されたオブジェクトを利用するタイプの外部スクリプトを、React.js
でうまく扱う方法についての質問です。

たとえば、決済代行業者 PAY.JP が提供する [payjp.js](https://pay.jp/docs/payjs-guidance)
はまさにそのパターンで、素の HTML の場合は以下のようにして利用できます：

[code]

    <!-- 外部スクリプトのロード -->
    <script src="https://js.pay.jp/v2/pay.js"></script>
    <!-- ロードしたスクリプトを用いて各種処理を実行 -->
    <script>
    // スクリプトによってグローバル空間に定義された `Payjp` を用いる
    const payjp = Payjp('API キー')
    payjp.foo() // 何らかの処理
    </script>
    
[/code]

こういった外部スクリプトを React.js で扱う場合、以下のような問題点があると考えます：

  1. `<script>` を書く位置を気を付けないと、コンポーネントのライフサイクルの中でスクリプトが二重にロードされることがある
  2. **グローバル空間にロードが完了したタイミングを動的に検知する必要がある**

1 については、コンポーネントではなくテンプレート HTML 側に `<script>` を記述したり、Next.js
のようなテンプレートがないフレームワークでは Layout コンポーネントの中で `<script>` を記述することで、何とかなると思っています。

**問題は 2 の方です。** このような React の制御外にあるイベントを上手く検知するにはどうすれば良いのでしょうか。よろしくお願いします。


CC BY-SA 4.0
2020-06-01T01:34:18.507
67178
2020-11-10T09:51:51.287
2020-11-10T09:51:51.287
32986
14965
question
javascript html reactjs typescript next.js
umd形式でのみ提供されているブラウザ用ライブラリのReactによる動的読み込みとその完了検知の方法


## 質問中の1の方法

グローバルに定義するしか無いようなライブラリがあった場合、基本的にはこの戦略を取ることが多いです。  
まさにReactやReactDOMをアプリケーションのコードから分離するような場合と同様に扱うことで依存関係を解決します。

[code]

    <!-- 外部スクリプトはアプリケーションのコードより前に読み込む -->
    <script src="react.js" >
    <script src="react-dom.js" >
    <script src="https://js.pay.jp/v2/pay.js" >
    <!-- 実装本体は外部スクリプトが読み込み終わってから実行する -->
    <script src="application.js" >
    
[/code]

この順序で読み込むことが確実であれば`applicatino.js`でglobalに定義された変数にアクセスしても問題ないでしょう。

> を書く位置を気を付けないと、コンポーネントのライフサイクルの中でスクリプトが二重にロードされることがある

HTMLに直書きであればReactのライフサイクル外なので気にしなくても良いと思います。

## 質問中の2の方法

問題として扱ってる2の方はReactで外部ライブラリをロードする場合に扱うパターンになりますが、この場合は以下のように記述することができます。

[code]

    const loadScript = async (url) => {
      return new Promise((resolve, reject) => {
        const script = document.createElement("script");
        script.src = url;
        script.addEventListener("load", function() {
          resolve();
        });
        script.addEventListener("error", function(e) {
          reject();
        });
        document.body.appendChild(script);
      });
    };
    
    export default function App() {
      const onClick = async () => {
        await loadScript("https://js.pay.jp/v2/pay.js"); // 動的にライブラリを読み込む
        console.log("Loaded: ", window.Payjp);           // 読み込み後に利用可能
      };
      return (
        <div className="App">
          <button onClick={onClick}>Load dynamic library</button>
        </div>
      );
    }
    
[/code]

> ロードが完了したタイミングを動的に検知する必要がある

上記のloadScriptの挿す場所次第ですか、Promiseが解決した後でglobalの変数にアクセスすれば問題ないでしょう。

## その他方法1

payjp.jsはnpmライブラリとしても提供されているようです。おそらくこれを利用するのが最も簡単でしょう。

  * <https://www.npmjs.com/package/payjp>

コード中でimportもしくはrequireすれば利用可能です。

## その他方法2

`https://js.pay.jp/v2/pay.js`自体を直接ダウンロードしてきて、import/requireする方法です。つまり、自前で管理してしまうパターンです。

`pay.js`としてダウンロードしてきたものを保存すると、

[code]

    const onCLick = async () => {
        await import("./pay.js");
        // window.Payjp にアクセス可能
    }
    
[/code]

というようにDynamic Loadできます。

**参考**

  * <https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Import>

これはnext.jsを利用中であればdynamic-importが利用できます。

  * <https://nextjs.org/docs/advanced-features/dynamic-import>


CC BY-SA 4.0
2020-06-01T02:52:14.787
67182
2020-06-01T13:50:21.330
2020-06-01T13:50:21.330
7997
7997
67178
answer