ASP.NET Core Blazor의 .NET 메서드에서 JavaScript 함수 호출Call JavaScript functions from .NET methods in ASP.NET Core Blazor

Blazor 앱은 .NET 메서드에서 JavaScript 함수를 호출하고 JavaScript 함수에서 .NET 메서드를 호출할 수 있습니다.A Blazor app can invoke JavaScript functions from .NET methods and .NET methods from JavaScript functions. 이러한 시나리오를 JavaScript 상호 운용성(JS interop)이라고 합니다.These scenarios are called JavaScript interoperability (JS interop).

이 문서에서는 .NET에서 JavaScript 함수를 호출하는 방법을 설명합니다.This article covers invoking JavaScript functions from .NET. JavaScript에서 .NET 메서드를 호출하는 방법에 대한 자세한 내용은 ASP.NET Core Blazor의 JavaScript 함수에서 .NET 메서드 호출를 참조하세요.For information on how to call .NET methods from JavaScript, see ASP.NET Core Blazor의 JavaScript 함수에서 .NET 메서드 호출.

예제 코드 살펴보기 및 다운로드 (다운로드 방법)View or download sample code (how to download)

참고

wwwroot/index.html 파일(Blazor WebAssembly) 또는 Pages/_Host.cshtml 파일(Blazor Server)의 닫는 </body> 태그 앞에 JS 파일(<script> 태그)을 추가합니다.Add JS files (<script> tags) before the closing </body> tag in the wwwroot/index.html file (Blazor WebAssembly) or Pages/_Host.cshtml file (Blazor Server). JS interop 메서드가 있는 JS 파일은 Blazor 프레임워크 JS 파일 전에 포함되어야 합니다.Ensure that JS files with JS interop methods are included before Blazor framework JS files.

.NET에서 JavaScript를 호출하려면 IJSRuntime 추상화를 사용합니다.To call into JavaScript from .NET, use the IJSRuntime abstraction. JS interop 호출을 실행하려면 구성 요소에 IJSRuntime 추상화를 주입합니다.To issue JS interop calls, inject the IJSRuntime abstraction in your component. InvokeAsync는 원하는 수의 JSON 직렬화 가능 인수와 함께 호출하려는 JavaScript 함수에 대한 식별자를 사용합니다.InvokeAsync takes an identifier for the JavaScript function that you wish to invoke along with any number of JSON-serializable arguments. 함수 식별자는 전역 범위(window)를 기준으로 합니다.The function identifier is relative to the global scope (window). window.someScope.someFunction을 호출하려는 경우 식별자는 someScope.someFunction입니다.If you wish to call window.someScope.someFunction, the identifier is someScope.someFunction. 호출되기 전에 함수를 등록할 필요는 없습니다.There's no need to register the function before it's called. 반환 형식 T 또한 JSON 직렬화 가능해야 합니다.The return type T must also be JSON serializable. T는 반환되는 JSON 형식에 가장 잘 매핑되는 .NET 형식과 일치해야 합니다.T should match the .NET type that best maps to the JSON type returned.

프라미스를 반환하는 JavaScript 함수는 InvokeAsync를 사용하여 호출됩니다.JavaScript functions that return a Promise are called with InvokeAsync. InvokeAsync는 프라미스의 래핑을 해제하고 프라미스가 대기한 값을 반환합니다.InvokeAsync unwraps the Promise and returns the value awaited by the Promise.

사전 렌더링을 사용하도록 설정한 Blazor Server 앱의 경우 초기 사전 렌더링 중에 JavaScript를 호출할 수 없습니다.For Blazor Server apps with prerendering enabled, calling into JavaScript isn't possible during the initial prerendering. JavaScript interop 호출은 브라우저와의 연결이 설정될 때까지 지연됩니다.JavaScript interop calls must be deferred until after the connection with the browser is established. 자세한 내용은 Blazor Server 앱이 사전 렌더링 중인 경우 검색 섹션을 참조하세요.For more information, see the Detect when a Blazor Server app is prerendering section.

다음 예제는 JavaScript 기반 디코더인 TextDecoder를 기준으로 합니다.The following example is based on TextDecoder, a JavaScript-based decoder. 이 예제에서는 C# 메서드에서 개발자 코드의 요구 사항을 기존 JavaScript API로 오프로드하는 JavaScript 함수를 호출하는 방법을 보여 줍니다.The example demonstrates how to invoke a JavaScript function from a C# method that offloads a requirement from developer code to an existing JavaScript API. JavaScript 함수는 C# 메서드에서 바이트 배열을 수신하고, 배열을 디코딩하고, 표시를 위해 구성 요소에 텍스트를 반환합니다.The JavaScript function accepts a byte array from a C# method, decodes the array, and returns the text to the component for display.

wwwroot/index.html(Blazor WebAssembly) 또는 Pages/_Host.cshtml(Blazor Server)의 <head> 요소 내에 TextDecoder를 사용하여 전달된 배열을 디코딩하고 디코딩된 값을 반환하는 JavaScript 함수를 제공합니다.Inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server), provide a JavaScript function that uses TextDecoder to decode a passed array and return the decoded value:

<script>
  window.convertArray = (win1251Array) => {
    var win1251decoder = new TextDecoder('windows-1251');
    var bytes = new Uint8Array(win1251Array);
    var decodedArray = win1251decoder.decode(bytes);
    console.log(decodedArray);
    return decodedArray;
  };
</script>

앞의 예제에 표시된 코드와 같은 JavaScript 코드는 스크립트 파일에 대한 참조를 사용하여 JavaScript 파일(.js)에서 로드할 수도 있습니다.JavaScript code, such as the code shown in the preceding example, can also be loaded from a JavaScript file (.js) with a reference to the script file:

<script src="exampleJsInterop.js"></script>

다음 구성 요소는The following component:

  • 구성 요소 단추( Convert Array )가 선택된 경우 JS을 사용하여 convertArray JavaScript 함수를 호출합니다.Invokes the convertArray JavaScript function using JS when a component button (Convert Array) is selected.
  • JavaScript 함수를 호출한 후에는 전달된 배열이 문자열로 변환됩니다.After the JavaScript function is called, the passed array is converted into a string. 이 문자열은 표시를 위해 구성 요소로 반환됩니다.The string is returned to the component for display.
@page "/call-js-example"
@inject IJSRuntime JS;

<h1>Call JavaScript Function Example</h1>

<button type="button" class="btn btn-primary" @onclick="ConvertArray">
    Convert Array
</button>

<p class="mt-2" style="font-size:1.6em">
    <span class="badge badge-success">
        @convertedText
    </span>
</p>

@code {
    // Quote (c)2005 Universal Pictures: Serenity
    // https://www.uphe.com/movies/serenity
    // David Krumholtz on IMDB: https://www.imdb.com/name/nm0472710/

    private MarkupString convertedText =
        new MarkupString("Select the <b>Convert Array</b> button.");

    private uint[] quoteArray = new uint[]
        {
            60, 101, 109, 62, 67, 97, 110, 39, 116, 32, 115, 116, 111, 112, 32,
            116, 104, 101, 32, 115, 105, 103, 110, 97, 108, 44, 32, 77, 97,
            108, 46, 60, 47, 101, 109, 62, 32, 45, 32, 77, 114, 46, 32, 85, 110,
            105, 118, 101, 114, 115, 101, 10, 10,
        };

    private async Task ConvertArray()
    {
        var text =
            await JS.InvokeAsync<string>("convertArray", quoteArray);

        convertedText = new MarkupString(text);
    }
}

IJSRuntimeIJSRuntime

IJSRuntime 추상화를 사용하려면 다음 방법 중 하나를 채택합니다.To use the IJSRuntime abstraction, adopt any of the following approaches:

  • Razor 구성 요소(.razor)에 IJSRuntime 추상화를 주입합니다.Inject the IJSRuntime abstraction into the Razor component (.razor):

    @inject IJSRuntime JS
    
    @code {
        protected override void OnInitialized()
        {
            StocksService.OnStockTickerUpdated += stockUpdate =>
            {
                JS.InvokeVoidAsync("handleTickerChanged",
                    stockUpdate.symbol, stockUpdate.price);
            };
        }
    }
    

    wwwroot/index.html(Blazor WebAssembly) 또는 Pages/_Host.cshtml(Blazor Server)의 <head> 요소 내에 handleTickerChanged JavaScript 함수를 제공합니다.Inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server), provide a handleTickerChanged JavaScript function. 이 함수는 JSRuntimeExtensions.InvokeVoidAsync를 사용하여 호출되며 값을 반환하지 않습니다.The function is called with JSRuntimeExtensions.InvokeVoidAsync and doesn't return a value:

    <script>
      window.handleTickerChanged = (symbol, price) => {
        // ... client-side processing/display code ...
      };
    </script>
    
  • IJSRuntime 추상화를 클래스(.cs)에 주입합니다.Inject the IJSRuntime abstraction into a class (.cs):

    public class JsInteropClasses
    {
        private readonly IJSRuntime js;
    
        public JsInteropClasses(IJSRuntime js)
        {
            this.js = js;
        }
    
        public ValueTask<string> TickerChanged(string data)
        {
            return js.InvokeAsync<string>(
                "handleTickerChanged",
                stockUpdate.symbol,
                stockUpdate.price);
        }
    }
    

    wwwroot/index.html(Blazor WebAssembly) 또는 Pages/_Host.cshtml(Blazor Server)의 <head> 요소 내에 handleTickerChanged JavaScript 함수를 제공합니다.Inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server), provide a handleTickerChanged JavaScript function. 이 함수는 JS.InvokeAsync를 사용하여 호출되며 값을 반환합니다.The function is called with JS.InvokeAsync and returns a value:

    <script>
      window.handleTickerChanged = (symbol, price) => {
        // ... client-side processing/display code ...
        return 'Done!';
      };
    </script>
    
  • BuildRenderTree를 사용하여 동적 콘텐츠를 생성하려면 [Inject] 특성을 사용합니다.For dynamic content generation with BuildRenderTree, use the [Inject] attribute:

    [Inject]
    IJSRuntime JS { get; set; }
    

이 항목과 함께 제공되는 클라이언트 쪽 샘플 앱에서는 사용자 입력을 수신하고 환영 메시지를 표시하기 위해 DOM과 상호 작용하는 두 가지 JavaScript 함수를 앱에서 사용할 수 있습니다.In the client-side sample app that accompanies this topic, two JavaScript functions are available to the app that interact with the DOM to receive user input and display a welcome message:

  • showPrompt: 사용자 입력(사용자 이름)을 수락하라는 메시지를 표시하고 호출자에게 이름을 반환합니다.showPrompt: Produces a prompt to accept user input (the user's name) and returns the name to the caller.
  • displayWelcome: 호출자의 환영 메시지를 idwelcome인 DOM 개체에 할당합니다.displayWelcome: Assigns a welcome message from the caller to a DOM object with an id of welcome.

wwwroot/exampleJsInterop.js:wwwroot/exampleJsInterop.js:

window.exampleJsFunctions = {
  showPrompt: function (text) {
    return prompt(text, 'Type your name here');
  },
  displayWelcome: function (welcomeMessage) {
    document.getElementById('welcome').innerText = welcomeMessage;
  },
  returnArrayAsyncJs: function () {
    DotNet.invokeMethodAsync('BlazorWebAssemblySample', 'ReturnArrayAsync')
      .then(data => {
        data.push(4);
          console.log(data);
      });
  },
  sayHello: function (dotnetHelper) {
    return dotnetHelper.invokeMethodAsync('SayHello')
      .then(r => console.log(r));
  }
};

JavaScript 파일을 참조하는 <script> 태그를 wwwroot/index.html 파일(Blazor WebAssembly) 또는 Pages/_Host.cshtml 파일(Blazor Server)에 배치합니다.Place the <script> tag that references the JavaScript file in the wwwroot/index.html file (Blazor WebAssembly) or Pages/_Host.cshtml file (Blazor Server).

wwwroot/index.html (Blazor WebAssembly):wwwroot/index.html (Blazor WebAssembly):

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>Blazor WebAssembly Sample</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />
    <link href="BlazorWebAssemblySample.styles.css" rel="stylesheet" />
</head>

<body>
    <div id="app">Loading...</div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="exampleJsInterop.js"></script>
    <script src="_framework/blazor.webassembly.js"></script>
</body>

</html>

Pages/_Host.cshtml (Blazor Server):Pages/_Host.cshtml (Blazor Server):

@page "/"
@namespace BlazorServerSample.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = null;
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Blazor Server Sample</title>
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />
    <link href="BlazorServerSample.styles.css" rel="stylesheet" />
</head>
<body>
    <component type="typeof(App)" render-mode="ServerPrerendered" />

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="exampleJsInterop.js"></script>
    <script src="_framework/blazor.server.js"></script>
</body>
</html>

<script> 태그를 동적으로 업데이트할 수 없으므로 구성 요소 파일에 <script> 태그를 넣지 마세요.Don't place a <script> tag in a component file because the <script> tag can't be updated dynamically.

.NET 메서드는 IJSRuntime.InvokeAsync을 호출하여 exampleJsInterop.js 파일의 JavaScript 함수와 상호 운용합니다..NET methods interop with the JavaScript functions in the exampleJsInterop.js file by calling IJSRuntime.InvokeAsync.

IJSRuntime 추상화는 Blazor Server 시나리오를 허용하기 위해 비동기적으로 진행됩니다.The IJSRuntime abstraction is asynchronous to allow for Blazor Server scenarios. 앱이 Blazor WebAssembly 앱이고 JavaScript 함수를 동기적으로 호출하려는 경우에는 IJSInProcessRuntime으로 다운캐스트하고 대신 Invoke를 호출합니다.If the app is a Blazor WebAssembly app and you want to invoke a JavaScript function synchronously, downcast to IJSInProcessRuntime and call Invoke instead. 대부분의 JS interop 라이브러리는 비동기 API를 사용하여 모든 시나리오에서 라이브러리를 사용할 수 있도록 하는 것이 좋습니다.We recommend that most JS interop libraries use the async APIs to ensure that the libraries are available in all scenarios.

참고

표준 JavaScript 모듈에서 JavaScript 격리를 사용하도록 설정하려면 Blazor JavaScript 격리 및 개체 참조 섹션을 참조하세요.To enable JavaScript isolation in standard JavaScript modules, see the Blazor JavaScript isolation and object references section.

샘플 앱에는 JS interop를 시연하기 위한 구성 요소가 포함되어 있습니다.The sample app includes a component to demonstrate JS interop. 이 구성 요소는 다음을 수행합니다.The component:

  • JavaScript 프롬프트를 통해 사용자 입력을 받습니다.Receives user input via a JavaScript prompt.
  • 처리를 위해 구성 요소에 텍스트를 반환합니다.Returns the text to the component for processing.
  • DOM과 상호 작용하여 환영 메시지를 표시하는 두 번째 JavaScript 함수를 호출합니다.Calls a second JavaScript function that interacts with the DOM to display a welcome message.

Pages/JsInterop.razor:Pages/JsInterop.razor:

@page "/JSInterop"
@using {APP ASSEMBLY}.JsInteropClasses
@inject IJSRuntime JS

<h1>JavaScript Interop</h1>

<h2>Invoke JavaScript functions from .NET methods</h2>

<button type="button" class="btn btn-primary" @onclick="TriggerJsPrompt">
    Trigger JavaScript Prompt
</button>

<h3 id="welcome" style="color:green;font-style:italic"></h3>

@code {
    public async Task TriggerJsPrompt()
    {
        var name = await JS.InvokeAsync<string>(
                "exampleJsFunctions.showPrompt",
                "What's your name?");

        await JS.InvokeVoidAsync(
                "exampleJsFunctions.displayWelcome",
                $"Hello {name}! Welcome to Blazor!");
    }
}

자리 표시자 {APP ASSEMBLY}는 앱의 앱 어셈블리 이름입니다(예: BlazorSample).The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, BlazorSample).

  1. 구성 요소의 Trigger JavaScript Prompt 단추를 선택하여 TriggerJsPrompt가 실행되면 wwwroot/exampleJsInterop.js 파일에 제공된 JavaScript showPrompt 함수가 호출됩니다.When TriggerJsPrompt is executed by selecting the component's Trigger JavaScript Prompt button, the JavaScript showPrompt function provided in the wwwroot/exampleJsInterop.js file is called.
  2. showPrompt 함수는 HTML로 인코딩되고 구성 요소로 반환되는 사용자 입력(사용자 이름)을 수락합니다.The showPrompt function accepts user input (the user's name), which is HTML-encoded and returned to the component. 이 구성 요소는 지역 변수, name에 사용자 이름을 저장합니다.The component stores the user's name in a local variable, name.
  3. name에 저장된 문자열은 환영 메시지에 통합됩니다.그런 후 이 메시지는 JavaScript 함수, displayWelcome에 전달되고, 이 함수는 환영 메시지를 제목 태그로 렌더링합니다.The string stored in name is incorporated into a welcome message, which is passed to a JavaScript function, displayWelcome, which renders the welcome message into a heading tag.

Void JavaScript 함수 호출Call a void JavaScript function

다음에 JSRuntimeExtensions.InvokeVoidAsync을 사용합니다.Use JSRuntimeExtensions.InvokeVoidAsync for the following:

  • void(0)/void 0 또는 undefined를 반환하는 JavaScript 함수JavaScript functions that return void(0)/void 0 or undefined.
  • .NET에서 JavaScript 호출 결과를 읽을 필요가 없는 경우If .NET isn't required to read the result of a JavaScript call.

Blazor Server 앱을 미리 렌더링 중인 경우 검색Detect when a Blazor Server app is prerendering

이 섹션은 Blazor Server와 Razor 구성 요소를 미리 렌더링한 호스트된 Blazor WebAssembly 앱에 적용합니다.This section applies to Blazor Server and hosted Blazor WebAssembly apps that prerender Razor components.

앱이 미리 렌더링하는 동안, JavaScript를 호출하는 것과 같은 특정 작업은 불가능합니다.While an app is prerendering, certain actions, such as calling into JavaScript, aren't possible. 미리 렌더링된 경우 구성 요소를 다르게 렌더링해야 할 수 있습니다.Components may need to render differently when prerendered.

이러한 호출이 작동하도록 보장된 시점이 될 때까지 JavaScript interop 호출을 지연시키려면 OnAfterRender{Async} 수명 주기 이벤트를 재정의합니다.To delay JavaScript interop calls until a point where such calls are guaranteed to work, override the OnAfterRender{Async} lifecycle event. 이 이벤트는 애플리케이션이 완전히 렌더링된 후에만 호출됩니다.This event is only called after the app is fully rendered.

Pages/PrerenderedInterop1.razor:Pages/PrerenderedInterop1.razor:

@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JSRuntime.InvokeVoidAsync(
                "setElementText1", divElement, "Text after render");
        }
    }
}
@page "/prerendered-interop-1"
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JSRuntime.InvokeVoidAsync(
                "setElementText1", divElement, "Text after render");
        }
    }
}

앞에 나온 예제 코드에서, wwwroot/index.html(Blazor WebAssembly) 또는 Pages/_Host.cshtml(Blazor Server)의 <head> 요소 안에 setElementText1 JavaScript 함수를 제공합니다.For the preceding example code, provide a setElementText1 JavaScript function inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server). 이 함수는 JSRuntimeExtensions.InvokeVoidAsync를 사용하여 호출되며 값을 반환하지 않습니다.The function is called with JSRuntimeExtensions.InvokeVoidAsync and doesn't return a value:

<script>
  window.setElementText1 = (element, text) => element.innerText = text;
</script>

경고

앞에 나온 예제에서 DOM(문서 개체 모델)을 직접 수정한 것은 단지 데모용일 뿐입니다.The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. JavaScript를 사용하여 DOM을 직접 수정할 경우 JavaScript가 Blazor의 변경 내용 추적을 방해할 수 있으므로 대부분의 시나리오에서 권장되지 않습니다.Directly modifying the DOM with JavaScript isn't recommended in most scenarios because JavaScript can interfere with Blazor's change tracking.

참고

앞의 예제에서는 전역 메서드를 사용하여 클라이언트를 오염시킵니다.The preceding example pollutes the client with global methods. 프로덕션 앱에서의 더 나은 방법은 Blazor JavaScript 격리 및 개체 참조를 참조하세요.For a better approach in production apps, see Blazor JavaScript isolation and object references.

예제:Example:

export setElementText1 = (element, text) => element.innerText = text;

다음 구성 요소는 미리 렌더링과 호환되는 방식으로 구성 요소의 초기화 논리의 일부로서 JavaScript interop를 사용하는 방법을 보여 줍니다.The following component demonstrates how to use JavaScript interop as part of a component's initialization logic in a way that's compatible with prerendering. 이 구성 요소는 OnAfterRenderAsync 내부에서 렌더링 업데이트를 트리거하는 것이 가능함을 보여 줍니다.The component shows that it's possible to trigger a rendering update from inside OnAfterRenderAsync. 개발자는 이 시나리오에서 무한 루프를 만들지 않도록 주의해야 합니다.The developer must be careful to avoid creating an infinite loop in this scenario.

JSRuntime.InvokeAsync가 호출될 경우, 구성 요소가 렌더링되기 전까지는 JavaScript 요소가 없으므로 ElementRefOnAfterRenderAsync에서만 사용되고 그 전의 수명 주기 메서드에서는 사용되지 않습니다.Where JSRuntime.InvokeAsync is called, ElementRef is only used in OnAfterRenderAsync and not in any earlier lifecycle method because there's no JavaScript element until after the component is rendered.

JavaScript interop 호출에서 얻은 새 상태로 구성 요소를 다시 렌더링하도록 StateHasChanged가 호출됩니다(자세한 내용은 ASP.NET Core Blazor 구성 요소 렌더링 참조).StateHasChanged is called to rerender the component with the new state obtained from the JavaScript interop call (for more information, see ASP.NET Core Blazor 구성 요소 렌더링). StateHasChangedinfoFromJsnull인 경우에만 호출되기 때문에 이 코드는 무한 루프를 만들지 않습니다.The code doesn't create an infinite loop because StateHasChanged is only called when infoFromJs is null.

Pages/PrerenderedInterop2.razor:Pages/PrerenderedInterop2.razor:

@page "/prerendered-interop-2"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(infoFromJs ?? "No value yet")</strong>
</p>

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string infoFromJs;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && infoFromJs == null)
        {
            infoFromJs = await JSRuntime.InvokeAsync<string>(
                "setElementText2", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop-2"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(infoFromJs ?? "No value yet")</strong>
</p>

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string infoFromJs;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && infoFromJs == null)
        {
            infoFromJs = await JSRuntime.InvokeAsync<string>(
                "setElementText2", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

앞에 나온 예제 코드에서, wwwroot/index.html(Blazor WebAssembly) 또는 Pages/_Host.cshtml(Blazor Server)의 <head> 요소 안에 setElementText2 JavaScript 함수를 제공합니다.For the preceding example code, provide a setElementText2 JavaScript function inside the <head> element of wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server). 이 함수는 IJSRuntime.InvokeAsync를 사용하여 호출되며 다음 값을 반환합니다.The function is called withIJSRuntime.InvokeAsync and returns a value:

<script>
  window.setElementText2 = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

경고

앞에 나온 예제에서 DOM(문서 개체 모델)을 직접 수정한 것은 단지 데모용일 뿐입니다.The preceding example modifies the Document Object Model (DOM) directly for demonstration purposes only. JavaScript를 사용하여 DOM을 직접 수정할 경우 JavaScript가 Blazor의 변경 내용 추적을 방해할 수 있으므로 대부분의 시나리오에서 권장되지 않습니다.Directly modifying the DOM with JavaScript isn't recommended in most scenarios because JavaScript can interfere with Blazor's change tracking.

참고

앞의 예제에서는 전역 메서드를 사용하여 클라이언트를 오염시킵니다.The preceding example pollutes the client with global methods. 프로덕션 앱에서의 더 나은 방법은 Blazor JavaScript 격리 및 개체 참조를 참조하세요.For a better approach in production apps, see Blazor JavaScript isolation and object references.

예제:Example:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

요소에 대한 참조 캡처Capture references to elements

일부 JS interop 시나리오에는 HTML 요소에 대한 참조가 필요합니다.Some JS interop scenarios require references to HTML elements. 예를 들어, UI 라이브러리에는 초기화를 위해 요소 참조가 필요하거나, focus 또는 play와 같은 요소에서 명령 같은 API를 호출해야 할 수 있습니다.For example, a UI library may require an element reference for initialization, or you might need to call command-like APIs on an element, such as focus or play.

다음 방법을 사용하여 구성 요소의 HTML 요소에 대한 참조를 캡처합니다.Capture references to HTML elements in a component using the following approach:

  • HTML 요소에 @ref 특성을 추가합니다.Add an @ref attribute to the HTML element.
  • 해당 이름이 @ref 특성 값과 일치하는 ElementReference 형식의 필드를 정의합니다.Define a field of type ElementReference whose name matches the value of the @ref attribute.

다음 예에서는 username <input> 요소에 대한 참조를 캡처하는 방법을 보여 줍니다.The following example shows capturing a reference to the username <input> element:

<input @ref="username" ... />

@code {
    ElementReference username;
}

경고

요소 참조만 사용하여 Blazor와 상호 작용하지 않는 빈 요소의 내용을 변경합니다.Only use an element reference to mutate the contents of an empty element that doesn't interact with Blazor. 이 시나리오는 타사 API가 요소에 콘텐츠를 제공하는 경우에 유용합니다.This scenario is useful when a third-party API supplies content to the element. Blazor는 요소와 상호 작용하지 않으므로 요소의 Blazor 표시와 DOM 간에 충돌이 발생할 가능성이 없습니다.Because Blazor doesn't interact with the element, there's no possibility of a conflict between Blazor's representation of the element and the DOM.

다음 예제에서는 Blazor가 DOM과 상호 작용하여 이 요소의 목록 항목(<li>)을 채우기 때문에 순서가 지정되지 않은 목록(ul)의 콘텐츠를 변경하는 것은 위험 합니다.In the following example, it's dangerous to mutate the contents of the unordered list (ul) because Blazor interacts with the DOM to populate this element's list items (<li>):

<ul ref="MyList">
    @foreach (var item in Todos)
    {
        <li>@item.Text</li>
    }
</ul>

JS interop이 요소 MyList의 내용을 변경하고 Blazor가 요소에 diff를 적용하려고 하면 diff는 DOM과 일치하지 않습니다.If JS interop mutates the contents of element MyList and Blazor attempts to apply diffs to the element, the diffs won't match the DOM.

ElementReference는 JS interop을 통해 JavaScript 코드로 전달됩니다.An ElementReference is passed through to JavaScript code via JS interop. JavaScript 코드는 일반적인 DOM API에서 사용할 수 있는 HTMLElement 인스턴스를 수신합니다.The JavaScript code receives an HTMLElement instance, which it can use with normal DOM APIs. 예를 들어 다음 코드는 요소에 마우스 클릭을 전송할 수 있도록 하는 .NET 확장 메서드를 정의합니다.For example, the following code defines a .NET extension method that enables sending a mouse click to an element:

exampleJsInterop.js:exampleJsInterop.js:

window.interopFunctions = {
  clickElement : function (element) {
    element.click();
  }
}

참고

Blazor 프레임워크에 기본 제공되고 요소 참조에서 작동하는 FocusAsync를 C# 코드에서 사용하여 요소에 포커스를 둡니다.Use FocusAsync in C# code to focus an element, which is built-into the Blazor framework and works with element references.

값을 반환하지 않는 JavaScript 함수를 호출하려면 JSRuntimeExtensions.InvokeVoidAsync를 사용합니다.To call a JavaScript function that doesn't return a value, use JSRuntimeExtensions.InvokeVoidAsync. 다음 코드는 캡처된 ElementReference로 이전 JavaScript 함수를 호출하여 클라이언트 쪽 Click 이벤트를 트리거합니다.The following code triggers a client-side Click event by calling the preceding JavaScript function with the captured ElementReference:

@inject IJSRuntime JS

<button @ref="exampleButton">Example Button</button>

<button @onclick="TriggerClick">
    Trigger button click on exampleButton
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await JS.InvokeVoidAsync(
            "interopFunctions.clickElement", exampleButton);
    }
}

확장 메서드를 사용하려면 IJSRuntime 인스턴스를 수신하는 정적 확장 메서드를 만듭니다.To use an extension method, create a static extension method that receives the IJSRuntime instance:

public static async Task TriggerClickEvent(this ElementReference elementRef, 
    IJSRuntime js)
{
    await js.InvokeVoidAsync("interopFunctions.clickElement", elementRef);
}

clickElement 메서드는 개체에서 직접 호출됩니다.The clickElement method is called directly on the object. 다음 예제에서는 TriggerClickEvent 메서드를 JsInteropClasses 네임스페이스에서 사용할 수 있다고 가정합니다.The following example assumes that the TriggerClickEvent method is available from the JsInteropClasses namespace:

@inject IJSRuntime JS
@using JsInteropClasses

<button @ref="exampleButton" />

<button @onclick="TriggerClick">
    Trigger button click on exampleButton
</button>

@code {
    private ElementReference exampleButton;

    public async Task TriggerClick()
    {
        await exampleButton.TriggerClickEvent(JS);
    }
}

중요

exampleButton 변수는 구성 요소가 렌더링된 후에만 채워집니다.The exampleButton variable is only populated after the component is rendered. JavaScript 코드에 채워지지 않은 ElementReference가 전달되면 JavaScript 코드는 null 값을 받습니다.If an unpopulated ElementReference is passed to JavaScript code, the JavaScript code receives a value of null. 구성 요소에서 렌더링을 완료한 후에 구성 요소 참조를 조작하려면 OnAfterRenderAsync 또는 OnAfterRender 구성 요소 수명 주기 메서드를 사용합니다.To manipulate element references after the component has finished rendering use the OnAfterRenderAsync or OnAfterRender component lifecycle methods.

제네릭 형식으로 작업하고 값을 반환하는 경우 ValueTask<TResult>를 사용합니다.When working with generic types and returning a value, use ValueTask<TResult>:

public static ValueTask<T> GenericMethod<T>(this ElementReference elementRef, 
    IJSRuntime js)
{
    return js.InvokeAsync<T>("exampleJsFunctions.doSomethingGeneric", elementRef);
}

GenericMethod는 형식이 있는 개체에 대해 직접 호출됩니다.GenericMethod is called directly on the object with a type. 다음 예제에서는 GenericMethodJsInteropClasses 네임스페이스에서 사용할 수 있다고 가정합니다.The following example assumes that the GenericMethod is available from the JsInteropClasses namespace:

@inject IJSRuntime JS
@using JsInteropClasses

<input @ref="username" />
<button @onclick="OnClickMethod">Do something generic</button>

<p>
    returnValue: @returnValue
</p>

@code {
    private ElementReference username;
    private string returnValue;

    private async Task OnClickMethod()
    {
        returnValue = await username.GenericMethod<string>(JS);
    }
}

구성 요소 간 참조 요소Reference elements across components

다음과 같은 이유로 구성 요소 간에 ElementReference를 전달할 수 없습니다.An ElementReference can't be passed between components because:

부모 구성 요소는 다른 구성 요소에서 요소 참조를 사용할 수 있도록 하기 위해 다음을 수행할 수 있습니다.For a parent component to make an element reference available to other components, the parent component can:

  • 자식 구성 요소가 콜백을 등록할 수 있도록 허용합니다.Allow child components to register callbacks.
  • 전달된 요소 참조를 사용하여 OnAfterRender 이벤트 중에 등록된 콜백을 호출합니다.Invoke the registered callbacks during the OnAfterRender event with the passed element reference. 간접적으로 이 방법을 사용하면 자식 구성 요소가 부모의 요소 참조와 상호 작용할 수 있습니다.Indirectly, this approach allows child components to interact with the parent's element reference.

다음 Blazor WebAssembly 예제에서 이 방법을 보여 줍니다.The following Blazor WebAssembly example illustrates the approach.

wwwroot/index.html<head>에서 다음을 수행합니다.In the <head> of wwwroot/index.html:

<style>
    .red { color: red }
</style>

wwwroot/index.html<body>에서 다음을 수행합니다.In the <body> of wwwroot/index.html:

<script>
    function setElementClass(element, className) {
        /** @type {HTMLElement} **/
        var myElement = element;
        myElement.classList.add(className);
    }
</script>

Pages/Index.razor(부모 구성 요소):Pages/Index.razor (parent component):

@page "/"

<h1 @ref="title">Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Parent="this" Title="How is Blazor working for you?" />

Pages/Index.razor.cs:Pages/Index.razor.cs:

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;

namespace {APP ASSEMBLY}.Pages
{
    public partial class Index : 
        ComponentBase, IObservable<ElementReference>, IDisposable
    {
        private bool disposing;
        private IList<IObserver<ElementReference>> subscriptions = 
            new List<IObserver<ElementReference>>();
        private ElementReference title;

        protected override void OnAfterRender(bool firstRender)
        {
            base.OnAfterRender(firstRender);

            foreach (var subscription in subscriptions)
            {
                try
                {
                    subscription.OnNext(title);
                }
                catch (Exception)
                {
                    throw;
                }
            }
        }

        public void Dispose()
        {
            disposing = true;

            foreach (var subscription in subscriptions)
            {
                try
                {
                    subscription.OnCompleted();
                }
                catch (Exception)
                {
                }
            }

            subscriptions.Clear();
        }

        public IDisposable Subscribe(IObserver<ElementReference> observer)
        {
            if (disposing)
            {
                throw new InvalidOperationException("Parent being disposed");
            }

            subscriptions.Add(observer);

            return new Subscription(observer, this);
        }

        private class Subscription : IDisposable
        {
            public Subscription(IObserver<ElementReference> observer, Index self)
            {
                Observer = observer;
                Self = self;
            }

            public IObserver<ElementReference> Observer { get; }
            public Index Self { get; }

            public void Dispose()
            {
                Self.subscriptions.Remove(Observer);
            }
        }
    }
}

자리 표시자 {APP ASSEMBLY}는 앱의 앱 어셈블리 이름입니다(예: BlazorSample).The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, BlazorSample).

Shared/SurveyPrompt.razor(자식 구성 요소):Shared/SurveyPrompt.razor (child component):

@inject IJSRuntime JS

<div class="alert alert-secondary mt-4" role="alert">
    <span class="oi oi-pencil mr-2" aria-hidden="true"></span>
    <strong>@Title</strong>

    <span class="text-nowrap">
        Please take our
        <a target="_blank" class="font-weight-bold" 
            href="https://go.microsoft.com/fwlink/?linkid=2109206">brief survey</a>
    </span>
    and tell us what you think.
</div>

@code {
    [Parameter]
    public string Title { get; set; }
}

Shared/SurveyPrompt.razor.cs:Shared/SurveyPrompt.razor.cs:

using System;
using Microsoft.AspNetCore.Components;

namespace {APP ASSEMBLY}.Shared
{
    public partial class SurveyPrompt : 
        ComponentBase, IObserver<ElementReference>, IDisposable
    {
        private IDisposable subscription = null;

        [Parameter]
        public IObservable<ElementReference> Parent { get; set; }

        protected override void OnParametersSet()
        {
            base.OnParametersSet();

            if (subscription != null)
            {
                subscription.Dispose();
            }

            subscription = Parent.Subscribe(this);
        }

        public void OnCompleted()
        {
            subscription = null;
        }

        public void OnError(Exception error)
        {
            subscription = null;
        }

        public void OnNext(ElementReference value)
        {
            JS.InvokeAsync<object>(
                "setElementClass", new object[] { value, "red" });
        }

        public void Dispose()
        {
            subscription?.Dispose();
        }
    }
}

자리 표시자 {APP ASSEMBLY}는 앱의 앱 어셈블리 이름입니다(예: BlazorSample).The placeholder {APP ASSEMBLY} is the app's app assembly name (for example, BlazorSample).

JS interop 호출 강화Harden JS interop calls

JS interop는 네트워킹 오류로 인해 실패할 수 있으며 신뢰할 수 없는 것으로 처리되어야 합니다.JS interop may fail due to networking errors and should be treated as unreliable. 기본적으로 Blazor Server 앱은 서버에서 1분 후에 JS interop 호출 시간을 제한합니다.By default, a Blazor Server app times out JS interop calls on the server after one minute. 앱에서 좀 더 적극적인 시간 제한을 허용할 수 있는 경우 다음 방법 중 하나를 사용하여 시간 제한을 설정합니다.If an app can tolerate a more aggressive timeout, set the timeout using one of the following approaches:

  • Startup.ConfigureServices에서 전역적으로 시간 제한을 지정합니다.Globally in Startup.ConfigureServices, specify the timeout:

    services.AddServerSideBlazor(
        options => options.JSInteropDefaultCallTimeout = TimeSpan.FromSeconds({SECONDS}));
    
  • 구성 요소 코드의 호출마다 단일 호출은 다음과 같이 제한 시간을 지정할 수 있습니다.Per-invocation in component code, a single call can specify the timeout:

    var result = await JS.InvokeAsync<string>("MyJSOperation", 
        TimeSpan.FromSeconds({SECONDS}), new[] { "Arg1" });
    

리소스 소모에 대한 자세한 내용은 ASP.NET Core Blazor Server를 위한 위협 완화 지침를 참조하세요.For more information on resource exhaustion, see ASP.NET Core Blazor Server를 위한 위협 완화 지침.

클래스 라이브러리의 interop 코드 공유Share interop code in a class library

JS interop 코드는 클래스 라이브러리에 포함할 수 있습니다. 이렇게 하면 NuGet 패키지에서 코드를 공유할 수 있습니다.JS interop code can be included in a class library, which allows you to share the code in a NuGet package.

클래스 라이브러리는 기본 제공 어셈블리에 JavaScript 리소스를 포함하는 작업을 처리합니다.The class library handles embedding JavaScript resources in the built assembly. JavaScript 파일은 wwwroot 폴더에 배치됩니다.The JavaScript files are placed in the wwwroot folder. 라이브러리가 빌드될 때 도구에서 리소스를 포함하는 작업을 처리합니다.The tooling takes care of embedding the resources when the library is built.

기본 제공되는 NuGet 패키지는 앱의 프로젝트 파일에서 다른 NuGet 패키지가 참조되는 것과 같은 방식으로 참조됩니다.The built NuGet package is referenced in the app's project file the same way that any NuGet package is referenced. 패키지가 복원된 후에는 앱 코드가 C#을 호출하는 것처럼 JavaScript를 호출할 수 있습니다.After the package is restored, app code can call into JavaScript as if it were C#.

자세한 내용은 Razor 클래스 라이브러리로부터 ASP.NET Core Razor구성요소를 이용합니다.를 참조하세요.For more information, see Razor 클래스 라이브러리로부터 ASP.NET Core Razor구성요소를 이용합니다..

순환 개체 참조 방지Avoid circular object references

순환 참조를 포함하는 개체는 다음 중 하나에 대해 클라이언트에서 직렬화될 수 없습니다.Objects that contain circular references can't be serialized on the client for either:

  • .NET 메서드 호출.NET method calls.
  • 반환 형식에 순환 참조가 있는 경우 C#에서 JavaScript 메서드 호출JavaScript method calls from C# when the return type has circular references.

자세한 내용은 순환 참조가 지원되지 않음, 두 가지 사용(dotnet/aspnetcore #20525)을 참조하세요.For more information, see Circular references are not supported, take two (dotnet/aspnetcore #20525).

Blazor JavaScript 격리 및 개체 참조Blazor JavaScript isolation and object references

Blazor에서는 표준 JavaScript 모듈에서 JavaScript 격리를 사용하도록 설정합니다.Blazor enables JavaScript isolation in standard JavaScript modules. JavaScript 격리는 다음과 같은 이점을 제공합니다.JavaScript isolation provides the following benefits:

  • 가져온 JavaScript는 전역 네임스페이스를 더 이상 오염시키지 않습니다.Imported JavaScript no longer pollutes the global namespace.
  • 라이브러리 및 구성 요소의 소비자는 관련 JavaScript를 가져올 필요가 없습니다.Consumers of a library and components aren't required to import the related JavaScript.

예를 들어 다음 JavaScript 모듈은 브라우저 프롬프트를 표시하기 위해 JavaScript 함수를 내보냅니다.For example, the following JavaScript module exports a JavaScript function for showing a browser prompt:

export function showPrompt(message) {
  return prompt(message, 'Type anything here');
}

위의 JavaScript 모듈을 .NET 라이브러리에 정적 웹 자산(wwwroot/exampleJsInterop.js)으로 추가한 다음, IJSRuntime 서비스에서 InvokeAsync를 호출하여 .NET 코드로 모듈을 가져옵니다.Add the preceding JavaScript module to a .NET library as a static web asset (wwwroot/exampleJsInterop.js) and then import the module into the .NET code by calling InvokeAsync on the IJSRuntime service. 다음 예제에서는 서비스가 js(표시되지 않음)으로 삽입됩니다.The service is injected as js (not shown) for the following example:

var module = await js.InvokeAsync<IJSObjectReference>(
    "import", "./_content/MyComponents/exampleJsInterop.js");

위 예제에서 import 식별자는 JavaScript 모듈을 가져오기 위해 특별히 사용되는 특수 식별자입니다.The import identifier in the preceding example is a special identifier used specifically for importing a JavaScript module. 안정적인 정적 웹 자산 경로 ./_content/{LIBRARY NAME}/{PATH UNDER WWWROOT}를 사용하여 모듈을 지정합니다.Specify the module using its stable static web asset path: ./_content/{LIBRARY NAME}/{PATH UNDER WWWROOT}. JavaScript 파일의 올바른 정적 자산 경로를 만들려면 현재 디렉터리의 패스 세그먼트(./)가 필요합니다.The path segment for the current directory (./) is required in order to create the correct static asset path to the JavaScript file. 모듈을 동적으로 가져오려면 네트워크 요청이 필요하므로 InvokeAsync를 호출하여 비동기적으로 수행해야 합니다.Dynamically importing a module requires a network request, so it can only be achieved asynchronously by calling InvokeAsync. {LIBRARY NAME} 자리 표시자는 라이브러리 이름입니다.The {LIBRARY NAME} placeholder is the library name. {PATH UNDER WWWROOT} 자리 표시자는 wwwroot 아래의 스크립트에 대한 경로입니다.The {PATH UNDER WWWROOT} placeholder is the path to the script under wwwroot.

IJSRuntime은 모듈을 IJSObjectReference로 가져와 .NET 코드에서 JavaScript 개체에 대한 참조를 나타냅니다.IJSRuntime imports the module as a IJSObjectReference, which represents a reference to a JavaScript object from .NET code. IJSObjectReference를 사용하여 모듈에서 내보낸 JavaScript 함수를 호출합니다.Use the IJSObjectReference to invoke exported JavaScript functions from the module:

public async ValueTask<string> Prompt(string message)
{
    return await module.InvokeAsync<string>("showPrompt", message);
}

IJSInProcessObjectReference는 함수를 동기적으로 호출할 수 있는 JavaScript 개체에 대한 참조를 나타냅니다.IJSInProcessObjectReference represents a reference to a JavaScript object whose functions can be invoked synchronously.

UI(DOM 요소)를 렌더링하는 JavaScript 라이브러리 사용Use of JavaScript libraries that render UI (DOM elements)

브라우저 DOM 내에 표시되는 사용자 인터페이스 요소를 생성하는 JavaScript 라이브러리를 사용하려는 경우가 있습니다.Sometimes you may wish to use JavaScript libraries that produce visible user interface elements within the browser DOM. 처음에는, Blazor의 diff 시스템은 DOM 요소의 트리를 제어해야 하는데 일부 외부 코드가 DOM 트리를 변경하고 diff 적용 메커니즘을 무효화하면 오류가 발생하기 때문에 이것이 어려워 보일 수 있습니다.At first glance, this might seem difficult because Blazor's diffing system relies on having control over the tree of DOM elements and runs into errors if some external code mutates the DOM tree and invalidates its mechanism for applying diffs. 이는 Blazor 특정 제한 사항이 아닙니다.This isn't a Blazor-specific limitation. 동일한 문제가 모든 diff 기반 UI 프레임워크에서 발생합니다.The same challenge occurs with any diff-based UI framework.

다행히도 Blazor 구성 요소 UI 내에 외부에서 생성된 UI를 안정적으로 포함하는 것은 간단합니다.Fortunately, it's straightforward to embed externally-generated UI within a Blazor component UI reliably. 구성 요소 코드(.razor 파일)가 빈 요소를 생성하도록 하는 것이 좋습니다.The recommended technique is to have the component's code (.razor file) produce an empty element. Blazor의 diff 시스템이 관련되는 한 요소는 항상 비어 있으므로 렌더러는 요소로 재귀되지 않으며 대신 콘텐츠를 그대로 둡니다.As far as Blazor's diffing system is concerned, the element is always empty, so the renderer does not recurse into the element and instead leaves its contents alone. 이렇게 하면 외부 관리형 임의 콘텐츠로 요소를 안전하게 채울 수 있습니다.This makes it safe to populate the element with arbitrary externally-managed content.

다음 예제에서는 개념을 보여 줍니다.The following example demonstrates the concept. firstRendertrue인 경우 if 문 내에서 myElement를 사용하여 작업을 수행합니다.Within the if statement when firstRender is true, do something with myElement. 예를 들어 외부 JavaScript 라이브러리를 호출하여 요소를 채웁니다.For example, call an external JavaScript library to populate it. Blazor는 해당 구성 요소 자체가 제거될 때까지 요소 콘텐츠를 그대로 둡니다.Blazor leaves the element's contents alone until this component itself is removed. 구성 요소가 제거되면 구성 요소의 전체 DOM 하위 트리도 제거됩니다.When the component is removed, the component's entire DOM subtree is also removed.

<h1>Hello! This is a Blazor component rendered at @DateTime.Now</h1>

<div @ref="myElement"></div>

@code {
    HtmlElement myElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            ...
        }
    }
}

더 자세한 예제로는 오픈 소스 Mapbox API를 사용하여 대화형 맵을 렌더링하는 다음 구성 요소를 고려합니다.As a more detailed example, consider the following component that renders an interactive map using the open-source Mapbox APIs:

@inject IJSRuntime JS
@implements IAsyncDisposable

<div @ref="mapElement" style='width: 400px; height: 300px;'></div>

<button @onclick="() => ShowAsync(51.454514, -2.587910)">Show Bristol, UK</button>
<button @onclick="() => ShowAsync(35.6762, 139.6503)">Show Tokyo, Japan</button>

@code
{
    ElementReference mapElement;
    IJSObjectReference mapModule;
    IJSObjectReference mapInstance;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            mapModule = await JS.InvokeAsync<IJSObjectReference>(
                "import", "./mapComponent.js");
            mapInstance = await mapModule.InvokeAsync<IJSObjectReference>(
                "addMapToElement", mapElement);
        }
    }

    Task ShowAsync(double latitude, double longitude)
        => mapModule.InvokeVoidAsync("setMapCenter", mapInstance, latitude, 
            longitude).AsTask();

    private async ValueTask IAsyncDisposable.DisposeAsync()
    {
        await mapInstance.DisposeAsync();
        await mapModule.DisposeAsync();
    }
}

wwwroot/mapComponent.js에 배치해야 하는 해당 JavaScript 모듈은 다음과 같습니다.The corresponding JavaScript module, which should be placed at wwwroot/mapComponent.js, is as follows:

import 'https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js';

// TO MAKE THE MAP APPEAR YOU MUST ADD YOUR ACCESS TOKEN FROM 
// https://account.mapbox.com
mapboxgl.accessToken = '{ACCESS TOKEN}';

export function addMapToElement(element) {
  return new mapboxgl.Map({
    container: element,
    style: 'mapbox://styles/mapbox/streets-v11',
    center: [-74.5, 40],
    zoom: 9
  });
}

export function setMapCenter(map, latitude, longitude) {
  map.setCenter([longitude, latitude]);
}

앞의 예제에서 {ACCESS TOKEN} 문자열을 https://account.mapbox.com 에서 가져올 수 있는 유효한 액세스 토큰으로 바꿉니다.In the preceding example, replace the string {ACCESS TOKEN} with a valid access token that you can get from https://account.mapbox.com.

올바른 스타일을 생성하려면 호스트 HTML 페이지(index.html 또는 _Host.cshtml)에 다음 스타일시트 태그를 추가합니다.To produce correct styling, add the following stylesheet tag to the host HTML page (index.html or _Host.cshtml):

<link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" />

앞의 예제에서는 사용자가 다음을 수행하는 대화형 맵 UI를 생성합니다.The preceding example produces an interactive map UI, in which the user:

  • 끌어서 스크롤하거나 확대/축소할 수 있습니다.Can drag to scroll or zoom.
  • 단추를 클릭하여 미리 정의된 위치로 이동합니다.Click buttons to jump to predefined locations.

영국 브리스틀 및 일본 도쿄를 선택하는 단추가 포함된 일본 도쿄의 Mapbox 거리 지도

이해해야 할 주요 사항은 다음과 같습니다.The key points to understand are:

  • Blazor가 관련되는 한 @ref="mapElement"가 있는 <div>는 비어 있습니다.The <div> with @ref="mapElement" is left empty as far as Blazor is concerned. 따라서 mapbox-gl.js가 안전하게 요소를 채우고 시간이 지남에 따라 해당 콘텐츠를 수정할 수 있습니다.It's therefore safe for mapbox-gl.js to populate it and modify its contents over time. UI를 렌더링하는 JavaScript 라이브러리와 함께 이 방법을 사용할 수 있습니다.You can use this technique with any JavaScript library that renders UI. 페이지의 다른 부분에 연결하고 해당 부분을 수정하려고 시도하지 않는 한 Blazor 구성 요소 내부에 타사 JavaScript SPA 프레임워크의 구성 요소를 포함할 수도 있습니다.You could even embed components from a third-party JavaScript SPA framework inside Blazor components, as long as they don't try to reach out and modify other parts of the page. 외부 JavaScript 코드는 Blazor가 비어 있는 것으로 간주하지 않는 요소를 안전하게 수정할 수 ‘없습니다’.It is not safe for external JavaScript code to modify elements that Blazor does not regard as empty.
  • 이 접근 방식을 사용하는 경우 Blazor가 DOM 요소를 유지하거나 제거하는 방법에 대한 규칙에 고려해야 합니다.When using this approach, bear in mind the rules about how Blazor retains or destroys DOM elements. 앞의 예제에서 구성 요소는 단추 클릭 이벤트를 안전하게 처리하며, 기본적으로 가능한 경우 DOM 요소가 유지되므로 기존 맵 인스턴스를 업데이트합니다.In the preceding example, the component safely handles button click events and updates the existing map instance because DOM elements are retained where possible by default. @foreach 루프 내부에서 맵 요소 목록을 렌더링한 경우 @key를 사용하여 구성 요소 인스턴스를 유지할 수 있습니다.If you were rendering a list of map elements from inside a @foreach loop, you want to use @key to ensure the preservation of component instances. 그렇지 않으면 목록 데이터의 변경으로 인해 안타깝게도 구성 요소 인스턴스가 이전 인스턴스의 상태를 유지할 수 있습니다.Otherwise, changes in the list data could cause component instances to retain the state of previous instances in an undesirable manner. 자세한 내용은 @key를 사용하여 요소 및 구성 요소 유지를 참조하세요.For more information, see using @key to preserve elements and components.

또한 앞의 예제에서는 ES6 모듈 내에서 JavaScript 논리 및 종속성을 캡슐화하고 import 식별자를 사용하여 동적으로 로드하는 방법을 보여 줍니다.Additionally, the preceding example shows how it's possible to encapsulate JavaScript logic and dependencies within an ES6 module and load it dynamically using the import identifier. 자세한 내용은 JavaScript 격리 및 개체 참조를 참조하세요.For more information, see JavaScript isolation and object references.

JS Interop 호출의 크기 제한Size limits on JS interop calls

Blazor WebAssembly에서 프레임워크는 JS interop 입력 및 출력 크기에 제한을 두지 않습니다.In Blazor WebAssembly, the framework doesn't impose a limit on the size of JS interop inputs and outputs.

Blazor Server에서 JS interop 호출의 크기는 HubOptions.MaximumReceiveMessageSize에 의해 적용되는 허브 메서드에 허용되는 최대 수신 SignalR 메시지 크기로 제한됩니다(기본값: 32KB).In Blazor Server, JS interop calls are limited in size by the maximum incoming SignalR message size permitted for hub methods, which is enforced by HubOptions.MaximumReceiveMessageSize (default: 32 KB). .NET SignalR 메시지에 대한 JS가 MaximumReceiveMessageSize보다 크면 오류가 throw됩니다.JS to .NET SignalR messages larger than MaximumReceiveMessageSize throw an error. 프레임워크는 허브에서 클라이언트로 전송되는 SignalR 메시지의 크기에 제한을 적용하지 않습니다.The framework doesn't impose a limit on the size of a SignalR message from the hub to a client. 자세한 내용은 ASP.NET Core Blazor의 JavaScript 함수에서 .NET 메서드 호출를 참조하세요.For more information, see ASP.NET Core Blazor의 JavaScript 함수에서 .NET 메서드 호출.

JS 모듈JS modules

JS 격리의 경우 JS interop은 ESM(EcmaScript 모듈)(ECMAScript 사양)에 대한 브라우저의 기본 지원과 함께 작동합니다.For JS isolation, JS interop works with the browser's default support for EcmaScript modules (ESM) (ECMAScript specification).

역 마샬링된 JS interopUnmarshalled JS interop

.NET 개체가 JS interop에 대해 직렬화되고 다음 중 하나가 참인 경우 Blazor WebAssembly 구성 요소의 성능이 저하될 수 있습니다.Blazor WebAssembly components may experience poor performance when .NET objects are serialized for JS interop and either of the following are true:

  • 많은 양의 .NET 개체가 빠르게 직렬화됩니다.A high volume of .NET objects are rapidly serialized. 예: JS interop 호출이 마우스 휠 회전과 같은 입력 디바이스의 이동을 기반으로 이루어집니다.Example: JS interop calls are made on the basis of moving an input device, such as spinning a mouse wheel.
  • 많은 양의 .NET 개체 또는 여러 .NET 개체가 JS interop에 대해 직렬화되어야 합니다.Large .NET objects or many .NET objects must be serialized for JS interop. 예: JS interop 호출에 수십 개의 파일의 직렬화가 필요합니다.Example: JS interop calls require serializing dozens of files.

IJSUnmarshalledObjectReference는 .NET 데이터를 직렬화하는 오버헤드 없이 함수를 호출할 수 있는 JavaScript 개체에 대한 참조를 나타냅니다.IJSUnmarshalledObjectReference represents a reference to an JavaScript object whose functions can be invoked without the overhead of serializing .NET data.

다음 예제에서는In the following example:

  • 문자열과 정수를 포함하는 구조체가 직렬화되지 않은 상태로 JavaScript로 전달됩니다.A struct containing a string and an integer is passed unserialized to JavaScript.
  • JavaScript 함수가 데이터를 처리하고 부울 또는 문자열을 호출자에게 반환합니다.JavaScript functions process the data and return either a boolean or string to the caller.
  • JavaScript 문자열은 .NET string 개체로 직접 변환할 수 없습니다.A JavaScript string isn't directly convertible into a .NET string object. unmarshalledFunctionReturnString 함수가 Javascript 문자열의 변환을 관리하기 위해 BINDING.js_string_to_mono_string을 호출합니다.The unmarshalledFunctionReturnString function calls BINDING.js_string_to_mono_string to manage the conversion of a Javascript string.

참고

JavaScript로 전달된 구조체의 결과로 성능이 저하되지 않기 때문에 다음 예제는 이 시나리오의 일반적인 사용 사례가 아닙니다.The following examples aren't typical use cases for this scenario because the struct passed to JavaScript doesn't result in poor component performance. 예제에서는 직렬화되지 않은 .NET 데이터의 전달이라는 개념을 보여 주기 위한 용도로 작은 개체를 사용합니다.The example uses a small object merely to demonstrate the concepts for passing unserialized .NET data.

wwwroot/index.html 또는 wwwroot/index.html에서 참조하는 외부 Javascript의 <script> 블록 내용:Content of a <script> block in wwwroot/index.html or an external Javascript file referenced by wwwroot/index.html:

window.returnJSObjectReference = () => {
    return {
        unmarshalledFunctionReturnBoolean: function (fields) {
            const name = Blazor.platform.readStringField(fields, 0);
            const year = Blazor.platform.readInt32Field(fields, 8);

            return name === "Brigadier Alistair Gordon Lethbridge-Stewart" &&
                year === 1968;
        },
        unmarshalledFunctionReturnString: function (fields) {
            const name = Blazor.platform.readStringField(fields, 0);
            const year = Blazor.platform.readInt32Field(fields, 8);

            return BINDING.js_string_to_mono_string(`Hello, ${name} (${year})!`);
        }
    };
}

경고

js_string_to_mono_string 함수 이름, 동작 및 존재는 향후 .NET 릴리스에서 변경될 수 있습니다.The js_string_to_mono_string function name, behavior, and existence is subject to change in a future release of .NET. 예를 들면 다음과 같습니다.For example:

  • 함수의 이름이 바뀔 수 있습니다.The function is likely to be renamed.
  • 함수가 제거되고 프레임워크에 의한 자동 문자열 변환 기능이 사용될 수 있습니다.The function itself might be removed in favor of automatic conversion of strings by the framework.

Pages/UnmarshalledJSInterop.razor(URL: /unmarshalled-js-interop):Pages/UnmarshalledJSInterop.razor (URL: /unmarshalled-js-interop):

@page "/unmarshalled-js-interop"
@using System.Runtime.InteropServices
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Unmarshalled JS interop</h1>

@if (callResultForBoolean)
{
    <p>JS interop was successful!</p>
}

@if (!string.IsNullOrEmpty(callResultForString))
{
    <p>@callResultForString</p>
}

<p>
    <button @onclick="CallJSUnmarshalledForBoolean">
        Call Unmarshalled JS & Return Boolean
    </button>
    <button @onclick="CallJSUnmarshalledForString">
        Call Unmarshalled JS & Return String
    </button>
</p>

<p>
    <a href="https://www.doctorwho.tv">Doctor Who</a>
    is a registered trademark of the <a href="https://www.bbc.com/">BBC</a>.
</p>

@code {
    private bool callResultForBoolean;
    private string callResultForString;

    private void CallJSUnmarshalledForBoolean()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnJSObjectReference");

        callResultForBoolean = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, bool>(
                "unmarshalledFunctionReturnBoolean", GetStruct());
    }

    private void CallJSUnmarshalledForString()
    {
        var unmarshalledRuntime = (IJSUnmarshalledRuntime)JS;

        var jsUnmarshalledReference = unmarshalledRuntime
            .InvokeUnmarshalled<IJSUnmarshalledObjectReference>(
                "returnJSObjectReference");

        callResultForString = 
            jsUnmarshalledReference.InvokeUnmarshalled<InteropStruct, string>(
                "unmarshalledFunctionReturnString", GetStruct());
    }

    private InteropStruct GetStruct()
    {
        return new InteropStruct
        {
            Name = "Brigadier Alistair Gordon Lethbridge-Stewart",
            Year = 1968,
        };
    }

    [StructLayout(LayoutKind.Explicit)]
    public struct InteropStruct
    {
        [FieldOffset(0)]
        public string Name;

        [FieldOffset(8)]
        public int Year;
    }
}

IJSUnmarshalledObjectReference 인스턴스가 C# 코드에서 삭제되지 않을 경우 JavaScript에서 삭제될 수 있습니다.If an IJSUnmarshalledObjectReference instance isn't disposed in C# code, it can be disposed in JavaScript. 다음 dispose 함수는 JavaScript에서 호출된 경우 개체 참조를 삭제합니다.The following dispose function disposes the object reference when called from JavaScript:

window.exampleJSObjectReferenceNotDisposedInCSharp = () => {
    return {
        dispose: function () {
            DotNet.disposeJSObjectReference(this);
        },

        ...
    };
}

배열 형식이 js_typed_array_to_array를 사용하여 JavaScript 개체에서 .NET으로 변환될 수 있지만 JavaScript 배열은 형식화된 배열이어야 합니다.Array types can be converted from JavaScript objects into .NET objects using js_typed_array_to_array, but the JavaScript array must be a typed array. JavaScript의 배열은 C# 코드에서 .NET 개체 배열(object[])로 읽힐 수 있습니다.Arrays from JavaScript can be read in C# code as a .NET object array (object[]).

문자열 배열과 같은 기타 데이터 형식은 변환될 수 있지만 이를 위해서는 새 Mono 배열 개체(mono_obj_array_new)를 만들고 값을 설정(mono_obj_array_set)해야 합니다.Other data types, such as string arrays, can be converted but require creating a new Mono array object (mono_obj_array_new) and setting its value (mono_obj_array_set).

경고

Blazor 프레임워크에서 제공하는 JavaScript 함수(예: js_typed_array_to_array, mono_obj_array_new, mono_obj_array_set)는 .NET의 향후 릴리스에서 이름이 바뀌거나 동작이 변경되거나 제거될 수 있습니다.JavaScript functions provided by the Blazor framework, such as js_typed_array_to_array, mono_obj_array_new, and mono_obj_array_set, are subject to name changes, behavioral changes, or removal in future releases of .NET.

추가 자료Additional resources