공부/인프런 - Rookiss

Part 6-5-8. Blazor 입문 : SPA 구조, Router

셩잇님 2024. 9. 9. 12:22
반응형

 

 

🦮 Blazor 입문

 

 지난 시간에는 Blazor의 Dependency Injection 개념에 대해서 학습하는 시간을 가져보았다. Dependency Injection는 말 그대로 의존성 주입을 뜻하는데, Program에 내가 사용할 서비스를 AddSingleton으로 처리하여 특정 서비스를 new를 통해 새롭게 생성하는 것이 아닌 임의로 지정하여 이를 처리할 수 있었다. 이 후 ASP.NET 차원에서 PaymentSevice까지 생성자에서 자동으로 특정 서비스를 찾는 것 까지 살펴보았다. 마지막으로 Program에서 사용되는 Singleton, Transient, Scoped  서비스의 생명주기까지 파악해보았다. 이번 시간에는 SPA 구조, Router의 개념에 대해서 학습하는 시간을 가져보며, 이 또한 직접 실습하며 페이지를 제작해보도록 한다.

 


 

🏄‍♀️ SPA 구조

 

 SPA 구조는 이미 개론시간에 한번 언급했었지만, 처음에 비해 지금 기존 지식이 많이 쌓여있기 때문에 지금의 지식으로 이를 다시 복습하며 분석해보도록 하자. 지금 프로젝트는 너무 방대해졌기 때문에 BlazorStudy라는 새로운 블레이저 서버 앱 프로젝트를 만들어 학습하도록 하자. 

 

 SPA는 Single Page Application의 약자로 페이지 전체를 다시 로드하는 방식이 아닌, 바뀌는 부분만 JavaScript와 DOM을 통해 업데이트 하는 방식이다. 예전의 HTML에서는 페이지의 변경사항이 있을 경우 바꾸지 않아도 될 부분까지 데이터를 전송하여 CPU나 네트워크 패킷을 낭비하는 형태로 이루어져 있었다. 따라서 이러한 낭비를 없애고자 착안한 개념이 바로 SPA의 개념이다. 

 

 Blazor에서 SPA는 LayoutComponentBase가 이 역할을 대체하는데 해당 함수는 MainLayout 컴포넌트에 붙어있는 것을 알 수 있다. 

 

@inherits LayoutComponentBase

<PageTitle>BlazorStudy</PageTitle>

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

        <article class="content px-4">
            // LayoutComponentBase의 Body를 활용하는 것 😁
            @Body
        </article>
    </main>
</div>

 

 LayoutComponentBase를 F12를 눌러 확인해 보면 이 LayoutComponentBase 또한 RenderFragment를 통해 구현되어 있는 것을 알 수 있다. 따라서 MainLayout에서 @Body를 사용하는 것은 LayoutComponentBase의 Body를 사용하는 것으로 알 수 있다. 

 


 

 그렇다면 항상 기본적인 레이아웃만 사용해야 하는걸까? 당연히 그렇지 않다. Shared 폴더에 MainLayout2 컴포넌트를 새롭게 만들어주고 MainLayout와 다른 차이점을 두기 위해 target에 Layout2라고 적어주도록 하자. 

 

@inherits LayoutComponentBase

<div class="page">
    ...

    <main>
        <div class="top-row px-4">
            // Layout2라고 적어 차이점을 표시하자.
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">Layout2</a>
        </div>

        ...
    </main>
</div>

 

 이 후, Counter 컴포넌트에서 이를 사용하기 위해 아래와 같이 변경해보자. 

 

@page "/counter"
@* 새로 생성한 MainLayout2 적용 *@
@layout MainLayout2

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

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

@code {
    
    private int currentCount = 0;
    
    private void IncrementCount()
    {
        currentCount++;
    }
}

 

 이 후 실행해보면 다른 페이지와는 달리 Counter 페이지의 우측 상단이 바뀌어 있는 것을 볼 수 있다.

 

Counter의 우측 상단이 Layout2로 변한 것을 볼 수 있다.

 

 그렇다면 아무것도 건드린 것이 없는 기존의 Home 화면의 About은 적용되는 것일까? 이는 바로 App 컴포넌트에서 적용되는 것을 알 수 있다.

 

<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>

 

 해당 컴포넌트에서 DefaultLayout을 MainLayout으로 설정해주고 있는 것을 볼 수 있다.

 


 

 그렇다면 _Import는 어떤 역할을 하는 함수일까? 기존 함수를 복사하여 Pages안에 넣어주고, 아래와 같이 코드를 작성하도록 하자.

 

@* _Imports는 같은 폴더에 있는 레이저 컴포넌트에 모두 적용이 된다. *@
@layout MainLayout2

 

이 후 실행을 해보면 Page 폴더 안에있는 컴포넌트들은 모두 _Imports에 의해 기본 레이아웃이 MainLayout2로 적용되는 모습을 볼 수 있다. 따라서 _Imports는 대왕님 같은 파일이라고 생각하면 된다. 

 


 

🏄‍♀️ Router

 

 그렇다면 라우터란 도대체 무엇일까? 결론부터 얘기하자면 라우터는 URI 요청이 왔을 때 어떤 페이지 컴포넌트를 보여줘야하는지 규칙을 정하는 것이다. 라우터에 대해서 알아보기 위해 F12를 통해 라우터 코드를 확인해보도록 하자.

 

 

 라우터 스크립트를 확인하면 결국 라우터 또한 하나의 컴포넌트에 불과하다. 따라서 App 컴포넌트에 있는 Found와 NotFound 자체도 라우터 내부에 있는 RenderFragment를 구현한 것을 알 수 있다. Program 스크립트를 보면 app.UseRouting()를 사용하는 것을 볼 수 있는데 결국 실제로 라우팅 기능을 하는 역할은 App 컴포넌트의 라우터라는 부품이 해주는 것을 알 수 있다.

 


 

@* 모바일 화면에서 볼 수 있으므로 이 때 나타나는 메뉴(≡) *@
<div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="">BlazorStudy</a>
        <button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
            <span class="navbar-toggler-icon"></span>
        </button>
    </div>
</div>

...

@code {
    ...
}

 

 NavMenu 컴포넌트를 이어서 확인하다보면 Home, Counter, Fetch data외 내비게이션 메뉴바가 있는 것을 볼 수 있는데 이는 모바일 환경을 고려한 메뉴 페이지이다. 

 

 


 

 기본적으로 NavMenu에서는 nav-link를 사용하는데, 사실 이 뿐만 아니라 이전에 학습한 href를 통해서도 충분히 이를 구현할 수 있다. Counter 컴포넌트에서 이를 구현해보도록 하자.

 

@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

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

<!-- href를 이용한 페이지 이동 -->
<a class="btn btn-primary" href="FetchData"></a>

@code {
    
    private int currentCount = 0;
    
    private void IncrementCount()
    {
        currentCount++;
    }
}

 

 위와 같이 코드를 작성하고 프로그램을 실행해보면 Click me 오른쪽에 새로운 버튼이 나나타는데, 해당 버튼을 클릭하면 nav-link와 마찬가지로 FetchData로 이동하는 것을 볼 수 있다.

 

 

 위 방법을 사용하지 않고 코드로 이를 구현하고 싶을 경우, NavigationManager 사용하면 된다. NavigationManager를 사용하는 예제 코드는 다음과 같다.

 

@page "/counter"
@* 코드 상에서 navLink 사용 할 경우 NavigationManager를 사용한다. *@
@inject NavigationManager navManager

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

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

<a class="btn btn-primary" href="FetchData"></a>

@code {

	private int currentCount = 0;
    
    private void IncrementCount()
    {
        currentCount++;
        navManager.NavigateTo("fetchdata");
    }
}

 

 위 코드는 이전의 예제와 마찬가지로 카운트를 증가시키는 버튼을 클릭하면 fetchData로 이동하게끔 설정한다. 

 


 

 마지막으로 경우에 따라 필요한 인자가 있을 경우 CurrentCount를 중간에 캐치하여 사용할 수 있다. 예시 코드를 통해 알아보도록 하자.

 

@page "/counter"
@* 경우에 따라 필요한 인자가 있을 경우 중간에 캐치하여 사용할 수 있다. *@
@page "/counter/{CurrentCount:int}"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @CurrentCount</p>

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

@code {
    
    private void IncrementCount()
    {
        CurrentCount++;
    }

    [Parameter]
    public int CurrentCount { get; set; }
}

 

 위와 같이 코드를 작성하고 프로그램을 실행해보도록 하자. 이 후 주소창의 "/87"과 같은 정수를 입력할 경우 Current count가 87의 값이 들어가는 것을 볼 수 있다. 따라서 이를 통해 값을 캐치하여 사용할 수 있다.

 

URL에 /87을 입력 시 Count의 값이 변경된다.

 


 

결론

 가장 중요한 것은 결국 레이아웃이다. SPA를 만들 때 레이아웃의 정의는 반드시 하나가 필요하며, 이 레이아웃 컴포넌트는 베이시를 상속받은 레이아웃 컴포넌트가 필요하다. 이 후 이를 다양한 방법으로 적용할 수 있는 방법에 대해서 학습하였고 기본적으로는 app 컴포넌트의 default-layout을 통해 레이아웃이 사용되는 것 까지 알아보았다.

 

 라우터는 결국 컴포넌트 방식으로 이루어진 것을 알 수 있고 이 또한 C# 파일에서 파생된 것을 알 수 있었다. 또한 found와 notfound 또한 RenderFragment를 통해 이루어진 것을 확인하였다.

 

 

반응형