0. 앞선 글.

 

언젠가 웹 프로젝트로 그럴듯한 페이지를 만들어보고 싶단 생각을 하곤 했습니다. 그를 위해 틈틈이 React에 대해 공부도 해보고 간단한 페이지도 만들어보고 했지만 이것저것 붙이는 순간 번번이 무너져 내리곤 했습니다.

 

진짜 React가 맞는가에 대한 고민을 하며 Vue와 Svlte에 대해서도 알아보던 와중 Blazor와 React를 비교한 글이 있어 해당 글을 읽고 번역을 해 남겨둡니다.

 

만약 여러분이 C#을 어느 정도 할 줄 알고 아직 웹 프레임워크를 선택하지 않았다면 이 글이 약간이나마 도움이 될 수 있기를 바랍니다.

 

이 글은 다음 글을 번역한 글입니다: React vs. Blazor: Minimize JavaScript in your SPAs

 

React vs. Blazor: Minimize JavaScript in your SPAs - LogRocket Blog

Explore React and Blazor, two different client-side options for minimizing JavaScript, comparing the two in terms of features.

blog.logrocket.com

 

 

 

1. SPA에서 JavaScript 최소화 하기.

 

SPA(단일 페이지 애플리케이션, Single Page Application)을 구축할 때 개발자가 선택할 수 있는 몇 가지 프레임워크가 있습니다. 가장 있기 있는 세 가지 프레임워크는 Angular, React, Vue입니다. 하지만 이 세 프레임워크를 사용해 SPA를 구축하려면 JavaScript가 필요합니다.

 

개발자가 SPA를 만드는데 관심이 있지만 JavaScript의 문제들을 처리하고 싶지 않다면 어떻게 해야 할까요? 이 글에서는 JavaScript를 최소화하기 위한 두 가지 다른 클라이언트 측(client-side) 옵션인 React와 Blazor를 살펴보고 그 기능을 비교합니다. 이제 시작해 봅시다.

 

 

 

2. Blazor가 뭔가요?

 

Blazor는 C# dotNet과 WebAssembly를 활용하여 그 고유한 방식을 따라 웹 브라우저에서 실행되는 SPA를 만드는 Microsoft UI 프레임워크입니다. 기본적으로 Blazor를 사용하면 개발자가 HTML, CSS, C#을 사용해 대화형 클라이언트 측 애플리케이션을 빌드할 수 있습니다.

 

반면에 React는 유저 인터페이스와 UI 컴포넌트를 빌드하기 위한 선언적이고 효율적이며 유연한 JavaScript 라이브러리입니다. React와 Blazor는 풍부하고 대화형을 지원하면서 현대적인 클라이언트 측 애플리케이션을 구축하기 위한 프레임워크와 라이브러리라는 유사성을 갖고 있습니다.

 

Blazor의 고유한 기능 중 하나는 JavaScript의 상호 운용성입니다. 즉 Blazor앱은 dotNet 메서드에서 JavaScript 함수를 호출하고 JavaScript 함수에서 dotNet 메서드를 호출할 수 있습니다.

 

Blazor는 클라이언트 측 Blazor WebAssembly와 서버 측 Blazor Server의 두 가지 주요 프로젝트로 구성됩니다. Blazor 서버 구현은 보다 전통적인 dotNet 앱 접근 방식을 사용하며 서버가 메시지 전달을 위해 SignalR을 사용해 프런트엔드로 연결이 필요합니다. 반면 Blazor WebAssembly는 프로젝트를 배포 가능한 정적 번들로 패키징 합니다. 이 글에서는 Blazor WebAssembly와 React를 비교하는데 초점을 둡니다.

 

 

 

3. Blazor Wasm(WebAssembly)

 

Blazor Wasm은 Xamarin Mono 프레임워크의 컴파일된 버전과 함께 실행됩니다. 최종 결과는 실제 브라우저에서 직접 dotNet C# 애플리케이션을 실행할 수 있습니다. Blazor는 dotNet 코드를 Wasm으로 컴파일 한 다음 정적 번들로 배포할 수 있습니다. 오래된 브라우저의 경우 Blazor는 asm.js를 사용해 브라우저가 지원하는 공통 프레임워크로 컴파일합니다.

 

Blazor는 JavaScript 라이브러리와 상호 운용성을 지원합니다. npm 모듈을 빌드와 함께 가져와 Blazor 프로젝트와 함께 사용할 수 있습니다. C# 코드와 JavaScript 패키지에 직접 상호작용 하기 위해 Blazor는 IJSRuntime in C# 및 IJSRuntime을 제공합니다. 자세한 설명은 Microsoft의 문서를 확인하시기 바랍니다.

 

 

 

4. 폴더 구조.

 

Blazor 앱과 React 앱을 모두 만들어보고 기본적으로 무엇이 있는지 비교해 보겠습니다. 새 Blazor 프로젝트를 만들려면 dotNet SDK를 다운로드해 설치하고 터미널에서 다음 명령어를 실행해야 합니다.

 

dotnet new blazorwasm -n BlazorApp

 

blazorwasm 명령은 WebAssembly로 새로운 Blazor 프로젝트를 생성한다는 의미입니다. -n 플래그는 프로젝트 이름을 의미합니다. 

 

Blazor Foler Structure

 

program.cs 파일에는 WebAssembly 앱을 시작하고 실행하는데 필요한 주요 메서드가 포함되어 있습니다.

 

program.cs

 

또한 앱을 마운트하고 앱 ID가 있는 태그를 선택합니다. HTTP 클라이언트는 종속성 주입을 사용해 로드되고 Blazor 앱의  HTTP 모듈은 JavaScript Fetch API를 기반으로 빌드됩니다.

 

React Folder Structure

 

React 앱에서 index.js는 Blazor 앱의 program.cs와 유사합니다. 아래 코드 스니펫에서 React DOM은 앱 구성요소를 렌더링하고 루트 구성요소로 ID가 root인 요소를 선택합니다.

 

index.js

 

 

 

5. 프로그래밍 언어.

 

Blazor는 JavaScript 대신 C#을 프로그래밍 언어로 사용해 dotNet 라이브러리의 기존 dorNet 에코시스템을 활용합니다. 이 기능을 통해 C# 개발자는 C#을 사용해 백엔드 코드를 작성하는 것을 시작으로 C#을 사용해 웹 및 모바일 애플리케이션을 구축하는 풀 스택까지 기술을 확장할 수 있습니다.

 

처음에는 이 C#이 JavaScript 개발자에게 자연스럽지 않을 수 있지만 C#을 잘 이해하면 완전히 C#으로 작성된 강력한 풀 스택 애플리케이션을 빌드할 수 있습니다.

 

React는 JavaScript를 프로그래밍 언어로 사용합니다. 기본적으로 웹 개발자는 가장 익숙한 언어를 사용할 수 있습니다. 또한 React Native를 사용하면 동일한 코드를 공유하는 웹, 안드로이드, iOS 애플리케이션을 구축할 수 있습니다.

 

 

 

6. 템플릿

 

React로 SPA를 생성할 때 Create React App 툴 체인 사용을 권장합니다. Create React App 툴 체인은 기본적으로 JavaScript용 구문 확장인 JSX로 구성된 React 앱으로 초기화합니다. JSX는 React에서 HTML을 작성하고 HTML로 JavaScript를 작성할 수 있게 해주는 템플릿 엔진처럼 동작합니다.

 

JSX

 

Blazor는 수년간 사용되어 온 Razor 템플릿 엔진을 사용합니다. 이 템플릿 엔진은 C#과 ASP.NET 에코시스템에서 새로운 것이 아닙니다. 이 엔진은 C#과 ASP.NET을 사용해 웹 페이지에 서버 측 코드를 포함하는 데 사용됩니다.

 

Razor 템플릿

 

JSX처럼 Razor 템플릿 엔진을 사용하면 마크업에 C# 코드를 작성할 수 있습니다.

 

 

 

7. 성능.

 

Blazer 프로젝트는 브라우저에서 필요한 DLL 라이브러리와 전체 dotNet 런타임을 다운로드해야 하므로 더 느립니다. 또한 Blazor 앱에는 대기시간에 문제가 있습니다. 따라서 전 세계 사람들이 액세스 할 수 있는 웹 애플리케이션을 구축하는 경우에 Blazor는 가장 적합한 프레임워크가 될 수 없습니다.

 

Blazor 점수

 

위 다이어 그램에서 Lighthouse 점수는 Blazor앱에 몇 가지 심각한 성능 문제가 있음을 보여줍니다. 초기 페이지 로드 시 필요한 종속성을 다운로드해야 하므로 초기 페이지 로드 시간이 느립니다.

 

React 점수

 

이 부분에 대해서 React가 빛을 발합니다. UI 라이브러리로서 React 핵심 패키지는 매우 간결합니다. 구성 요소 기반 패러다임을 사용해 놀랍도록 빠르고 현대적인 클라이언트 측 애플리케이션을 구축하기 위해 완전히 최적화되어 있습니다. 

 

 

 

8. 생태계.

 

이 글을 쓰는 시점에서 Microsoft는 Blazor WebAssembly 및 Blazor Server를 포함해 4개의 새로운 Blazor 에디션을 발표했습니다.

 

반면에 React 생태계는 구현하려는 거의 모든 것에 대한 패키지를 npm에서 찾을 수 있을 정도로 매우 큽니다. React는 Facebook의 전폭적인 지원을 받으며 클라이언트 측 애플리케이션이 구축되는 방식이 많이 바뀌었기 때문에 커뮤니티에서 많은 지원을 받았습니다. 또한 React는 여전히 JavaScript이기 때문에 웹 개발자에게 익숙합니다.

 

React의 판매 가치는 "한번 배워서 어디서나 사용"입니다. 기본적으로  React, React DOM, React Native를 사용하면 고도화되고 풍부한 대화형 프런트엔드 웹, 안드로이드, iOS 애플리케이션을 구축할 수 있습니다. 이는 대부분의 회사가 React와 React Native에 능숙한 개발자를 고용해 비교적 저렴한 비용으로 견고한 제품과 플랫폼을 쉽게 구축할 수 있도록 합니다. 

 

 

 

9. GitHub 평가.

 

이 글을 작성하는 시점에 React는 GitHub에서 188,000개 이상의 별을 받았습니다. 틀림없이 일반적으로 가장 사랑받는 JavaScript 라이브러리 중 하나입니다. 한편 Blazor는 GitHub에서 약 28,000개의 별을 받았습니다.

 

Blazor가 2018년도에 처음 출시되었고 이 글을 시점에서 개발자 커뮤니티 내에서 비교적 새롭다는 사실을 감안하면 타당한 이유가 된다고 생각합니다.

 

 

 

10. PWA(Progressive Web App) 지원.

 

Blazor PWA는 개발자가 고급 Progressive Web App을 빌드할 수 있도록 지원합니다. React 애플리케이션에서 PWA 지원을 추가하는 것은 아주 쉽습니다. 다음 명령을 실행하면 서비스 워커 파일이 추가된 React 앱이 초기화됩니다.

 

npx create-react-app my-app --template cra-template-pwa

 

 

 

11. Blazor Native와 Blazor Hybrid vs. React Native

 

이 글을 작성하는 당시 실험적인 Blazor Native를 사용하면 기본적으로 개발자가 Mobile Blazor 바인딩을 사용해 Blazor로 모바일 앱을 빌드할 수 있습니다. C#과 dotNet을 사용해 Blazor로 안드로이드와 iOS 앱을 개발하는 것은 실제로 가능합니다.

 

Blazor Hybrid 앱은 단일 앱에 기본 UI와 웹 UI를 결합한 것입니다. Blazor를 사용하면 앱의 기본 UI를 작성하고 앱에 웹 UI를 만들 수 있으므로 Blazor를 사용해 웹과 모바일 앱 모두 빌드할 수 있습니다. 기본적으로 웹과 모바일 앱에서 코드 스니펫을 공유합니다. 이는 확실히 C# dotNet 개발을 하기 좋은 타이밍입니다.

 

React는 React Native를 사용하여 네이티브 모바일 앱을 빌드하므로 React 개발자가 React로 모바일 앱을 빌드할 수 있습니다. React Native를 사용하면 기본 UI 컨트롤을 사용하며 기본 플랫폼에 대한 전체 액세스 권한을 가질 수 있습니다.

 

React Native는 프로덕션 단계에서 사용되지만 Blazor Native는 아직 개발 단계에 있습니다. React Native와 비교할 때 Blazor Native는 커뮤니티 지원이 부족합니다.

 

 

 

12. 패키지 관리자.

 

다른 JavaScript 프레임워크나 라이브러리와 마찬가지로 React는 종속성을 관리하기 위한 패키지 관리자로 npm과 Yarn을 사용합니다. Blazor WebAssembly앱에서는 다음 방법 중 하나로 패키지를 손쉽게 설치할 수 있습니다: PackageReference, dotNET CLI, Package manager, Paket CLI.

 

PackageReference를 사용해 패키지를 설치하려면 Balzorize.csproj 파일로 이동해 ItemGroup 태그 내에 다음과 같이 패키지를 추가합니다:

 

<PackageReference Include="System.Net.Http.Json" Version="5.0.0" />

 

dotNET CLI를 사용해 패키지를 설치하려면 터미널에서 앱의 루트 디렉터리로 이동하고 다음 명령을 실행합니다: 

 

dotnet add package Microsoft.AspNetCore.Blazor.HttpClient --version 3.2.0-preview3.20168.3

 

dotNET CLI 및 Paket CLI에 대해 알아보려면 가이드를 확인하시기 바랍니다.

 

 

 

13. 구성요소 간의 통신.

 

기본적으로 React는 구성요소의 상태를 처리하기 위해 두 가지 주요한 접근 방식을 제공합니다. 구성요소는 자체 상태나 데이터를 처리하거나 props를 통해 데이터를 받을 수 있습니다. HTTP 섹션에서 구성요소가 자체 상태를 처리하는 방법을 확인할 수 있습니다. 

 

다음은 구성요소가 React 앱의 상위 구성요소에서 props를 통해 데이터를 받는 방법의 예시입니다: 

 

// Parent Component
export default function Blog() {
    const blogPosts = [
        {
            id: 1,
            title: 'This is the title',
            content: 'This is some random content',
        },
		//...
        {
            id: 4,
            title: 'This is the title',
            content: 'This is some random content',
        },
    ]
    return (
        <>
            <BlogCard blogPosts={blogPosts}/>
        </>
    )
}

 

블로그 게시물을 포함하는 각 객체의 배열인 blogPosts를 하위 구성요소인 BlogCard 구성요소에 다음과 같이 전달합니다: 

 

// Child Component
export default function BlogCard( { blogPosts } ) {
    return (
        <>
            {blogPosts.map(blogPost => (
                <div className="blog-post" key={blogPost.id}>
                    <h1>{blogPost.title}</h1>
                    <p>{blogPost.content}</p>
                </div>
                ))}
        </>
    )
}

 

이제 하위 구성요소인 BlogCard를 렌더링 할 때 props를 통해 블로그 게시물 목록을 받아올 수 있습니다. React의 props에 대해 더 자세히 알아보려면 공식 문서를 확인해 주시기 바랍니다. 

 

Blazor에서 자식 구성요소는 다음과 같이 매개변수를 통해 부모 구성요소에서 데이터를 받아옵니다:

 

// Child Component
<h2>@Title</h2>
<p>@Content</p>
@code {    
   // Demonstrates how a parent component can supply parameters
    [Parameter]
    public string Title { get; set; }
    [Parameter]
    public string Content { get; set; }
}

 

BlogCard 구성요소를 렌더링 할 때 제목과 콘텐츠를 전달할 수 있으며 다음과 같이 렌더링 됩니다:

 

<BlogCard Title="What is Blazor?" Content="Blazor is a web UI framework using C#/Razor and HTML..." />

 

 

 

14. 라우팅.

 

React 애플리케이션에서 라우터는 미리 구성되거나 설치되지 않습니다. React Router 패키지는 대부분 클라이언트 측 탐색을 구현하는 데 사용됩니다. 

 

문서에 따르면 React Router는 React Native  프로젝트를 포함하여 React가 렌더링 되는 모든 곳에서 작동합니다. API는 정말 간단하지만 URL 매개변수, 구성요소 리디렉션, 지연 로딩, 페이지 전환, 중첩 라우팅 등과 같은 많은 강력한 기능을 처리합니다.

 

React 앱에서 라우팅을 설정하려면 일반적으로 react-router-dom 패키지를 설치하고 React Router DOM 패키지의 브라우저 라우터 모듈을 사용해 index.js에 전체 앱을 래핑 합니다. 그런 다음 App.js 구성요소에서 React Router DOM의 경로 모듈을 사용해 페이지를 렌더링 합니다.

 

다음 이미지는 React에서 라우팅이 동작하는 방식을 보여줍니다:

 

Routing

 

Routing Path

 

Blazor WebAssembly 클라이언트 측 애플리케이션에서 라우팅 시스템은 ASP.NET의 기존 라우팅 엔진에 의존합니다. @page 지시문을 사용하고 파일 맨 위에 연결하려는 경로를 사용하여 Blazor 구성요소에 대한 경로를 매핑할 수 있습니다.

 

다른 페이지로 이동하려면 react-router-dom에서 NavLink 구성요소가 작동하는 방식과 유사한 NavLink 구성요소를 사용해야 합니다. 

 

NavLink

 

다음 코드를 사용해 파일 맨 위에 NavigationManager를 넣어 Blazor 앱의 페이지를 탐색합니다.

 

@inject NavigationManager NavManager

 

그런 다음 아래와 같이 주입한 NavManager의 NavigateTo 메서드를 호출합니다:

 

@inject NavigationManager NavManager
<p>Learn more about us</p>
<button @onclick="navigateHome">Go back home</button>
@code {
    private void navigateHome()
    {
        NavManager.NavigateTo("");
    }
}

 

 

 

15. HTTP

 

Blazor 애플리케이션에서 HTTP 요청을 다루는 법에 대해 알아보기 위해서 페이지 디렉터리에 FetchPost.razor 파일을 만듭니다:

 

@page "/http"
@inject HttpClient Http
<h1>Blog post</h1>
@if (posts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Title</th>
                <th>Body</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var post in posts)
            {
                <tr>
                    <td>@post.title</td>
                    <td>@post.body</td>
                </tr>
            }
        </tbody>
    </table>
}
@code {
    private BlogPost[] posts;
    protected override async Task OnInitializedAsync()
    {
        posts = await Http.GetJsonAsync<BlogPost[]>("https://jsonplaceholder.typicode.com/posts");
        Console.WriteLine(posts);
    }
    public class BlogPost
    {
        public int id { get; set; }
        public String title { get; set; }
        public String body { get; set; }
    }
}

 

이 코드에서 세 가지 일이 일어나고 있습니다. 먼저 HTTP 클라이언트를 사용해 HTTP 호출을 수행하는데 도움을 주는 서비스인 HttpClient를 주입합니다. Blazor 3.1.0 버전부터 Blazorize.csproj 파일에 Blazor HTTP 클라이언트를 추가해야 합니다.

 

그리고 GetJsonAsync() 메서드를 사용하여 HttpClient를 호출해 정의된 엔드포인트에서 JSON을 가져옵니다. 마지막으로 가져온 JSON을 기반으로 BlogPost 결과를 만듭니다.

 

HttpClient를 주입하려면 상단에 @inject HttpClient Http를 추가해야 합니다. 데이터를 능동적으로 가져오기 위해 HttpClient를 호출합니다. 페이지가 초기화될 때 실행이 보장되는 수명 주기 메서드인 OnInitializedAsync()를 정의하고 이를 수행합니다.

 

React와 같은 라이브러리를 사용하는 이점 중 하나는 도구의 유연성입니다. React는 Blazor와 같은 HTTP 클라이언트를 제공하지 않습니다. React 개발자는 Fetch API, Axios, XHR을 사용해 HTTP 요청을 할 수 있습니다.

 

여기서 HTTP 요청의 경우 JavaScript Fetch API를 사용합니다. 아래 코드는 내장 JavaScript Fetch API를 사용해 React 앱에서 간단한 HTTP 요청을 만드는 방법을 보여줍니다:

 

// post.js
import React, { useState, useEffect } from 'react'
export default function Post() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
        fetch("https://jsonplaceholder.typicode.com/posts")
            .then(response => response.json())
            .then(post=> {
                setPosts(post)
                setLoading(false)
            })
    }, [])
   return (
        <div>
            {
            loading ?
                (
                    <p>Loading...</p>
                ) : (
                    posts.map((post, i) => (
                        <div>
                            <p>{post.title}</p>
                            <p>{post.body}</p>
                        </div>
                    ))
                )
            }
        </div>
    )
}

 

useEffect Hook에서 엔드포인트를 호출하고 있는 것에 주목하시기 바랍니다. React는 렌더링 후 구성요소가 API에서 데이터를 가져와야 한다고 알려줍니다. 그런 다음 요청이 성공하면 setPosts를 호출하고 API에서 post 객체를 전달해야 합니다. React에서 userState Hooks가 작동하는 방식에 대한 자세한 내용은 공식 문서를 확인해 주시기 바랍니다.

 

 

 

16. 결론.

 

SPA를 구축하기 위한 최상의 프런트엔드 프레임워크를 선택하는 것은 팀 내 선호도, 에코시스템, 성능, 확장성을 포함한 많은 요인에 따라 달라집니다. 이 글에서는 React와 Blazor를 비교해 이 두 가지 멋진 프레임워크가 어떻게 작동되고 있는지 확인해 보았습니다.

 

React와 Blazor는 몇 가지 면에서 유사하며 동일한 작업을 수행하는 데 사용할 수 있습니다. 그렇다면 어떤 것을 선택해야 할까요? 인기도, 구축 중인 프로텍트 우형, 확장성, 유지 관리 가능성 등 다양한 요인을 고려해야 합니다.

 

이 글을 통해 다음 프로젝트를 위한 프런트엔드 프레임워크를 선택할 때 도움이 되었길 바랍니다. 즐거운 코딩 되세요.

 

 

 

 

 

반응형

본 글은 다음 글을 번역한 글입니다: The 2021 Web Development (Frontend + Backend) RoadMap

 

The 2021 Web Development (Frontend + Backend) RoadMap

An illustrated guide to becoming a Web Developer with links to relevant courses

dev.to

 

 

 

https://medium.com/@digitalact/2020-front-end-web-developer-road-map-ed3fcdec1c14

 

목차

 

  1. 2021/01/15 - [Programming] - 2021년 웹 개발 로드 맵 - 모든 개발자가 배워야 할 8가지.
  2. [현재 글] 2021년 웹 개발 로드 맵 - 프런트엔드
  3. 2021/01/15 - [Programming] - 2021년 웹 개발 로드 맵 - 백엔드

 

 

 

2021 프런트엔드 개발자 로드맵

 

2021년에 프런트엔드 개발자가 되고 싶다면 아래 프런트 엔드 개발자 로드맵을 따르는 게 좋습니다. 여기서 배우고 따라야 할 중요사항은 노란색 상자에 강조되어 있으며 이 상자에 집중하는 것이 좋습니다. 로드맵 아래에는 학습에 도움되는 책과 코스 같은 추가 리소스가 있습니다.

 

https://github.com/kamranahmedse/developer-roadmap/blob/master/img/frontend.png?year-2021-2

 

1. 웹 개발 기초.

 

웹 개발자가 되고 싶다면 인터넷, 웹 애플리케이션, HTTP와 같은 프로토콜 및 웹 개발 전반에 대한 기본 사항을 알아야 합니다. 고맙게도 이것들 뿐 아니라 그 이상을 배울 수 있는 수많은 강의들이 있습니다.

 

웹 개발을 배우고 싶은 모든 프로그래머들에게 다음 리소스를 권장합니다.

  1. The Web Developer Bootcamp by Colt Steel
  2. The 2021 Complete Web Developer Bootcamp by Angela Yu
  3. The Advanced Web Developer Bootcamp

 

 

2. HTML과 CSS

 

 

HTML과 CSS는 모든 웹사이트의 중심입니다. HTML은 구조를 제공하고 CSS를 스타일을 제공해 시각적으로 더 멋지게 보이도록 도와줍니다. 진정한 프런트엔드 개발자가 되고 싶다면 이 두 가지를 마스터해야 합니다. 이 두 가지를 시작하려면 이 무료 HTML과 CSS 강의를 확인해 온라인으로 배울 수 있습니다.

 

 

3. 자바스크립트

 

 

객체 지향 프로그래밍에서 추상화, 캡슐화, 다형성, 상속의 네 개의 기둥이 있는 것처럼 웹 개발에는 HTML, CSS, 자바스크립트라는 세 개의 주요 기둥이 있습니다.

 

처음 두 개는 구조와 스타일을 제공하지만 상호 작용을 추가해 생생하게 만드는 것은 자바스크립트입니다. 요즘의 자바 개발자가 자바 스크립트를 배우는 것은 매우 중요하며 고맙게도 초급 자바스크립트와 고급 자바 스크립트를 모두 배울 수 있는 많은 강의가 존재합니다. 다음 몇 가지 권장사항이 있습니다.

  1. The Complete JavaScript Cource 2021
  2. A Beginer's Guide to Advanced JavaScript and ES6

 

 

4. 타입 스크립트

 

 

C와 C++가 있는 것처럼 타입 스크립트는 C++ 만큼 인기가 많지 않지만 자바 스크립트++로 여겨질 수 있습니다. 타입 스크립트의 좋은 부분은 자바스크립트 코드에 타입 안정성을 추가해 개발 단계에 불쾌한 자바스크립트 타입 관련 오류를 확인할 수 있다는 것입니다.

 

또한 자바스크립트용 객체 지향 코드를 쉽게 개발할 수 있습니다. 타입 스크립트를 배우고 싶다면 "Understanding TypeScript"는 시작하기 좋은 강의입니다. 더 많은 선택 사항이나 무료 강의가 필요한 경우 시작할 수 있는 또 다른 무료 타입 스크립트 강의가 더 있습니다.

 

 

5. 앵귤러

 

 

사람들이 일반적인 HTML, CSS, 자바스크립트를 사용해 웹사이트를 구축하던 시대는 지났습니다. 요즘 대부분의 작업은 앵귤러, 리액트, 뷰 js와 같은 프레임워크에서 수행됩니다. 코드를 저장하기 위한 구조를 제공해 줄 뿐 아니라 웹 애플리케이션을 빠른 시간 내 쉽게 개발할 수 있습니다. 

 

앵귤러는 구글에서 지원하므로 앵귤러를 배우는 것은 모든 최산 웨 개발자에게 좋은 선택입니다. 앵귤러를 배우고 싶다면 Max의 "Angular - The Complete Guide"보다 좋은 과정은 없을 겁니다.

 

 

6. 리액트 

 

앵귤러와 마찬가지로 리액트는 웹 애플리케이션 개발에 널리 사용되는 라이브러리입니다. 재사용 가능한 컴포넌트를 작성하여 현대적인 인터랙티브 웹 페이지를 만드는 데 사용됩니다.

 

앵귤러가 구글에서 지원하는 것처럼 리액트는 페이스북에서 지원하므로 유명합니다. 리액트를 배우고 싶다면 Stepgen Grider의 "Modern React with Redux" 또는 Max의 "React - The Complete Guide on Udemy"에 참여하세요. 둘 다 온라인에서 리액트를 배울 수 있는 훌륭한 과정입니다. 링크는 다음과 같습니다:

  1. Modern React with Redux
  2. React - The Complete Guide

 

 

7. 뷰 js

 

 

뷰 또는 뷰 JS는 작년에 속도에 대한 성능이 개선되었으며 점점 더 많은 사람들이 뷰 js를 추천하고 상요하고 있습니다. 저는 여전히 뷰의 초보자이며 아마도 뷰를 배우고 뷰 기반의 애플리케이션으로 마이그레이션 하는데 시간이 걸릴 것입니다. 그러나 이미 뷰 js를 배우기로 결정을 내린 경우 Udemy의 Vue.js Essentials 강의부터 시작할 수 있습니다.

 

 

 

 

 

 

 

반응형

이 글은 다음 글을 번역한 글입니다: React Folder Structure in 5 Steps

 

React Folder Structure in 5 Steps - RWieruch

How to structure large React apps into folders and files for a scalable React project ...

www.robinwieruch.de

 

 

 

1. 앞선 글

 

https://www.robinwieruch.de/react-folder-structure

 

큰 규모의 리액트 앱을 폴더와 파일로 구성하는 방법은 서로 다른 의견이 많은 주제입니다. 정답이 존재하지 않는 주제이기 때문에 이 주제에 대해 글을 쓰는 한동안 고생했습니다. 하지만 매주 사람들이 내게 자신의 리액트 프로젝트를 어떻게 구성해야 하는지 물어봅니다. 폴더 구조는 작은 규모의 리액트 프로젝트뿐 아니라 확장 가능한 리액트 애플리케이션에서 더 중요합니다. 수년간 개인 프로젝트, 고객의 프로젝트의 리액트 애플리케이션을 구현했고 이러한 문제에 내가 어떻게 접근했는지 설명하도록 하겠습니다. 5개의 단계만 거치면 여러분에게 의미가 있는 게 어떤 것인지 정할 수 있고 어느 정도까지 원하는지 알 수 있습니다.  그럼 시작해 보도록 하겠습니다.

 

"난 내가 좋다고 느껴질 때까지 파일을 옮깁니다"라고 말하는 사람은 아마 혼자 개발하는 사람이나 소규모 팀에서는 괜찮을 수도 있습니다. 하지만 한 기능을 4명이 개발하고 5개의 팀이 있는 회사에서 실제로 수행할 수 있을까요? 더 큰 규모의 팀에서는 "명확한 비전 없이 그냥 파일을 이동하는 것"은 더 까다롭습니다. 게다가 고객이 내게 이 문제에 대해 물어봤을 때 말할 수 있는 내용이 아닙니다. 따라서 이 연습은 좀 더 명확한 것을 찾는 모든 이들을 위한 참조 가이드입니다.

 

 

 

2. 단일 리액트 파일.

 

첫 번째 단계는 다음 규칙을 따릅니다: 하나의 파일로 모두를 규정짓습니다. 대부분의 리액트 프로젝트는 src 폴더와 App 컴포넌트가 있는 하나의 src/App.js 파일로 시작합니다. creatr-react-app을 사용했을 때 얻을 수 있는 최소한의 것입니다. 이 App 함수형 컴포넌트는 다음과 같은 것을 렌더링 합니다:

 

import React from 'react';
const App = () => {
  const title = 'React';
  return (
    <div>
      <h1>Hello {title}</h1>
    </div>
  );
}
export default App;

 

결국 이 컴포넌트에 더 많은 기능을 추가하고 자연스럽게 사이즈가 커지게 되며 일부를 독립된 리액트 컴포넌트로 추출해내야 합니다. 다음은 App 컴포넌트에서 자식 컴포넌트로 list 컴포넌트를 추출해 낸 것입니다:

 

import React from 'react';
const list = [
  {
    id: 'a',
    firstname: 'Robin',
    lastname: 'Wieruch',
    year: 1988,
  },
  {
    id: 'b',
    firstname: 'Dave',
    lastname: 'Davidds',
    year: 1990,
  },
];
const App = () => <List list={list} />;
const List = ({ list }) => (
  <ul>
    {list.map(item => (
      <ListItem key={item.id} item={item} />
    ))}
  </ul>
);
const ListItem = ({ item }) => (
  <li>
    <div>{item.id}</div>
    <div>{item.firstname}</div>
    <div>{item.lastname}</div>
    <div>{item.year}</div>
  </li>
);

 

새로운 리액트 프로젝트를 시작할 때마다 한 파일에 여러 컴포넌트를 포함시키는 것이 괜찮다고 말합니다. 심지어 큰 규모의 프로젝트에서 한 컴포넌트가 다른 컴포넌트에 긴밀히 묶이는 것도 괜찮습니다. 하지만 이 시나리오에서는 결국 리액트 프로젝트에서 한 파일만으로는 더 이상 충분하지 않습니다. 이제 2단계로 전환을 해야 할 때입니다.

 

 

 

3. 여러 리액트 파일.

 

두 번째 단계는 다음 규칙을 따릅니다: 여러 파일로 모두를 규정짓습니다. List와 ListItem 컴포넌트를 갖는 이전 App 컴포넌트를 예시로 들어보겠습니다. 모든 것을 가진 하나의 src/App.js 파일 대신 컴포넌트들을 여러 개의 파일로 분리할 수 있습니다. 여기서 여러분이 얼마나 멀리 갈지 정합니다. 예를 들어 전 다음 폴더구조 까지만 갈 겁니다:

 

- src/
--- App.js
--- List.js

 

src/List.js에는 List와 ListItem 컴포넌트의 자세한 구현을 갖고 있을 수도 있지만 List 컴포넌트를 분리해 공용 API로 다음과 같은 파일로 내보냅니다.

 

const List = ({ list }) => (
  <ul>
    {list.map(item => (
      <ListItem key={item.id} item={item} />
    ))}
  </ul>
);
const ListItem = ({ item }) => (
  <li>
    <div>{item.id}</div>
    <div>{item.firstname}</div>
    <div>{item.lastname}</div>
    <div>{item.year}</div>
  </li>
);
export default List;

 

그러면 src/App.js는 List 컴포넌트를 import해와 사용할 수 있습니다.

 

import React from 'react';
import List from './List';
const list = [
  {
    id: 'a',
    firstname: 'Robin',
    lastname: 'Wieruch',
    year: 1988,
  },
  {
    id: 'b',
    firstname: 'Dave',
    lastname: 'Davidds',
    year: 1990,
  },
];
const App = () => <List list={list} />;

 

만약 더 나아가길 원한다면 ListItem 컴포넌트도 별도 파일로 추출하고 List 컴포넌트가 ListItem 컴포넌트를 import 하도록 할 수 있습니다.

 

- src/
--- App.js
--- List.js
--- ListItem.js

 

하지만 앞서 말한 것처럼 ListItem 컴포넌트는 List 컴포넌트와 강하게 묶여 있기 때문에 좀 더 멀리 가야 할 수도 있습니다. 그래서 ListItem을 src/List.js 파일에 남겨두는 것도 괜찮습니다. 저는 리액트 컴포넌트가 재사용 가능한 리액트 컴포넌트가 될 때마다 다른 리액트 컴포넌트에서 액세스 할 수 있도록 마치 앞서 List 컴포넌트에서 했던 것처럼 독립적인 파일로 나누는 경험의 법칙을 따릅니다. 

 

 

 

4. 리액트 파일에서 리액트 폴더로.

 

여기부터 더 흥미로워지고 의견이 다양해집니다. 모든 리액트 컴포넌트는 결국 복잡해집니다. 더 많은 조건부 렌더링이나 리액트 훅과 같은 더 많은 로직이 더해지고 스타일링과 테스트 같은 더 많은 기술적 고려사항 해문입니다. 별다른 지식이 필요하지 않은 접근 방식은 리액트 컴포넌트 옆에 더 많은 파일을 추가하는 것입니다. 예를 들어 모든 리액트 컴포넌트에 테스트와 스타일 파일이 있다고 가정해 봅시다.

 

- src/
--- App.js
--- App.test.js
--- App.css
--- List.js
--- List.test.js
--- List.css

 

src 폴더 내의 모든 추가 컴포넌트에 대해 개별 컴포넌트를 보기 힘들어질 것이므로 잘 확장되지 않는다는 것을 이미 알 수 있습니다. 이로 인해 저는 각각의 리액트 컴포넌트에 하나의 폴더를 갖게 합니다.

 

- src/
--- App/
----- index.js
----- test.js
----- style.css
--- List/
----- index.js
----- test.js
----- style.css

 

이 파일들의 네이밍은 여러분에게 달려있습니다. 예를 들어 index.js, component.js, test.js, spec.js가 될 수 있습니다. 게다가 만약 여러분이 CSS를 사용하지 않고 Styled Components와 같은 것을 사용하고 싶다면 파일 확장자도 style.css에서 style.js가 될 수 있습니다. 여러분들의 네이밍 규칙에 익숙해졌다면 IDE에서 "List index" 혹은 "App test"를 검색해 파일을 열 수도 있습니다. 컴포넌트의 폴더를 모두 축소하면 여러분은 매우 간결하고 명확한 폴더 구조를 볼 수 있습니다.

 

- src/
--- App/
--- List/

 

예를 들어 특정 컴포넌트에 대한 커스컴 훅을 파일로 추출해야 하는 것과 같이 컴포넌트에 기술적으로 고려해야 할 점이 많다면 컴포넌트 폴더 내에서 수평적으로 확장하는 방법으로 접근할 수 있습니다.

 

- src/
--- App/
----- index.js
----- test.js
----- style.css
--- List/
----- index.js
----- test.js
----- style.css
----- hooks.js

 

ListItem 컴포넌트를 별도의 파일로 추출해 List/Indes.js를 좀 더 가볍게 유지하기로 정했다면 다음과 같은 폴더구조를 가질 수 있습니다.

 

- src/
--- App/
----- index.js
----- test.js
----- style.css
--- List/
----- index.js
----- test.js
----- style.css
----- ListItem.js

 

여기에 다시 한 단계 더 나아가 컴포넌트 폴더에 테스트와 스타일과 같은 기술적 고려사항을 더할 수도 있습니다.

 

- src/
--- App/
----- index.js
----- test.js
----- style.css
--- List/
----- index.js
----- test.js
----- style.css
----- ListItem/
------- index.js
------- test.js
------- style.css

 

중요한 점은 여기부터 컴포넌트가 서로 너무 깊게 중첩되지 않도록 신경 써야 합니다. 제 경험에 따라 저는 컴포넌트가 두 레벨 이상 중첩되지 않도록 합니다. 따라서 List 폴더 내 ListItem 폴더는 괜찮지만 ListItem 폴더에 다른 중첩된 폴더가 있어서는 안 됩니다. 

 

중간 사이즈의 리액트 프로젝트 규모를 넘어설 것이 아니라면 이것이 여러분의 리액트 컴포넌트를 구조화하는 방법이라고 생각합니다. 하지만 제가 언급했듯이 이는 이견이 있을 수 있고 모든 이들의 입맛에 맞지는 않을 수 있습니다.

 

 

 

5. 기술적인 폴더를 분리.

 

다음 단계는 중대형 이상 규모의 리액트 애플리케이션을 구조화하는데 도움이 될 것입니다. 여러 컴포넌트에서 사용하는 기능을 컴포넌트로부터 분리합니다. 다음 폴더 구조에서 예를 들어보도록 하겠습니다.

 

- src/
--- components/
----- App/
------- index.js
------- test.js
------- style.css
----- List/
------- index.js
------- test.js
------- style.css

 

이전 리액트 컴포넌트를 새로운 component 폴더로 그룹화합니다. 이것은 다른 카테고리 폴더를 생성하기 위한 수직적 레이어를 제공해 줍니다. 예를 들어 어느 시점에 여러 컴포넌트에서 사용할 수 있는 리액트 훅이 있을 수도 있습니다. 이 훅을 컴포넌트에 강하게 결합하는 대신 모든 컴포넌트에서 사용할 수 있도록 전용 폴더에 훅의 구현을 위치시킬 수 있습니다.

 

- src/
--- components/
----- App/
------- index.js
------- test.js
------- style.css
----- List/
------- index.js
------- test.js
------- style.css
--- hooks/
----- useClickOutside/
------- index.js
----- useData/
------- index.js

 

모든 훅이 폴더 내에 있어야 한다는 의미는 아닙니다. 한 컴포넌트에만 사용되는 리액트 훅은 여전히 컴포넌트 파일에 남아 있거나 별도의 hook.js 파일에 있어야 합니다. 오로지 모든 리액트 컴포넌트에서 사용될 수 있는 훅만이 hook 폴더에 있어야 합니다. 

 

만약 여러분이 프로젝트에서 다른 모든 파일에서 글로벌하게 액세스 되어야 하는 React Context를 사용하는 경우 같은 전략이 적용될 수 있습니다.

 

- src/
--- components/
----- App/
------- index.js
------- test.js
------- style.css
----- List/
------- index.js
------- test.js
------- style.css
--- hooks/
----- useClickOutside/
------- index.js
----- useData/
------- index.js
--- context/
----- Session/
------- index.js

 

여기에 컴포넌트와 훅에 접근이 필요한 다른 유틸리티가 있을 수도 있습니다. 다양한 유틸리티의 경우 저는 주로 service 폴더를 생성합니다. 이름을 여러분에게 달려있지만 이 기술적인 분리를 이끄는 것은 우리 프로젝트의 다른 코드에서 로직을 사용할 수 있도록 한다는 원칙입니다. 

 

- src/
--- components/
----- App/
------- index.js
------- test.js
------- style.css
----- List/
------- index.js
------- test.js
------- style.css
--- hooks/
----- useClickOutside/
------- index.js
----- useData/
------- index.js
--- context/
----- Session/
------- index.js
--- services/
----- ErrorTracking/
------- index.js
------- test.js
----- Format/
------- Date/
--------- index.js
--------- test.js
------- Currency/
--------- index.js
--------- test.js

 

예를 들어 Data/index.js 파일을 봅시다. 자세한 구현은 다음과 같습니다:

 

export const formatDateTime = (date) =>
  new Intl.DateTimeFormat('en-US', {
    year: 'numeric',
    month: 'numeric',
    day: 'numeric',
    hour: 'numeric',
    minute: 'numeric',
    second: 'numeric',
    hour12: false,
  }).format(date);
export const formatMonth = (date) =>
  new Intl.DateTimeFormat('en-US', {
    month: 'long',
  }).format(date);

 

다행히도 자바스크립트의 Intl API는 날짜 변환을 위한 훌륭한 도구들을 제공합니다. 하지만 리액트 컴포넌트에서 API를 직접적으로 사용하는 것 대신에 서비스를 제공하는 방식이 더 좋습니다. 이 방식을 사용하면 내 컴포넌트에 애플리케이션에서 사용할 수 있는 날짜 형식 옵션을 보장할 수 있기 때문입니다. 그렇지 않으면 애플리케이션이 커가면서 많은 다른 날짜 포맷이 있을 겁니다. 

 

이제 날짜 형식 함수를 개별적으로 import 해올 수 있습니다.

 

import { formatMonth } from '../../services/format/date';
const month = formatMonth(new Date());

 

하지만 제가 더 선호하는 서비스로서 캡슐화된 코드는 다음과 같습니다.

 

import * as dateService from '../../services/format/date';
const month = dateService.formatMonth(new Date());

 

상대 경로가 있는 항목을 가져오는 것은 어려울 수도 있습니다. 따라서 전 항상 별칭을 위한 Babel의 Module Resolver를 사용합니다. 이후 import는 다음과 같이 바꿔 쓸 수 있습니다.

 

import * as dateService from '@format/date';
const month = dateService.formatMonth(new Date());

 

저는 모든 폴더에 전용의 목적을 부여하고 애플리케이션에서 기능을 공유하도록 하기 때문에 이러한 기술적인 분리의 고려를 좋아합니다.

 

 

 

6. 도메인 폴더 분리.

 

마지막 단계는 대규모 리액트 애플리케이션을 구성하는데 도움이 됩니다. 기술적으로 분리된 폴더에 많은 서브 폴더가 있는 경우 발생할 수 있는 일입니다. 예시는 전체를 보여주진 않지만 요점을 이해하길 바랍니다.

 

- src/
--- components/
----- App/
----- List/
----- Input/
----- Button/
----- Checkbox/
----- Profile/
----- Avatar/
----- MessageItem/
----- MessageList/
----- PaymentForm/
----- PaymentWizard/
----- ErrorMessage/
----- ErrorBoundary/

 

여기부터는 UI 컴포넌트처럼 재사용 가능한 컴포넌트만 components 폴더를 사용합니다. 다른 모든 컴포넌트는 도메인 중심의 폴더로 이동시켜야 합니다. 폴더 이름은 여러분에게 달려있습니다.

 

- src/
--- domain/
----- User/
------- Profile/
------- Avatar/
----- Message/
------- MessageItem/
------- MessageList/
----- Payment/
------- PaymentForm/
------- PaymentWizard/
----- Error/
------- ErrorMessage/
------- ErrorBoundary/
--- components/
----- App/
----- List/
----- Input/
----- Button/
----- Checkbox/

 

PaymentForm이 Button이나 Input에 액세스 해야 하는 경우 재사용 가능한 UI 컴포넌트 폴더에서 가져와 사용하면 됩니다. MessageList 컴포넌트에 추상 List 컴포넌트가 필요하면 List 컴포넌트를 가져와 사용하면 됩니다. 더 나아가 이전 단계의 서비스가 도메인에 강하게 결합되어 있으면 서비스를 특정 도메인 폴더로 이동시킵니다. 앞서 기술적으로 분리된 폴더에도 동일하게 적용될 수 있습니다.

 

- src/
--- domain/
----- User/
------- Profile/
------- Avatar/
----- Message/
------- MessageItem/
------- MessageList/
----- Payment/
------- PaymentForm/
------- PaymentWizard/
------- services/
--------- Currency/
----------- index.js
----------- test.js
----- Error/
------- ErrorMessage/
------- ErrorBoundary/
------- services/
--------- ErrorTracking/
----------- index.js
----------- test.js
--- components/
--- hooks/
--- context/
--- services/
----- Format/
------- Date/
--------- index.js
--------- test.js

 

각 도메인 폴더에 중간중간 services 폴더가 필요한지 여부는 여러분에게 달려있습니다. services 폴더를 제외하고 ErrorTracking폴더의 내용을 Error폴더에 직접 넣을 수도 있습니다만 ErrorTracking은 리액트 컴포넌트가 아닌 서비스로 표시되어야 하기 때문에 혼란스러울 수도 있습니다. 대안으로 ErrorTracking대신 error-tracking처럼 PascalCase보다 kebab-case를 사용해 명명할 수 있습니다.

 

여기에는 개인적인 공간이 존재합니다. 경국 이 단계는 도메인을 하나로 모아 회사의 여러 팀이 프로젝트 전체에서 특정 도메인에서 작업할 수 있도록 하는 것입니다.

 

 

 

7. 맺는 글

 

모든 글이 끝났습니다. 여러분 또는 다른 팀이 리액트 프로젝트를 구성하는데 도움이 되기를 바랍니다. 표시된 접근 방식 중 어느 것도 바꾸기 어렵거나 불가능한 것이 아닙니다. 오히려 전 여러분들이 이것들을 개인에 맞게 적용해 사용하기를 바랍니다. 모든 리액트 프로젝트는 시간이 지남에 따라 규모가 커지므로 대부분의 폴더 구조도 그에 맞게 자연스레 변합니다. 따라서 이 5단계의 과정은 문제가 발생할 경우 몇 가지 지침을 제공하는 가이드일 뿐입니다.

 

 

 

 

 

반응형

 

Storybook 패키지에 대해 알아봅니다.

 

 

 

1. Storybook이란?

 

최근 프론트엔드 개발은 페이지 단위가 아닌 컴포넌트 단위로 개발된 다는 것을 들어본 적이 있을 겁니다. 프로젝트 내에서 컴포넌트를 개발하다 보면 초기에는 별 문제가 없지만 프로젝트의 규모가 커질수록 컴포넌트 개발에 독립성을 유지하기 힘들어집니다. 이런 경우 스토리북을 이용하면 보다 편하게 컴포넌트를 개발할 수 있습니다.

 

Build bulletproof UI components faster
Storybook is an open source tool for developing UI components in isolation for React, Vue, and Angular. It makes building stunning UIs organized and efficient.

 

스토리북의 공식 홈페이지를 방문하면 가장 먼저 우리를 반겨주는 문구입니다. 위의 설명대로 스토리북은 독립된 환경에서 컴포넌트를 개발하고 이를 확인해 볼 수 있는 오픈소스 라이브러리 입니다. 이러한 환경에서 개발된 컴포넌트는 독립성이 유지되기 때문에 자연히 재사용성이 좋아집니다. 또한 스토리북은 개발뿐 아니라 문서화, 테스팅에 대해서도 좋은 기능을 제공해 줍니다.

 

 

 

2. Storybook 사용해보기.

 

먼저 스토리북을 사용하기 위해 예시 프로젝트를 생성합니다. 이 글에서는 create-react-app을 통해 예시 프로젝트를 생성하겠습니다.

 

> create-react-app storybook-ex

> cd storybook-ex 

> npx -p @storybook/cli sb init --type react_scripts

 

여기까지 진행한 뒤 package.json파일을 확인합니다. script항목에 보면 전에 없던 스토리북에 관련된 스크립트를 확인할 수 있습니다. 

 

  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "storybook": "start-storybook -p 9009 -s public",
    "build-storybook": "build-storybook -s public"
  },

 

이제 스토리북을 실행시켜 9009번 포트로 접속해 봅시다.

 

> npm run strorybook

> http://localhost:9009/로 이동.

 

 

정상적으로 스토리북이 실행된 것을 확인할 수 있습니다. 좌측에 "Welcom"과 "Button"이 보이시나요? 이것들을 스토리북에선 스토리라고 부릅니다. 스토리란 결국 우리가 컴포넌트를 구현하게 될 독립적 공간이 되는것 입니다. 

 

이제 다시 프로젝트로 돌아가서 위의 스토리들이 어디에 위치하는지 확인해 봅시다. ../.storybook 폴더 내의 main.js 파일을 열어보세요.

 

module.exports = {
  stories: ['../src/**/*.stories.js'],
  addons: [
    '@storybook/preset-create-react-app',
    '@storybook/addon-actions',
    '@storybook/addon-links',
  ],
};

 

스토리북에서 사용하는 애드온들과 스토리에 대한 설정이 정의되어 있습니다. stories에 설정된 값을 확인해보면 src 폴더 내에 있는 .stories.js로 끝나는 모든 파일을 스토리 파일로 인식하도록 설정되어 있습니다. 실제로 위의 "Welcom"과 "Button" 스토리들은 src폴더 내의 stories폴더에 존재하고 있습니다.

 

새로운 스토리를 생성하기 위해서는 src폴더 내 임의 폴더에 파일이름.stories.js로 파일을 생성 한 뒤 컴포넌트를 작성하면 됩니다.

 

 

 

 

 

반응형

이 글은 Velopert의 "리액트를 다루는 기술"을 참고하였습니다.

 

 

1. 불변성 관리

 

리액트를 다루면서 불변성을 유지하면서 상태를 업데이트하는 것이 중요하단 걸 모든 개발자는 알 것입니다.

이를 위해 전개 연산자 및 배열의 내장 함수를 사용하면 배열이나 객체를 복사하고 새로운 값을 덮어쓸 수 있습니다.

그러나 객체의 구조가 커지고 깊이가 깊어지면 불변성을 유지하면서 업데이트하기가 매우 힘들어집니다. 값 하나를 업데이트하기 위해 수많은 코드를 작성해야 할 수도 있습니다.

 

이러한 상황에서 immer 라이브러리를 이용하면 구조가 복잡한 객체도 짧은 코드를 사용해 불변성을 유지하면서 업데이트를 할 수 있습니다.

 

 

 

2. immer

 

Immer은 보다 편리한 방식으로 불변의 상태로 작업을 가능하게 해주는 패키지입니다. copy-on-write 메커니즘을 기반으로 합니다.

기본 아이디어는 모든 변경사항을 currentState의 프록시인 임시 draftState에 반영하는 것입니다. 

이후 모든 변경이 끝이 나면 Immer는 draftState에 대한 변경내용을 기반으로 nextState를 생성합니다.

 

 

즉, Immer는 현재 상태를 가져와서 변경 사항을 기록하기 위해 draft를 만들고 작업이 끝나면 draft를 토대로 nextState를 만듭니다.

 

기본적으로 Immer는 다음과 같이 사용합니다.

import produce from 'immer';
const nextState = produce(orgState, draft => {
  //값 바꾸기
  draft.somewhere.deep.inside = changedValue;
});

procude 함수는 두 파라미터를 받습니다. 첫 번째 파라미터는 수정하고 싶은 상태이며 두 번째 파라미터는 상태를 어떻게 업데이트할지 정의하는 함수 합니다.

draft내부에서 값을 변경하면 produce 함수가 불변성을 유지하면서 상태를 업데이트해 줍니다.

 

 

 

3. 불변성 유지를 위해 Immer를 사용하지 않을 때

 

다음 예시 코드는 Immer를 사용하지 않고 불변성을 유지할 때의 예시 코드입니다.

 

import React, {useRef, useCallback, useState} from 'react';

const App = () =>{
  const nextId = useRef(1);
  const [form, setForm] = useState({name: '', userName: ''});
  const [data, setData] = useState({
    array: [],
    uselessValue: null
  });

  const onChange = useCallback(e=>{
    const {name, value} = e.target;
    setForm({...form, [name]: [value]});
  }, [form]);

  const onSubmit = useCallback(e=>{
    e.preventDefault();
    const info = {
      id: nextId.current,
      name: form.name,
      userName: form.userName
    };
    setData({...data, array: data.array.concat(info)});
    setForm({name: '', userName: ''});
    nextId.current += 1;
  }, [data, form.name, form.userName]);

  const onRemove = useCallback(id=>{
    setData({...data, array: data.array.filter(info=>info.id !== id)});
  }, [data]);

  return(
    <div>
      <form onSubmit={onSubmit}>
        <input name="userName" placeholder="ID" value={form.userName} onChange={onChange}/>
        <input name="name" placeholder="Name" value={form.name} onChange={onChange}/>
        <button type="submit">Register</button>
      </form>
      <div>
        <ul>
          {data.array.map(info=>(
            <li key={info.id} onClick={()=>onRemove(info.id)}>
              {info.userName} ({info.name})
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

export default App;

위와 같이 전개 연산자와 배열의 내장 함수를 사용하면 어렵진 않지만 상태가 복잡해지면 귀찮은 작업이 필요하게 될 수 있습니다. 

 

 

 

4. 불변성 유지를 위해 Immer를 사용할 때

 

다음 예시 코드는 Immer를 적용한 코드입니다.

 

import React, {useRef, useCallback, useState} from 'react';
import produce from 'immer';

const App = () => {
    const nextId = useRef(1);
    const [form, setForm] = useState({ name: '', userName: '' });
    const [data, setData] = useState({
      array: [],
      uselessValue: null
    });

  const onChange = useCallback(e=>{
    const {name, value} = e.target;
    setForm(
      produce(form, draft=>{
        draft[name] = value;
      })
    );
  }, [form]);

  const onSubmit = useCallback(e=>{
    e.preventDefault();
    const info = {
      id: nextId.current,
      name: form.name,
      userName: form.userName
    };
    setData(
      produce(data, draft=>{
        draft.array.push(info);
      })
    );
    setForm({name: '', userName: ''});
    nextId.current += 1;
  }, [data, form.name, form.userName]);

  const onRemove = useCallback(id=>{
    setData(
      produce(data, draft => {
        draft.array.splice(draft.array.findIndex(info=>info.id === id), 1);
      })
    );
  }, [data]);

  return(
    <div>
      <form onSubmit={onSubmit}>
        <input name="userName" placeholder="ID" value={form.userName} onChange={onChange}/>
        <input name="name" placeholder="Name" value={form.name} onChange={onChange}/>
        <button type="submit">Register</button>
      </form>
      <div>
        <ul>
          {data.array.map(info=>(
            <li key={info.id} onClick={()=>onRemove(info.id)}>
              {info.userName} ({info.name})
            </li>
          ))}
        </ul>
      </div>
    </div>
  );    
};

 

Immer는 필수적으로 사용해야 하는 라이브러리는 아닙니다. 사용하는 게 더 불편하다면 사용할 이유가 없습니다. 단지 복잡한 객체의 상태를 업데이트해야 할 때 많은 도움이 되며 생산성을 높여줍니다.

 

 

 

 

반응형

 

 

이 글은 Janelle WongAtomic Design Pattern: How to structure your React application를 번역한 글입니다.

 

 

 

리액트에서 애플리케이션을 확장해 나감에 따라 종종 컴포넌트 구조의 복잡도가 올라가는 것을 관리하는 것에 대한 어려움에 직면합니다. 필자가 이 포스트에서 빼버리고 싶은 것은 왜 전형적인 디자인 패턴(아토믹 디자인)을 구현하는 것이 애플리케이션 코드의 가독성, 확장성, 유연성에 중요한지 이해하는 것입니다. 아토믹 디자인 패턴은 리액트 컴포넌트 특징에 있어서 대단히 적합하다고 증명되었습니다.

 

 

 

Atomic Design

Brad Frost 와 Dave Olsen에 의해 개발된 아토믹 디자인은 다섯 가지 기초 블록으로 시스템 설계를 만들기 위한 방법론이며 이 블록을 합치면 일관성, 모듈성, 확장성을 촉진시킵니다. 이글에서는 이러한 원리들이 어떻게 React에서 인터페이스를 만들 때 자연스럽게 적합한지, 그리고 추상적 생태계 안에서 동적 수명주기의 컴포넌트를 매핑할 수 있는 것과 같이 어떻게 아토믹 은유(Atomic metaphor)를 유용한 방법으로 확장하는지에 대해 알아보겠습니다.

 

 

 

Atomic Development

아토믹 디자인은 다섯개의 레벨로 구분되어 있으며 React의 컴포넌트 기반 구조에 놀라울 정도로 잘 매핑됩니다. - 원자(Atoms) > 분자(Molcules) > 유기체(Organisms) > 탬플릿(Templates) > 페이지(Pages)

 

 

  • 원자: 버튼, 인풋, 폼라벨 등과 같은 기본적인 블록입니다. 그 자체로는 유용하지 않습니다.
  • 분자: 버튼, 인풋, 폼라벨을 묶어 기능을 만드는 것과 같이 원자 아이템을 그룹화합니다.
  • 유기체: 분자를 묶어 네비게이션 바와 같은 인터페이스에서 구분되는 부분인 유기체를 만듭니다. 
  • 탬플릿: 페이지는 주로 유기체의 그룹으로 구성되며 클라이언트가 볼 수 있는 최종 디자인입니다.
  • 페이지: 다른 템플릿의 렌더를 보는 생태계 입니다. 단일 환경(애플리케이션)에 여러 생태계 생성할 수 있습니다.

 

 

 

File Structure

리액트가 컴포넌트 기반의 구조기 때문에 컴포넌트를 기능보다 유형에 따라 구성하는 것은 꽤 일반적입니다. 만약 각각의 컴포넌트 기능에 대해 하위 생태계를 만들고 싶다면 어떻게 해야 할까요?

 

 

각각의 컴포넌트나 서비스는 자신의 인스턴스가 동작하는데 필요한 모든 것이 포함된 독립된 환경을 갖습니다. 각각의 컴포넌트, 버튼, 폼은 앱에서 독립된 요소들과 같이 동작하는 자신들의 스타일, 액션, 단위 또는 통합 테스트를 갖고 있으며 이미지나 다른 로컬 변수를 추가할 수도 있습니다. 이것은 효율적이고 일관적이며 코드 테스트를 더 쉽게 만들고 노력을 줄여줍니다.


이런 유형의 조직은 한 컴포넌트 안에 다른 컴포넌트를 중첩시킬 수 있습니다. 만약 /Delete, /Submit, /Login 또는 /Register 내부에 새 컴포넌트를 정의한다면 내부의 컴포넌트는 사촌 컴포넌트는 사용이 불가하며 바로 위의 부모 컴포넌트만 사용할 수 있습니다. 

 

 

 

왜 이런 걸 하나요?

React 파일 구조를 조직할 때 아토믹 디자인 패턴을 따르는 주요한 목적은 각각의 컴포넌트를 독립된 환경에 두는 것입니다. 부작용이 독립되어 있을 때 코드는 더 읽기 쉬워지고 모듈화 됩니다. 기능의 단일 인스턴스 테스트는 좀 더 직관적이 될 것이고 그러면 애플리케이션의 전체 품질 보증이 향상됩니다. 애플리케이션의 상태 관리의 복잡도가 증가함에 따라 이런 패턴의 파일 구조는 상태를 확인하고 다루는 데 있어서 도움이 될 것입니다.

 

 

 

아래에 정보를 얻기에 좋은 자료가 있습니다.

 

 

 

반응형

+ Recent posts