Partilhar via


ASP.NET Core Blazor práticas recomendadas de desempenho para interoperabilidade de JavaScript (JS interop)

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

As chamadas entre .NET e JavaScript exigem sobrecarga adicional porque:

  • As chamadas são assíncronas.
  • Parâmetros e valores de retorno são serializados em JSON para fornecer um mecanismo de conversão fácil de entender entre os tipos .NET e JavaScript.

Além disso, para aplicativos de Blazor do lado do servidor, essas chamadas são passadas pela rede.

Evite chamadas excessivamente refinadas

Uma vez que cada chamada envolve alguma sobrecarga, pode ser valioso reduzir o número de chamadas. Considere o seguinte código, que armazena uma coleção de itens no localStoragedo navegador:

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

O exemplo anterior faz uma chamada de interoperabilidade de JS separada para cada item. Em vez disso, a seguinte abordagem reduz o JS interop a uma única chamada:

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

A função JavaScript correspondente armazena toda a coleção de itens no cliente:

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

Para aplicativos Blazor WebAssembly, rolar chamadas individuais de interoperabilidade de JS em uma única chamada geralmente só melhora significativamente o desempenho se o componente fizer um grande número de chamadas de interoperabilidade JS.

Considere o uso de chamadas síncronas

Chamar JavaScript a partir do .NET

Esta seção só se aplica a componentes do lado do cliente.

As chamadas de interop JS são assíncronas, independentemente de o código chamado ser síncrono ou assíncrono. As chamadas são assíncronas para garantir que os componentes sejam compatíveis entre os modos de renderização do lado do servidor e do lado do cliente. No servidor, todas as chamadas de interoperabilidade JS devem ser assíncronas porque são enviadas por uma conexão de rede.

Se tiveres a certeza de que o teu componente só funciona em WebAssembly, podes optar por realizar chamadas de interoperabilidade JS síncronas. Isso tem um pouco menos de sobrecarga do que fazer chamadas assíncronas e pode resultar em menos ciclos de renderização porque não há nenhum estado intermediário enquanto aguarda resultados.

Para efetuar uma chamada síncrona do .NET para JavaScript num componente do lado do cliente, é necessário converter IJSRuntime para IJSInProcessRuntime de modo a realizar a chamada interop JS.

@inject IJSRuntime JS

...

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

Ao trabalhar com componentes do lado do cliente no .NET 5 ou posterior, pode-se usar IJSObjectReference de forma síncrona. IJSInProcessObjectReference implementa IAsyncDisposable/IDisposable e deve ser descartado para coleta de lixo para evitar um vazamento de memória, como demonstra o exemplo a seguir:

@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();
        }
    }
}

No exemplo anterior, um JSDisconnectedException não fica preso durante o descarte do módulo porque não há um circuito Blazor-SignalR em uma aplicação Blazor WebAssembly para perder. Para obter mais informações, consulte Interoperabilidade do JavaScript do ASP.NET Core Blazor (JS interop).

Chamar .NET a partir de JavaScript

Esta seção só se aplica a componentes do lado do cliente.

As chamadas de interop JS são assíncronas, independentemente de o código chamado ser síncrono ou assíncrono. As chamadas são assíncronas para garantir que os componentes sejam compatíveis entre os modos de renderização do lado do servidor e do lado do cliente. No servidor, todas as chamadas de interoperabilidade JS devem ser assíncronas porque são enviadas por uma conexão de rede.

Se tiveres a certeza de que o teu componente só funciona em WebAssembly, podes optar por realizar chamadas de interoperabilidade JS síncronas. Isso tem um pouco menos de sobrecarga do que fazer chamadas assíncronas e pode resultar em menos ciclos de renderização porque não há nenhum estado intermediário enquanto aguarda resultados.

Para fazer uma chamada síncrona de JavaScript para .NET em um componente do lado do cliente, use DotNet.invokeMethod em vez de DotNet.invokeMethodAsync.

As chamadas síncronas funcionam se:

  • O componente só é renderizado para execução em WebAssembly.
  • A função chamada retorna um valor de forma síncrona. A função não é um método async e não retorna um Task .NET ou JavaScript Promise.

Esta seção só se aplica a componentes do lado do cliente.

As chamadas de interop JS são assíncronas, independentemente de o código chamado ser síncrono ou assíncrono. As chamadas são assíncronas para garantir que os componentes sejam compatíveis entre os modos de renderização do lado do servidor e do lado do cliente. No servidor, todas as chamadas de interoperabilidade JS devem ser assíncronas porque são enviadas por uma conexão de rede.

Se tiveres a certeza de que o teu componente só funciona em WebAssembly, podes optar por realizar chamadas de interoperabilidade JS síncronas. Isso tem um pouco menos de sobrecarga do que fazer chamadas assíncronas e pode resultar em menos ciclos de renderização porque não há nenhum estado intermediário enquanto aguarda resultados.

Para efetuar uma chamada síncrona do .NET para JavaScript num componente do lado do cliente, é necessário converter IJSRuntime para IJSInProcessRuntime de modo a realizar a chamada interop JS.

@inject IJSRuntime JS

...

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

Ao trabalhar com componentes do lado do cliente no .NET 5 ou posterior, pode-se usar IJSObjectReference de forma síncrona. IJSInProcessObjectReference implementa IAsyncDisposable/IDisposable e deve ser descartado para coleta de lixo para evitar um vazamento de memória, como demonstra o exemplo a seguir:

@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();
        }
    }
}

No exemplo anterior, um JSDisconnectedException não fica preso durante o descarte do módulo porque não há um circuito Blazor-SignalR em uma aplicação Blazor WebAssembly para perder. Para obter mais informações, consulte Interoperabilidade do JavaScript do ASP.NET Core Blazor (JS interop).

Considere o uso de chamadas não processadas

Esta secção aplica-se apenas a aplicações Blazor WebAssembly.

Ao executar em Blazor WebAssembly, é possível fazer chamadas não organizadas do .NET para o JavaScript. Estas são chamadas síncronas que não realizam a serialização JSON dos argumentos ou dos valores de retorno. Todos os aspetos do gerenciamento de memória e traduções entre representações .NET e JavaScript são deixados para o desenvolvedor.

Advertência

Embora o uso do IJSUnmarshalledRuntime tenha a menor sobrecarga das abordagens de interoperabilidade JS, as APIs JavaScript necessárias para interagir com essas APIs estão atualmente não documentadas e sujeitas a alterações significativas em versões futuras.

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");
    }
}

Usar a interoperabilidade JavaScript [JSImport]/[JSExport]

A interoperabilidade JavaScript [JSImport]/[JSExport] para aplicativos Blazor WebAssembly oferece melhor desempenho e estabilidade em relação à API de interoperabilidade JS em lançamentos anteriores ao ASP.NET Core no .NET 7.

Para obter mais informações, consulte Interoperabilidade JSImport/JSExport JavaScript com ASP.NET Core Blazor.