ASP.NET Core Blazor JavaScript 互作性(JS 互作)性能最佳做法

注释

此版本不是本文的最新版本。 要查看当前版本,请参阅本文的.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互操作简化为一次调用:

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 互作调用滚动到单个调用通常只会在组件进行大量 JS 互作调用时显著提高性能。

考虑使用同步调用

从 .NET 调用 JavaScript

本部分仅适用于客户端组件。

JS 互操作调用是异步的,无论调用的代码是同步还是异步。 调用是异步的,以确保组件在服务器端和客户端呈现模式之间都兼容。 在服务器上,所有 JS 互操作调用都必须是异步的,因为它们通过网络连接发送。

如果你确定组件只在 WebAssembly 上运行,则可以选择执行同步 JS 互操作调用。 这比进行异步调用的开销略少,并且可能会导致呈现周期更少,因为在等待结果时没有中间状态。

若要在客户端组件中进行从 .NET 到 JavaScript 的同步调用,请将 IJSRuntime 强制转换为 IJSInProcessRuntime 以进行 JS 互操作调用:

@inject IJSRuntime JS

...

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

在 .NET 5 或更高版本的客户端组件中使用IJSObjectReference时,可以改为同步使用IJSInProcessObjectReferenceIJSInProcessObjectReference 实现 IAsyncDisposable/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 BlazorJavaScript 互操作性(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 的同步调用,请将 IJSRuntime 强制转换为 IJSInProcessRuntime 以进行 JS 互操作调用:

@inject IJSRuntime JS

...

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

在 .NET 5 或更高版本的客户端组件中使用IJSObjectReference时,可以改为同步使用IJSInProcessObjectReferenceIJSInProcessObjectReference 实现 IAsyncDisposable/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 BlazorJavaScript 互操作性(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] 互操作

JavaScript [JSImport]/[JSExport] 互操作对于 Blazor WebAssembly 应用程序,在 ASP.NET Core 于 .NET 7 发布之前的框架版本中提供了改进的性能和稳定性,优于之前的 JS 互操作 API。

有关详细信息,请参阅 JavaScript JSImport/JSExport 与 ASP.NET Core Blazor 互操作