ASP.NET Core Blazor 고급 시나리오(렌더링 트리 생성)

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

이 문서에서는 RenderTreeBuilder를 사용하여 Blazor 렌더링 트리를 수동으로 빌드하는 고급 시나리오를 설명합니다.

Warning

RenderTreeBuilder를 사용하여 구성 요소를 만드는 것은 ‘고급 시나리오’입니다. 구성 요소의 형식이 잘못된 경우(예: 닫히지 않은 태그) 정의되지 않은 동작이 발생할 수 있습니다. 정의되지 않은 동작으로는 손상된 콘텐츠 렌더링, 앱 기능 손실, ‘보안 손상’ 등이 있습니다.

수동으로 렌더링 트리 빌드(RenderTreeBuilder)

RenderTreeBuilder는 C# 코드를 사용한 구성 요소 수동 빌드를 포함하여 구성 요소와 요소를 조작하기 위한 메서드를 제공합니다.

다른 구성 요소에 수동으로 렌더링할 수 있는 다음 PetDetails 구성 요소를 고려합니다.

PetDetails.razor:

<h2>Pet Details</h2>

<p>@PetDetailsQuote</p>

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

다음 BuiltContent 구성 요소에서 CreateComponent 메서드의 루프는 세 가지 PetDetails 구성 요소를 생성합니다.

시퀀스 번호를 사용하는 RenderTreeBuilder 메서드에서 시퀀스 번호는 소스 코드 줄 번호입니다. Blazor diff 알고리즘은 개별 호출이 아니라 개별 코드 줄에 해당하는 시퀀스 번호를 사용합니다. RenderTreeBuilder 메서드를 사용하여 구성 요소를 만드는 경우 시퀀스 번호의 인수를 하드 코딩합니다. 계산 또는 카운터를 사용하여 시퀀스 번호를 생성하면 성능이 저하될 수 있습니다. 자세한 내용은 시퀀스 번호는 실행 순서가 아니라 코드 줄 번호와 관련이 있음 섹션을 참조하세요.

BuiltContent.razor:

@page "/built-content"

<PageTitle>Built Content</PageTitle>

<h1>Built Content Example</h1>

<div>
    @CustomRender
</div>

<button @onclick="RenderComponent">
    Create three Pet Details components
</button>

@code {
    private RenderFragment? CustomRender { get; set; }

    private RenderFragment CreateComponent() => builder =>
    {
        for (var i = 0; i < 3; i++) 
        {
            builder.OpenComponent(0, typeof(PetDetails));
            builder.AddAttribute(1, "PetDetailsQuote", "Someone's best friend!");
            builder.CloseComponent();
        }
    };

    private void RenderComponent()
    {
        CustomRender = CreateComponent();
    }
}
@page "/built-content"

<h1>Build a component</h1>

<div>
    @CustomRender
</div>

<button @onclick="RenderComponent">
    Create three Pet Details components
</button>

@code {
    private RenderFragment? CustomRender { get; set; }

    private RenderFragment CreateComponent() => builder =>
    {
        for (var i = 0; i < 3; i++) 
        {
            builder.OpenComponent(0, typeof(PetDetails));
            builder.AddAttribute(1, "PetDetailsQuote", "Someone's best friend!");
            builder.CloseComponent();
        }
    };

    private void RenderComponent()
    {
        CustomRender = CreateComponent();
    }
}

Warning

Microsoft.AspNetCore.Components.RenderTree의 형식을 사용하여 렌더링 작업 ‘결과’를 처리할 수 있습니다. 해당 형식은 Blazor 프레임워크 구현의 내부 세부 정보이며, ‘불안정’하고 이후 릴리스에서 변경될 수 있는 것으로 간주되어야 합니다.

시퀀스 번호는 실행 순서가 아니라 코드 줄 번호와 관련이 있음

Razor 구성 요소 파일(.razor)은 항상 컴파일됩니다. 컴파일된 코드를 생성하는 컴파일 단계를 사용하여 런타임에 앱 성능을 개선하는 정보를 삽입할 수 있으므로 컴파일된 코드 실행은 코드 해석보다 이점이 있습니다.

해당 개선 사항의 주요 예로 ‘시퀀스 번호’가 있습니다. 시퀀스 번호는 정렬된 개별 코드 줄에서 생성된 해당 출력을 런타임에 표시합니다. 런타임은 이 정보를 사용하여 선형 시간으로 효율적인 트리 diff를 생성하며, 일반적인 트리 diff 알고리즘에서 가능한 속도보다 훨씬 더 빠릅니다.

다음 Razor 구성 요소 파일(.razor)을 고려합니다.

@if (someFlag)
{
    <text>First</text>
}

Second

위의 Razor 태그 및 텍스트 콘텐츠는 다음과 유사한 C# 코드로 컴파일됩니다.

if (someFlag)
{
    builder.AddContent(0, "First");
}

builder.AddContent(1, "Second");

코드가 처음 실행되고 someFlagtrue이면 작성기는 다음 표와 같은 시퀀스를 받습니다.

Sequence Type 데이터
0 텍스트 노드 First
1 텍스트 노드 둘째

someFlagfalse가 되고 태그는 다시 렌더링된다고 가정합니다. 이번에는 작성기가 다음 표와 같은 시퀀스를 받습니다.

Sequence Type 데이터
1 텍스트 노드 둘째

런타임은 diff를 통해 시퀀스 0의 항목이 제거된 것을 확인하고 한 단계로 다음과 같은 간단한 ‘편집 스크립트’를 생성합니다.

  • 첫 번째 텍스트 노드를 제거합니다.

프로그래밍 방식으로 시퀀스 번호를 생성할 경우 발생하는 문제

대신, 다음과 같은 렌더링 트리 작성기 논리를 작성했다고 가정합니다.

var seq = 0;

if (someFlag)
{
    builder.AddContent(seq++, "First");
}

builder.AddContent(seq++, "Second");

첫 번째 출력은 다음 표와 같습니다.

Sequence Type 데이터
0 텍스트 노드 First
1 텍스트 노드 둘째

이 결과는 이전 사례와 동일하므로 부정적인 이슈가 없습니다. 두 번째 렌더링에서는 someFlagfalse이며 출력은 다음 표와 같습니다.

Sequence Type 데이터
0 텍스트 노드 둘째

이번에는 diff 알고리즘에서 ‘두 가지’ 변경 사항이 발생했음을 확인합니다. 이 알고리즘은 다음과 같은 편집 스크립트를 생성합니다.

  • 첫 번째 텍스트 노드의 값을 Second로 변경합니다.
  • 두 번째 텍스트 노드를 제거합니다.

시퀀스 번호를 생성하면 원본 코드에서 if/else 분기 및 루프가 있던 위치에 대한 유용한 정보가 모두 손실됩니다. 이로 인해 diff의 길이가 이전보다 두 배가 됩니다.

여기서는 간단한 예제를 사용했지만, 복잡하고 깊이 중첩된 구조와 특히 루프를 사용하는 보다 현실적인 사례에서는 일반적으로 성능 비용이 더 높습니다. 삽입되거나 제거된 루프 블록이나 분기를 즉시 확인하는 대신 diff 알고리즘은 렌더링 트리를 재귀적으로 깊이 반복해야 합니다. 이로 인해 이전 구조와 새 구조 간의 관계에 대한 잘못된 정보가 diff 알고리즘에 제공되기 때문에 일반적으로 긴 편집 스크립트가 작성됩니다.

지침 및 결론

  • 시퀀스 번호를 동적으로 생성하면 앱 성능이 저하됩니다.
  • 컴파일 시간에 캡처하지 않는 한, 필요한 정보가 없기 때문에 프레임워크에서 런타임에 고유한 시퀀스 번호를 자동으로 만들 수 없습니다.
  • 수동으로 구현된 긴 RenderTreeBuilder 논리 블록을 작성하면 안 됩니다. .razor 파일을 사용하고 컴파일러를 통해 시퀀스 번호를 처리하는 것이 좋습니다. 수동 RenderTreeBuilder 논리를 사용해야 하는 경우, 긴 코드 블록을 OpenRegion/CloseRegion 호출에 래핑된 작은 조각으로 분할합니다. 영역마다 고유한 시퀀스 번호 공간이 있으므로, 각 영역 내에서 0(또는 다른 임의 숫자)부터 다시 시작할 수 있습니다.
  • 시퀀스 번호를 하드 코딩한 경우, 시퀀스 번호의 값만 증가하면 diff 알고리즘을 사용할 수 있습니다. 초기 값과 간격은 관련이 없습니다. 한 가지 타당한 옵션은 코드 줄 번호를 시퀀스 번호로 사용하거나 0부터 시작하고 1씩, 100씩 또는 선호하는 간격만큼 늘리는 것입니다.
  • Blazor는 시퀀스 번호를 사용하는 반면, 다른 트리 diff UI 프레임워크는 시퀀스 번호를 사용하지 않습니다. diff는 시퀀스 번호를 사용할 때 훨씬 더 빠르며, Blazor는 .razor 파일을 작성하는 개발자를 위해 시퀀스 번호를 자동으로 처리하는 컴파일 단계의 이점이 있습니다.