Prácticas recomendadas para mejorar el rendimiento de la interoperabilidad de JavaScript en ASP.NET Core Blazor (interoperabilidad de JS)

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 10 de este artículo.

Advertencia

Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la política de soporte de .NET y .NET Core. Para la versión actual, consulte la versión de .NET 10 de este artículo.

Las llamadas entre .NET y JavaScript requieren sobrecarga adicional porque:

  • Las llamadas son asincrónicas.
  • Los parámetros y los valores devueltos se serializan mediante JSON para proporcionar un mecanismo de conversión fácil de entender entre los tipos de .NET y JavaScript.

Además, para las aplicaciones del lado del servidor Blazor, estas llamadas circulan a través de la red.

Evitación de llamadas excesivamente detalladas

Dado que cada llamada implica cierta sobrecarga, puede ser útil reducir el número de llamadas. Tenga en cuenta el código siguiente, que almacena una colección de elementos en el explorador localStorage:

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

En el ejemplo anterior se realiza una llamada de interoperabilidad independiente JS para cada elemento. En su lugar, el enfoque siguiente reduce la interoperabilidad de JS a una sola llamada:

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

La función de JavaScript correspondiente almacena toda la colección de elementos en el cliente:

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

En el caso de las aplicaciones Blazor WebAssembly, la conversión de llamadas de interoperabilidad de JS individuales en una sola llamada normalmente solo mejora significativamente el rendimiento si el componente realiza un gran número de llamadas de interoperabilidad de JS.

Considere el uso de llamadas sincrónicas

Llamada a JavaScript desde .NET

Esta sección solo se aplica a los componentes del lado cliente.

JS Las llamadas de interop son asincrónicas, independientemente de si el código al que se llama es sincrónico o asincrónico. Las llamadas son asincrónicas para asegurarse de que los componentes son compatibles entre los modos de representación del lado servidor y del lado cliente. En el servidor, todas las JS llamadas de interoperabilidad deben ser asincrónicas porque se envían a través de una conexión de red.

Si sabe que el componente solo se ejecuta en WebAssembly, puede optar por realizar llamadas de interoperabilidad sincrónicas JS . Esto tiene una sobrecarga ligeramente menor que realizar llamadas asincrónicas y puede dar lugar a menos ciclos de representación porque no hay ningún estado intermedio mientras espera los resultados.

Para realizar una llamada sincrónica de .NET a JavaScript en un componente del lado cliente, convierta IJSRuntime a IJSInProcessRuntime para realizar la llamada de interoperabilidad JS.

@inject IJSRuntime JS

...

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

Al trabajar con IJSObjectReference en componentes del lado cliente de .NET 5 o posteriores, puede usar IJSInProcessObjectReference de forma sincrónica en su lugar. IJSInProcessObjectReference implementa IAsyncDisposable/IDisposable y debe desecharse para la recolección de basura para evitar una pérdida de memoria, como se muestra en el siguiente ejemplo:

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

En el ejemplo anterior, no está atrapado un JSDisconnectedException durante la eliminación del módulo porque no hay ningún circuito de Blazor-SignalR en una aplicación Blazor WebAssembly que perder. Para obtener más información, consulta ASP.NET Core Blazor Interoperabilidad de JavaScript (JS interop).

Llamada a .NET desde JavaScript

Esta sección solo se aplica a los componentes del lado cliente.

JS Las llamadas de interoperabilidad son asincrónicas, independientemente de si el código al que se llama es sincrónico o asincrónico. Las llamadas son asincrónicas para asegurarse de que los componentes son compatibles entre los modos de representación del lado servidor y del lado cliente. En el servidor, todas las JS llamadas de interoperabilidad deben ser asincrónicas porque se envían a través de una conexión de red.

Si sabe que el componente solo se ejecuta en WebAssembly, puede optar por realizar llamadas de interoperabilidad sincrónicas JS . Esto tiene una sobrecarga ligeramente menor que realizar llamadas asincrónicas y puede dar lugar a menos ciclos de representación porque no hay ningún estado intermedio mientras espera los resultados.

Para realizar una llamada sincrónica de JavaScript a .NET en un componente del lado cliente, use DotNet.invokeMethod en lugar de DotNet.invokeMethodAsync.

Las llamadas sincrónicas funcionan si:

  • El componente solo se representa para su ejecución en WebAssembly.
  • La función llamada devuelve un valor sincrónicamente. La función no es un async método y no devuelve .NET Task ni JavaScript Promise.

Esta sección solo se aplica a los componentes del lado cliente.

Las llamadas de interop de JS son asincrónicas, independientemente de si el código al que se llama es sincrónico o asincrónico. Las llamadas son asincrónicas para asegurarse de que los componentes son compatibles entre los modos de representación del lado servidor y del lado cliente. En el servidor, todas las JS llamadas de interoperabilidad deben ser asincrónicas porque se envían a través de una conexión de red.

Si sabe que el componente solo se ejecuta en WebAssembly, puede optar por realizar llamadas de interoperabilidad sincrónicas JS . Esto tiene una sobrecarga ligeramente menor que realizar llamadas asincrónicas y puede dar lugar a menos ciclos de representación porque no hay ningún estado intermedio mientras espera los resultados.

Para realizar una llamada sincrónica de .NET a JavaScript en un componente del lado cliente, convierta IJSRuntime a IJSInProcessRuntime para realizar la llamada de interoperabilidad JS.

@inject IJSRuntime JS

...

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

Al trabajar con IJSObjectReference en componentes del lado cliente de .NET 5 o posteriores, puede usar IJSInProcessObjectReference de forma sincrónica en su lugar. IJSInProcessObjectReference implementa IAsyncDisposable/IDisposable y debe desecharse para la recolección de basura para evitar una pérdida de memoria, como se muestra en el siguiente ejemplo:

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

En el ejemplo anterior, no está atrapado un JSDisconnectedException durante la eliminación del módulo porque no hay ningún circuito de Blazor-SignalR en una aplicación Blazor WebAssembly que perder. Para obtener más información, consulta ASP.NET Core Blazor Interoperabilidad de JavaScript (JS interop).

Considere el uso de llamadas deserializadas

Esta sección solo se aplica a las Blazor WebAssembly aplicaciones.

Cuando se ejecuta en Blazor WebAssembly, es posible realizar llamadas deserializadas de .NET a JavaScript. Se trata de llamadas sincrónicas que no realizan la serialización JSON de argumentos o valores devueltos. Todos los aspectos de la administración de memoria y las traducciones entre representaciones de .NET y JavaScript se dejan al desarrollador.

Advertencia

Aunque el uso de IJSUnmarshalledRuntime es el método de interoperabilidad de JS con menor sobrecarga, las API de JavaScript necesarias para interactuar con estas API no están documentadas en este momento y están sujetas a cambios importantes en versiones 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");
    }
}

Uso de la interoperabilidad [JSImport]/[JSExport] de JavaScript

La interoperabilidad de JavaScript [JSImport]/[JSExport] para aplicaciones Blazor WebAssembly ofrece un mejor rendimiento y estabilidad en comparación con la API de interoperabilidad JS en los lanzamientos de framework anteriores a ASP.NET Core en .NET 7.

Para obtener más información, consulta Interoperabilidad JSImport/JSExport de JavaScript con ASP.NET Core Blazor.