2023/03/03 にある meetup app osaka@7 で話すやつです。
meetupapp.connpass.com
BlazorでちょいちょいJavaScript書くときありますよね、でもちょっとしたやつやからJavaScript別に書くの面倒なんですよねー、でDynamic使ったらラップできるんじゃないかってことでやってみました。Nugetにも公開しててコードはこちら
github.com
こんな感じで書けます。
//JavaScriptを書くためのオブジェクト作成usingvar js =await JS.CreateDymaicRuntimeAsync(); //ここからはdynamicでJavaScript書けます。dynamic window = js.GetWindow(); dynamic button = window.document.createElement("button"); button.innerText ="new button"; dynamic div = window.document.getElementById("parent-div"); div.append(button); //コールバックも書けます button.addEventListener("click", (Action<dynamic>)(e => { string detail = e.detail.toString(); window.console.log($"clicked {detail}"); }));
結構頑張っててコールバックもかけるんですよね。「遅いんじゃないの?」って言われそうですけど、まあそのとおりですねw、一行につき一回くらJavaScriptの呼び出しが入ります。とはいえそんな速くはないけどWebAssemblyだとこれくらいの行数やったら実使用上は問題ないんちゃうかな?
グローバルなやつだけじゃなくてimportもこんな感じで書けます。
usingvar js =await JS.CreateDymaicRuntimeAsync(); //絶対パスを指定してねdynamic mod =await _js!.ImportAsync("/module.js"); //module.js に sum ってメソッドがあるとしてint sum = mod.sum(1, 2, 3, 4);
いい感じの書き心地ですよね。ここまではよかったんですけどね・・・、new と 非同期がいまいち。まずは new
<script> Rectangle: class{ constructor(height, width){this.height = height;this.width = width;}}</script>
usingvar js =await JS.CreateDymaicRuntimeAsync(); dynamic window = js.GetWindow(); //new XXXってやる部分を JSSynctaxで囲むdynamic rect =new JSSyntax(window.Rectangle).New(10, 20); //moduleの場合も同様dynamic mod =await _js!.ImportAsync("/module.js"); //moduleにもRectangleが定義されているとしてdynamic rect2=new JSSyntax(mod.Rectangle).New(10, 20); //あんまりなんでグローバルのクラスはこう書けるようにもしてますvar rect3 = js.New("Rectangle", 1, 2);
JSSyntaxってのが苦肉の策。ここはシンタックスなんですよー、シンタックスに対する操作なんですよーって感じ。非同期も同様に JSSyntax で囲むようにしました。
usingvar js =await JS.CreateDymaicRuntimeAsync(); dynamic window = js.GetWindow(); dynamic div = window.document.getElementById("parent-div"); awaitnew JSSyntax(div.append).InvokeAsync(button); //コールバックも非同期にできます button.addEventListener("click", (Func<dynamic, Task>)(async e => { await Task.CompletedTask; string detail = e.detail.toString(); window.console.log($"clicked {detail}"); }));
でも正直 WebAssemblyの方は別にasyncでなくてもええんちゃう?って思って僕は非同期使ってないけどどうなんやろ?あとmodule使うにせよmoduleのクラスをC#からnewすることってないから(グローバルはjs.New()でいけるようにしたし)僕が使う分には許容範囲の書き心地です。
Blazor.DynamicJS管理下のJavaScriptオブジェクト
dynamicで受けたやつは何かというと以下二つです。
①呼び出し情報
②JavaScriptオブジェクト(numberとかも含む)
②はそれに対して操作することもできるし、それがJsonにできるものであればC#の世界に持ってこれます。
dynamic window = js.GetWindow(); //これはまだJavaScriptの呼び出しは発生していないくて、"window.document"っていう文字列の呼び出し情報dynamic document = window.document; //ここで JavaScript を呼び出して戻り値のボタンは Blazor.DynamicJSのヘルパJavaScriptで管理していてそのIDを返してます。dynamic button = window.document.createElement("button"); //buttonを操作するときはそのIDとプロパティ名とかで呼び出す感じ button.innerText ="new button"; //dynamic以外の型に変換するとシリアライズされて持ってこれるstring innerText = button.innerText;
引数
引数はもともとの IJSRuntime に渡せるもの(Jsonにできるものと ElementReference)と前述したJavaScriptオブジェクトを渡せます。
dynamic button = window.document.createElement("button"); dynamic div = window.document.getElementById("parent-div"); //引数にJavaScriptのオブジェクトを渡してます。 div.append(button);
実はFriendlyと同じ設計思想
Friendlyは別プロセスのメソッドをリフレクションで呼び出して、これはJavaScriptに対して同じ思想でやってるだけでした。オブジェクトの管理とかシリアライズとか引数のルールとかも実は同じです。
連載物です。
「ちょっとやってみるかなー」って軽く始めたけど結構ガッツリ実装ました。これは dynamic を使って緩く書く書き方なんですけど、DispatchProxy で型を決めて呼び出す方式も実装しちゃったんですよね。なんであと何回か書きます。内部のつくりとかも書けたら書きます。