레이저 요소 인스턴스 와 렌더링

블레이저에서 사용되는 레이저 요소는 아래의 사이클을 반복합니다.

렌더링 조건 성립 => if (IsFirstRender) { 인스턴스 생성 및 초기화, 렌더링 } else { 렌더링 }

일반적인 객체는 인스턴스 생성 후 상태가 변하지만, 레이저 요소는 렌더링이 생애주기의 기준이 되고, 인스턴스 생성은 곁다리 느낌입니다.

렌더링 성립 조건 중 가장 근원적인 것은 부모의 렌더링과 이벤트의 발생입니다.

먼저 "부모의 렌더링"에 관해 살펴 보면,

부모의 렌더링

부모 요소가 자식의 렌더링을 통제하는 일반적인 방법은 아래와 같이 자식을 선택하는 구조로 만드는 것입니다.

//Parent.razor
@if (...) {
   <Child1/>
}
else if(...){
   <Child2/>
}
...
else if(...){
   <Child_n/>
}
else {
   <p> 조건에 부합하는 자식 요소가 없습니다 </p>
}

if 식은 부모 요소가 렌더링될 때마다 평가됩니다.

평가가 어떻게 되든, 결국에는 자식 요소로 포함된 요소만이 부모 요소와 함께 렌더링됩니다.

만약 자식 요소가 기존에 렌더링되었는데, 새로운 렌더링에서 배제되었다면, 그 요소의 인스턴스는 소멸됩니다.

반대로, 기존에 배제되었는데, 새로운 렌더링에서 포함되어 있다면, 그 요소는 “최초의 렌더링” 과정을 거칩니다. 즉, 새로운 인스턴스가 생성됩니다.

최상단 부모

부모의 렌더링은 부모의 부모의 렌더링을 전제하기에, 상층에 위치한 부모일 수록 더 많은 요소들의 인스턴스 생성에 관여함을 의미합니다.

블레이저 프로젝트 템플릿에서 블레이저가 실행될 때 가장 먼저 렌더링( =생성) 하는 요소는 App.razor 입니다. 서버든, WASM 이든 마찬가지입니다.

따라서, 이 요소를 “최초의 요소” 혹은 “최상단 부모” 라고 부를 수 있습니다.

최상단 부모는 블레이저의 수명 주기와 같아, 아래의 경우에만 최초로 렌더링되어 인스턴스가 생성됩니다.

  1. 최초로 접속(하여 블레이저 다운로드)
  2. 브라우저 리프레시

App.razor

이 최초의 부모 요소도 예외 없이, 자신이 렌더링될 때 자식으로 포함된 요소들을 렌더링 시킵니다.

참고로, App.razor 의 코드는 보통 아래와 같이 되어 있습니다.

<Router AppAssembly="@typeof(App).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

이벤트 발생

렝더링을 유발하는 두 번째 요인은 이벤트 발생입니다.

부모 요소는 자식 요소로부터 이벤트를 전파받았을 때 리렌더링하게 됩니다.

App 요소는 기본적으로 Router 요소 하나만 자식 요소로 보유하는데, 이 요소는 if-else 조건이 없기 때문에, App 요소가 렌더링할 때마다 언제나 자식 요소로 포함됩니다.

이 말에는 두 가지 의미가 있습니다.

  1. Router 인스턴스의 생애주기는 App 과 같다.
  2. App 은 Router의 이벤트에 의해서만 리렌더링된다.

Router

이 요소는 두 가지 역할을 합니다.

  1. 자식 요소의 선택
    앞서 살펴 본 @if-else 와 같은 역할을 합니다.
    다만, if의 조건문이 네비게이션 경로를 가진 페이지 요소가 있느냐 없느냐에 의해 판별되는 것 뿐입니다.

  2. 이벤트 발생
    부모 요소(App.razor)가 리렌더링 되도록 이벤트를 전파합니다. 이 이벤트는 라우팅이 일어날 때마다 발생합니다.
    이때, 라우팅에 매칭되는 페이지 요소가 있으면 자신의 자식 요소로 포함시키고, 기존의 자식 요소는 배제합니다. 이는 결과적으로 Router의 자식 요소는 App의 자식 요소가 됩니다.

Router 요소 덕분에, 사용자가 새로운 네비게이션을 할 때 마다, 새로운 페이지와 그 자식 요소들은 최초의 렌더링 과정을 수행하게 되고, 배제되는 페이지와 자식 인스턴스들은 소멸하게 됩니다.

Route 특성

Router 요소의 주요 임무는 페이지 요소를 찾는 것입니다.
페이지 요소란, 클래스 수준 특성인 RouteAttribute 가 부여된 레이저 요소를 의미합니다.

페이지 요소를 만드는 방법은, 레이저 요소에 이 특성을 부여하는 것이 전부입니다.
보통 세 가지 방법이 있습니다.

//Compo1.razor
@page "/compo1"
(//Compo1.razor)
@attribute [Route("/compo1")]
// //Compo1.razor.cs
[Route("/compo1")]
public partial class Compo1

우리는 라우터가 페이지 요소를 찾을 수 있도록 페이지 요소들이 포함된 어셈블리를 알려 줘야 합니다.

<Router AppAssembly="@typeof(App).Assembly">

RouteView.DefaultLayout

RouteView.DefaultLayout 에 할당된 요소는 RouteView가 페이지 요소를 렌더링할 때 사용됩니다.

<Router AppAssembly="@typeof(App).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />

여기에 할당된 요소는 엄밀하게 말하면, 자식 요소라기 보다는, RouteView 가 사용하는 템플릿 문서와 같습니다.

이 템플릿 문서는 RouteView 와 생애 주기를 함께 합니다.
즉, 네비게이션이 성공하는 한, 템플릿이 재생성되는 경우는 없습니다.

이와 반대로, NotFound 에 포함된 LayoutView 는 Router의 자식 요소이고, 결과적으로 App 의 자식 요소입니다.

    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, there's nothing at this address.</p>
        </LayoutView>

사용자가 올바르지 않은 경로를 계속 입력하는 한 재생성되지 않습니다.

렌더링 순서

블레이저에서는 부모는 자식보다 먼저 렌더링됨이 보장됩니다.

문제 하나 내보겠습니다.

//Child.razor.cs
private bool _showing;

public void Show()
{
    _showing = true;
}
public void Hide()
{
    _showing = false ;
}

부모는 자식 인스턴스의 참조를 보유합니다.

//Parent.razor.cs
private Child? _child;
//Parent.razor
<Child @ref="_child"/>

부모의 동기적 수명 주기 메서드 중에, _child?.Show() 나 _child?.Hide() 를 호출하기에 적정한 곳은 어디일까요?

  1. SetParameters
  2. OnInitialized
  3. OnParameterSet
  4. OnAfterRender
4개의 좋아요

제가 지식이 짧아서 퀴즈에 참가하지 않고 다른 분들의 댓글과 쓴이분의 정답 해설을 기다리고 있었는데 ㅠ 해답 듣고 싶어서 참가합니다! 4번?

3개의 좋아요

4번 맞습니다.

부모가 최초로 렌더링 되는 경우, 1 ~ 3 번째 메서드가 호출될 때까지는 자식 요소의 인스턴스는 생성되지 않은 상태입니다. 즉, _child 는 null 입니다.
따라서, 이 경우 1~3 번에서 _child?.Show()를 호출해도 원하는 동작을 얻을 수 없습니다.

그러나, 자식 요소의 인스턴스가 살아 있을 때는 어디에서 수행해도 상관이 없습니다.

부모의 OnAfterRender는 렌더링에 포함된 자식 요소의 인스턴스가 생성되었음을 보장받는 장소입니다.

3개의 좋아요

감사합니다!
블레이저에 관한 이러한 찐 설명글을 많이 써주시면 매우 감사드립니다.

3개의 좋아요

메뉴를 누르시고, 화면 좌측 메뉴 > 태그에서 블레이저팁스 를 누르시면 연관 글을 보실 수 있습니다.

4개의 좋아요

좋은 내용 감사합니다~~
작업하면서도 렌더링에 대해 대충만 생각했는데 좋네요~~

3개의 좋아요