次の方法で共有


ASP.NET Core Blazor JavaScript の相互運用性 (JS interop) のパフォーマンス向上のためのベストプラクティス

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。

警告

このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft は、ここで提供される情報に関して明示的または黙示的な保証を行いません。

現在のリリースについては、この記事の .NET 9 バージョンを参照してください。

.NET と JavaScript の間の呼び出しには、次の理由から追加のオーバーヘッドが必要です。

  • 呼び出しは非同期です。
  • 既定では、パラメーターと戻り値は JSON でシリアル化され、.NET および JavaScript 型の間でわかりやすい変換メカニズムが提供されます。

さらにサーバー側の Blazor アプリの場合、これらの呼び出しはネットワーク経由で渡されます。

過度に細かい呼び出しを避ける

各呼び出しにはある程度のオーバーヘッドが伴うため、呼び出しの回数を減らすことが有益な場合があります。 ブラウザーの localStorage に項目のコレクションを格納する次のコードについて考えてみます。

private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)
{
    foreach (var item in items)
    {
        await JS.InvokeVoidAsync("localStorage.setItem", item.Id, 
            JsonSerializer.Serialize(item));
    }
}

上の例では、各項目に対して個別の JS 相互運用呼び出しを行います。 代わりに、次の方法では JS 相互運用を 1 回の呼び出しに減らしています。

private async Task StoreAllInLocalStorage(IEnumerable<TodoItem> items)
{
    await JS.InvokeVoidAsync("storeAllInLocalStorage", items);
}

対応する JavaScript 関数では、クライアントの項目のコレクション全体を格納します。

function storeAllInLocalStorage(items) {
  items.forEach(item => {
    localStorage.setItem(item.id, JSON.stringify(item));
  });
}

Blazor WebAssembly アプリの場合、個々の JS 相互運用呼び出しを 1 回の呼び出しにローリングすると、通常、コンポーネントで多数の JS 相互運用呼び出しを行う場合にのみ、パフォーマンスが大幅に向上します。

同期呼び出しの使用を検討する

.NET から JavaScript を呼び出す

このセクションはクライアント側コンポーネントにのみ適用されます。

JS 相互運用呼び出しは、呼び出されたコードが同期であるか非同期であるかに関係なく、非同期となります。 呼び出しが非同期であるのは、サーバー側とクライアント側のレンダリング モードでコンポーネントの互換性を確保するためです。 サーバーでは、すべての JS 相互運用呼び出しはネットワーク接続を介して送信されるため、非同期である必要があります。

コンポーネントが WebAssembly でのみ実行されることが確実にわかっている場合は、同期 JS 相互運用呼び出しを行うように選択できます。 これにより、非同期呼び出しを行う場合よりもオーバーヘッドがわずかに減少し、レンダリング サイクルが少なくなる可能性があります。これは、結果を待機する間の中間状態が存在しないためです。

クライアント側コンポーネントで .NET から JavaScript への同期呼び出しを行うには、IJSRuntimeIJSInProcessRuntime にキャストして JS 相互運用呼び出しを行います。

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

.NET 5 以降のクライアント側コンポーネントで IJSObjectReference を使用する場合は、代わりに IJSInProcessObjectReference を同期的に使用できます。 IJSInProcessObjectReferenceIAsyncDisposable/IDisposable を実装しており、次の例に示すように、メモリ リークを防ぐためにガベージ コレクションに破棄する必要があります。

@inject IJSRuntime JS
@implements IDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var jsInProcess = (IJSInProcessRuntime)JS;
            module = await jsInProcess.Invoke<IJSInProcessObjectReference>("import", 
                "./scripts.js");
            var value = module.Invoke<string>("javascriptFunctionIdentifier");
        }
    }

    ...

    void IDisposable.Dispose()
    {
        if (module is not null)
        {
            await module.Dispose();
        }
    }
}

前の例では、JSDisconnectedException アプリに Blazor-SignalR 接続がないため、モジュールの破棄中に Blazor WebAssembly がトラップされません。 詳しくは、「ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)」をご覧ください。

JavaScript から .NET を呼び出す

このセクションはクライアント側コンポーネントにのみ適用されます。

JS 相互運用呼び出しは、呼び出されたコードが同期であるか非同期であるかに関係なく、非同期となります。 呼び出しが非同期であるのは、サーバー側とクライアント側のレンダリング モードでコンポーネントの互換性を確保するためです。 サーバーでは、すべての JS 相互運用呼び出しはネットワーク接続を介して送信されるため、非同期である必要があります。

コンポーネントが WebAssembly でのみ実行されることが確実にわかっている場合は、同期 JS 相互運用呼び出しを行うように選択できます。 これにより、非同期呼び出しを行う場合よりもオーバーヘッドがわずかに減少し、レンダリング サイクルが少なくなる可能性があります。これは、結果を待機する間の中間状態が存在しないためです。

クライアント側コンポーネントで JavaScript から .NET への同期呼び出しを行うには、DotNet.invokeMethod ではなく DotNet.invokeMethodAsync を使用します。

同期呼び出しは、次の場合に機能します。

  • コンポーネントは、WebAssembly で実行するためにのみレンダリングされます。
  • 呼び出された関数から同期的に値が返される。 この関数は async メソッドではなく、.NET Task や JavaScript Promise は返されません。

このセクションはクライアント側コンポーネントにのみ適用されます。

JS 相互運用呼び出しは、呼び出されたコードが同期であるか非同期であるかに関係なく、非同期となります。 呼び出しが非同期であるのは、サーバー側とクライアント側のレンダリング モードでコンポーネントの互換性を確保するためです。 サーバーでは、すべての JS 相互運用呼び出しはネットワーク接続を介して送信されるため、非同期である必要があります。

コンポーネントが WebAssembly でのみ実行されることが確実にわかっている場合は、同期 JS 相互運用呼び出しを行うように選択できます。 これにより、非同期呼び出しを行う場合よりもオーバーヘッドがわずかに減少し、レンダリング サイクルが少なくなる可能性があります。これは、結果を待機する間の中間状態が存在しないためです。

クライアント側コンポーネントで .NET から JavaScript への同期呼び出しを行うには、IJSRuntimeIJSInProcessRuntime にキャストして JS 相互運用呼び出しを行います。

@inject IJSRuntime JS

...

@code {
    protected override void HandleSomeEvent()
    {
        var jsInProcess = (IJSInProcessRuntime)JS;
        var value = jsInProcess.Invoke<string>("javascriptFunctionIdentifier");
    }
}

.NET 5 以降のクライアント側コンポーネントで IJSObjectReference を使用する場合は、代わりに IJSInProcessObjectReference を同期的に使用できます。 IJSInProcessObjectReferenceIAsyncDisposable/IDisposable を実装しており、次の例に示すように、メモリ リークを防ぐためにガベージ コレクションに破棄する必要があります。

@inject IJSRuntime JS
@implements IDisposable

...

@code {
    ...
    private IJSInProcessObjectReference? module;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            var jsInProcess = (IJSInProcessRuntime)JS;
            module = await jsInProcess.Invoke<IJSInProcessObjectReference>("import", 
                "./scripts.js");
            var value = module.Invoke<string>("javascriptFunctionIdentifier");
        }
    }

    ...

    void IDisposable.Dispose()
    {
        if (module is not null)
        {
            await module.Dispose();
        }
    }
}

前の例では、JSDisconnectedException アプリに Blazor-SignalR 接続がないため、モジュールの破棄中に Blazor WebAssembly がトラップされません。 詳しくは、「ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)」をご覧ください。

マーシャリングされていない呼び出しの使用を検討する

"このセクションは Blazor WebAssembly アプリにのみ適用されます。"

Blazor WebAssembly で実行する場合、.NET から JavaScript へのマーシャリング解除された呼び出しを行うことができます。 これらは、引数または戻り値の JSON シリアル化を実行しない同期呼び出しです。 .NET と JavaScript の表現の間のメモリ管理と翻訳のすべての側面は、開発者に任されます。

警告

IJSUnmarshalledRuntime を使用すると JS 相互運用アプローチのオーバーヘッドが最小限になりますが、これらの API とのやりとりに必要な JavaScript API は現在ドキュメントに記載されていません。また、今後のリリースでの破壊的変更の対象となることがあります。

function jsInteropCall() {
  return BINDING.js_to_mono_obj("Hello world");
}
@inject IJSRuntime JS

@code {
    protected override void OnInitialized()
    {
        var unmarshalledJs = (IJSUnmarshalledRuntime)JS;
        var value = unmarshalledJs.InvokeUnmarshalled<string>("jsInteropCall");
    }
}

JavaScript [JSImport]/[JSExport] 相互運用の使用

[JSImport] アプリの JavaScript /[JSExport]Blazor WebAssembly 相互運用により、.NET 7 の ASP.NET Core より前のフレームワーク リリースの JS 相互運用 API よりもパフォーマンスと安定性が向上します。

詳細については、「ASP.NET Core Blazor を使用した JavaScript Import/Export 相互運用」をご覧ください。