共用方式為


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

備註

這不是本文的最新版本。 關於目前版本,請參閱 本文的 .NET 10 版本

警告

此版本的 ASP.NET Core 已不再受支援。 如需詳細資訊,請參閱 .NET 和 .NET Core 支援政策。 如需目前的版本,請參閱 本文的 .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 的 Interop 呼叫。 相反地,下列方法會將 JS Interop 減少為單一呼叫:

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 Interop 呼叫合併為單一呼叫,通常只有在元件進行大量 JS Interop 呼叫時,才會顯著提升效能。

請考慮使用同步呼叫

從 .NET 呼叫 JavaScript

本節僅適用於用戶端元件。

不論所呼叫的程式碼是同步還是非同步的,JS Interop 通話都是非同步的。 為了確保元件能在伺服器端和客戶端呈現模式中相容,呼叫會以非同步方式進行。 在伺服器上,所有 JS Interop 呼叫都必須以非同步方式進行,因為這些呼叫會透過網路連線來傳送。

如果您確定元件只會在 WebAssembly 上執行,則可以選擇進行同步的 JS Interop 呼叫。 進行同步呼叫的額外負荷會略低於進行非同步呼叫,而且可減少轉譯週期,因為在等候結果時不會有中繼狀態。

若要在用戶端元件中進行從 .NET 到 JavaScript 的同步呼叫,請將 IJSRuntime 轉換成 IJSInProcessRuntime 以進行 JS Interop 呼叫:

@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 Blazor JavaScript 互通性 (JS Interop)

從 JavaScript 呼叫 .NET

本節僅適用於用戶端元件。

不論所呼叫的程式碼是同步還是非同步的,JS Interop 通話都是非同步的。 為了確保元件能在伺服器端和客戶端呈現模式中相容,呼叫會以非同步方式進行。 在伺服器上,所有 JS Interop 呼叫都必須以非同步方式進行,因為這些呼叫會透過網路連線來傳送。

如果您確定元件只會在 WebAssembly 上執行,則可以選擇進行同步的 JS Interop 呼叫。 進行同步呼叫的額外負荷會略低於進行非同步呼叫,而且可減少轉譯週期,因為在等候結果時不會有中繼狀態。

若要在用戶端元件中從 JavaScript 同步呼叫 .NET,請使用 DotNet.invokeMethod 而非 DotNet.invokeMethodAsync

同步呼叫正常運作的條件是:

  • 元件只會在 WebAssembly 上被渲染以執行。
  • 呼叫的函式會以同步方式傳回值。 函式不是 async 方法,也不會傳回 .NET Task 或 JavaScript Promise

本節僅適用於用戶端元件。

不論所呼叫的程式碼是同步還是非同步的,JS Interop 通話都是非同步的。 為了確保元件能在伺服器端和客戶端呈現模式中相容,呼叫會以非同步方式進行。 在伺服器上,所有 JS Interop 呼叫都必須以非同步方式進行,因為這些呼叫會透過網路連線來傳送。

如果您確定元件只會在 WebAssembly 上執行,則可以選擇進行同步的 JS Interop 呼叫。 進行同步呼叫的額外負荷會略低於進行非同步呼叫,而且可減少轉譯週期,因為在等候結果時不會有中繼狀態。

若要在用戶端元件中進行從 .NET 到 JavaScript 的同步呼叫,請將 IJSRuntime 轉換成 IJSInProcessRuntime 以進行 JS Interop 呼叫:

@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 Blazor JavaScript 互通性 (JS Interop)

請考慮使用未分批的呼叫

本節僅適用於 Blazor WebAssembly 應用程式。

在 Blazor WebAssembly 上執行時,可以從 .NET 對 JavaScript 進行未封送的呼叫。 這些是同步呼叫,不會執行自變數或傳回值的 JSON 串行化。 .NET 與 JavaScript 表示法之間的記憶體管理和翻譯的所有層面都會留給開發人員。

警告

使用 IJSUnmarshalledRuntime 時,JS 的 Interop 方法帶來的額外負擔最小,且目前尚未記載與這些 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] Interop

在.NET 7中,適用於ASP.NET Core應用程式的JavaScript[JSImport]/[JSExport]互操作性Blazor WebAssembly,相比於ASP.NET Core之前框架版本的JS互操作API,提供了更佳的效能和穩定性。

如需更多資訊,請參閱 JavaScript JSImport/JSExport 與 ASP.NET Core 的互操作性 Blazor