비고
이 기사는 최신 버전이 아닙니다. 현재 릴리스는 이 문서의 .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를 사용하여 작업하는 경우, 대신 동기적으로 IJSInProcessObjectReference을 사용할 수 있습니다. IJSInProcessObjectReference는 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)을 참조하세요.
JavaScript에서 .NET 호출
이 섹션은 클라이언트 쪽 구성 요소에만 적용됩니다.
JS interop 호출은 호출된 코드가 동기 또는 비동기인지 여부에 관계없이 비동기입니다. 호출은 구성 요소가 서버 쪽 및 클라이언트 쪽 렌더링 모드에서 호환되도록 비동기적입니다. 서버에서 모든 JS interop 호출은 네트워크 연결을 통해 전송되므로 비동기적이어야 합니다.
구성 요소가 WebAssembly에서만 실행된다는 것을 확실히 알고 있는 경우 동기 JS interop 호출을 하도록 선택할 수 있습니다. 이렇게 하면 비동기 호출을 수행하는 것보다 오버헤드가 약간 감소하며 결과를 기다리는 동안 중간 상태가 없기 때문에 렌더링 주기가 감소할 수 있습니다.
클라이언트 쪽 구성 요소에서 JavaScript에서 .NET으로 동기 호출을 하려면 DotNet.invokeMethod를 사용하고 DotNet.invokeMethodAsync 대신에 사용하십시오.
동기 호출이 작동하는 경우는 다음과 같습니다.
이 섹션은 클라이언트 쪽 구성 요소에만 적용됩니다.
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를 사용하여 작업하는 경우, 대신 동기적으로 IJSInProcessObjectReference을 사용할 수 있습니다. IJSInProcessObjectReference는 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)을 참조하세요.
마샬링 해제된 호출 사용을 고려하십시오
‘이 섹션은 Blazor WebAssembly 앱에만 적용됩니다.’
Blazor WebAssembly에서 실행될 때, .NET에서 JavaScript로 비구조화된 호출을 수행할 수 있습니다. 해당 동기 호출은 인수 또는 반환 값의 JSON serialization을 수행하지 않습니다. .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 사용
앱용 [JSImport] JavaScript/[JSExport]Blazor WebAssembly interop는 .NET 7에서 Core를 ASP.NET 전에 프레임워크 릴리스에서 interop API보다 향상된 성능과 안정성 JS 을 제공합니다.
자세한 내용은 ASP.NET Core와의 JavaScript JSImport/JSExport interop를 참조하세요 Blazor.
ASP.NET Core