1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Blazor WebAssembly Client】Javascriptコードをモジュール化して呼び出す

1
Last updated at Posted at 2023-01-12

サンプルコード

状況

index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>BlazorApp1</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <link href="BlazorApp1.Client.styles.css" rel="stylesheet" />
    <script type="text/javascript">
        var temp;
        function add(a, b) {
            return a + b;
        }
        function subtract(a, b) {
            return a - b;
        }
        function multiply(a, b) {
            return a * b;
        }
        function divide(a, b) {
            return a / b;
        }
        function push(val) {
            temp = val;
        }
        function pop() {
            return temp;
        }
    </script>
</head>

<body>
    <div id="app">
        <svg class="loading-progress">
            <circle r="40%" cx="50%" cy="50%" />
            <circle r="40%" cx="50%" cy="50%" />
        </svg>
        <div class="loading-progress-text"></div>
    </div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
</body>

</html>
Index.razor
@page "/"
@inject IJSRuntime JsRuntime;

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<p>1 + 1 = @sum</p>
<p>6 - 2 = @difference</p>
<p>11.1 * 3 = @product</p>
<p>33.3 / 3 = @quotient</p>

<SurveyPrompt Title="How is Blazor working for you?" />

@code {
    private double sum;
    private double difference;
    private double product;
    private double quotient;
    protected override async Task OnInitializedAsync()
    {
        sum = await JsRuntime.InvokeAsync<double>("add", 1, 1);
        difference = await JsRuntime.InvokeAsync<double>("subtract", 6, 2);
        product = await JsRuntime.InvokeAsync<double>("multiply", 11.1, 3);
        quotient = await JsRuntime.InvokeAsync<double>("divide", 33.3, 3);
        await base.OnInitializedAsync();
    }
}

javascriptのコードを次々に書いているとどうしてもモジュール化まで手が回らない状況が予想されます。上記のコードはその状況を再現したコードとなっており、index.html上に6つの関数と1つのグローバル変数が展開されています。これがもし増えたらどうなるでしょうか。。。それはindex.htmlの肥大化です!index.html上にjavascriptコードを書かれてしまうと、そのコードをユニットテストするのが難しくなるので避けるべきです。以下の手順でモジュール化しましょう♫

なお、モジュール化する際に、Mochaでテストできるようにするために、CommonJSモジュール化(module.exportsを使う)を施します。

Javascriptコードをモジュール化する手順

  1. モジュール化したい関数やグローバル変数を新しいファイルにまとめる

    手始めにモジュール化したい関数とグローバル変数を新しいファイル(今回はjs/module.jsとします)に移します。モジュール化により、グローバル変数はグローバルではなくなります。

    index.html
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        <title>BlazorApp1</title>
        <base href="/" />
        <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
        <link href="css/app.css" rel="stylesheet" />
        <link rel="icon" type="image/png" href="favicon.png" />
        <link href="BlazorApp1.Client.styles.css" rel="stylesheet" />
    -   <script type="text/javascript">
    -       var temp;
    -       function add(a, b) {
    -           return a + b;
    -       }
    -       function subtract(a, b) {
    -           return a - b;
    -       }
    -       function multiply(a, b) {
    -           return a * b;
    -       }
    -       function divide(a, b) {
    -           return a / b;
    -       }
    -       function push(val) {
    -           temp = val;
    -       }
    -       function pop() {
    -           return temp;
    -       }
    -   </script>
    +   <script type="text/javascript" src="js/module.js"></script>
    </head>
    
    <body>
        <div id="app">
            <svg class="loading-progress">
                <circle r="40%" cx="50%" cy="50%" />
                <circle r="40%" cx="50%" cy="50%" />
            </svg>
            <div class="loading-progress-text"></div>
        </div>
    
        <div id="blazor-error-ui">
            An unhandled error has occurred.
            <a href="" class="reload">Reload</a>
            <a class="dismiss">🗙</a>
        </div>
        <script src="_framework/blazor.webassembly.js"></script>
    </body>
        
    </html>
    
    js/module.js
    
    
  2. 各パブリックインスタンスフィールドの先頭についている、余計な"var"キーワードを取り除く

    モジュール化すると、変更前のグローバル変数はクラスのパブリックインスタンスフィールドになります。パブリックインスタンスフィールドには前置詞は必要ないので、varがついていたら削除します。

    js/module.js
    -   var temp;
    +   temp;
        function add(a, b) {
            return a + b;
        }
        function subtract(a, b) {
            return a - b;
        }
        function multiply(a, b) {
            return a * b;
        }
        function divide(a, b) {
            return a / b;
        }
        function push(val) {
            temp = val;
        }
        function pop() {
            return temp;
        }
    
  3. 各メソッドの先頭についている、余計な"function"キーワードを取り除く

    こちらも同様、モジュール化する=クラス化するということなのですが、関数はメソッドに変換されるので、メソッドにはfunctionキーワードは必要ないよね、ということで削除します。

    js/module.js
        temp;
    -   function add(a, b) {
    +   add(a, b) {
            return a + b;
        }
    -   function subtract(a, b) {
    +   subtract(a, b) {
            return a - b;
        }
    -   function multiply(a, b) {
    +   multiply(a, b) {
            return a * b;
        }
    -   function divide(a, b) {
    +   divide(a, b) {
            return a / b;
        }
    -   function push(val) {
    +   push(val) {
            temp = val;
        }
    -   function pop() {
    +   pop() {
            return temp;
        }
    
  4. クラス定義で囲む

    モジュールとして公開する部分をクラス定義で囲みます。この時、クラス名は特に制限が加わるといったことはないと思います。たしか。

    js/module.js
    -       temp;
    -       add(a, b) {
    -           return a + b;
    -       }
    -       subtract(a, b) {
    -           return a - b;
    -       }
    -       multiply(a, b) {
    -           return a * b;
    -       }
    -       divide(a, b) {
    -           return a / b;
    -       }
    -       push(val) {
    -           temp = val;
    -       }
    -       pop() {
    -           return temp;
    +       class Module {
    +           temp;
    +           add(a, b) {
    +               return a + b;
    +           }
    +           subtract(a, b) {
    +               return a - b;
    +           }
    +           multiply(a, b) {
    +               return a * b;
    +           }
    +           divide(a, b) {
    +               return a / b;
    +           }
    +           push(val) {
    +               temp = val;
    +           }
    +           pop() {
    +               return temp;
    +           }
            }
    
  5. reduce関数やsome関数で無名関数を使用している場合に、その中でthisを使用している場合は無名関数外部に"const that = this;"を定義して差し替える

    今回の例では採用していなかったのですが、モジュールの公開するメソッド内で、もしreduce関数やsome関数等のいわゆるコレクション関数で無名関数を使用する場合、かつ無名関数内部でthisを使用している場合には一工夫が必要です。別途例を挙げます。

    sample.js
        draw(context, items, ...) {
            this.items = items;
            var count = 0;
            const that = this;
            this.items.some(function (elem) {
                :
                :
                that.drawA(context, ...);
                count++;
                return false;
            });
            :
        }
    

    この例だとわかりやすいのですが、モジュール化した後の公開するメソッド内でsome関数を使っています。さらに、some関数のパラメーターの無名関数内で、同クラスの別メソッドをコールしています。通常、thisを使うとsome関数のドット演算子の手前のオブジェクトがthisに当てられたかと思います(?)それだと、クラスのメソッドにアクセスできないので、some関数の外でthat変数を定義してやり、thisで初期化します。そして、some関数の中でそれを使ってやればよいのです。

  6. ファイルの終わりに特殊な定義を追記する

    参考:http://dotnsf.blog.jp/archives/1080323559.html
    最後に、ファイル末尾に3行追記すればモジュール化は完了です。

    js/module.js
        class Module {
            temp;
            add(a, b) {
                return a + b;
            }
            subtract(a, b) {
                return a - b;
            }
            multiply(a, b) {
                return a * b;
            }
            divide(a, b) {
                return a / b;
            }
            push(val) {
                temp = val;
            }
            pop() {
                return temp;
            }
        }
        
    +   if (typeof module === 'object') {
    +       module.exports = Module;
    +   }
    

Javascriptモジュールを利用する側 (Blazor WebAssembly Client プロジェクト) のコードの書き方

index.html
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        <title>BlazorApp1</title>
        <base href="/" />
        <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
        <link href="css/app.css" rel="stylesheet" />
        <link rel="icon" type="image/png" href="favicon.png" />
        <link href="BlazorApp1.Client.styles.css" rel="stylesheet" />
        <script type="text/javascript" src="js/module.js"></script>
+       <script type="text/javascript">
+           var singletonObj;
+           function instanciate() {
+               if (singletonObj == null) {
+                   singletonObj = new Module();
+               }
+               return singletonObj;
+           }
+       </script>
    </head>
    
    <body>
        <div id="app">
            <svg class="loading-progress">
                <circle r="40%" cx="50%" cy="50%" />
                <circle r="40%" cx="50%" cy="50%" />
            </svg>
            <div class="loading-progress-text"></div>
        </div>
    
        <div id="blazor-error-ui">
            An unhandled error has occurred.
            <a href="" class="reload">Reload</a>
            <a class="dismiss">🗙</a>
        </div>
        <script src="_framework/blazor.webassembly.js"></script>
    </body>
    
    </html>

Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeAsyncメソッドを使用したいので、別途Nugetで、Microsoft.JSInterop をインストールします。

image.png

あと一息です!!!

Index.razor
    @page "/"
    @inject IJSRuntime JsRuntime;
    
    <PageTitle>Index</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <p>1 + 1 = @sum</p>
    <p>6 - 2 = @difference</p>
    <p>11.1 * 3 = @product</p>
    <p>33.3 / 3 = @quotient</p>
    
    <SurveyPrompt Title="How is Blazor working for you?" />
    
    @code {
+       private IJSObjectReference? _jsClass;
        private double sum;
        private double difference;
        private double product;
        private double quotient;
        protected override async Task OnInitializedAsync()
        {
-           sum = await JsRuntime.InvokeAsync<double>("add", 1, 1);
-           difference = await JsRuntime.InvokeAsync<double>("subtract", 6, 2);
-           product = await JsRuntime.InvokeAsync<double>("multiply", 11.1, 3);
-           quotient = await JsRuntime.InvokeAsync<double>("divide", 33.3, 3);
+           _jsClass = await JsRuntime.InvokeAsync<IJSObjectReference>("instanciate");
+           sum = await Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeAsync<double>(_jsClass, "add", 1, 1);
+           difference = await Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeAsync<double>(_jsClass, "subtract", 6, 2);
+           product = await Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeAsync<double>(_jsClass, "multiply", 11.1, 3);
+           quotient = await Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeAsync<double>(_jsClass, "divide", 33.3, 3);
            await base.OnInitializedAsync();
        }
    }

image.png

できました!!!

終わりに

ここまで読んでくださった方は、「モジュール化するのはいいんだけど、ユニットテストにはしないの???」と疑問を持つ方もいると思います。なので、次回の記事で「Mochaを使ったユニットテストプロジェクトを作成する」を書きたいと思います。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?