ASP.NET Core Razor 구성 요소 만들기 및 사용Create and use ASP.NET Core Razor components

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

Blazor 앱은 구성 요소 를 사용하여 빌드됩니다.Blazor apps are built using components. 구성 요소는 페이지, 대화 상자 또는 양식과 같은 UI(사용자 인터페이스)의 자체 포함 청크입니다.A component is a self-contained chunk of user interface (UI), such as a page, dialog, or form. 구성 요소는 데이터를 주입하거나 UI 이벤트에 응답하는 데 필요한 HTML 태그와 처리 논리를 포함합니다.A component includes HTML markup and the processing logic required to inject data or respond to UI events. 구성 요소는 유연하고 간단합니다.Components are flexible and lightweight. 프로젝트 간에 중첩, 재사용 및 공유될 수 있습니다.They can be nested, reused, and shared among projects.

구성 요소 클래스Component classes

구성 요소는 C# 및 HTML 태그 조합을 사용하여 Razor 구성 요소 파일(.razor)에서 구현됩니다.Components are implemented in Razor component files (.razor) using a combination of C# and HTML markup. Blazor의 구성 요소는 공식적으로 ‘Razor 구성 요소’라고 합니다.A component in Blazor is formally referred to as a Razor component.

Razor 구문Razor syntax

Blazor 앱의 Razor 구성 요소는 Razor 구문을 광범위하게 사용합니다.Razor components in Blazor apps extensively use Razor syntax. Razor 태그 언어에 익숙하지 않은 경우 계속하기 전에 ASP.NET Core용 Razor 구문 참조를 읽는 것이 좋습니다.If you aren't familiar with the Razor markup language, we recommend reading ASP.NET Core용 Razor 구문 참조 before proceeding.

Razor 구문에서 콘텐츠에 액세스하는 경우 다음 섹션에 특히 주의해야 합니다.When accessing the content on Razor syntax, pay special attention to the following sections:

  • 지시문: 일반적으로 구성 요소 태그가 구문 분석되거나 작동하는 방식을 변경하는 @ 접두사가 있는 예약 키워드입니다.Directives: @-prefixed reserved keywords that typically change the way component markup is parsed or function.
  • 지시문 특성: 일반적으로 구성 요소가 구문 분석되거나 작동하는 방식을 변경하는 @ 접두사가 있는 예약 키워드입니다.Directive attributes: @-prefixed reserved keywords that typically change the way component elements are parsed or function.

이름Names

구성 요소의 이름은 대문자로 시작해야 합니다.A component's name must start with an uppercase character. 예를 들어, MyCoolComponent.razor는 유효하고 myCoolComponent.razor는 유효하지 않습니다.For example, MyCoolComponent.razor is valid, and myCoolComponent.razor is invalid.

라우팅Routing

Blazor의 라우팅은 앱에서 액세스 가능한 각 구성 요소에 경로 템플릿을 제공하여 수행됩니다.Routing in Blazor is achieved by providing a route template to each accessible component in the app. @page 지시문을 포함하는 Razor 파일이 컴파일되면 생성된 클래스에 경로 템플릿을 지정하는 RouteAttribute가 제공됩니다.When a Razor file with an @page directive is compiled, the generated class is given a RouteAttribute specifying the route template. 런타임에 라우터는 RouteAttribute를 사용하여 구성 요소 클래스를 검색하고, 요청된 URL과 일치하는 경로 템플릿을 포함하는 구성 요소를 렌더링합니다.At runtime, the router looks for component classes with a RouteAttribute and renders whichever component has a route template that matches the requested URL. 자세한 내용은 ASP.NET Core Blazor 라우팅를 참조하세요.For more information, see ASP.NET Core Blazor 라우팅.

@page "/ParentComponent"

...

태그Markup

구성 요소의 UI는 HTML을 사용하여 정의됩니다.The UI for a component is defined using HTML. 동적 렌더링 논리(예: 루프, 조건, 식)는 Razor 라고 하는 포함된 C# 구문을 사용하여 추가됩니다.Dynamic rendering logic (for example, loops, conditionals, expressions) is added using an embedded C# syntax called Razor. 앱이 컴파일되면 HTML 태그 및 C# 렌더링 논리는 구성 요소 클래스로 변환됩니다.When an app is compiled, the HTML markup and C# rendering logic are converted into a component class. 생성된 클래스의 이름은 파일 이름과 일치합니다.The name of the generated class matches the name of the file.

구성 요소 클래스의 멤버는 @code 블록에서 정의됩니다.Members of the component class are defined in an @code block. @code 블록에서 구성 요소 상태(속성, 필드)는 이벤트 처리 또는 다른 구성 요소 논리 정의를 위한 메서드로 지정됩니다.In the @code block, component state (properties, fields) is specified with methods for event handling or for defining other component logic. 두 개 이상의 @code 블록이 허용됩니다.More than one @code block is permissible.

구성 요소 멤버는 @으로 시작되는 C# 식을 사용하는 구성 요소 렌더링 논리의 일부로 사용할 수 있습니다.Component members can be used as part of the component's rendering logic using C# expressions that start with @. 예를 들어, C# 필드는 필드 이름에 앞에 @을 붙여 렌더링됩니다.For example, a C# field is rendered by prefixing @ to the field name. 다음 예제는 다음 작업을 수행합니다.The following example evaluates and renders:

  • headingFontStyle을 평가하고 font-style에 대한 CSS 속성 값으로 렌더링합니다.headingFontStyle to the CSS property value for font-style.
  • headingText를 평가하고 <h1> 요소의 내용으로 렌더링합니다.headingText to the content of the <h1> element.
<h1 style="font-style:@headingFontStyle">@headingText</h1>

@code {
    private string headingFontStyle = "italic";
    private string headingText = "Put on your new Blazor!";
}

구성 요소가 처음 렌더링되면 구성 요소는 이벤트에 대한 응답으로 렌더링 트리를 다시 생성합니다.After the component is initially rendered, the component regenerates its render tree in response to events. 그런 후 Blazor는 새로운 렌더링 트리를 이전 렌터링 트리와 비교하여 수정 사항을 브라우저 DOM(문서 개체 모델)에 적용합니다.Blazor then compares the new render tree against the previous one and applies any modifications to the browser's Document Object Model (DOM). 추가 세부 정보는 ASP.NET Core Blazor 구성 요소 렌더링에서 제공됩니다.Additional detail is provided in ASP.NET Core Blazor 구성 요소 렌더링.

구성 요소는 일반 C# 클래스이며 프로젝트 내의 어느 위치에나 배치할 수 있습니다.Components are ordinary C# classes and can be placed anywhere within a project. 웹 페이지를 생성하는 구성 요소는 일반적으로 Pages 폴더에 있습니다.Components that produce webpages usually reside in the Pages folder. 페이지와 무관한 구성 요소는 Shared 폴더 또는 프로젝트에 추가된 사용자 지정 폴더에 자주 배치됩니다.Non-page components are frequently placed in the Shared folder or a custom folder added to the project.

네임스페이스Namespaces

일반적으로 구성 요소의 네임스페이스는 앱의 루트 네임스페이스와 앱 내의 구성 요소 위치(폴더)에서 파생됩니다.Typically, a component's namespace is derived from the app's root namespace and the component's location (folder) within the app. 앱의 루트 네임스페이스가 BlazorSample이고 Counter 구성 요소가 Pages 폴더에 있다면 다음이 적용됩니다.If the app's root namespace is BlazorSample and the Counter component resides in the Pages folder:

  • Counter 구성 요소의 네임스페이스는 BlazorSample.Pages입니다.The Counter component's namespace is BlazorSample.Pages.
  • 구성 요소의 정규화된 형식 이름은 BlazorSample.Pages.Counter입니다.The fully qualified type name of the component is BlazorSample.Pages.Counter.

구성 요소를 포함하는 사용자 지정 폴더의 경우 부모 구성 요소 또는 앱의 _Imports.razor 파일에 @using 지시문을 추가합니다.For custom folders that hold components, add a @using directive to the parent component or to the app's _Imports.razor file. 다음 예에서는 Components 폴더의 구성 요소를 사용할 수 있도록 만듭니다.The following example makes components in the Components folder available:

@using BlazorSample.Components

구성 요소는 정규화된 이름을 사용하여 참조할 수도 있습니다. 이 경우에는 @using 지시문이 필요하지 않습니다.Components can also be referenced using their fully qualified names, which doesn't require the @using directive:

<BlazorSample.Components.MyComponent />

Razor로 작성된 구성 요소의 네임스페이스는 다음을 기준으로 합니다(우선 순위에 따름).The namespace of a component authored with Razor is based on (in priority order):

  • Razor 파일(.razor) 태그(@namespace BlazorSample.MyNamespace)의 @namespace 지정@namespace designation in Razor file (.razor) markup (@namespace BlazorSample.MyNamespace).
  • 프로젝트 파일(<RootNamespace>BlazorSample</RootNamespace>)에서 프로젝트의 RootNamespaceThe project's RootNamespace in the project file (<RootNamespace>BlazorSample</RootNamespace>).
  • 프로젝트 파일의 파일 이름(.csproj)에서 가져온 프로젝트 이름, 프로젝트 루트에서 구성 요소로의 경로.The project name, taken from the project file's file name (.csproj), and the path from the project root to the component. 예를 들어 프레임워크는 {PROJECT ROOT}/Pages/Index.razor(BlazorSample.csproj)를 BlazorSample.Pages 네임스페이스로 확인합니다.For example, the framework resolves {PROJECT ROOT}/Pages/Index.razor (BlazorSample.csproj) to the namespace BlazorSample.Pages. 구성 요소는 C# 이름 바인딩 규칙을 따릅니다.Components follow C# name binding rules. 이 예의 Index 구성 요소에서는 범위의 구성 요소가 모든 구성 요소입니다.For the Index component in this example, the components in scope are all of the components:
    • 동일한 폴더의 Pages.In the same folder, Pages.
    • 다른 네임스페이스를 명시적으로 지정하지 않는 프로젝트 루트의 구성 요소The components in the project's root that don't explicitly specify a different namespace.

참고

global:: 한정자는 지원되지 않습니다.The global:: qualification isn't supported.

별칭이 지정된 using 문(예: @using Foo = Bar)을 사용하여 구성 요소를 가져오는 것은 지원되지 않습니다.Importing components with aliased using statements (for example, @using Foo = Bar) isn't supported.

부분적으로 정규화된 이름은 지원되지 않습니다.Partially qualified names aren't supported. 예를 들어 <Shared.NavMenu></Shared.NavMenu>를 사용하여 @using BlazorSample을 추가하고 NavMenu 구성 요소(NavMenu.razor)를 참조하는 것은 지원되지 않습니다.For example, adding @using BlazorSample and referencing the NavMenu component (NavMenu.razor) with <Shared.NavMenu></Shared.NavMenu> isn't supported.

Partial 클래스 지원Partial class support

Razor 구성 요소가 partial 클래스로 생성됩니다.Razor components are generated as partial classes. Razor 구성 요소는 다음 방법 중 하나를 사용하여 작성됩니다.Razor components are authored using either of the following approaches:

  • C# 코드는 단일 파일에서 HTML 태그와 Razor 코드를 사용하여 @code 블록에 정의됩니다.C# code is defined in an @code block with HTML markup and Razor code in a single file. Blazor 템플릿은 이 접근 방식을 사용하여 Razor 구성 요소를 정의합니다.Blazor templates define their Razor components using this approach.
  • C# 코드는 partial 클래스로 정의된 코드 숨김 파일에 배치됩니다.C# code is placed in a code-behind file defined as a partial class.

다음 예제에서는 Blazor 템플릿에서 생성된 앱에서 @code 블록을 포함하는 기본 Counter 구성 요소를 보여 줍니다.The following example shows the default Counter component with an @code block in an app generated from a Blazor template. HTML 태그, Razor 코드 및 C# 코드는 다음과 같은 동일한 파일에 있습니다.HTML markup, Razor code, and C# code are in the same file:

Pages/Counter.razor:Pages/Counter.razor:

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    void IncrementCount()
    {
        currentCount++;
    }
}

Partial 클래스에서 코드 숨김 파일을 사용하여 Counter 구성 요소를 만들 수도 있습니다.The Counter component can also be created using a code-behind file with a partial class:

Pages/Counter.razor:Pages/Counter.razor:

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

Counter.razor.cs:Counter.razor.cs:

namespace BlazorSample.Pages
{
    public partial class Counter
    {
        private int currentCount = 0;

        void IncrementCount()
        {
            currentCount++;
        }
    }
}

필요한 경우 partial 클래스 파일에 필요한 네임스페이스를 추가합니다.Add any required namespaces to the partial class file as needed. Razor 구성 요소에 사용되는 일반적인 네임스페이스는 다음과 같습니다.Typical namespaces used by Razor components include:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.AspNetCore.Components.Web;

중요

@using _Imports.razor 파일의 지시문은 C# 파일(.cs)이 아닌 Razor 파일(.razor)에만 적용됩니다.@using directives in the _Imports.razor file are only applied to Razor files (.razor), not C# files (.cs).

기본 클래스 지정Specify a base class

@inherits 지시어를 사용하여 구성 요소에 대한 기본 클래스를 지정할 수 있습니다.The @inherits directive can be used to specify a base class for a component. 다음 예제에서는 구성 요소가 기본 클래스 BlazorRocksBase를 상속하여 구성 요소의 속성과 메서드를 제공하는 방법을 보여 줍니다.The following example shows how a component can inherit a base class, BlazorRocksBase, to provide the component's properties and methods. 기본 클래스는 ComponentBase에서 파생되어야 합니다.The base class should derive from ComponentBase.

Pages/BlazorRocks.razor:Pages/BlazorRocks.razor:

@page "/BlazorRocks"
@inherits BlazorRocksBase

<h1>@BlazorRocksText</h1>

BlazorRocksBase.cs:BlazorRocksBase.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample
{
    public class BlazorRocksBase : ComponentBase
    {
        public string BlazorRocksText { get; set; } = 
            "Blazor rocks the browser!";
    }
}

구성 요소 사용Use components

구성 요소는 HTML 요소 구문을 사용하여 선언함으로써 다른 구성 요소를 포함할 수 있습니다.Components can include other components by declaring them using HTML element syntax. 구성 요소 사용을 위한 태그는 태그 이름이 구성 요소 유형인 HTML 태그처럼 보입니다.The markup for using a component looks like an HTML tag where the name of the tag is the component type.

Pages/Index.razor의 다음 태그는 HeadingComponent 인스턴스를 렌더링합니다.The following markup in Pages/Index.razor renders a HeadingComponent instance:

<HeadingComponent />

Shared/HeadingComponent.razor:Shared/HeadingComponent.razor:

@using System.Globalization

<h1 style="font-style:@headingFontStyle">@headingText</h1>

<form>
    <div>
        <label class="form-check-label">
            <input type="checkbox" id="italicsCheck" 
               @bind="italicsCheck" />
            Use italics
        </label>
    </div>

    <button type="button" class="btn btn-primary" @onclick="UpdateHeading">
        Update heading
    </button>
</form>

@code {
    private static TextInfo tinfo = CultureInfo.CurrentCulture.TextInfo;
    private string headingText = 
        tinfo.ToTitleCase("welcome to blazor!");
    private string headingFontStyle = "normal";
    private bool italicsCheck = false;

    public void UpdateHeading()
    {
        headingFontStyle = italicsCheck ? "italic" : "normal";
    }
}
@using System.Globalization

<h1 style="font-style:@headingFontStyle">@headingText</h1>

<form>
    <div>
        <label class="form-check-label">
            <input type="checkbox" id="italicsCheck" 
               @bind="italicsCheck" />
            Use italics
        </label>
    </div>

    <button type="button" class="btn btn-primary" @onclick="UpdateHeading">
        Update heading
    </button>
</form>

@code {
    private static TextInfo tinfo = CultureInfo.CurrentCulture.TextInfo;
    private string headingText = 
        tinfo.ToTitleCase("welcome to blazor!");
    private string headingFontStyle = "normal";
    private bool italicsCheck = false;

    public void UpdateHeading()
    {
        headingFontStyle = italicsCheck ? "italic" : "normal";
    }
}

구성 요소에 구성 요소 이름과 일치하지 않는 HTML 요소(첫 글자가 대문자임)가 포함되면 요소에 예기치 않은 이름이 있음을 나타내는 경고가 발생합니다.If a component contains an HTML element with an uppercase first letter that doesn't match a component name, a warning is emitted indicating that the element has an unexpected name. 구성 요소 네임스페이스에 대한 @using 지시문을 추가하면 해당 구성 요소를 사용할 수 있게 되므로 경고가 해결됩니다.Adding an @using directive for the component's namespace makes the component available, which resolves the warning.

매개 변수Parameters

구성 요소 매개 변수Component parameters

구성 요소에는 [Parameter] 특성이 있는 구성 요소 클래스의 퍼블릭 단순 속성 또는 복합 속성을 사용하여 정의되는 구성 요소 매개 변수 가 있을 수 있습니다.Components can have component parameters, which are defined using public simple or complex properties on the component class with the [Parameter] attribute. 특성을 사용하여 태그에서 구성 요소의 인수를 지정합니다.Use attributes to specify arguments for a component in markup.

Shared/ChildComponent.razor:Shared/ChildComponent.razor:

<div class="panel panel-default">
    <div class="panel-heading">@Title</div>
    <div class="panel-body">@ChildContent</div>

    <button class="btn btn-primary" @onclick="OnClickCallback">
        Trigger a Parent component method
    </button>
</div>

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

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

구성 요소 매개 변수에는 기본값을 할당할 수 있습니다.Component parameters can be assigned a default value:

[Parameter]
public string Title { get; set; } = "Panel Title from Child";

샘플 앱의 다음 예제에서는 ParentComponentChildComponentTitle 속성 값을 설정합니다.In the following example from the sample app, the ParentComponent sets the value of the Title property of the ChildComponent.

Pages/ParentComponent.razor:Pages/ParentComponent.razor:

@page "/ParentComponent"

<h1>Parent-child example</h1>

<ChildComponent Title="Panel Title from Parent"
                OnClickCallback="@ShowMessage">
    Content of the child component is supplied
    by the parent component.
</ChildComponent>

Razor의 예약된 @ 기호를 사용하여 구성 요소 매개 변수에 C# 필드, 속성 및 메서드를 HTML 특성 값으로 할당합니다.Assign C# fields, properties, and methods to component parameters as HTML attribute values using Razor's reserved @ symbol:

  • 부모 구성 요소의 필드, 속성 또는 메서드를 자식 구성 요소의 매개 변수에 할당하려면 필드, 속성 또는 메서드 이름에 @ 기호를 접두사로 사용합니다.To assign a parent component's field, property, or method to a child component's parameter, prefix the field, property, or method name with the @ symbol. 암시적 C# 식의 결과를 매개 변수에 할당하려면 암시적인 식에 @ 기호를 접두사로 사용합니다.To assign the result of an implicit C# expression to a parameter, prefix the implicit expression with an @ symbol.

    다음 부모 구성 요소는 위 ChildComponent 구성 요소의 인스턴스 4개를 표시하고 해당 Title 매개 변수 값을 다음 값으로 설정합니다.The following parent component displays four instances of the preceding ChildComponent component and sets their Title parameter values to:

    • title 필드의 값The value of the title field.
    • GetTitle C# 메서드의 결과The result of the GetTitle C# method.
    • ToLongDateString를 사용하는 긴 형식의 현재 현지 날짜The current local date in long format with ToLongDateString.
    • person 개체의 Name 속성The person object's Name property.

    Pages/ParentComponent.razor:Pages/ParentComponent.razor:

    <ChildComponent Title="@title">
        Title from field.
    </ChildComponent>
    
    <ChildComponent Title="@GetTitle()">
        Title from method.
    </ChildComponent>
    
    <ChildComponent Title="@DateTime.Now.ToLongDateString()">
        Title from implicit Razor expression.
    </ChildComponent>
    
    <ChildComponent Title="@person.Name">
        Title from implicit Razor expression.
    </ChildComponent>
    
    @code {
        private string title = "Panel Title from Parent";
        private Person person = new();
    
        private string GetTitle()
        {
            return "Panel Title from Parent";
        }
    
        private class Person
        {
            public string Name { get; set; } = "Dr. Who";
        }
    }
    
    <ChildComponent Title="@title">
        Title from field.
    </ChildComponent>
    
    <ChildComponent Title="@GetTitle()">
        Title from method.
    </ChildComponent>
    
    <ChildComponent Title="@DateTime.Now.ToLongDateString()">
        Title from implicit Razor expression.
    </ChildComponent>
    
    <ChildComponent Title="@person.Name">
        Title from implicit Razor expression.
    </ChildComponent>
    
    @code {
        private string title = "Panel Title from Parent";
        private Person person = new Person();
    
        private string GetTitle()
        {
            return "Panel Title from Parent";
        }
    
        private class Person
        {
            public string Name { get; set; } = "Dr. Who";
        }
    }
    

    Razor 페이지(.cshtml)에서와 달리 Blazor는 구성 요소를 렌더링하는 동안 Razor 식에서 비동기 작업을 수행할 수 없습니다.Unlike in Razor pages (.cshtml), Blazor can't perform asynchronous work in a Razor expression while rendering a component. 이는 Blazor가 대화형 Ui를 렌더링하는 용도로 설계되었기 때문입니다.This is because Blazor is designed for rendering interactive UIs. 대화형 UI에서 화면은 항상 무언가를 표시해야 하므로 렌더링 흐름을 차단하는 것은 적합하지 않습니다.In an interactive UI, the screen must always display something, so it doesn't make sense to block the rendering flow. 대신 비동기 작업은 비동기 수명 주기 이벤트 중 하나에서 수행됩니다.Instead, asynchronous work is performed during one of the asynchronous lifecycle events. 각 비동기 수명 주기 이벤트 이후 구성 요소가 다시 렌더링될 수 있습니다.After each asynchronous lifecycle event, the component may render again. 다음 Razor 구문은 지원되지 않습니다.The following Razor syntax is not supported:

    <ChildComponent Title="@await ...">
        ...
    </ChildComponent>
    

    위 예제의 코드는 앱이 빌드되면 컴파일러 오류를 생성합니다.The code in the preceding example generates a compiler error if the app is built:

    'Await' 연산자는 비동기 메서드 내에서만 사용할 수 있습니다.The 'await' operator can only be used within an async method. 이 메서드를 'Async' 한정자로 표시하고 해당 반환 형식을 'Task'로 변경하세요.Consider marking this method with the 'async' modifier and changing its return type to 'Task'.

    위 예제의 Title 매개 변수에 대한 값을 비동기식으로 가져오려면 다음 예제와 같이 구성 요소에서 OnInitializedAsync 수명 주기 이벤트를 사용할 수 있습니다.To obtain a value for the Title parameter in the preceding example asychronously, the component can use the OnInitializedAsync lifecycle event, as the following example demonstrates:

    <ChildComponent Title="@title">
        Title from implicit Razor expression.
    </ChildComponent>
    
    @code {
        private string title;
    
        protected override async Task OnInitializedAsync()
        {
            title = await ...;
        }
    }
    
  • 부모 구성 요소의 명시적 C# 식 결과를 자식 구성 요소의 매개 변수에 할당하려면 식을 괄호로 묶고 @ 기호를 접두사로 사용합니다.To assign the result of an explicit C# expression in the parent component to a child component's parameter, surround the expression in parentheses with an @ symbol prefix.

    다음 자식 구성 요소에는 DateTime 구성 요소 매개 변수인 ShowItemsSinceDate가 있습니다.The following child component has a DateTime component parameter, ShowItemsSinceDate.

    Shared/ChildComponent.razor:Shared/ChildComponent.razor:

    <div class="panel panel-default">
        <div class="panel-heading">Explicit DateTime Expression Example</div>
        <div class="panel-body">
            <p>@ChildContent</p>
            <p>One week ago date: @ShowItemsSinceDate</p>
        </div>
    </div>
    
    @code {
        [Parameter]
        public DateTime ShowItemsSinceDate { get; set; }
    
        [Parameter]
        public RenderFragment ChildContent { get; set; }
    }
    

    다음 부모 구성 요소는 자식 요소의 ShowItemsSinceDate 매개 변수에 할당하기 위해 이전에는 1주였던 명시적 C# 식으로 날짜를 계산합니다.The following parent component calculates a date with an explicit C# expression that's one week in the past for assignment to the child's ShowItemsSinceDate parameter.

    Pages/ParentComponent.razor:Pages/ParentComponent.razor:

    <ChildComponent ShowItemsSinceDate="@(DateTime.Now - TimeSpan.FromDays(7))">
        Title from explicit Razor expression.
    </ChildComponent>
    

    식 결과와 텍스트를 연결하여 매개 변수에 할당하기 위한 명시적 식의 사용은 지원되지 않습니다.Use of an explicit expression to concatenate text with an expression result for assignment to a parameter is not supported. 다음 예제에서는 "SKU-" 텍스트를 부모 구성 요소의 product 개체에서 제공하는 제품 재고 번호(SKU 속성, "재고 관리 단위")와 연결하려고 합니다.The following example seeks to concatenate the text "SKU-" with a product stock number (SKU property, "Stock Keeping Unit") provided by a parent component's product object. 이 구문은 Razor 페이지(.cshtml)에서 지원되지만 자식 요소의 Title 매개 변수에 할당하는 데 유효하지 않습니다.Although this syntax is supported in a Razor page (.cshtml), it isn't valid for assignment to the child's Title parameter.

    <ChildComponent Title="SKU-@(product.SKU)">
        Title from composed Razor expression. This doesn't compile.
    </ChildComponent>
    

    위 예제의 코드는 앱이 빌드되면 컴파일러 오류를 생성합니다.The code in the preceding example generates a compiler error if the app is built:

    구성 요소 특성은 복합 콘텐츠(C# 및 마크업 혼합)를 지원하지 않습니다.Component attributes do not support complex content (mixed C# and markup).

    구성된 값의 할당을 지원하려면 메서드, 필드 또는 속성을 사용합니다.To support the assignment of a composed value, use a method, field, or property. 다음 예제에서는 GetTitle C# 메서드에서 "SKU-"와 제품 재고 번호의 연결을 수행합니다.The following example performs the concatination of "SKU-" and a product's stock number in the C# method GetTitle:

    <ChildComponent Title="@GetTitle()">
        Composed title from method.
    </ChildComponent>
    
    @code {
        private Product product = new();
    
        private string GetTitle() => $"SKU-{product.SKU}";
    
        private class Product
        {
            public string SKU { get; set; } = "12345";
        }
    }
    
    <ChildComponent Title="@GetTitle()">
        Composed title from method.
    </ChildComponent>
    
    @code {
        private Product product = new Product();
    
        private string GetTitle() => $"SKU-{product.SKU}";
    
        private class Product
        {
            public string SKU { get; set; } = "12345";
        }
    }
    

자세한 내용은 ASP.NET Core용 Razor 구문 참조를 참조하세요.For more information, see ASP.NET Core용 Razor 구문 참조.

경고

자체 구성 요소 매개 변수 에 쓰는 구성 요소를 만들지 말고 대신 private 필드를 사용합니다.Don't create components that write to their own component parameters, use a private field instead. 자세한 내용은 덮어쓴 매개 변수 섹션을 참조하세요.For more information, see the Overwritten parameters section.

구성 요소 매개 변수는 auto 속성이어야 함Component parameters should be auto-properties

구성 요소 매개 변수는 auto 속성으로 선언되어야 합니다. 즉, getter 또는 setter에 사용자 지정 논리를 포함하지 않아야 합니다.Component parameters should be declared as auto-properties, meaning that they shouldn't contain custom logic in their getters or setters. 예를 들어 다음 StartData 속성은 auto 속성입니다.For example, the following StartData property is an auto-property:

[Parameter]
public DateTime StartData { get; set; }

구성 요소 매개 변수는 순전히 부모 구성 요소에서 자식 구성 요소로 정보를 전달하는 통로로서 사용하기 위한 것이므로 사용자 지정 논리를 get 또는 set 접근자에 배치하지 마세요.Don't place custom logic in the get or set accessor because component parameters are purely intended for use as a channel for a parent component to flow information to a child component. 자식 구성 요소 속성의 setter가 부모 구성 요소의 렌더링을 야기하는 논리를 포함하는 경우 무한 렌더링 루프가 발생합니다.If a setter of a child component property contains logic that causes rerendering of the parent component, an infinite rendering loop results.

받은 매개 변수 값을 변환해야 하는 경우 다음을 수행합니다.If you need to transform a received parameter value:

  • 매개 변수 속성을 순수 auto 속성으로 남겨 두어 제공된 원시 데이터를 표시합니다.Leave the parameter property as a pure auto-property to represent the supplied raw data.
  • 매개 변수 속성을 기반으로 변환된 데이터를 제공하는 다른 속성 또는 메서드를 만듭니다.Create some other property or method that supplies the transformed data based on the parameter property.

OnParametersSetAsync를 재정의하여 새 데이터를 받을 때마다 받은 매개 변수를 변환할 수 있습니다.You can override OnParametersSetAsync if you want to transform a received parameter each time new data is received.

경로 매개 변수Route parameters

구성 요소는 @page 지시문에 제공된 경로 템플릿에서 경로 매개 변수를 받을 수 있습니다.Components can receive route parameters from the route template provided in the @page directive. 라우터는 경로 매개 변수를 사용하여 해당하는 구성 요소 매개 변수를 채웁니다.The router uses route parameters to populate the corresponding component parameters.

선택적 매개 변수가 지원됩니다.Optional parameters are supported. 다음 예제에서 text 선택적 매개 변수는 경로 세그먼트의 값을 구성 요소의 Text 속성에 할당합니다.In the following example, the text optional parameter assigns the value of the route segment to the component's Text property. 세그먼트가 없으면 Text 값이 fantastic으로 설정됩니다.If the segment isn't present, the value of Text is set to fantastic.

Pages/RouteParameter.razor:Pages/RouteParameter.razor:

@page "/RouteParameter/{text?}"

<h1>Blazor is @Text!</h1>

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

    protected override void OnInitialized()
    {
        Text = Text ?? "fantastic";
    }
}

Pages/RouteParameter.razor:Pages/RouteParameter.razor:

@page "/RouteParameter"
@page "/RouteParameter/{text}"

<h1>Blazor is @Text!</h1>

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

    protected override void OnInitialized()
    {
        Text = Text ?? "fantastic";
    }
}

선택적 매개 변수는 지원되지 않으므로 앞의 예제에서 두 개의 @page 지시문이 적용됩니다.Optional parameters aren't supported, so two @page directives are applied in the preceding example. 첫 번째 지시문은 매개 변수 없이 구성 요소 탐색을 허용합니다.The first permits navigation to the component without a parameter. 두 번째 @page 지시문은 {text} 경로 매개 변수를 받고 Text 속성에 값을 할당합니다.The second @page directive receives the {text} route parameter and assigns the value to the Text property.

여러 폴더 경계에서 경로를 캡처하는 범용 경로 매개 변수({*pageRoute})에 대한 자세한 내용은 ASP.NET Core Blazor 라우팅을 참조하세요.For information on catch-all route parameters ({*pageRoute}), which capture paths across multiple folder boundaries, see ASP.NET Core Blazor 라우팅.

자식 콘텐츠Child content

구성 요소는 다른 구성 요소의 콘텐츠를 설정할 수 있습니다.Components can set the content of another component. 할당 구성 요소는 받는 구성 요소를 지정하는 태그 간 콘텐츠를 제공합니다.The assigning component provides the content between the tags that specify the receiving component.

다음 예제에서 ChildComponent에는 렌더링할 UI의 세그먼트를 나타내는 RenderFragment를 나타내는 ChildContent 속성이 있습니다.In the following example, the ChildComponent has a ChildContent property that represents a RenderFragment, which represents a segment of UI to render. ChildContent의 값은 콘텐츠를 렌더링해야 하는 구성 요소의 태그에 배치됩니다.The value of ChildContent is positioned in the component's markup where the content should be rendered. ChildContent 값은 부모 구성 요소에서 수신되고 부트스트랩 패널의 panel-body 내에서 렌더링됩니다.The value of ChildContent is received from the parent component and rendered inside the Bootstrap panel's panel-body.

Shared/ChildComponent.razor:Shared/ChildComponent.razor:

<div class="panel panel-default">
    <div class="panel-heading">@Title</div>
    <div class="panel-body">@ChildContent</div>

    <button class="btn btn-primary" @onclick="OnClickCallback">
        Trigger a Parent component method
    </button>
</div>

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

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

참고

RenderFragment 콘텐츠를 받는 속성은 규칙에 따라 이름 ChildContent가 지정되어야 합니다.The property receiving the RenderFragment content must be named ChildContent by convention.

샘플 앱의 ParentComponent는 콘텐츠를 <ChildComponent> 태그 안에 배치하여 ChildComponent를 렌더링하기 위한 콘텐츠를 제공할 수 있습니다.The ParentComponent in the sample app can provide content for rendering the ChildComponent by placing the content inside the <ChildComponent> tags.

Pages/ParentComponent.razor:Pages/ParentComponent.razor:

@page "/ParentComponent"

<h1>Parent-child example</h1>

<ChildComponent Title="Panel Title from Parent"
                OnClickCallback="@ShowMessage">
    Content of the child component is supplied
    by the parent component.
</ChildComponent>

Blazor가 자식 콘텐츠를 렌더링하는 방식 때문에 for 루프 내에서 구성 요소를 렌더링하려면 자식 구성 요소 콘텐츠에서 증분 루프 변수를 사용할 경우 로컬 인덱스 변수가 필요합니다.Due to the way that Blazor renders child content, rendering components inside a for loop requires a local index variable if the incrementing loop variable is used in the child component's content:

@for (int c = 0; c < 10; c++)
{
    var current = c;
    <ChildComponent Title="@c">
        Child Content: Count: @current
    </ChildComponent>
}

또는 Enumerable.Range를 활용하여 foreach 루프를 사용할 수 있습니다.Alternatively, use a foreach loop with Enumerable.Range:

@foreach(var c in Enumerable.Range(0,10))
{
    <ChildComponent Title="@c">
        Child Content: Count: @c
    </ChildComponent>
}

Razor 구성 요소 UI에 대해 RenderFragment이 템플릿으로 사용될 수 있는 방법을 자세히 알아보려면 다음 문서를 참조하십시오.For information on how a RenderFragment can be used as a template for Razor component UI, see the following articles:

특성 스플래팅 및 임의 매개 변수Attribute splatting and arbitrary parameters

구성 요소는 구성 요소의 선언된 매개 변수 외에, 추가 특성도 캡처하고 렌더링할 수 있습니다.Components can capture and render additional attributes in addition to the component's declared parameters. 추가 특성을 사전에 캡처한 다음, @attributes Razor 지시문을 사용하여 구성 요소를 렌더링할 때 요소에 ‘스플래팅’할 수 있습니다.Additional attributes can be captured in a dictionary and then splatted onto an element when the component is rendered using the @attributes Razor directive. 이 시나리오는 다양한 사용자 지정을 지원하는 태그 요소를 생성하는 구성 요소를 정의할 때 유용합니다.This scenario is useful when defining a component that produces a markup element that supports a variety of customizations. 예를 들어, 많은 매개 변수를 지원하는 <input>에 대해 개별적으로 특성을 정의하는 것이 번거로울 수 있습니다.For example, it can be tedious to define attributes separately for an <input> that supports many parameters.

다음 예제에서 첫 번째 <input> 요소(id="useIndividualParams")는 개별 구성 요소 매개 변수를 사용하지만 두 번째 <input> 요소(id="useAttributesDict")는 특성 스플래팅을 사용합니다.In the following example, the first <input> element (id="useIndividualParams") uses individual component parameters, while the second <input> element (id="useAttributesDict") uses attribute splatting:

<input id="useIndividualParams"
       maxlength="@maxlength"
       placeholder="@placeholder"
       required="@required"
       size="@size" />

<input id="useAttributesDict"
       @attributes="InputAttributes" />

@code {
    public string maxlength = "10";
    public string placeholder = "Input placeholder text";
    public string required = "required";
    public string size = "50";

    public Dictionary<string, object> InputAttributes { get; set; } =
        new Dictionary<string, object>()
        {
            { "maxlength", "10" },
            { "placeholder", "Input placeholder text" },
            { "required", "required" },
            { "size", "50" }
        };
}

매개 변수의 형식은 문자열 키를 사용하여 IEnumerable<KeyValuePair<string, object>> 또는 IReadOnlyDictionary<string, object>를 구현해야 합니다.The type of the parameter must implement IEnumerable<KeyValuePair<string, object>> or IReadOnlyDictionary<string, object> with string keys.

두 방법을 사용하여 렌더링되는 <input> 요소는 동일합니다.The rendered <input> elements using both approaches is identical:

<input id="useIndividualParams"
       maxlength="10"
       placeholder="Input placeholder text"
       required="required"
       size="50">

<input id="useAttributesDict"
       maxlength="10"
       placeholder="Input placeholder text"
       required="required"
       size="50">

임의 특성을 허용하려면 CaptureUnmatchedValues 속성이 true로 설정된 [Parameter] 특성을 사용하여 구성 요소 매개 변수를 정의합니다.To accept arbitrary attributes, define a component parameter using the [Parameter] attribute with the CaptureUnmatchedValues property set to true:

@code {
    [Parameter(CaptureUnmatchedValues = true)]
    public Dictionary<string, object> InputAttributes { get; set; }
}

[Parameter]CaptureUnmatchedValues 속성을 사용하면 해당 매개 변수는 다른 매개 변수와 일치하지 않는 모든 특성을 일치시킬 수 있습니다.The CaptureUnmatchedValues property on [Parameter] allows the parameter to match all attributes that don't match any other parameter. 구성 요소는 CaptureUnmatchedValues를 사용하여 단일 매개 변수만 정의할 수 있습니다.A component can only define a single parameter with CaptureUnmatchedValues. CaptureUnmatchedValues에 사용되는 속성 형식은 문자열 키가 있는 Dictionary<string, object>에서 할당할 수 있어야 합니다.The property type used with CaptureUnmatchedValues must be assignable from Dictionary<string, object> with string keys. 이 시나리오에서는 IEnumerable<KeyValuePair<string, object>> 또는 IReadOnlyDictionary<string, object>도 옵션입니다.IEnumerable<KeyValuePair<string, object>> or IReadOnlyDictionary<string, object> are also options in this scenario.

요소 특성의 위치에 상대적인 @attributes의 위치는 중요합니다.The position of @attributes relative to the position of element attributes is important. 요소에 @attributes가 스플래팅되면 오른쪽에서 왼쪽으로(마지막에 도달하면 처음으로) 특성이 처리됩니다.When @attributes are splatted on the element, the attributes are processed from right to left (last to first). Child 구성 요소를 사용하는 구성 요소의 다음 예를 살펴보세요.Consider the following example of a component that consumes a Child component:

ParentComponent.razor:ParentComponent.razor:

<ChildComponent extra="10" />

ChildComponent.razor:ChildComponent.razor:

<div @attributes="AdditionalAttributes" extra="5" />

[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> AdditionalAttributes { get; set; }

Child 구성 요소의 extra 특성은 @attributes 오른쪽으로 설정됩니다.The Child component's extra attribute is set to the right of @attributes. 특성은 오른쪽에서 왼쪽으로(마지막에 도달하면 처음으로) 처리되기 때문에 Parent 구성 요소의 렌더링된 <div>는 추가 특성을 통해 전달될 때 extra="5"를 포함합니다.The Parent component's rendered <div> contains extra="5" when passed through the additional attribute because the attributes are processed right to left (last to first):

<div extra="5" />

다음 예에서는 Child 구성 요소의 <div>에서 extra@attributes의 순서가 반대로 바뀝니다.In the following example, the order of extra and @attributes is reversed in the Child component's <div>:

ParentComponent.razor:ParentComponent.razor:

<ChildComponent extra="10" />

ChildComponent.razor:ChildComponent.razor:

<div extra="5" @attributes="AdditionalAttributes" />

[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> AdditionalAttributes { get; set; }

Parent 구성 요소의 렌더링된 <div>에는 추가 특성을 통해 전달될 경우 extra="10"을 포함됩니다.The rendered <div> in the Parent component contains extra="10" when passed through the additional attribute:

<div extra="10" />

구성 요소에 대한 참조 캡처Capture references to components

구성 요소 참조에서는 해당 인스턴스에 대해 명령(예: (Show 또는 Reset)을 실행할 수 있도록 구성 요소 인스턴스를 참조하는 방법을 제공합니다.Component references provide a way to reference a component instance so that you can issue commands to that instance, such as Show or Reset. 구성 요소 참조를 캡처하려면 다음을 수행합니다.To capture a component reference:

  • 자식 구성 요소에 @ref 특성을 추가합니다.Add an @ref attribute to the child component.
  • 자식 구성 요소와 동일한 유형으로 필드를 정의합니다.Define a field with the same type as the child component.
<CustomLoginDialog @ref="loginDialog" ... />

@code {
    private CustomLoginDialog loginDialog;

    private void OnSomething()
    {
        loginDialog.Show();
    }
}

구성 요소가 렌더링되면 loginDialog 필드가 CustomLoginDialog 자식 구성 요소 인스턴스로 채워집니다.When the component is rendered, the loginDialog field is populated with the CustomLoginDialog child component instance. 그런 다음, 구성 요소 인스턴스에서 .NET 메서드를 호출할 수 있습니다.You can then invoke .NET methods on the component instance.

중요

loginDialog 변수는 구성 요소가 렌더링된 후에만 채워지고 출력에는 MyLoginDialog 요소가 포함됩니다.The loginDialog variable is only populated after the component is rendered and its output includes the MyLoginDialog element. 구성 요소가 렌더링될 때까지는 참조할 요소가 없습니다.Until the component is rendered, there's nothing to reference.

구성 요소에서 렌더링을 완료한 후에 구성 요소 참조를 조작하려면 OnAfterRender 또는 OnAfterRenderAsync 메서드를 사용합니다.To manipulate components references after the component has finished rendering, use the OnAfterRender or OnAfterRenderAsync methods.

이벤트 처리기에서 참조 변수를 사용하려면 람다 식을 사용하거나 OnAfterRender 또는 OnAfterRenderAsync 메서드에 이벤트 처리기 대리자를 할당합니다.To use a reference variable with an event handler, use a lambda expression or assign the event handler delegate in the OnAfterRender or OnAfterRenderAsync methods. 이렇게 하면 이벤트 처리기가 할당되기 전에 참조 변수가 할당됩니다.This ensures that the reference variable is assigned before the event handler is assigned.

<button type="button" 
    @onclick="@(() => loginDialog.DoSomething())">Do Something</button>

<MyLoginDialog @ref="loginDialog" ... />

@code {
    private MyLoginDialog loginDialog;
}

루프의 구성 요소를 참조하려면 Capture references to multiple similar child-components(dotnet/aspnetcore #13358)(여러 비슷한 자식 구성 요소에 대한 참조 캡처)를 참조하세요.To reference components in a loop, see Capture references to multiple similar child-components (dotnet/aspnetcore #13358).

구성 요소 참조 캡처에는 요소 참조 캡처와 유사한 구문을 사용하지만 JavaScript interop 기능이 아닙니다.While capturing component references use a similar syntax to capturing element references, it isn't a JavaScript interop feature. 구성 요소 참조가 JavaScript 코드로 전달되지 않습니다.Component references aren't passed to JavaScript code. 구성 요소 참조는 .NET 코드에서만 사용됩니다.Component references are only used in .NET code.

참고

구성 요소 참조를 사용하여 자식 구성 요소의 상태를 변경하지 않도록 합니다.Do not use component references to mutate the state of child components. 대신, 일반 선언적 매개 변수를 사용하여 자식 구성 요소에 데이터를 전달합니다.Instead, use normal declarative parameters to pass data to child components. 일반 선언적 매개 변수를 사용하면 자식 구성 요소가 올바른 시간에 자동으로 다시 렌더링됩니다.Use of normal declarative parameters result in child components that rerender at the correct times automatically.

동기화 컨텍스트Synchronization context

Blazor는 동기화 컨텍스트(SynchronizationContext)를 사용하여 단일 논리적 실행 스레드를 적용합니다.Blazor uses a synchronization context (SynchronizationContext) to enforce a single logical thread of execution. 구성 요소의 수명 주기 메서드 및 Blazor에서 발생하는 모든 이벤트 콜백은 이 동기화 컨텍스트에서 실행됩니다.A component's lifecycle methods and any event callbacks that are raised by Blazor are executed on the synchronization context.

Blazor Server의 동기화 컨텍스트는 단일 스레드인 브라우저의 WebAssembly 모델과 거의 일치하도록 단일 스레드 환경 에뮬레이션을 시도합니다.Blazor Server's synchronization context attempts to emulate a single-threaded environment so that it closely matches the WebAssembly model in the browser, which is single threaded. 지정된 시점에서 작업이 정확히 하나의 스레드에서만 수행되어 단일 논리적 스레드의 느낌을 제공합니다.At any given point in time, work is performed on exactly one thread, giving the impression of a single logical thread. 두 작업이 동시에 실행되지는 않습니다.No two operations execute concurrently.

스레드 차단 호출 방지Avoid thread-blocking calls

일반적으로 다음 메서드를 호출하지 마세요.Generally, don't call the following methods. 다음 메서드는 스레드를 차단하므로 기본 Task가 완료될 때까지 앱이 작업을 다시 시작하지 못하게 차단합니다.The following methods block the thread and thus block the app from resuming work until the underlying Task is complete:

외부에서 구성 요소 메서드를 호출하여 상태 업데이트Invoke component methods externally to update state

외부 이벤트(예: 타이머 또는 다른 알림)를 기준으로 구성 요소를 업데이트해야 하는 경우 Blazor의 동기화 컨텍스트에 디스패치되는 InvokeAsync 메서드를 사용합니다.In the event a component must be updated based on an external event, such as a timer or other notifications, use the InvokeAsync method, which dispatches to Blazor's synchronization context. 예를 들어, 업데이트된 상태를 수신 구성 요소에 알릴 수 있는 알림 서비스 을 고려해 보세요.For example, consider a notifier service that can notify any listening component of the updated state:

public class NotifierService
{
    // Can be called from anywhere
    public async Task Update(string key, int value)
    {
        if (Notify != null)
        {
            await Notify.Invoke(key, value);
        }
    }

    public event Func<string, int, Task> Notify;
}

NotifierService를 등록합니다.Register the NotifierService:

  • Blazor WebAssembly에서 Program.Main의 singleton으로 서비스를 등록합니다.In Blazor WebAssembly, register the service as singleton in Program.Main:

    builder.Services.AddSingleton<NotifierService>();
    
  • Blazor Server에서 Startup.ConfigureServices에 지정된 범위대로 서비스를 등록합니다.In Blazor Server, register the service as scoped in Startup.ConfigureServices:

    services.AddScoped<NotifierService>();
    

NotifierService를 사용하여 구성 요소를 업데이트합니다.Use the NotifierService to update a component:

@page "/"
@inject NotifierService Notifier
@implements IDisposable

<p>Last update: @lastNotification.key = @lastNotification.value</p>

@code {
    private (string key, int value) lastNotification;

    protected override void OnInitialized()
    {
        Notifier.Notify += OnNotify;
    }

    public async Task OnNotify(string key, int value)
    {
        await InvokeAsync(() =>
        {
            lastNotification = (key, value);
            StateHasChanged();
        });
    }

    public void Dispose()
    {
        Notifier.Notify -= OnNotify;
    }
}

앞의 예제에서:In the preceding example:

  • NotifierService는 Blazor의 동기화 컨텍스트 외부에서 구성 요소의 OnNotify 메서드를 호출합니다.NotifierService invokes the component's OnNotify method outside of Blazor's synchronization context. InvokeAsync는 올바른 컨텍스트로 전환하고 렌더링을 큐에 대기하는 데 사용됩니다.InvokeAsync is used to switch to the correct context and queue a render. 자세한 내용은 ASP.NET Core Blazor 구성 요소 렌더링를 참조하세요.For more information, see ASP.NET Core Blazor 구성 요소 렌더링.
  • 구성 요소는 IDisposable을 구현하고, OnNotify 대리자는 구성 요소가 삭제될 때 프레임워크에서 호출되는 Dispose 메서드에서 구독 취소됩니다.The component implements IDisposable, and the OnNotify delegate is unsubscribed in the Dispose method, which is called by the framework when the component is disposed. 자세한 내용은 ASP.NET Core Blazor 수명 주기를 참조하세요.For more information, see ASP.NET Core Blazor 수명 주기.

@ 키를 사용하여 요소 및 구성 요소 유지Use @key to control the preservation of elements and components

요소 또는 구성 요소 목록을 렌더링하고, 이후에 요소 또는 구성 요소가 변경되는 경우 Blazor의 Diff 알고리즘은 유지할 수 있는 이전 요소 또는 구성 요소와 모델 개체가 이러한 요소에 매핑되는 방법을 결정해야 합니다.When rendering a list of elements or components and the elements or components subsequently change, Blazor's diffing algorithm must decide which of the previous elements or components can be retained and how model objects should map to them. 일반적으로 이 프로세스는 자동이며 무시해도 되지만 프로세스를 제어하려는 경우가 있습니다.Normally, this process is automatic and can be ignored, but there are cases where you may want to control the process.

다음 예제를 참조하세요.Consider the following example:

@foreach (var person in People)
{
    <DetailsEditor Details="@person.Details" />
}

@code {
    [Parameter]
    public IEnumerable<Person> People { get; set; }
}

People 컬렉션의 콘텐츠는 삽입, 삭제 또는 다시 정렬된 항목으로 변경될 수 있습니다.The contents of the People collection may change with inserted, deleted, or re-ordered entries. 구성 요소가 다시 렌더링되면 <DetailsEditor> 구성 요소가 다른 Details 매개 변수 값을 수신하도록 변경될 수 있습니다.When the component rerenders, the <DetailsEditor> component may change to receive different Details parameter values. 이로 인해 다시 렌더링이 예상보다 더 복잡해질 수 있습니다.This may cause more complex rerendering than expected. 경우에 따라 다시 렌더링을 수행하면 요소 포커스 손실과 같은 동작 차이가 표시될 수 있습니다.In some cases, rerendering can lead to visible behavior differences, such as lost element focus.

매핑 프로세스는 @key 지시문 특성을 사용하여 제어할 수 있습니다.The mapping process can be controlled with the @key directive attribute. @key를 사용하면 diff 알고리즘은 키의 값에 따라 요소 또는 구성 요소가 유지되도록 합니다.@key causes the diffing algorithm to guarantee preservation of elements or components based on the key's value:

@foreach (var person in People)
{
    <DetailsEditor @key="person" Details="@person.Details" />
}

@code {
    [Parameter]
    public IEnumerable<Person> People { get; set; }
}

People 컬렉션이 변경되면 diff 알고리즘은 <DetailsEditor> 인스턴스와 person 인스턴스 간 연결을 유지합니다.When the People collection changes, the diffing algorithm retains the association between <DetailsEditor> instances and person instances:

  • People 목록에서 Person이 삭제된 경우 해당 <DetailsEditor> 인스턴스만 UI에서 제거됩니다.If a Person is deleted from the People list, only the corresponding <DetailsEditor> instance is removed from the UI. 다른 인스턴스는 변경되지 않은 상태로 유지됩니다.Other instances are left unchanged.
  • Person이 목록의 특정 위치에 삽입되는 경우 해당 위치에 하나의 새 <DetailsEditor> 인스턴스가 삽입됩니다.If a Person is inserted at some position in the list, one new <DetailsEditor> instance is inserted at that corresponding position. 다른 인스턴스는 변경되지 않은 상태로 유지됩니다.Other instances are left unchanged.
  • Person 항목이 다시 정렬되면 해당 <DetailsEditor> 인스턴스가 유지되고 UI에서 다시 정렬됩니다.If Person entries are re-ordered, the corresponding <DetailsEditor> instances are preserved and re-ordered in the UI.

일부 시나리오에서는 @key를 사용하여 재랜더링의 복잡성을 최소화하고, DOM의 상태 저장 부분(예: 포커스 위치)이 변경될 수 있는 잠재적 문제를 방지할 수 있습니다.In some scenarios, use of @key minimizes the complexity of rerendering and avoids potential issues with stateful parts of the DOM changing, such as focus position.

중요

키는 각 컨테이너 요소 또는 구성 요소에 대해 로컬입니다.Keys are local to each container element or component. 문서 전체에서 키가 전역적으로 비교되지 않습니다.Keys aren't compared globally across the document.

@ 키를 사용하는 경우When to use @key

일반적으로 목록이 렌더링될 때마다(예: foreach 블록에서) @key를 사용하는 것이 적절하며 @key를 정의하기 위한 적절한 값이 있습니다.Typically, it makes sense to use @key whenever a list is rendered (for example, in a foreach block) and a suitable value exists to define the @key.

@key를 사용하여 개체가 변경될 때 Blazor가 요소 또는 구성 요소 하위 트리를 유지하지 않도록 할 수도 있습니다.You can also use @key to prevent Blazor from preserving an element or component subtree when an object changes:

<div @key="currentPerson">
    ... content that depends on currentPerson ...
</div>

@currentPerson이 변경되면 @key 특성 지시문은 Blazor가 강제로 전체 <div> 및 해당 하위 항목을 삭제하고 새 요소와 구성 요소를 사용하여 UI 내에서 하위 트리를 다시 빌드하도록 합니다.If @currentPerson changes, the @key attribute directive forces Blazor to discard the entire <div> and its descendants and rebuild the subtree within the UI with new elements and components. 이것은 @currentPerson이 변경될 때 UI 상태가 유지되지 않도록 해야 하는 경우에 유용할 수 있습니다.This can be useful if you need to guarantee that no UI state is preserved when @currentPerson changes.

@ 키를 사용하지 않는 경우When not to use @key

@key로 diff를 수행할 때 성능 비용이 발생합니다.There's a performance cost when diffing with @key. 성능 비용은 크지 않지만 요소 또는 구성 요소 유지 규칙을 제어할 때 앱에 도움이 되는 경우에만 @key를 지정합니다.The performance cost isn't large, but only specify @key if controlling the element or component preservation rules benefit the app.

@key가 사용되지 않더라도 Blazor는 자식 요소와 구성 요소 인스턴스를 최대한 많이 보존합니다.Even if @key isn't used, Blazor preserves child element and component instances as much as possible. @key를 사용할 때의 유일한 장점은 매핑을 선택하는 diff 알고리즘 대신, 모델 인스턴스가 유지된 구성 요소 인스턴스에 매핑되는 방법 을 제어할 수 있다는 것입니다.The only advantage to using @key is control over how model instances are mapped to the preserved component instances, instead of the diffing algorithm selecting the mapping.

@ 키에 사용할 값What values to use for @key

일반적으로 @key에 대해 다음과 같은 종류의 값 중 하나를 제공하는 것이 좋습니다.Generally, it makes sense to supply one of the following kinds of value for @key:

  • 모델 개체 인스턴스(예: 이전 예제와 같은 Person 인스턴스)Model object instances (for example, a Person instance as in the earlier example). 이렇게 하면 개체 참조 같음에 따라 보존이 이루어집니다.This ensures preservation based on object reference equality.
  • 고유 식별자(예: int, string 또는 Guid 형식의 기본 키 값)Unique identifiers (for example, primary key values of type int, string, or Guid).

@key에 사용되는 값이 충돌하지 않는지 확인합니다.Ensure that values used for @key don't clash. 동일한 부모 요소 내에서 충돌하는 값이 감지되면 이전 요소나 구성 요소를 새 요소나 구성 요소에 확정적으로 매핑할 수 없으므로 Blazor는 예외를 throw합니다.If clashing values are detected within the same parent element, Blazor throws an exception because it can't deterministically map old elements or components to new elements or components. 개체 인스턴스 또는 기본 키 값과 같은 고유 값만 사용합니다.Only use distinct values, such as object instances or primary key values.

덮어쓴 매개 변수Overwritten parameters

Blazor 프레임워크는 일반적으로 안전한 부모-자식 매개 변수 할당을 적용합니다.The Blazor framework generally imposes safe parent-to-child parameter assignment:

  • 매개 변수는 예기치 않게 덮어쓰지 않습니다.Parameters aren't overwritten unexpectedly.
  • 부작용이 최소화됩니다.Side-effects are minimized. 예를 들어 추가 렌더링은 무한 렌더링 루프를 만들 수 있으므로 피합니다.For example, additional renders are avoided because they may create infinite rendering loops.

자식 구성 요소는 부모 구성 요소가 렌더링될 때 기존 값을 덮어쓸 수 있는 새 매개 변수 값을 받습니다.A child component receives new parameter values that possibly overwrite existing values when the parent component rerenders. 자식 구성 요소의 매개 변수 값을 실수로 덮어쓰게 되면 하나 이상의 데이터 바인딩 매개 변수가 있는 구성 요소를 개발하고 개발자가 자식의 매개 변수에 직접 쓸 때 종종 발생합니다.Accidentally overwriting parameter values in a child component often occurs when developing the component with one or more data-bound parameters and the developer writes directly to a parameter in the child:

  • 자식 구성 요소는 부모 구성 요소의 매개 변수 값을 하나 이상 사용하여 렌더링됩니다.The child component is rendered with one or more parameter values from the parent component.
  • 자식은 매개 변수 값에 직접 씁니다.The child writes directly to the value of a parameter.
  • 부모 구성 요소는 자식 매개 변수의 값을 렌더링하고 덮어씁니다.The parent component rerenders and overwrites the value of the child's parameter.

매개 변수 값을 덮어쓸 수 있는 가능성은 자식 구성 요소의 속성 setter로도 확장됩니다.The potential for overwriting parameter values extends into the child component's property setters, too.

일반적인 지침은 자체 매개 변수에 직접 쓰는 구성 요소를 만드는 것이 아닙니다.Our general guidance is not to create components that directly write to their own parameters.

다음과 같은 잘못된 Expander 구성 요소를 고려해 보세요.Consider the following faulty Expander component that:

  • 자식 콘텐츠를 렌더링합니다.Renders child content.
  • 구성 요소 매개 변수(Expanded)로 자식 콘텐츠 표시를 설정/해제합니다.Toggles showing child content with a component parameter (Expanded).
  • 구성 요소는 Expanded 매개 변수에 직접 기록하는데, 이는 덮어쓴 매개 변수의 문제를 보여 주므로 피해야 합니다.The component writes directly to the Expanded parameter, which demonstrates the problem with overwritten parameters and should be avoided.
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
    <div class="card-body">
        <h2 class="card-title">Toggle (<code>Expanded</code> = @Expanded)</h2>

        @if (Expanded)
        {
            <p class="card-text">@ChildContent</p>
        }
    </div>
</div>

@code {
    [Parameter]
    public bool Expanded { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private void Toggle()
    {
        Expanded = !Expanded;
    }
}

Expander 구성 요소는 StateHasChanged를 호출할 수 있는 부모 구성 요소에 추가됩니다.The Expander component is added to a parent component that may call StateHasChanged:

@page "/expander"

<Expander Expanded="true">
    Expander 1 content
</Expander>

<Expander Expanded="true" />

<button @onclick="StateHasChanged">
    Call StateHasChanged
</button>

처음에 Expander 구성 요소는 Expanded 속성이 전환될 때 독립적으로 동작합니다.Initially, the Expander components behave independently when their Expanded properties are toggled. 자식 구성 요소는 상태를 예상대로 유지합니다.The child components maintain their states as expected. 부모에서 StateHasChanged가 호출되면 첫 번째 자식 구성 요소의 Expanded 매개 변수가 초기 값(true)으로 다시 설정됩니다.When StateHasChanged is called in the parent, the Expanded parameter of the first child component is reset back to its initial value (true). 두 번째 Expander 구성 요소에서는 렌더링디는 자식 콘텐츠가 없으므로 구성 요소의 Expanded 값이 다시 설정되지 않습니다.The second Expander component's Expanded value isn't reset because no child content is rendered in the second component.

앞의 시나리오에서 상태를 유지하려면 Expander 구성 요소에서 ‘private 필드’를 사용하여 전환된 상태를 유지합니다 .To maintain state in the preceding scenario, use a private field in the Expander component to maintain its toggled state.

다음은 수정된 Expander 구성 요소입니다.The following revised Expander component:

  • 부모의 Expanded 구성 요소 매개 변수 값을 허용합니다.Accepts the Expanded component parameter value from the parent.
  • OnInitialized 이벤트에서 구성 요소 매개 변수 값을 ‘private 필드’(expanded)에 할당합니다.Assigns the component parameter value to a private field (expanded) in the OnInitialized event.
  • 프라이빗 필드를 사용하여 내부 설정/해제 상태를 유지합니다. 이 상태는 매개 변수에 직접 쓰는 것을 방지하는 방법을 보여줍니다.Uses the private field to maintain its internal toggle state, which demonstrates how to avoid writing directly to a parameter.
<div @onclick="Toggle" class="card bg-light mb-3" style="width:30rem">
    <div class="card-body">
        <h2 class="card-title">Toggle (<code>expanded</code> = @expanded)</h2>

        @if (expanded)
        {
            <p class="card-text">@ChildContent</p>
        }
    </div>
</div>

@code {
    private bool expanded;

    [Parameter]
    public bool Expanded { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    protected override void OnInitialized()
    {
        expanded = Expanded;
    }

    private void Toggle()
    {
        expanded = !expanded;
    }
}

자세한 내용은 Blazor 양방향 바인딩 오류(dotnet/aspnetcore #24599)를 참조하세요.For additional information, see Blazor Two Way Binding Error (dotnet/aspnetcore #24599).

특성 적용Apply an attribute

@attribute 지시문을 사용하여 Razor 구성 요소에 특성을 적용할 수 있습니다.Attributes can be applied to Razor components with the @attribute directive. 다음 예제에서는 [Authorize] 특성을 구성 요소 클래스에 적용합니다.The following example applies the [Authorize] attribute to the component class:

@page "/"
@attribute [Authorize]

조건부 HTML 요소 특성Conditional HTML element attributes

HTML 요소 특성은 .NET 값에 따라 조건부로 렌더링됩니다.HTML element attributes are conditionally rendered based on the .NET value. 값이 false 또는 null이면 특성이 렌더링되지 않습니다.If the value is false or null, the attribute isn't rendered. 값이 true이면 특성이 최소화된 상태로 렌더링됩니다.If the value is true, the attribute is rendered minimized.

다음 예제에서 IsCompletedchecked가 요소의 태그에서 렌더링되는지를 확인합니다.In the following example, IsCompleted determines if checked is rendered in the element's markup:

<input type="checkbox" checked="@IsCompleted" />

@code {
    [Parameter]
    public bool IsCompleted { get; set; }
}

IsCompletedtrue이면 확인란이 다음과 같이 렌더링됩니다.If IsCompleted is true, the check box is rendered as:

<input type="checkbox" checked />

IsCompletedfalse이면 확인란이 다음과 같이 렌더링됩니다.If IsCompleted is false, the check box is rendered as:

<input type="checkbox" />

자세한 내용은 ASP.NET Core용 Razor 구문 참조를 참조하세요.For more information, see ASP.NET Core용 Razor 구문 참조.

경고

aria-pressed 같은 일부 HTML 특성은 .NET 형식이 bool일 경우 제대로 작동하지 않습니다.Some HTML attributes, such as aria-pressed, don't function properly when the .NET type is a bool. 이러한 경우 bool 대신 string 형식을 사용합니다.In those cases, use a string type instead of a bool.

원시 HTMLRaw HTML

일반적으로 문자열은 DOM 텍스트 노드를 사용하여 렌더링됩니다. 즉, 포함될 수 있는 모든 태그는 무시되고 리터럴 텍스트로 처리됩니다.Strings are normally rendered using DOM text nodes, which means that any markup they may contain is ignored and treated as literal text. 원시 HTML을 렌더링하려면 HTML 콘텐츠를 MarkupString 값으로 래핑합니다.To render raw HTML, wrap the HTML content in a MarkupString value. 값은 HTML 또는 SVG로 구문 분석되고 DOM에 삽입됩니다.The value is parsed as HTML or SVG and inserted into the DOM.

경고

신뢰할 수 없는 원본에서 생성된 원시 HTML을 렌더링할 경우 보안 위험 이 있으므로 피해야 합니다.Rendering raw HTML constructed from any untrusted source is a security risk and should be avoided!

다음 예제에서는 MarkupString 형식을 사용하여 정적 HTML 콘텐츠 블록을 구성 요소의 렌더링된 출력에 추가하는 방법을 보여 줍니다.The following example shows using the MarkupString type to add a block of static HTML content to the rendered output of a component:

@((MarkupString)myMarkup)

@code {
    private string myMarkup = 
        "<p class='markup'>This is a <em>markup string</em>.</p>";
}

Razor 템플릿Razor templates

렌더링 조각은 Razor 템플릿 구문을 사용하여 정의할 수 있습니다.Render fragments can be defined using Razor template syntax. Razor 템플릿은 UI 코드 조각을 정의하는 방법으로, 다음 형식을 가정합니다.Razor templates are a way to define a UI snippet and assume the following format:

@<{HTML tag}>...</{HTML tag}>

다음 예제에서는 RenderFragmentRenderFragment<TValue> 값을 지정하고 구성 요소에서 직접 템플릿을 렌더링하는 방법을 보여 줍니다.The following example illustrates how to specify RenderFragment and RenderFragment<TValue> values and render templates directly in a component. 렌더링 조각은 템플릿 구성 요소에 인수로 전달될 수도 있습니다.Render fragments can also be passed as arguments to templated components.

@timeTemplate

@petTemplate(new Pet { Name = "Rex" })

@code {
    private RenderFragment timeTemplate = @<p>The time is @DateTime.Now.</p>;
    private RenderFragment<Pet> petTemplate = (pet) => @<p>Pet: @pet.Name</p>;

    private class Pet
    {
        public string Name { get; set; }
    }
}

이전 코드의 렌더링된 출력:Rendered output of the preceding code:

<p>The time is 10/04/2018 01:26:52.</p>

<p>Pet: Rex</p>

정적 자산Static assets

Blazor는 프로젝트의 web root (wwwroot) 폴더 아래에 정적 자산을 배치하는 ASP.NET Core 앱의 규칙을 따릅니다.Blazor follows the convention of ASP.NET Core apps placing static assets under the project's web root (wwwroot) folder.

기본 상대 경로(/)를 사용하여 정적 자산의 웹 루트를 참조합니다.Use a base-relative path (/) to refer to the web root for a static asset. 다음 예제에서 logo.png는 실제로 {PROJECT ROOT}/wwwroot/images 폴더에 있습니다.In the following example, logo.png is physically located in the {PROJECT ROOT}/wwwroot/images folder:

<img alt="Company logo" src="/images/logo.png" />

Razor 구성 요소는 물결표-슬래시 표기법(~/)을 지원하지 않습니다.Razor components do not support tilde-slash notation (~/).

앱의 기본 경로를 설정하는 방법에 대한 내용은 ASP.NET Core 호스트 및 배포 Blazor를 참조하세요.For information on setting an app's base path, see ASP.NET Core 호스트 및 배포 Blazor.

태그 도우미는 구성 요소에서 지원되지 않습니다.Tag Helpers aren't supported in components

Tag Helpers는 Razor 구성 요소(.razor 파일)에서 지원되지 않습니다.Tag Helpers aren't supported in Razor components (.razor files). Blazor에서 태그 도우미와 유사한 기능을 제공하려면 대신, 태그 도우미와 동일한 기능을 포함하는 구성 요소를 만들고 해당 구성 요소를 사용합니다.To provide Tag Helper-like functionality in Blazor, create a component with the same functionality as the Tag Helper and use the component instead.

SVG(Scalable Vector Graphics) 이미지Scalable Vector Graphics (SVG) images

Blazor는 HTML을 렌더링하므로 SVG(Scalable Vector Graphics) 이미지(.svg)를 포함하는 브라우저 지원 이미지는 <img> 태그를 통해 지원됩니다.Since Blazor renders HTML, browser-supported images, including Scalable Vector Graphics (SVG) images (.svg), are supported via the <img> tag:

<img alt="Example image" src="some-image.svg" />

마찬가지로, 스타일시트 파일(.css)의 CSS 규칙에서는 SVG 이미지가 지원됩니다.Similarly, SVG images are supported in the CSS rules of a stylesheet file (.css):

.my-element {
    background-image: url("some-image.svg");
}

그러나 인라인 SVG 태그는 일부 시나리오에서 지원되지 않습니다.However, inline SVG markup isn't supported in all scenarios. <svg> 태그를 구성 요소 파일(.razor)에 직접 배치하면 기본 이미지 렌더링이 지원되지만 고급 시나리오 중 아직 지원되지 않는 시나리오가 많습니다.If you place an <svg> tag directly into a component file (.razor), basic image rendering is supported but many advanced scenarios aren't yet supported. 예를 들어, <use> 태그는 현재 적용되지 않으며 @bind를 일부 SVG 태그에서는 사용할 수 없습니다.For example, <use> tags aren't currently respected, and @bind can't be used with some SVG tags. 자세한 내용은 Blazor(dotnet/aspnetcore #18271)에서 SVG 지원을 참조하세요.For more information, see SVG support in Blazor (dotnet/aspnetcore #18271).

공백 렌더링 동작Whitespace rendering behavior

@preservewhitespace 지시문이 true 값과 함께 사용되지 않을 때 다음과 같은 추가 공백이 제거됩니다.Unless the @preservewhitespace directive is used with a value of true, extra whitespace is removed by default if:

  • 요소 내의 선행 또는 후행 공백.Leading or trailing within an element.
  • RenderFragment 매개 변수 내의 선행 또는 후행 공백.Leading or trailing within a RenderFragment parameter. 예: 자식 콘텐츠가 다른 구성 요소로 전달됨.For example, child content passed to another component.
  • C# 코드 블록(예: @if 또는 @foreach)의 앞 또는 뒤에 있는 공백.It precedes or follows a C# code block, such as @if or @foreach.

white-space: pre와 같은 CSS 규칙을 사용하는 경우 공백 제거는 렌더링된 출력에 영향을 줄 수 있습니다.Whitespace removal might affect the rendered output when using a CSS rule, such as white-space: pre. 성능 최적화를 사용하지 않고 공백을 유지하려면 다음 작업 중 하나를 수행합니다.To disable this performance optimization and preserve the whitespace, take one of the following actions:

  • .razor 파일의 맨 위에 @preservewhitespace true 지시문을 추가하여 특정 구성 요소에 기본 설정을 적용합니다.Add the @preservewhitespace true directive at the top of the .razor file to apply the preference to a specific component.
  • _Imports.razor 파일 안에 @preservewhitespace true 지시문을 추가하여 전체 하위 디렉터리 또는 전체 프로젝트에 기본 설정을 적용합니다.Add the @preservewhitespace true directive inside an _Imports.razor file to apply the preference to an entire subdirectory or the entire project.

대부분의 경우 앱이 일반적으로 계속해서 정상적으로(그러나 더 빠르게) 동작하므로 수행해야 할 조치가 없습니다.In most cases, no action is required, as apps typically continue to behave normally (but faster). 공백 제거 시 특정 구성 요소에 문제가 발생하는 경우에는 해당 구성 요소에서 @preservewhitespace true를 사용하여 이 최적화를 사용하지 않도록 설정합니다.If stripping whitespace causes any problem for a particular component, use @preservewhitespace true in that component to disable this optimization.

구성 요소의 소스 코드에서는 공백이 유지됩니다.Whitespace is retained in a component's source code. 공백만 있는 텍스트는 시각적 효과가 없는 경우에도 브라우저의 DOM(문서 개체 모델)에서 렌더링됩니다.Whitespace-only text renders in the browser's Document Object Model (DOM) even when there's no visual effect.

다음 Razor 구성 요소를 고려해 보세요.Consider the following Razor component code:

<ul>
    @foreach (var item in Items)
    {
        <li>
            @item.Text
        </li>
    }
</ul>

앞의 예제에서는 다음과 같은 불필요한 공백을 렌더링합니다.The preceding example renders the following unnecessary whitespace:

  • @foreach 코드 블록의 외부Outside of the @foreach code block.
  • <li> 요소의 주위Around the <li> element.
  • @item.Text 출력의 주위Around the @item.Text output.

100개의 항목을 포함하는 목록의 경우 402개의 공백 영역이 있으며, 추가 공백은 렌더링된 출력에 시각적으로 영향을 주지 않습니다.A list containing 100 items results in 402 areas of whitespace, and none of the extra whitespace visually affects the rendered output.

구성 요소의 정적 HTML을 렌더링하면 태그 안의 공백이 유지되지 않습니다.When rendering static HTML for components, whitespace inside a tag isn't preserved. 예를 들어, 렌더링된 출력에서 다음 구성 요소의 소스를 살펴보겠습니다.For example, view the source of the following component in rendered output:

<img     alt="My image"   src="img.png"     />

앞의 Razor 마크업에서 공백이 유지되지 않습니다.Whitespace isn't preserved from the preceding Razor markup:

<img alt="My image" src="img.png" />

추가 자료Additional resources