サンプルコード
状況
<!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>
@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コードをモジュール化する手順
-
モジュール化したい関数やグローバル変数を新しいファイルにまとめる
手始めにモジュール化したい関数とグローバル変数を新しいファイル(今回は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 -
各パブリックインスタンスフィールドの先頭についている、余計な"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; } -
各メソッドの先頭についている、余計な"function"キーワードを取り除く
こちらも同様、モジュール化する=クラス化するということなのですが、関数はメソッドに変換されるので、メソッドにはfunctionキーワードは必要ないよね、ということで削除します。
js/module.jstemp; - 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; } -
クラス定義で囲む
モジュールとして公開する部分をクラス定義で囲みます。この時、クラス名は特に制限が加わるといったことはないと思います。たしか。
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; + } } -
reduce関数やsome関数で無名関数を使用している場合に、その中でthisを使用している場合は無名関数外部に"const that = this;"を定義して差し替える
今回の例では採用していなかったのですが、モジュールの公開するメソッド内で、もしreduce関数やsome関数等のいわゆるコレクション関数で無名関数を使用する場合、かつ無名関数内部でthisを使用している場合には一工夫が必要です。別途例を挙げます。
sample.jsdraw(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関数の中でそれを使ってやればよいのです。
-
ファイルの終わりに特殊な定義を追記する
参考:http://dotnsf.blog.jp/archives/1080323559.html
最後に、ファイル末尾に3行追記すればモジュール化は完了です。js/module.jsclass 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 プロジェクト) のコードの書き方
<!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 をインストールします。
あと一息です!!!
@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();
}
}
できました!!!
終わりに
ここまで読んでくださった方は、「モジュール化するのはいいんだけど、ユニットテストにはしないの???」と疑問を持つ方もいると思います。なので、次回の記事で「Mochaを使ったユニットテストプロジェクトを作成する」を書きたいと思います。

