<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ React - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/korean/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ React - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/korean/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Wed, 24 Jun 2026 09:44:08 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/korean/news/tag/react/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ React 서버 컴포넌트를 사용해야 하는 이유와 방법 ]]>
                </title>
                <description>
                    <![CDATA[ 2020년 말, React 팀은 "제로-번들-사이즈 React 서버 컴포넌트" 개념을 도입했습니다. 그 이후로 React 개발자 커뮤니티는 이 미래 지향적인 접근 방식을 실험하고 적용하는 방법을 학습해 왔습니다. React는 UI를 구축하는 방식에 대한 우리의 생각을 바꾸었습니다. 그리고 React 서버 컴포넌트를 사용하는 새로운 모델은 훨씬 더 구조화되고 편리하며, 유지 관리하기 쉽고 더 나은 사용자 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/how-to-use-react-server-components/</link>
                <guid isPermaLink="false">6598f5974759f103f7f97680</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Soobin Bak ]]>
                </dc:creator>
                <pubDate>Mon, 08 Jan 2024 08:53:44 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2024/01/React-Server-Components-2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/how-to-use-react-server-components/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">React Server Components – How and Why You Should Use Them in Your Code</a>
      </p><p>2020년 말, React 팀은 "제로-번들-사이즈 React 서버 컴포넌트" 개념을 도입했습니다. 그 이후로 React 개발자 커뮤니티는 이 미래 지향적인 접근 방식을 실험하고 적용하는 방법을 학습해 왔습니다.</p><p>React는 UI를 구축하는 방식에 대한 우리의 생각을 바꾸었습니다. 그리고 React 서버 컴포넌트를 사용하는 새로운 모델은 훨씬 더 구조화되고 편리하며, 유지 관리하기 쉽고 더 나은 사용자 경험을 제공합니다.</p><p><code>Next.js</code>의 최신 릴리즈는 “서버 컴포넌트로 생각하기” 방향으로 나아갔습니다. 그리고 React 개발자로서 우리는 애플리케이션을 구축하는데 이 기술을 최대한 활용하기 위하여 새로운 사고 모델에 적응해야 합니다.</p><p>이 튜토리얼에서는 RSC(React Server Component)에 대해 배울 것입니다. React 서버 컴포넌트가 무엇인지, 어떻게 작동하는지, 그리고 더 중요하게는 이것이 어떤 문제를 해결하는지 중점적으로 알아볼 것입니다.</p><p>또한 왜 RSC가 필요한지 이해할 수 있도록 많은 예시를 보여줄 것입니다. 마지막으로 <code>React 서버 컴포넌트</code>와 비슷해 보이지만 다른 기능인 <code>서버 사이드 렌더링(SSR)</code> 간의 차이점을 배울 것입니다.</p><p>React를 처음 접하는 경우, React 서버 컴포넌트에 대해 학습하기 전에, 컴포넌트 아키텍처, 상태(state), 속성(props)을 이용한 데이터 전달, 가상 돔(Virtual DOM) 트리에 대한 기본 지식이 필요합니다.</p><p>또한 이 글을 읽은 다음 freeCodeCamp에서 <a href="https://www.freecodecamp.org/news/react-fundamentals-for-beginners/">전체 로드맵을 참고</a>하여 React 기초를 확고히 다질 수도 있습니다.</p><p>준비가 되었나요? 시작해봅시다.</p><p>만약 비디오 콘텐츠를 통해 학습하고 싶다면, 이 글을 비디오 튜토리얼로도 볼 수 있습니다. 🙂</p><figure class="kg-card kg-embed-card" data-test-label="fitted">
        <div class="fluid-width-video-container">
          <div style="padding-top: 56.49999999999999%;" class="fluid-width-video-wrapper">
            <iframe width="200" height="113" src="https://www.youtube.com/embed/5DZvdoMogys?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="" title="React Server Components Made Easy(With Examples and Demo)" name="fitvid0"></iframe>
          </div>
        </div>
      </figure><h2 id="-ui-react">클라이언트 사이드 UI 라이브러리로서의 React</h2><p>React는 처음부터 클라이언트 사이드 UI 라이브러리였습니다. 웹 및 모바일 개발자가 컴포넌트 기반 아키텍처를 활용하여 애플리케이션을 개발하는 데 도움이 되는 JavaScript 기반의 오픈 소스 라이브러리입니다.<br>React 철학은 우리가 디자인 전체를 더 작고 독립적인 부분인 <code>컴포넌트</code>라고 불리는 작은 조각으로 나누는 것을 제안합니다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-169.png" class="kg-image" alt="image-169" width="600" height="400" loading="lazy"></figure><p>하나의 컴포넌트가 여러 개의 하위 컴포넌트로 분해되는 것을 보여주는 이미지입니다.</p><p>컴포넌트는 <code>state</code>라고 불리는 자체 비공개 데이터와 서로 다른 컴포넌트 간에 데이터를 전달하는 방법인 &nbsp;<code>props</code>를 가질 수 있습니다. 컴포넌트들을 컴포넌트 계층 구조로 나누고, 상태를 정의하고, 상태를 변경하여 발생되는 효과(effect)를 관리하고, 데이터 흐름(flow)을 결정합니다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-170.png" class="kg-image" alt="image-170" width="600" height="400" loading="lazy"></figure><p><em>state와 props의 작동 방식을 보여주는 이미지</em></p><p>전통적으로 이러한 모든 컴포넌트들은 자바스크립트의 함수입니다(여기서는 함수형 컴포넌트에 대해서만 이야기하고, 과거에 사용했던 클래스형 컴포넌트는 따로 이야기 하지 않겠습니다). 앱이 브라우저에서 로드될 때, 컴포넌트 코드를 다운로드하고 이를 사용하여 앱이 작동하도록 만듭니다.</p><p>여전히 ‘컴포넌트’라는 용어를 사용할 것입니다. 하지만 이 문서는 React 서버 컴포넌트의 개념을 소개하므로, 이러한 전통적인 컴포넌트를 <code>클라이언트 컴포넌트</code> 라고 부르겠습니다(클라이언트/브라우저에서 다운로드되고 React가 마법을 부려 렌더링하는 컴포넌트).</p><h2 id="react-">React 앱의 일반적인 문제</h2><p>React 클라이언트 컴포넌트는 훌륭하며 특정 사례를 해결하는 데 효과적입니다. 하지만 React 앱을 구축할 때, 패턴을 조금 다르게 고려해 볼 필요가 있습니다. 이는 다음과 같은 사항을 고려해야 하기 때문입니다.</p><ul><li><code>사용자 경험(User Experience)</code>: 우리는 사용자와 고객을 위한 소프트웨어 제품을 만듭니다. 앱이 성공하길 원한다면, 애플리케이션의 사용자 경험이 중요합니다.</li><li><code>유지 보수(Maintainability)</code>: 프로젝트 코드는 여러 개발팀을 거쳐 수년간 원활하게 유지 보수 되어야 합니다.</li><li><code>성능 비용(Performance Cost)</code>: 애플리케이션이 느려져서는 안 되며, 우리의 디자인(설계) 접근 방식이 느리게 만들어서도 안됩니다.</li></ul><p>이제 일상적으로 마주할 수 있는 몇 가지 예를 살펴보겠습니다. 또한 React를 사용하여 일상적인 웹 개발에서 각 핵심 사항을 어떻게 구현하고 설계할 수 있는지 이해할 수 있을 것입니다.</p><h3 id="-">레이아웃 이동 문제</h3><p>매우 흔한 UX 문제 중 하나는 컴포넌트가 렌더링될 때 레이아웃이 갑자기 이동되는 현상입니다. 아래의 코드 스니펫을 살펴봅시다.</p><pre><code class="language-jsx">&lt;CourseWraper&gt;
  &lt;CourseList /&gt;
  &lt;Testimonials /&gt;
&lt;/CourseWraper&gt;
</code></pre><p>다음은 익숙한 JSX 코드로, <code>CourseWrapper</code> 컴포넌트와 <code>CourseList</code> 및 <code>Testimonials</code> 두 개의 하위 컴포넌트가 있습니다. <code>CourseList</code> 와 <code>Testimonials</code> 두 컴포넌트 모두 데이터를 가져오기 위해 네트워크 호출(API 호출)을 수행한다고 가정해 봅시다.</p><p>다음은 <code>CourseList</code> 컴포넌트 입니다.</p><pre><code class="language-jsx">function CourseList() {

    // 실제 네트워크 호출을 가정하면,
    // useEffect를 사용하여 처리할 것입니다.
    const info = fetchCourseList();

    return(
      &lt;&gt; &lt;/&gt;)
}
</code></pre><p>그리고 <code>Testimonial</code> 컴포넌트 입니다.</p><pre><code class="language-jsx">function Testimonials() {

    // 실제 네트워크 호출을 가정하면,
    // useEffect를 사용하여 처리할 것입니다.
    const info = fetchTestimonials();

    return(
      &lt;&gt; &lt;/&gt;
    )
}
</code></pre><p>이러한 컴포넌트들이 네트워크 호출을 수행하는 경우, 응답이 돌아오는 순서에 대한 보장이 없습니다. 이는 네트워크 속도, 지연 시간 및 여러 다른 요인에 따라 달라집니다.<br><code>Testimonials</code> 컴포넌트의 네트워크 호출이 <code>CourseList</code> 컴포넌트보다 먼저 완료되는 상황에서, <code>Testimonials</code> 컴포넌트가 먼저 렌더링되고 그 다음에 <code>CourseList</code> 컴포넌트가 렌더링됩니다. 이로 인해 <code>Testimonials</code> 컴포넌트가 적합한 위치로 이동됩니다. 아래에서 제가 하는 말이 무엇인지 확인할 수 있습니다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/07/layoutshift-1.gif" class="kg-image" alt="layoutshift-1" width="600" height="400" loading="lazy"></figure><p><em>레이아웃 이동의 UX 문제를 보여주는 슬로우 모션</em></p><p>로딩 인디케이터나 깜빡임 효과를 통해서 사용자에게 잠시 후를 기대하도록 알려주어 UX를 좀 더 향상시킬 수 있습니다(그러나 언제가 될지는 확신하지 못합니다).</p><h3 id="-waterfall-">네트워크 워터폴(waterfall) 문제</h3><p>또 다른 전형적인 사용자 경험 문제에 대해 이야기해 봅시다. 지난 예제와 유사한 React 컴포넌트를 상상해 보세요.</p><pre><code class="language-jsx">function Course() {
  return (
    &lt;CourseWraper&gt;
      &lt;CourseList /&gt;
      &lt;Testimonials /&gt;   
    &lt;/CourseWraper&gt;
  )
}
</code></pre><p><em>두 개의 하위 컴포넌트를 가진 CourseWrapper 컴포넌트</em></p><p>여기서 조금 수정을 해보겠습니다. <code>CourseList</code> 과 <code>Testimonials</code> 컴포넌트와 함께 이제 <code>CourseWrapper</code>도 네트워크 호출을 수행합니다.</p><pre><code class="language-jsx">function CourseWrapper() {

    // 실제 네트워크 호출을 가정하면,
    // useEffect를 사용하여 처리할 것입니다.
    const info = fetchWrapperInfo();
    
    return(
      &lt;&gt; &lt;/&gt;
    )
}
</code></pre><p><em>CourseWrapper 컴포넌트 - 네트워크 호출 수행</em></p><p>따라서 부모 컴포넌트는 데이터를 가져오기 위해 네트워크 호출을 수행하고, 그 하위 컴포넌트들 또한 네트워크 호출을 수행합니다.</p><p>흥미로운 점은, 부모 컴포넌트는 네트워크 호출이 완료될 때까지 렌더링되지 않는다는 것입니다. 따라서 자식 컴포넌트들의 렌더링도 홀딩 합니다.</p><p>현재 작업을 시작하기 위해 이전 것의 응답이 완료되기를 기다리는 현상은 <code>워터폴(Waterfall)</code>라고 알려져 있습니다. 이 경우에는 네트워크 워터폴 및 컴포넌트 렌더링 워터폴 문제 모두 발생합니다.</p><p>이제 여러분은 각 컴포넌트에서의 모든 네트워크 호출을 제거하고 각각의 컴포넌트가 응답을 기다리지 않도록 부모 컴포넌트에서 단일 호출 하도록 네트워크 호출 로직을 끌어 올릴 수 있다고 생각할 수 있습니다. 이것은 현명한 생각이지만 유지 보수 측면에서 문제가 발생할 수 있습니다. 다음 섹션에서 이에 대해 더 자세히 알아보겠습니다.</p><h3 id="--1">유지 보수 관련 문제</h3><p>서버 측 상호 작용과 관련된 몇 가지 사용자 경험 문제를 살펴보았으므로, 이제 유지 보수 관련 문제를 고려해 봅시다.</p><p>모든 컴포넌트가 네트워크 호출을 수행하지 않는다고 가정해 봅시다. 단일 API 호출인 <code>fetchAllDetails()</code>를 사용하여 (부모 컴포넌트를 포함하여) 모든 컴포넌트의 세부 정보를 한 번에 가져옵니다.</p><p>그런 다음 필요한 정보를 각 컴포넌트에 props로 전달합니다. 이것은 위에서 본 "워터폴" 문제보다 더 나은 접근 방법이겠지요?</p><pre><code class="language-jsx">function Course() {
	
    // 실제 네트워크 호출을 가정하면,
    // useEffect를 사용하여 처리할 것입니다. 
    const info = fetchAllDetails();
    
    return(
    	&lt;CourseWrapper
        	ino={info.wrapperInfo} &gt;
            &lt;CourseList
        		ino={info.listInfo} /&gt;
            &lt;Testimonials
        		ino={info.testimonials} /&gt;
        &lt;/CourseWrapper&gt;     
    )
 }
</code></pre><p><em>Course 컴포넌트 - 최상위 API 호출 수행</em></p><p>그러나 이것은 일부 유지 보수 문제를 야기할 수 있습니다.</p><p>어느 화창한 날, 제품이 Testimonials 기능을 삭제하기로 결정했다고 가정해 봅시다. 우리는 위의 코드에서 Testimonials 컴포넌트를 간단히 삭제할 수 있습니다. 그런데, <code>fetchAllDetails()</code> 호출을 통해 가져온 데이터를 정리하는 것을 잊을 수 있습니다. 사용되지 않고 불필요한 상태로 남아 있을 수 있습니다.</p><p>이러한 문제를 완화하기 위해, 가능한 사용자 경험 문제를 설명하면서 이전 섹션에서 이미 논의한 방식으로 코드를 변경할 수 있을 것입니다. 따라서 우리는 더 나은 해결책을 찾아야 합니다. 하지만 그에 앞서, <code>성능 비용</code>이라는 또 하나의 고려 사항에 대해 먼저 이야기해 보겠습니다.</p><h3 id="--2">성능 비용</h3><p>우리가 논의할 마지막 문제 영역은 성능 비용입니다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-171.png" class="kg-image" alt="image-171" width="600" height="400" loading="lazy"></figure><p><em>인터넷에서 발견한 재미있는 밈 - 자바스크립트가 클라이언트에게 주는 무거움을 묘사</em></p><p>전통적으로, React 컴포넌트는 클라이언트 사이드 자바스크립트 함수입니다. 이는 React 애플리케이션의 구성 요소입니다. 클라이언트에서 애플리케이션을 로드할 때, 컴포넌트가 클라이언트에 다운로드되고 React가 그것들을 렌더링하는데 필요한 작업을 수행합니다.</p><p>그러나 이로 인해 두 가지 중요한 문제가 발생합니다.</p><p>먼저, 사용자가 요청을 보낼 때, 앱은 HTML과 연결된 JavaScript, CSS 및 이미지와 같은 다른 에셋을 다운로드합니다.</p><p>클라이언트 사이드(브라우저에서)에서 React는 마법을 부리기 시작하고 HTML 구조를 하이드레이트(hydrate) 시킵니다. 이것은 HTML을 구문 분석하고, 이벤트 리스너를 DOM에 연결하며 스토어에서 데이터를 가져옵니다. 따라서 사이트는 완전히 작동하는 React 앱이 됩니다.</p><p>그러나 중요한 점은 클라이언트에서 많은 일이 일어나고 있다는 것입니다. 결국 이 코드를 모두 클라이언트에서 다운로드하게 됩니다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-182.png" class="kg-image" alt="image-182" width="600" height="400" loading="lazy"></figure><p><em>브라우저에 다운로드된 스크립트의 양</em></p><p>대부분의 경우, 프로젝트에 종속성이 있는 외부 라이브러리(Node 모듈)가 필요합니다. 이러한 모든 종속성은 클라이언트 사이드에서 다운로드되어 더욱 무거워집니다.</p><p>이제 문제점을 이해했으므로, <code>React 서버 컴포넌트</code>가 제공하는 기능과 이러한 문제를 해결하는 방법에 대해서 확실히 이해할 수 있을 것입니다.</p><p>그러나 이에 대해 이야기하기 전에, 클라이언트와 서버에 대해 조금 더 알아보겠습니다.</p><h2 id="--3">클라이언트-서버 모델</h2><p>이 글에서 클라이언트와 서버 라는 용어를 여러 번 사용했습니다. 이제 이들에 대한 공식적인 정의를 내리고 높은 수준에서 그들의 관계를 설명해보겠습니다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/size/w1600/2023/07/image-175.png" class="kg-image" alt="image-175" width="600" height="400" loading="lazy"></figure><p><em>클라이언트와 서버의 관계를 보여주는 다이어그램</em></p><ul><li><code>클라이언트</code>: 애플리케이션의 클라이언트는 최종 사용자 측에서 작업을 실행하는 시스템입니다. 클라이언트의 예로는 PC, 랩탑, 모바일, 브라우저 등이 있습니다.</li><li><code>서버</code>: 이름에서 알 수 있듯이, 서버는 클라이언트에 서비스를 제공합니다. 빠른 데이터 액세스를 위해 데이터 저장소나 데이터베이스와 공존할 수 있습니다.</li><li><code>요청</code>: 요청은 클라이언트가 서버로부터 서비스를 요청하기 위해 사용하는 통신 모드입니다.</li><li><code>응답</code>: 응답은 또한 서버가 서비스(데이터/정보)를 클라이언트에게 다시 보내기 위해 사용하는 통신 모드입니다.</li></ul><h2 id="react--1">React 클라이언트 컴포넌트</h2><p>위에서 언급한대로, 전통적으로 React 컴포넌트는 클라이언트 사이드에서 실행됩니다. 서버와 상호 작용할 때, 요청을 보내고 응답이 돌아올 때까지 기다립니다. 응답을 받으면 클라이언트는 다음 작업을 트리거합니다.</p><p>요청한 서비스가 성공적으로 완료되면 클라이언트 컴포넌트는 UI에 맞게 작동하고 성공 메시지를 표시합니다. 오류가 발생한 경우 클라이언트 컴포넌트는 사용자에게 오류를 보고합니다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/size/w1600/2023/07/image-176.png" class="kg-image" alt="image-176" width="600" height="400" loading="lazy"></figure><p><em>클라이언트 서버 모델에서의 React 클라이언트 컴포넌트</em></p><p>네트워크 워터폴를 발생시킬 때 클라이언트 컴포넌트의 응답이 지연되어 사용자 경험이 저하됩니다. 그렇다면 이를 어떻게 완화할 수 있을까요?</p><h2 id="react-rscs-">React 서버 컴포넌트(RSCs)는 어떻게 도움을 줄까요?</h2><p>React 컴포넌트를 서버로 옮겨보는 것은 어떨까요? 그리고 아마도 데이터 저장소와 동일한 곳에 위치시키고... 그런데 이것이 실제로 가능한 일일까요?<br>네! 이제 <code>React Server Components</code>에 대해 알아보겠습니다. 이러한 새로운 컴포넌트는 서버에 있으므로 데이터를 더 빨리 가져올 수 있습니다. 네트워크를 통해 왕복하지 않고도 파일 시스템 및 데이터 저장소와 같은 서버 인프라에 액세스할 수 있습니다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/size/w1600/2023/07/image-177.png" class="kg-image" alt="image-177" width="600" height="400" loading="lazy"></figure><p><em>클라이언트 서버 모델에서의 React 서버 컴포넌트</em></p><p>이제 서버 컴포넌트 측면에서 생각해야 하기 때문에, React 개발자에게는 완전한 패러다임 전환입니다.</p><p>RSC를 사용하면, 데이터 가져오는 로직을 서버로 이동하여(네트워크 호출 없이 데이터를 가져오도록) 서버 자체에서 준비할 수 있습니다. 클라이언트로 돌아오는 데이터는 모든 데이터와 함게 잘 구성된 컴포넌트 입니다. 얼마나 놀라운 일인가요?</p><p>이는 React 서버 컴포넌트를 사용하면 다음과 같은 코드를 작성할 수 있음을 의미합니다.</p><pre><code class="language-jsx">import { dbConnect } from '@/services/mongo'

import { addCourseToDB } from './actions/add-course'

import CourseList from './components/CourseList'

export default async function Home() {

  // MongoDB 연결
  await dbConnect();
  
  // db의 모델을 이용하여 모든 course를 얻는다.
  const allCourses = await courses.find();
  
  // 서버 쪽에서 콘솔이 찍힌다.
  console.log({allCourses})

  return (
    &lt;main&gt;
      &lt;div&gt;
        &lt;CourseList allCourses={allCourses} /&gt;  
      &lt;/div&gt;
    &lt;/main&gt;
  )
}
</code></pre><p><em>React 서버 컴포넌트의 예</em></p><p>저것 보세요! 몇 가지 변경 사항을 즉시 알아챌 수 있습니다.</p><ul><li>이 컴포넌트는 비동기 호출을 처리하므로 <code>async</code> 타입 입니다.</li><li>우리는 컴포넌트 자체에서 데이터베이스(MongoDB)에 연결합니다. 와우! 보통 이러한 코드는 <code>Node.js</code> 또는 <code>Express</code>에서 볼 수 있습니다, 맞나요?</li><li>그런 다음 데이터베이스를 쿼리하고 렌더링을 위해 JSX에 전달할 데이터를 가져옵니다.</li></ul><p>콘솔 로그는 브라우저 콘솔이 아닌 서버 콘솔에서 찍히는 것에 주목하세요.</p><p>또한 상태 관리 (useState)와 이펙트 관리 (useEffect)를 완전히 제거했습니다. 깔끔하고 간단합니다.</p><p>React 서버 컴포넌트를 사용하면, useEffect를 사용할 필요가 없을 수도 있습니다(영원히!).</p><h3 id="react--2">React 서버 컴포넌트의 한계</h3><p>이러한 이점도 갖고 있지만, 기억해야 할 RSC의 제한 사항도 있습니다.</p><ul><li>RSC는 서버에 남아 있고 서버에서 렌더링됩니다. 클라이언트 사이드과 관련된 것이 없습니다. 이것은 서버 컴포넌트에 사용자 인터렉션을 추가할 수 없음을 의미합니다. 예를 들어, 이벤트 핸들러나 useState, useReducer, useEffect와 같은 React 훅을 서버 컴포넌트에서 사용할 수 없습니다.</li><li>서버 컴포넌트에서 localstorage, bluetooth, web USB와 같은 브라우저 웹 API를 사용할 수 없습니다.</li><li>클라이언트 상호 작용과 관련된 모든 것에 대해서는, 계속 클라이언트 컴포넌트를 사용해야 합니다.</li></ul><p>이해가 되시나요? 그렇다면 어떻게 애플리케이션에 맞게 컴포넌트를 가장 잘 정리할 수 있을까요?</p><h3 id="--4">클라이언트와 서버 컴포넌트를 함께 사용하는 방법</h3><p>당신의 앱은 서버 및 클라이언트 컴포넌트의 조합일 수 있습니다. 곧 예제를 보겠지만, 먼저 개념을 이해해 봅시다.</p><p>서버 컴포넌트는 클라이언트 컴포넌트를 가져와 렌더링할 수 있지만, 클라이언트 컴포넌트는 내부에서 서버 컴포넌트를 렌더링할 수 없습니다. 클라이언트 컴포넌트에서 서버 컴포넌트를 사용하려면 props로 전달하여 사용할 수 있습니다.</p><p>컴포넌트 계층 구조의 루트에 서버 컴포넌트를 두고 컴포넌트 트리의 말단으로 클라이언트 컴포넌트를 밀어 넣는 것이 좋습니다.</p><p>서버 컴포넌트의 최상단에서 데이터 페칭이 일어날 수 있으며, React가 허용하는 방식대로 전달할 수 있습니다. 사용자 상호 작용(이벤트 핸들러) 및 브라우저 API에 액세스는 말단의 클라이언트 컴포넌트에서 처리할 수 있습니다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-186.png" class="kg-image" alt="image-186" width="600" height="400" loading="lazy"></figure><p><em>서버와 클라이언트 컴포넌트를 포함하는 컴포넌트 트리</em></p><h3 id="-rsc-ssr-">잠깐, RSC는 서버 사이드 렌더링(SSR)과 동일하지 않나요?</h3><p>아니요, 그렇지 않습니다. RSC와 SSR 둘 다 이름에 "서버"라는 단어가 들어가 있지만 비슷한 부분은 이 뿐입니다.</p><p>서버 사이드 렌더링(SSR)에서는, 서버에서 날것의 HTML을 클라이언트로 보내고, 그런 다음 모든 클라이언트 사이드 자바스크립트가 다운로드됩니다. React는 HTML을 상호작용 가능한 React 컴포넌트로 변환하기 위해 하이드레이션(hydration) 프로세스를 시작합니다. SSR에서 컴포넌트는 서버에 머무르지 않습니다.</p><p>지금까지 React 서버 컴포넌트를 통해 컴포넌트가 서버에 남아 있고 네트워크 왕복을 거치지 않고 서버 인프라에 액세스할 수 있음을 알게 되었습니다.</p><p>SSR은 애플리케이션의 초기 페이지를 더 빠르게 로드하는 데 유용합니다. 앞으로 SSR과 RSC를 문제없이 함께 사용할 수 있습니다.</p><h2 id="next-js-rsc-mongodb-">Next.js(RSC 포함)와 MongoDB를 사용하여 강의 목록 페이지를 구축하는 방법</h2><p>이제 React 서버 컴포넌트를 사용하는 응용 프로그램을 만들어 보겠습니다. Next.js는 최근 릴리스에서 RSC를 채택한 주요 웹 프레임워크입니다.</p><p>그래서 우리는 이제 Next.js에서 서버 컴포넌트를 만드는 방법과 클라이언트 컴포넌트를 만드는 방법이 어떻게 다른지를 보여주기 위해 강의 목록 페이지를 만들 것입니다.</p><p>여기서는 Next.js나 MongoDB를 자세히 배우지 않을 것입니다. React 서버 컴포넌트가 어떻게 동작하는지 그리고 클라이언트 컴포넌트와 어떻게 다른지 알려주기 위한 예로 이 애플리케이션을 사용하고 있습니다.</p><p>먼저 데이터 저장소에 강의 데이터를 추가해 보겠습니다. 이 앱에서는 MongoDB를 사용했습니다. 아래 이미지는 세 개의 강의에 대해 세 개의 문서가 추가된 것을 보여줍니다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/size/w1600/2023/07/image-178.png" class="kg-image" alt="image-178" width="600" height="400" loading="lazy"></figure><p><em>Mongo Compass - 강좌 모음</em></p><p>다음으로, MongoDB에 연결하는 유틸리티 함수를 만들겠습니다. 이것은 Mongoose와 MongoDB URI를 사용하여 자바스크립트 기반 프로젝트에서 MongoDB에 연결하는 데 사용할 수 있는 일반적인 코드입니다.</p><pre><code class="language-jsx">import mongoose from "mongoose";

export async function dbConnect(): Promise&lt;any&gt; {
  try {
    const conn = await mongoose.connect(String(process.env.MONGO_DB_URI));
    console.log(`Database connected : ${conn.connection.host}`);
    return conn;
  } catch (err) {
    console.error(err);
  }
}
</code></pre><p><em>MongoDB 연결을 위한 코드 스니펫</em></p><p>이제 MongoDB 문서와 매핑하는 모델을 만들어야 합니다. 여기서는 강의 데이터를 다루고 있으므로, 이에 해당하는 모델은 다음과 같습니다.</p><pre><code class="language-jsx">import mongoose, { Schema } from "mongoose";

const schema = new Schema({
  name: {
      required: true,
      type: String
  },
  description: {
      required: true,
      type: String
  },
  cover: {
    required: true,
    type: String
  },
  rating: {
    required: true,
    type: Number
  },
  price: {
    required: true,
    type: Number
  },
  createdOn: {
    type: { type: Date, default: Date.now }
  },
  link: {
    required: true,
    type: String
  },
  type: {
    required: true,
    type: String
  },
  comments: {
    required: false,
    type: [{ body: String, date: Date }]
  }
});

export const courses = mongoose.models.course ?? mongoose.model("course", schema);
</code></pre><p><em>Mongoose를 사용한 강의 모델</em></p><p>이제 마법이 시작됩니다! Next.js 앱 라우터를 사용하면 모든 컴포넌트가 기본적으로 서버 컴포넌트입니다. 이것은 서버 근처에 위치하고 서버 생태계에 액세스할 수 있음을 의미합니다.</p><p>아래 코드는 일반적인 Next.js 컴포넌트지만 특별한 기능이 있습니다. 이 컴포넌트에서 데이터베이스 연결을 직접 하여 어떤 state와 effect 관리를 하지 않고도 데이터를 직접 쿼리할 수 있습니다. 멋지죠?</p><p>이 컴포넌트에서 기록하는 모든 내용은 브라우저 콘솔에 찍히지 않습니다. 왜냐하면 이것은 서버 컴포넌트이기 때문입니다. 로그는 서버 콘솔에서 확인할 수 있습니다(아마도 <code>yarn dev</code> 명령을 사용하여 서버를 시작한 터미널에서).</p><p>데이터베이스와의 상호 작용은 비동기적 이므로, 호출 시 <code>await</code> 키워드를 사용하고 컴포넌트에는 <code>async</code> 키워드를 사용합니다. 응답을 받으면 이를 자식 컴포넌트에 props로 전달합니다.</p><pre><code class="language-jsx">import { dbConnect } from '@/services/mongo'
import { courses } from '@/models/courseModel'
import { addCourseToDB } from './actions/add-course'

import AddCourse from './components/AddCourse'
import CourseList from './components/CourseList'

export default async function Home() {

  // MongoDB 연결
  await dbConnect();
  
  // 모델을 이용하여 db로 부터 모든 강의를 가져옴.
  const allCourses = await courses.find().select(
  						["name", "cover", "rating"]);
  
  // 서버 콘솔에서 모든 출력값 표시
  console.log({allCourses})

  return (
    &lt;main&gt;
      &lt;div&gt;
        &lt;h1&gt;Courses&lt;/h1&gt; 
        &lt;AddCourse addCourseToDB={addCourseToDB} /&gt;
        &lt;CourseList allCourses={allCourses} /&gt;  
      &lt;/div&gt;
    &lt;/main&gt;
  )
}
</code></pre><p><em>page.tsx - 서버 컴포넌트</em></p><p>Home 컴포넌트에는 아래 내용이 포함되어 있습니다.</p><ul><li>제목(Heading)</li><li>강의 추가 버튼을 래핑한 컴포넌트(AddCourse)</li><li>강의 목록을 표시하는 컴포넌트(CourseList)</li></ul><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/07/Screenshot-2023-07-25-at-9.58.57-AM.png" class="kg-image" alt="Screenshot-2023-07-25-at-9.58.57-AM" width="600" height="400" loading="lazy"></figure><p>강의 목록 페이지</p><p>서버 컴포넌트는 클라이언트 및 서버 컴포넌트 모두 렌더링할 수 있다는 것을 알고 있습니다. <code>AddCourse</code> 컴포넌트는 사용자 인터렉션이 필요하며, 사용자가 버튼을 클릭하여 강의를 추가해야 합니다. 따라서 이는 서버 컴포넌트가 될 수 없습니다 (앞에서 읽은 서버 컴포넌트의 제한 사항을 기억하세요)!</p><p>그러므로 <code>AddCourse</code>를 위한 클라이언트 컴포넌트를 만들어 봅시다. Next.js 앱 라우터를 사용하면 모든 컴포넌트는 기본적으로 서버 컴포넌트입니다. 클라이언트 컴포넌트를 만들고 싶다면 해당 컴포넌트의 맨 위에서(import 문 이전에도 됨) <code>'use client'</code>라는 지시문을 사용하여 명시적으로 만들어야 합니다.</p><pre><code class="language-jsx">'use client'

import { useState } from 'react';
import Modal from './Modal';
import AddCourseForm from "./AddCourseForm";

export default function AddCourse({
  addCourseToDB,
}: {
  addCourseToDB: (data: any) =&gt; Promise&lt;void&gt;
}) {
  const [showAddModal, setShowAddModal] = useState(false);
  const add = async(data: any) =&gt; {
    await addCourseToDB(data);
    setShowAddModal(false);
  }

  return (
    &lt;&gt;
    &lt;button
      onClick={() =&gt; setShowAddModal(true)}
    &gt;
      Add Course
    &lt;/button&gt;
    &lt;Modal 
      shouldShow={showAddModal} 
      body={
        &lt;AddCourseForm 
          saveAction={add} 
          cancelAction={() =&gt; setShowAddModal(false)} /&gt;} /&gt;
    &lt;/&gt;
  )
}
</code></pre><p><em>AddCourse - 클라이언트 컴포넌트</em></p><p><code>CourseList</code> 컴포넌트는 어떠한 이벤트 핸들러도 필요하지 않으므로, 서버 컴포넌트로 유지할 수 있습니다.</p><pre><code class="language-jsx">import Image from 'next/image'
import Link from 'next/link'

export default function CourseList(courseList: any) {
  const allCourses = courseList.allCourses;
  return(
    &lt;div&gt;
      {
        allCourses.map((course: any) =&gt;
        &lt;Link key={course['_id']} href={`/courses/${course['_id']}`}&gt;
          &lt;div&gt;
            &lt;Image
              src={course.cover}
              width={200}
              height={200}
              alt={course.name}
            /&gt;
            &lt;h2&gt;{course.name}&lt;/h2&gt;
            &lt;p&gt;{course.rating}&lt;/p&gt;
          &lt;/div&gt; 
        &lt;/Link&gt; 
      )}
    &lt;/div&gt;  
  )

}
</code></pre><p><em>CourseList - 서버 컴포넌트</em></p><p>또한 브라우저의 개발자 도구에서 <code>Sources</code> 탭을 확인하여 클라이언트에 다운로드되는 내용과 서버에 남아 있는 내용을 식별할 수 있습니다. 여기서 <code>page.tsx</code> 파일이나 <code>CourseList.tsx</code> 파일을 볼 수 있나요? 아니요. 왜냐하면 그것들은 서버 컴포넌트이며 클라이언트 번들의 일부가 아니기 때문입니다.</p><p>우리는 앱에서 명시적으로 클라이언트 컴포넌트라고 표시한 컴포넌트만 볼 수 있습니다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-179.png" class="kg-image" alt="image-179" width="600" height="400" loading="lazy"></figure><p><em>클라이언트 번들 확인</em></p><p>이 애플리케이션의 흐름을 통해 이론이 실제와 어떻게 연결되는지 보여주었기를 바랍니다. 이제 React 앱에서 서버 컴포넌트를 사용하는 방법을 이해하셨을 것입니다.</p><h2 id="--5">요약</h2><p>요약하면 다음과 같습니다.</p><ul><li>React 서버 컴포넌트는 네트워크 왕복 없이 백엔드 접근이 가능합니다.</li><li>React 서버 컴포넌트를 통해 네트워크 워터폴을 피할 수 있습니다.</li><li>React 서버 컴포넌트는 자동 코드 분할(splitting)을 지원하며 번들 크기를 제로로 줄여 앱의 성능을 향상시킵니다.</li><li>이러한 컴포넌트는 서버 측에 있으므로 클라이언트 사이드 이벤트 핸들러, state 및 effect에 액세스할 수 없습니다. 이는 이벤트 핸들러나 useState, useReducer, useEffect와 같은 React 훅을 사용할 수 없음을 의미합니다.</li><li>React 서버 컴포넌트는 클라이언트 컴포넌트를 가져와 렌더링할 수 있지만, 반대로는 불가능 합니다. 그러나 서버 컴포넌트를 클라이언트 컴포넌트에 props로 전달할 수 있습니다.</li></ul><h2 id="--6">마치기 전에..</h2><p>이상으로 마무리합니다. 이 글이 유익하고 통찰력을 얻었기를 바랍니다.</p><p>저와 소통해보세요.</p><ul><li>제 YouTube 채널인 <code>tapaScript</code>에서 교육을 진행하고 있습니다. JavaScript, ReactJS, Node.js, Git 및 웹 개발의 기본을 배우고 싶다면 <a href="https://www.youtube.com/tapasadhikary?sub_confirmation=1">구독</a>해주세요.</li><li>웹 개발 및 프로그래밍 팁을 매일 놓치고 싶지 않다면 <a href="https://twitter.com/tapasadhikary">Twitter</a> 또는 <a href="https://www.linkedin.com/in/tapasadhikary/">LinkedIn</a> 팔로우를 해주세요.</li><li><a href="https://github.com/atapas">GitHub</a>에서 제 오픈 소스 작업을 확인해보세요.</li></ul><p>다음 글에서 뵙겠습니다. 그 동안 자신을 돌보며 행복하게 지내세요.</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ React와 React 훅으로 검색 필터 만드는 법 ]]>
                </title>
                <description>
                    <![CDATA[ API에 GET 요청을 보내면 서버로부터 응답 데이터를 받습니다. 하지만 가끔은 이 데이터를 관리하는 게 문제가 되기도 합니다. 이 글에서는 React에서 검색 필터를 생성하는 법을 알려 드리려고 합니다. 함수형 컴포넌트와 React 훅을 이용하여 데이터에 있는 특정 단어를 검색할 것입니다. API에 GET 요청을 보내는 법 우선 서버에서 데이터를 불러오는 API에 GET 요청을 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/build-a-search-filter-using-react-and-react-hooks/</link>
                <guid isPermaLink="false">65338f09722f6d03ea1726bb</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ SeunghyunKim ]]>
                </dc:creator>
                <pubDate>Sat, 21 Oct 2023 10:32:03 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2023/10/main-image.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/build-a-search-filter-using-react-and-react-hooks/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Build a Search Filter using React and React Hooks</a>
      </p><!--kg-card-begin: markdown--><p>API에 GET 요청을 보내면 서버로부터 응답 데이터를 받습니다. 하지만 가끔은 이 데이터를 관리하는 게 문제가 되기도 합니다.</p>
<p>이 글에서는 React에서 검색 필터를 생성하는 법을 알려 드리려고 합니다. 함수형 컴포넌트와 React 훅을 이용하여 데이터에 있는 특정 단어를 검색할 것입니다.</p>
<h2 id="apiget">API에 GET 요청을 보내는 법</h2>
<p>우선 서버에서 데이터를 불러오는 API에 GET 요청을 만들어 보겠습니다. 이 데이터를 불러오기 위해 아무 서버를 사용해도 되지만 이 글에서 필자는 사용자 목록을 불러오기 위해 {JSON} placeholder를 사용할 것입니다.</p>
<p>이 예시에서 사용자들의 이름과 이메일을 보여주는 카드들이 있습니다. 또한 특정 사용자들 검색하기 위해 사용할 검색 입력 창이 있습니다.</p>
<pre><code class="language-js,caption=JSON">import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Card, Input } from 'semantic-ui-react'
export default function Posts() {
    const [APIData, setAPIData] = useState([])
    useEffect(() =&gt; {
        axios.get(`https://jsonplaceholder.typicode.com/users`)
            .then((response) =&gt; {
                setAPIData(response.data);
            })
    }, [])
    return (
        &lt;div style={{ padding: 20 }}&gt;
            &lt;Input icon='search'
                placeholder='Search...'
            /&gt;
            &lt;Card.Group itemsPerRow={3} style={{ marginTop: 20 }}&gt;
                {APIData.map((item) =&gt; {
                    return (
                        &lt;Card&gt;
                            &lt;Card.Content&gt;
                                &lt;Card.Header&gt;{item.name}&lt;/Card.Header&gt;
                                &lt;Card.Description&gt;
                                    {item.email}
                                &lt;/Card.Description&gt;
                            &lt;/Card.Content&gt;
                        &lt;/Card&gt;
                    )
                })}
            &lt;/Card.Group&gt;
        &lt;/div&gt;
    )
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/Screenshot-2021-08-14-202008.png" alt="화면 상위의 검색 입력과 사용자 목록 얻기" width="600" height="400" loading="lazy"></p>
<p>그리고 만약 React에서 GET API 호출을 다루는 법을 모르신다면 API 호출에 대해 설명하는 <a href="https://www.freecodecamp.org/news/how-to-perform-crud-operations-using-react/">React CRUD 작업</a>에 관한 제 블로그 혹은 비디오를 보시길 바랍니다.</p>
<h2 id="">검색 입력 박스로부터 검색 입력을 얻는 법</h2>
<p>이제 검색 입력 박스로 부터 검색 쿼리(query)를 가져와 보겠습니다.</p>
<p>검색 입력을 위한 state를 생성합니다.</p>
<pre><code class="language-js,caption=검색">const [searchInput, setSearchInput] = useState('');
</code></pre>
<p>여기 <code>searchInput</code>은 문자열이고 검색 입력 값 설정을 위해 <code>setSearchInput</code>을 사용할 것입니다.</p>
<p>이제 검색 기능을 처리할 함수를 만들어 보겠습니다.</p>
<pre><code class="language-js,caption=검색">const searchItems = () =&gt; {
        
}
</code></pre>
<p>그리고 이 함수를 <code>onChange</code> 이벤트로 검색 입력에 바인드(bind)합니다.</p>
<pre><code class="language-js,">&lt;Input icon='search'
    placeholder='Search...'
    onChange={() =&gt; searchItems()}
/&gt;
</code></pre>
<p>이리하여 입력 필드에 어떤 값이든 입력할 때마다 <code>searchItems</code>가 실행이 될 것입니다.</p>
<p>이제 <code>searchItems</code>에 입력 값을 전달해야 합니다.</p>
<pre><code class="language-js,caption=searchItems">&lt;Input icon='search'
    placeholder='Search...'
    onChange={(e) =&gt; searchItems(e.target.value)}
/&gt;
</code></pre>
<p>다음 검색 쿼리를 <code>searchItems</code> 함수에서 전달 받고 이전에 생성한 <code>setSearchInput</code>을 사용하여 <code>searchInput</code> state를 이 전달 받은 값으로 설정하겠습니다.</p>
<pre><code class="language-js,caption=searchInput">const searchItems = (searchValue) =&gt; {
    setSearchInput(searchValue)
}
</code></pre>
<p>콘솔로 <code>searchValue</code> 값을 확인함으로써 위 로직이 잘 실행되는지 확인할 수 있습니다.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/size/w1000/2021/08/Screenshot-2021-08-14-203750.png" alt="Screenshot-2021-08-14-203750" width="600" height="400" loading="lazy"></p>
<p>보시다시피 필자의 이름을 입력했고 콘솔에서 이름이 나타납니다.</p>
<h2 id="">검색 결과에 따른 아이템 필터링</h2>
<p>이제 filter 메소드를 사용하여 APIData를 필터링(filtering)할 것입니다.</p>
<pre><code class="language-js,caption=검색">const searchItems = (searchValue) =&gt; {
    setSearchInput(searchValue)
    APIData.filter((item) =&gt; {
        return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
    })
}
</code></pre>
<p><code>searchItems</code> 함수에서 서버로부터 얻는 데이터를 포함한 APIData state를 필터링하는 <code>filter</code> 메소드를 사용하고 있다는 것을 알 수 있습니다.</p>
<p>또한 객체 아이템으로부터 값들을 얻기 위해 <code>Object.values</code>를 사용하고 있습니다.</p>
<p>그런 다음 <code>join(' ')</code> 메소드를 사용하여 이 값들을 문자열로 바꾸어 줍니다.</p>
<p>다음 <code>toLowerCase</code> 메소드를 사용하여 이 문자열들을 소문자로 바꾸어 줍니다.</p>
<p>마지막으로 이 문자열들이 검색란에 입력한 값을 포함하고 있는지 확인합니다. 검색 입력 또한 소문자로 바꾸어 줍니다. 이는 단어를 대문자로 입력했을 시 검색을 더 효율적으로 만들어 주기 위해 그 문자열을 소문자로 바꾸게 하는 것입니다.</p>
<p>그런 다음 전체 쿼리를 반환합니다.</p>
<p>이제 이 필터링한 배열을 변수에 저장해야 합니다.</p>
<pre><code class="language-js,caption=필터링한">const filteredData = APIData.filter((item) =&gt; {
    return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
})
</code></pre>
<p>콘솔을 통해 위에서 만든 기능을 확인해 보겠습니다. 사용자 이름을 검색해보면 해당 사용자 이름에 맞는 데이터를 보게 될 것입니다.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/size/w1000/2021/08/Screenshot-2021-08-14-211709.png" alt="검색 아이템 콘솔에서 확인" width="600" height="400" loading="lazy"></p>
<p>이제 필터 데이터를 저장해야 하므로 state를 생성하겠습니다.</p>
<pre><code class="language-js,caption=필터">const [filteredResults, setFilteredResults] = useState([]);
</code></pre>
<p>필터링한 데이터를 담은 state를 생성합니다.</p>
<p>그런 다음 <code>setFilteredResults</code>를 사용하여 <code>searchItems</code> 함수에서 이 state를 <code>filteredData</code>로 설정합니다.</p>
<pre><code class="language-js,caption=filtedData를">const searchItems = (searchValue) =&gt; {
    setSearchInput(searchValue)
    const filteredData = APIData.filter((item) =&gt; {
        return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
    })
    setFilteredResults(filteredData)
}
</code></pre>
<h2 id="ui">필터링 결과를 UI에 보여주는 법</h2>
<p>이제 이 필터링 결과들을 주 UI에 보여주도록 하겠습니다.</p>
<p>우선 검색 입력란이 비어 있는지 확인하고 비어 있다면 모든 데이터를 보여주는 코드를 작성해야 합니다. 비어 있지 않다면 입력 값에 따라 결과를 필터링할 것입니다.</p>
<pre><code class="language-js,caption=검색">const searchItems = (searchValue) =&gt; {
    setSearchInput(searchValue)
    if (searchInput !== '') {
        const filteredData = APIData.filter((item) =&gt; {
            return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
        })
        setFilteredResults(filteredData)
    }
    else{
        setFilteredResults(APIData)
    }
}
</code></pre>
<p>또한 애플리케이션의 반환 부분에서도 똑같은 확인이 필요합니다.</p>
<p>그래서 검색 단어의 길이가 1보다 크면 필터가 된 데이터를 보여줄 것입니다. 1보다 작다면 APIData state에 담긴 모든 데이터를 보여줄 것입니다.</p>
<pre><code class="language-js,caption=길이가">&lt;Card.Group itemsPerRow={3} style={{ marginTop: 20 }}&gt;
    {searchInput.length &gt; 1 ? (
        filteredResults.map((item) =&gt; {
            return (
                &lt;Card&gt;
                    &lt;Card.Content&gt;
                        &lt;Card.Header&gt;{item.name}&lt;/Card.Header&gt;
                        &lt;Card.Description&gt;
                            {item.email}
                        &lt;/Card.Description&gt;
                    &lt;/Card.Content&gt;
                &lt;/Card&gt;
            )
        })
    ) : (
        APIData.map((item) =&gt; {
            return (
                &lt;Card&gt;
                    &lt;Card.Content&gt;
                        &lt;Card.Header&gt;{item.name}&lt;/Card.Header&gt;
                        &lt;Card.Description&gt;
                            {item.email}
                        &lt;/Card.Description&gt;
                    &lt;/Card.Content&gt;
                &lt;/Card&gt;
            )
        })
    )}
&lt;/Card.Group&gt;
</code></pre>
<p>이제 검색 필드에서 사용자 이름을 검색한다면 해당 사용자의 데이터를 얻게 됩니다.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/Screenshot-2021-08-14-212917.png" alt="UI에 검색 입력에 대한 결과" width="600" height="400" loading="lazy"></p>
<p>검색 창에서 이름을 지우면 모든 데이터를 얻게 됩니다.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/08/Screenshot-2021-08-14-213037.png" alt="검색 입력란이 비어 있을 경우 모든 데이터 출력" width="600" height="400" loading="lazy"></p>
<p>여기 참고할 분들을 위해 전체 코드를 적어 두겠습니다.</p>
<pre><code class="language-js,caption=검색">import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Card, Input } from 'semantic-ui-react'
export default function Posts() {
    const [APIData, setAPIData] = useState([])
    const [filteredResults, setFilteredResults] = useState([]);
    const [searchInput, setSearchInput] = useState('');
    useEffect(() =&gt; {
        axios.get(`https://jsonplaceholder.typicode.com/users`)
            .then((response) =&gt; {
                setAPIData(response.data);
            })
    }, [])
    const searchItems = (searchValue) =&gt; {
        setSearchInput(searchValue)
        if (searchInput !== '') {
            const filteredData = APIData.filter((item) =&gt; {
                return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
            })
            setFilteredResults(filteredData)
        }
        else{
            setFilteredResults(APIData)
        }
    }
    return (
        &lt;div style={{ padding: 20 }}&gt;
            &lt;Input icon='search'
                placeholder='Search...'
                onChange={(e) =&gt; searchItems(e.target.value)}
            /&gt;
            &lt;Card.Group itemsPerRow={3} style={{ marginTop: 20 }}&gt;
                {searchInput.length &gt; 1 ? (
                    filteredResults.map((item) =&gt; {
                        return (
                            &lt;Card&gt;
                                &lt;Card.Content&gt;
                                    &lt;Card.Header&gt;{item.name}&lt;/Card.Header&gt;
                                    &lt;Card.Description&gt;
                                        {item.email}
                                    &lt;/Card.Description&gt;
                                &lt;/Card.Content&gt;
                            &lt;/Card&gt;
                        )
                    })
                ) : (
                    APIData.map((item) =&gt; {
                        return (
                            &lt;Card&gt;
                                &lt;Card.Content&gt;
                                    &lt;Card.Header&gt;{item.name}&lt;/Card.Header&gt;
                                    &lt;Card.Description&gt;
                                        {item.email}
                                    &lt;/Card.Description&gt;
                                &lt;/Card.Content&gt;
                            &lt;/Card&gt;
                        )
                    })
                )}
            &lt;/Card.Group&gt;
        &lt;/div&gt;
    )
}
</code></pre>
<p>이제 React 훅을 이용하여 React에서 완전 함수형의 검색 필터를 가지게 되었습니다.</p>
<p>보통 이 기능은 API 엔드포인트에서 검색 쿼리 인자를 전달하여 백엔드에서 처리하지만 프론트엔드에서 그 기능을 처리하는 것을 아는 것도 중요합니다.</p>
<p>이 글에 대한 보충 설명이 필요하다면 <a href="https://www.youtube.com/watch?v=8YsQmvJ9UZE">React와 React 훅을 이용하여 검색 필터 만들기</a>에 관한 제 유투브 비디오를 확인하시면 되겠습니다.</p>
<blockquote>
<p>이상 마무리 짓겠습니다. 감사합니다.</p>
</blockquote>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 2022년에 알아야 할 10가지 리액트 인터뷰 질문 ]]>
                </title>
                <description>
                    <![CDATA[  리액트 지식에 자신이 있으신가요? 한번 시험해 보세요! 면접을 보는 상황이든 아니든 2022년에 리액트 개발자로서 알아야 할 10가지 주요 질문들을 준비했습니다. 이 질문들은 리액트의 핵심 개념부터 특정 기능을 언제 사용해야 하는지에 대한 실질적인 이해까지 모두 포함합니다. 이 가이드를 통해 최상의 결과를 얻으시려면 정답을 보기 전에 꼭 먼저 모든 질문에 스스로 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/2022nyeone-alaya-hal-10gaji-riaegteu-inteobyu-jilmun/</link>
                <guid isPermaLink="false">653061bedbcb8a03ea44a170</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Janghan Park ]]>
                </dc:creator>
                <pubDate>Thu, 19 Oct 2023 12:14:43 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2023/10/react-interview-questions-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/react-interview-questions-to-know/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">10 React Interview Questions You Should Know in 2022</a>
      </p><h4></h4><h3 id="-">리액트 지식에 자신이 있으신가요? 한번 시험해 보세요!</h3><p>면접을 보는 상황이든 아니든 2022년에 리액트 개발자로서 알아야 할 10가지 주요 질문들을 준비했습니다.</p><p>이 질문들은 리액트의 핵심 개념부터 특정 기능을 언제 사용해야 하는지에 대한 실질적인 이해까지 모두 포함합니다.</p><p>이 가이드를 통해 최상의 결과를 얻으시려면 정답을 보기 전에 꼭 먼저 모든 질문에 스스로 답해보세요.</p><p>그럼 시작해 봅시다!</p><p><br></p><h2 id="1-">1. 리액트는 무엇이고 언제 사용합니까?</h2><p>리액트는 라이브러리이지 프레임워크가 아닙니다.</p><p>리액트는 자바스크립트의 모든 기능을 제공합니다. 또한 리액트의 기본 특징들은 우리가 애플리케이션을 만들거나 그것에 대해 생각하는 방식을 향상시켜줍니다.</p><ul><li>JSX 와 같은 도구로 유저 인터페이스를 <strong>쉽게 만드는 방법</strong>을 제공합니다.</li><li>정적인 HTML이 할 수 없는 <strong>유저 인터페이스 공유</strong>를 쉽게 만들어 주는 컴포넌트를 제공합니다.</li><li>리액트 훅을 사용하여 컴포넌트들 사이에 <strong>재사용 가능한 동작</strong>을 만들 수 있습니다.</li><li>리액트는 직접 DOM을 건드리지 않고 데이터가 바뀔 때 UI를 업데이트합니다.</li></ul><p><strong>보너스 팁</strong>: 리액트에는 Next.js나 Gaysby처럼 애플리케이션을 만들 때 필요한 모든 기능을 제공하는 프레임워크들이 있습니다.</p><p>리액트는 특히 싱글 페이지 애플리케이션(SPA)을 위해 만들어졌습니다. 하지만 정적인 사이트부터 모바일 애플리케이션까지 모든 것을 똑같은 리액트 방식으로 만들 수 있습니다.</p><p><br></p><h2 id="2-jsx-">2. JSX는 무엇입니까?</h2><p>JSX는 HTML의 간단한 문법을 사용하지만 기능과 자바스크립트의 동적 요소 가 더해진 유저 인터페이스를 만드는 방법 중 하나입니다.</p><p>짧게 말해, 리액트 애플리케이션을 조직하는 <strong>HTML + Javascript</strong>입니다.</p><p>JSX는 HTML처럼 보이지만 사실은 자바스크립트 함수 호출입니다.</p><p>JSX 안에서 <code>div</code>를 사용하는 것은<code>React.createElement()</code>를 호출하는 것과 동일합니다.</p><p>직접 <code>React.createElement()</code>를 호출해 유저 인터페이스를 만들 수 있지만 요소들을 계속 더할수록 만들어놓은 구조를 읽는 게 계속해서 어려워집니다.</p><p><strong>웹 브라우저는 JSX를 이해할 수 없습니다.</strong> 그래서 보통 <strong>Babel</strong>이라고 불리는 컴파일러를 사용합니다. <strong>Babel</strong>은 HTML처럼 보이는 것을 자바스크립트 함수 호출로 변환하여 브라우저가 이해할 수 있게 만들어 줍니다.</p><p><br></p><h2 id="3-">3. 리액트 컴포넌트에 어떻게 데이터를 전달합니까?</h2><p>리액트 컴포넌트에 데이터를 전달하는 방법은 크게 두 가지가 있습니다.</p><ol><li>Props</li><li>Context API</li></ol><p>Props는 가까이에 있는 컴포넌트의 부모로부터 전달되는 데이터입니다. Props는 자식 컴포넌트에 선언되며 아무 이름과 유효한 값을 받을 수 있습니다.</p><pre><code class="language-jsx">function Blog() {
    const post = { title: "My Blog Post!" };

    return &lt;BlogPost post={post} /&gt;;
}
</code></pre><p>Props는 자식 컴포넌트에서 사용되고 자식 <strong>객체의 프로퍼티</strong>로 사용 가능합니다.</p><pre><code class="language-jsx">function BlogPost(props) {
    return &lt;h1&gt;{props.post.title}&lt;/h1&gt;
}
</code></pre><p>Props는 순전한 객체의 프로퍼티이기 때문에 더 쉬운 접근을 위해 디스트럭팅 (destructing) 될 수 있습니다.</p><pre><code class="language-jsx">function BlogPost({ post }) {
    return &lt;h1&gt;{post.title}&lt;/h1&gt;
}
</code></pre><p>Context는 context provider에서 전달되는 데이터이며 context를 사용하는 컴포넌트에서 사용됩니다.</p><p>Context를 사용하면 Props 없이 애플리케이션 내 어디서나 데이터에 접근할 수 있습니다. (context provider 가 전체 컨포넌트 트리를 감쌀 경우)</p><p>Context 데이터는 <code>Context.Provider</code>를 이용해 <code>value</code> prop 을 통해 전달되며 이 데이터들은 <code>Context.Consumer</code> 또는 <code>useContext</code> 훅 (hook) 을 이용해 사용됩니다.</p><pre><code class="language-jsx">import { createContext, useContext } from 'react';

const PostContext = createContext()

function App() {
    const post = { title: "My Blog Post!" };

    return (
        &lt;PostContext.Provider value={post}&gt;
            &lt;Blog /&gt;
        &lt;/PostContext.Provider&gt;
    );
}

function Blog() {
    return &lt;BlogPost /&gt;
}

function BlogPost() {
    const post = useContext(PostContext)

    return &lt;h1&gt;{post.title}&lt;/h1&gt;
}
</code></pre><p><br></p><h2 id="4-porps-state-">4. porps 와 state의 차이는 무엇입니까?</h2><p>State는 리액트 컴포넌트 안에서 읽고 업데이트 가능한 값입니다.</p><p>Props는 리액트 컴포넌트에 전달되는 읽기만 가능한 값입니다.<br><br><br>(Props는 업데이트 되면 안 됩니다.)</p><p>props는 컴포넌트 밖에 존재하는 함수의 인자로 생각할 수 있습니다. 반면에 state는 계속해서 바뀌는 값이며 컴포넌트 안에 존재하고 선언됩니다.</p><p>State 와 porps는 변경사항으로 인해 존재하는 컴포넌트가 다시 렌더링 된다는 점에서 유사합니다.</p><p><br></p><h2 id="5-fragment-">5. 리액트 fragment는 왜 사용합니까?</h2><p>fragment는 리액트에 있는 특별한 특징입니다. fragment를 사용하면 DOM에서 노드 (node) 없이 자식 요소나 자식 컴포넌트 그룹을 만들 수 있습니다.</p><p>fragment 문법은 빈 태그 <code>&lt;&gt;&lt;/&gt;</code>처럼 생겼거나 <code>React.Fragment</code>로 사용됩니다.</p><p>간단히 말하면, 때로는 여러 리액트 요소들을 한 부모 요소에 넣어야 하지만 <code>div</code>와 같은 일반적인 HTML 요소를 사용하고 싶지 않을 때입니다.</p><p>테이블을 만든다고 가정해 보면 아래는 올바르지 않은 HTML입니다.</p><pre><code class="language-jsx">function Table() {
   return (
     &lt;table&gt;
       &lt;tr&gt;
         &lt;Columns /&gt;
       &lt;/tr&gt;
     &lt;/table&gt;
   );
}

function Columns() {
   return (
     &lt;div&gt;
       &lt;td&gt;Column 1&lt;/td&gt;
       &lt;td&gt;Column 2&lt;/td&gt;
     &lt;/div&gt;
   );
}
</code></pre><p><code>Columns</code> 컴포넌트 안에서 <code>div</code> 요소 대신 fragment를 사용하면 이 문제를 해결할 수 있습니다.</p><pre><code class="language-jsx">function Columns() {
   return (
     &lt;&gt;
       &lt;td&gt;Column 1&lt;/td&gt;
       &lt;td&gt;Column 2&lt;/td&gt;
     &lt;/&gt;
   );
}
</code></pre><p>fragment를 사용하는 또 다른 이유는 HTML 요소를 추가할 때 CSS 스타일이 적용되는 방법이 바뀔 수 있기 때문입니다.</p><p><br></p><h2 id="6-list-key-">6. 리액트 리스트 (list)에 왜 key를 사용해야 합니까?</h2><p>Key는 고유한 값입니다. <code>.map()</code> 함수를 사용하여 요소나 컴포넌트를 루프 (loop) 할 때 반드시 <code>key</code> prop 을 전달해야 합니다.</p><p>요소를 매핑하는 경우 아래와 같을 겁니다.</p><pre><code class="language-jsx">posts.map(post =&gt; &lt;li key={post.id}&gt;{post.title}&lt;/li&gt;)
</code></pre><p>컴포넌트를 맵핑하는 경우</p><pre><code class="language-jsx">posts.map(post =&gt; &lt;li key={post.id}&gt;{post.title}&lt;/li&gt;)
</code></pre><p>둘 다의 경우 고유한 값인 key를 추가해야 합니다. 아니면 리액트가 경고 메시지를 보냅니다.</p><p>왜 그럴까요? key는 요소나 컴포넌트가 어느 리스트 (list)에 있는지 정의하기 때문입니다.</p><p>key를 사용하지 않을 경우 리스트에 있는 아이템을 어떤 방법으로 추가하거나 수정해서 바꾸려고 할 때 리액트가 바뀌어야 하는 순서를 알 수 없습니다.</p><p>리액트는 모든 DOM 비즈니스 업데이트를 책임지지만 (가상 DOM 사용) 이를 <strong>제대로 업데이트하려면 key 가 필요합니다</strong>.</p><p><br></p><h2 id="7-ref-">7. ref는 무엇이고 어떻게 사용해야 합니까?</h2><p>리액트에서 ref는 DOM 요소의 참조입니다.</p><p>Ref는 <code>useRef</code> 훅 (hook) 을 이용해 생성되며 바로 변수에 담을 수 있습니다.</p><p>그리고 이 변수는 리액트 요소 (컴포넌트 아님)에 근본적인 HTML 요소 (div, span 등등)의 참조를 얻기 위해 전달됩니다.</p><p>참조된 요소와 프로퍼티는 ref의 <strong>.current 프로퍼티</strong>를 통해 사용 가능합니다.</p><pre><code class="language-jsx">import { useRef } from 'react'

function MyComponent() {
   const ref = useRef();

   useEffect(() =&gt; {
     console.log(ref.current) // div 의 참조
   }, [])

   return &lt;div ref={ref} /&gt;
}
</code></pre><p>Ref는 가끔 "escape hatch (피난용 비상구)"라고 불리고 DOM 요소와 직접적으로 사용됩니다 그리고 리액트를 통해 할 수 없는 특정 연산 (input 태그를 포커스 하거나 클리어 등) 을 가능하게 해줍니다.</p><p><br></p><h2 id="8-useeffect-">8. useEffect는 왜 사용합니까?</h2><p>리액트 컴포넌트에서 <code>useEffect</code> 훅 (hook) 은 사이드 이펙트 (side effect)를 위해 사용됩니다.</p><p><strong>사이든 이펙트 (side effect)</strong> 는 "바깥세상 (outside world)" 나 리액트 context 밖에 존재하는 것을 담당하는 연산입니다.</p><p>사이트 이펙트 (side effect)의 예로는 외부 API 포인트로 GET이나 POST 요청을 보내거나 브라우저 API <code>window.navigator</code> 또는 <code>document.getElementById()</code>를 사용하는 것입니다.</p><p>리액트 컴포넌트 바디안에서 바로 저 연산들을 할 수 없습니다. <code>useEffect</code>는 사이드 이펙트 (side effect)를 수행할 수 있는 함수와 dependency 배열을 제공합니다. dependency 배열에는 함수가 의존하는 값들이 들어갑니다.</p><p>만약 dependency 배열의 값이 바뀌면 이펙트 (effect) 함수는 다시 호출됩니다.</p><p><br></p><h2 id="9-context-redux-">9. 리액트 Context 나 Redux는 언제 사용합니까?</h2><blockquote>Redux는 아마 리액트에서 가장 널리 사용되는 서드파티 (third party) global state 라이브러리 일 것입니다. 그러나 이 질문의 경우 "Redux"라는 단어를 다른 global state 라이브러리로 바꿀 수 있습니다.</blockquote><p>리액트 Context는 <strong>Prop 을 활용</strong>하지 않고 애플리케이션 내에서 데이터를 사용할 수 있습니다.</p><p>리액트 Context는 데이터가 필요하지 않는 컴포넌트를 통해 데이터를 전달할 때 생기는 **"프롭 드릴링 (props drilling)"**이라는 문제를 예방합니다.</p><p>대신 Context를 이용하면 데이터를 필요로 하는 컴포넌트에 정확히 사용할 수 있습니다.</p><p>Context를 사용하면 데이터를 데이터를 가져오거나 읽기만 할 뿐이지만 Redux 나 다른 서드파티 (third party) state library는 <strong>읽기와 업데이트가 가능합니다</strong>.</p><p><strong>State 업데이트 용으로 만들어지지 않았기 때문에</strong> Context는 Redux 와 같은 서드파티 라이브러리 대체재가 아닙니다 그 이유는 Context를 통해 사용되는 데이터가 업데이트될 때마다 모든 자식 컴포넌트는 다시 렌더링 되고 퍼포먼스에 큰 영향을 끼치기 때문입니다.</p><p><br></p><h2 id="10-usecallback-usememo-">10. useCallback 과 useMemo는 왜 사용합니까?</h2><p><code>useCallback</code> 과 <code>useMemo</code>는 컴포넌트 퍼포먼스를 향상시키기 위해 사용됩니다.</p><p><code>useCallback</code> 은 매 렌더링마다 컴포넌트 바디안에서 선언된 함수가 재생성되는 것을 예방합니다.</p><p>이것은 불필요한 퍼포먼스 문제를 야기할 수 있습니다. 특히 자식 컴포넌트에 전달되는 콜백 (callback) 함수들입니다.</p><p>반면에 <code>useMemo</code>는 무거운 연산 작업을 기업합니다.</p><p>메모이제이션 (memoization)은 인자가 변경되지 않는 경우 계산한 과거 값을 기억할 수 있는 함수의 기술적 용어입니다. 만약 인자가 그대로라면 함수는 기억된 값을 반환합니다.</p><p>다른 말로 하면 많은 계산 자원 (computing resources)를 필요로 하는 계산이 있을 경우 최대한 적게 계산을 하게 됩니다.</p><p>이런 경우 함수를 반환하는 <code>useCallback</code> 대신 &nbsp;값을 반환하는 <code>useMemo</code>를 사용할 수 있습니다.</p><p><br></p><h2 id="--1">전문적인 리액트 개발자 되기</h2><p>리액트는 어렵습니다. 혼자 모든 것을 알아내려고 하지 않아도 됩니다.</p><p>기록적인 시간에 목표 달성을 돕기 위해 리액트 강의에 리액트에 관해 아는 모든 것을 담았습니다.</p><h3 id="introducing-the-react-bootcamp"><a href="https://www.reactbootcamp.com/">Introducing: The React Bootcamp</a></h3><p>제가 리액트를 배우기 시작했을 때 알았으면 하는 강의입니다.</p><p>아래를 클릭하여 리액트 부트 캠프를 경험해 보세요.</p><figure class="kg-card kg-image-card"><img src="https://reedbarger.nyc3.digitaloceanspaces.com/reactbootcamp/react-bootcamp-cta-alt.png" class="kg-image" alt="react-bootcamp-cta-alt" width="600" height="400" loading="lazy"></figure> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 리액트에서 Props 사용하는 방법 ]]>
                </title>
                <description>
                    <![CDATA[ 이 튜토리얼에서는 리액트에서 중요한 개념인 props에 관해 얘기해보겠습니다. 앱에서 데이터 흐름을 동적으로 유지하는데 어떻게 props가 사용되는지 보여드릴게요. 전제 조건 이 튜토리얼을 따라 하려면 아래 내용을 알아야 합니다.  * 리액트 앱 추가로 이미 이것들을 이해하고 있다고 가정하겠습니다.  * 리액트에서 컴포넌트란 무엇이고 어떻게 사용하는가.  * 리액트에서 ES6 특징들 사용 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/how-to-use-props-in-react/</link>
                <guid isPermaLink="false">64d64c13e7425543c77094cb</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jeeann K. ]]>
                </dc:creator>
                <pubDate>Tue, 29 Aug 2023 09:32:22 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2023/08/3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/how-to-use-props-in-react/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Use Props in React</a>
      </p><!--kg-card-begin: markdown--><h3 id="propsprops">이 튜토리얼에서는 리액트에서 중요한 개념인 props에 관해 얘기해보겠습니다. 앱에서 데이터 흐름을 동적으로 유지하는데 어떻게 props가 사용되는지 보여드릴게요.</h3>
<h2 id="">전제 조건</h2>
<p>이 튜토리얼을 따라 하려면 아래 내용을 알아야 합니다.</p>
<ul>
<li>리액트 앱</li>
</ul>
<p>추가로 이미 이것들을 이해하고 있다고 가정하겠습니다.</p>
<ul>
<li>리액트에서 컴포넌트란 무엇이고 어떻게 사용하는가.</li>
<li>리액트에서 ES6 특징들 사용 방법 (잘 모르시겠나요? 이 <a href="https://www.freecodecamp.org/news/how-to-use-es6-javascript-features-in-react/">기사</a> 를 읽어주세요.</li>
<li>리액트에서 기본적인 state 관리 방법 (어떻게 되는지 모르시겠나요? 이 <a href="https://www.freecodecamp.org/news/introduction-to-react-hooks/">기사</a>를 읽어주세요).</li>
</ul>
<h2 id="props">리액트에서 props는 무엇일까요?</h2>
<p>리액트에서는 하나의 컴포넌트에서 다른 컴포넌트로 데이터를 전달할 때 props를 사용합니다(부모 컴포넌트에서 자식 컴포넌트(들)로). Props는 properties를 줄인 말입니다. 앱에서 데이터 흐름을 동적으로 만들고 싶을 때 유용합니다.</p>
<p>저의 <code>App.js</code> 컴포넌트입니다.</p>
<pre><code class="language-javascript">function App() {
  return &lt;div className="App"&gt;&lt;/div&gt;;
}

export default App;
</code></pre>
<p>이제 <code>Tool.js</code>라는 이름의 또 다른 컴포넌트를 만들어봅시다. 이 파일은 프로덕트 디자이너가 가장 좋아하는 도구에 대한 정보를 보여줍니다. Props를 사용하지 않을 경우 코드는 이런 식으로 생겼습니다.</p>
<pre><code class="language-javascript">function Tool() {
  return (
    &lt;div&gt;
      &lt;h1&gt;My name is Ihechikara.&lt;/h1&gt;
      &lt;p&gt;My favorite design tool is Figma.&lt;/p&gt;
    &lt;/div&gt;
  );
}
export default Tool;
</code></pre>
<p><code>App</code> 컴포넌트에 이 컴포넌트를 가져오겠습니다. 바로 이렇게요.</p>
<pre><code class="language-javascript">import Tool from './Tool';

function App() {
  return (
    &lt;div className="App"&gt;
      &lt;Tool /&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<p>Tool 컴포넌트가 여러 컴포넌트에 걸쳐 재사용이 가능해 여러 디자이너와 그들이 선호하는 도구를 설명한다고 가정해봅시다.</p>
<p>리액트를 사용하면 코드를 다시 쓰지 않아도 컴포넌트의 로직을 쉽게 가져올 수 있지만, 이 특정 컴포넌트에는 이미 하드 코드 된 데이터가 있습니다. 즉, 다른 컴포넌트를 위해 매번 해당 로직을 다시 쓰거나, 여러분이 추측했듯이 다른 컴포넌트의 데이터를 바꾸기 위해 props를 사용해야 합니다.</p>
<p>어떻게 작동하는지 아직 이해가 안된다면, props는 우리가 컴포넌트의 로직을 동적으로 재사용할 수 있도록 해준다고 생각해 보세요. 이 말은 컴포넌트 안의 데이터는 정적이지 않을 거라는 겁니다. 그래서 그 로직을 사용하는 다른 컴포넌트에 대해 해당 컴포넌트의 데이터를 요구사항에 맞게 수정할 수 있습니다.</p>
<h2 id="props">리액트에서 props 사용하는 방법</h2>
<p>이 섹션에서는, props를 사용하는 두 가지 방법을 배울 것입니다. 디스트럭쳐링(distructuring) 없이 하는 방법과 디스트럭쳐링(distructuring)으로 하는 방법입니다.</p>
<h3 id="distructuringprops">디스트럭쳐링(distructuring) 없이 props 사용하는 방법</h3>
<p>props를 사용하려면 함수에 props를 인자로 전달해야 합니다. 일반 자바스크립트의 함수에 인자를 전달하는 것과 비슷합니다. 여기 예시가 있습니다.</p>
<pre><code class="language-javascript">function Tool(props) {
  const name = props.name;
  const tool = props.tool;
  return (
    &lt;div&gt;
      &lt;h1&gt;My name is {name}.&lt;/h1&gt;
      &lt;p&gt;My favorite design tool is {tool}.&lt;/p&gt;
    &lt;/div&gt;
  );
}

export default Tool;
</code></pre>
<p>위에서 일어난 일을 하나하나 설명해드릴게요.</p>
<h2 id="1props">1단계 - Props를 인자로 전달</h2>
<p>위 코드의 첫 번째 줄에서 전달했습니다: <code>function Tool(props){}</code>. 이렇게 하면 자동으로 리액트 앱의 구성 요소에서 props를 사용할 수 있습니다.</p>
<h2 id="2props">2단계 - Props를 변수로 선언</h2>
<pre><code class="language-javascript">const name = props.name;
const tool = props.tool;
</code></pre>
<p>위에서 볼 수 있듯이, 이러한 변수는 그 안의 데이터를 props와 쓰기 때문에 일반적인 변수와는 다릅니다.</p>
<p>Props에 사용할 변수를 만들고 싶지 않으면, 다음과 같이 템플릿에 직접 전달할 수 있습니다: <code>&lt;h1&gt; My name is {props.name} &lt;/h1&gt;</code></p>
<h2 id="3jsx">3단계 - JSX 템플릿에 변수 사용</h2>
<p>변수를 선언했으므로 코드에서 원하는 위치에 변수를 사용할 수 있습니다.</p>
<pre><code class="language-javascript">return (
  &lt;div&gt;
    &lt;h1&gt;My name is {name}.&lt;/h1&gt;
    &lt;p&gt;My favorite design tool is {tool}.&lt;/p&gt;
  &lt;/div&gt;
);
</code></pre>
<h2 id="4appprops">4단계 - <code>App</code> 컴포넌트의 props에 데이터 전달</h2>
<p>Props 만들기가 끝났기 때문에 다음 단계는 그들에게 데이터를 전달하는 것입니다. 우리는 이미 <code>Tool</code> 컴포넌트를 가져왔으며 현재 브라우저에 표시되고 있습니다.</p>
<pre><code>My name is .
My favorite design tool is .
</code></pre>
<p>선언 시 props에 기본 데이터를 생성하여 변수가 비어있지 않게 표시 할 수 있습니다. 마지막 섹션에서 이 작업을 수행하는 방법을 확인할 수 있습니다.</p>
<p>이것이 <code>App</code> 컴포넌트의 현재 상태임을 기억하세요.</p>
<pre><code class="language-javascript">import Tool from './Tool';

function App() {
  return (
    &lt;div className="App"&gt;
      &lt;Tool /&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<p>데이터가 정확히 어디로 전달되는지 궁금하시겠죠. 이렇게 하려면 속성처럼 데이터를 전달합니다. 이렇게 생겼죠.</p>
<pre><code class="language-javascript">import Tool from './Tool';

function App() {
  return (
    &lt;div className="App"&gt;
      &lt;Tool name="Ihechikara" tool="Figma" /&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<p>변화를 눈치채셨나요? 여기 <code>&lt;Tool/&gt;</code>에서 <code>&lt;Tool name="Ihechikara" tool="Figma"/&gt;</code>까지. 이러한 속성은 <code>Tool</code> 컴포넌트에 생성된 props에 붙기 때문에 오류가 발생하지 않습니다.</p>
<p>브라우저에 이렇게 표시되어야 합니다.</p>
<pre><code>My name is Ihechikara.
My favorite design tool is Figma.
</code></pre>
<p>변수명은 props 자체가 아닙니다. 이런 식으로 <code>const myPropName = props.name</code> 식으로 변수를 만들고 템플릿에 변수를 <code>&lt;h1&gt;My name is {myPropName}.&lt;/h1&gt;</code>라고 썼다면, <code>&lt;Tool name="Ihechikara" tool="Figma"/&gt;</code> 할 경우 코드는 완벽히 작동할 겁니다. <code>name</code> 속성은 prop을 포함한 변수명에서 오는 게 아닌 <code>props.name</code>으로 부터 오는 겁니다.</p>
<p>이제 <code>Tool</code> 컴포넌트에 정의된 로직을 사용하여 모든 컴포넌트에 대한 데이트를 동적으로 생성할 수 있습니다. 여러분은 원하는 만큼 props를 선언할 수 있습니다.</p>
<p>다음으로 여러분은 어떻게 디스트럭쳐링(distructuring)으로 props를 사용하는지 배우게 될 겁니다.</p>
<h2 id="distructuringprops">디스트럭쳐링(distructuring)으로 props 사용하기</h2>
<p>이 섹션의 코드는 props를 선언하는 방법 외에 지난 섹션과 완전히 동일합니다. 자바스크립트에서 디스트럭쳐링(distructuring)을 잘 모르신다면 이 <a href="https://www.freecodecamp.org/news/how-to-use-es6-javascript-features-in-react/">기사</a>를 확인해주세요.</p>
<p>지난 섹션에서는 이런 식으로 props를 선언했습니다.</p>
<pre><code class="language-javascript">const name = props.name;
const tool = props.tool;
</code></pre>
<p>디스트럭쳐링(distructuring)을 하면 이럴 필요가 없습니다. 간단히 이렇게 하면 되죠.</p>
<pre><code class="language-javascript">function Tool({ name, tool }) {
  return (
    &lt;div&gt;
      &lt;h1&gt;My name is {name}.&lt;/h1&gt;
      &lt;p&gt;My favorite design tool is {tool}.&lt;/p&gt;
    &lt;/div&gt;
  );
}

export default Tool;
</code></pre>
<p>코드의 첫 번째 줄에 차이점이 있습니다. <code>props</code>를 인자로 전달하는 대신 변수를 분해하여 함수의 인자로 전달했습니다.</p>
<p>나머지는 같습니다.</p>
<p>Props로 단일 변수뿐만 아니라 함수와 오브젝트의 데이터도 동일하게 전달할 수 있습니다.</p>
<h2 id="props">Props에 기본값 설정하는 방법</h2>
<p>Props를 만들 때 빈값으로 두고 싶지 않다면 기본값을 전달할 수 있습니다. 방법은 이렇습니다.</p>
<pre><code class="language-javascript">function Tool({ name, tool }) {
  return (
    &lt;div&gt;
      &lt;h1&gt;My name is {name}.&lt;/h1&gt;
      &lt;p&gt;My favorite design tool is {tool}.&lt;/p&gt;
    &lt;/div&gt;
  );
}

Tool.defaultProps = {
  name: 'Designer',
  tool: 'Adobe XD',
};
export default Tool;
</code></pre>
<p>컴포넌트를 내보내기 전 코드 끝부분에 props에 대한 기본값을 선언했습니다. 이를 위해 컴포넌트의 이름과 리액트 앱을 만들때 기본으로 제공되는 <code>defaultProps</code>를 연결하는 점으로 시작했습니다.</p>
<p>이제 이 컴포넌트를 가져올 때마다 해당값이 공백이 아닌 초기값이 됩니다. 이전 섹션에서와 같이 자식 컴포넌트에 데이터를 전달하면 기본값으로 재정의됩니다.</p>
<h1 id="">결론</h1>
<p>이 기사에서는 props를 사용할 때 필요한 모든 것과 컴포넌트 간에 데이터를 동적으로 전달하는 방법에 대해 다뤘습니다.</p>
<p>이러한 개념을 이해하는 가장 좋은 방법은 직접 실습하고 멋진 결과물을 만들어 보는 것이니, 읽는데 그치지 말고 직접 만들어 보세요.</p>
<p>트위터에서 저를 찾으실 수 있어요<a href="https://twitter.com/Ihechikara2">@ihechikara2</a>. 해피 코딩하세요!</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 리액트에서 필터 컴포넌트 만드는 방법 ]]>
                </title>
                <description>
                    <![CDATA[ 필터 컴포넌트는 사용자가 필요한 결과를 빠르고 쉽게 찾을 수 있게 도와주기 때문에 웹사이트에서 유용합니다. 특히 API에서 데이터가 올 때 아주 유용한데, 사용자는 당신의 앱이 제공하는 모든 것을 살펴볼 수는 없기 때문입니다. 이 아티클에서는 Data.js라는 별도의 컴포넌트에 배열처럼 하드 코딩하고 저장한 가짜 데이터를 사용할 겁니다. 여기서 다룰 내용  1. 시작하기 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/how-to-make-a-filter-component-in-react/</link>
                <guid isPermaLink="false">64d64871e7425543c77094c7</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jeeann K. ]]>
                </dc:creator>
                <pubDate>Tue, 29 Aug 2023 09:31:09 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2023/08/2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/how-to-make-a-filter-component-in-react/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Make a Filter Component in React</a>
      </p><!--kg-card-begin: markdown--><h3 id="">필터 컴포넌트는 사용자가 필요한 결과를 빠르고 쉽게 찾을 수 있게 도와주기 때문에 웹사이트에서 유용합니다.</h3>
<h4 id="api">특히 API에서 데이터가 올 때 아주 유용한데, 사용자는 당신의 앱이 제공하는 모든 것을 살펴볼 수는 없기 때문입니다.</h4>
<p>이 아티클에서는 <strong>Data.js</strong>라는 별도의 컴포넌트에 배열처럼 하드 코딩하고 저장한 가짜 데이터를 사용할 겁니다.</p>
<h3 id="">여기서 다룰 내용</h3>
<ol>
<li>시작하기</li>
<li>리액트 앱 만들기</li>
<li>Hook을 사용해서 Data.js로부터 데이터 가져오기</li>
<li>앱의 UI 작업하기</li>
<li>필터 컴포넌트 만들기</li>
<li>마무리하기</li>
</ol>
<h2 id="">시작하기</h2>
<p>이 특정 프로젝트를 위해 아래 코드에서 보이듯 몇 가지의 key-value 쌍으로 이루어진 가짜 음식 데이터를 사용할 겁니다.</p>
<pre><code class="language-javascript">const Data = [
  {
    id: '1',
    title: 'Poha',
    category: 'Breakfast',
    price: '$1',
    img: 'https://c.ndtvimg.com/2021-08/loudr2go_aloo-poha_625x300_05_August_21.jpg?im=FeatureCrop,algorithm=dnn,width=620,height=350',
    desc: ' Poha. Light, filling and easy to make, poha is one famous breakfast that is eaten almost everywhere in the country. And the best part is- it can be made in a number of ways. Kanda poha, soya poha, Indori poha, Nagpur Tari Poha are a few examples',
  },
  {
    id: '2',
    title: 'Upma',
    category: 'Breakfast',
    price: '$1',
    img: 'https://c.ndtvimg.com/2021-04/37hi8sl_rava-upma_625x300_17_April_21.jpg?im=FeatureCrop,algorithm=dnn,width=620,height=350',
    desc: ' A quintessential South Indian Breakfast! Made with protein-packed urad dal and semolina followed by crunchy veggies and curd, this recipe makes for a hearty morning meal. With some grated coconut on top, it gives a beautiful south-Indian flavour.',
  },
  {
    id: '3',
    title: 'Cheela',
    category: 'Breakfast',
    price: '$1',
    img: 'https://c.ndtvimg.com/2019-05/1afu8vt8_weight-loss-friendly-breakfast-paneer-besan-chilla_625x300_25_May_19.jpg?im=FaceCrop,algorithm=dnn,width=620,height=350',
    desc: 'A staple across Indian households, moong dal is widely used in a number of Indian delicacies. One such delicacy is moong dal cheela. You can also add paneer to this recipe to amp up the nutritional value and make it, even more, protein-dense',
  },
  {
    id: '4',
    title: 'Channa Kulcha',
    category: 'Lunch',
    price: '$1',
    img: 'https://i.ndtvimg.com/i/2015-04/chana-kulcha_625x350_41429707001.jpg',
    desc: 'A classic dish that never goes out of style. The quintessential chana kulcha  needs only a few ingredients - cumin powder, ginger, coriander powder, carom powder, and some mango powder, which is what gives the chana its sour and tangy taste.',
  },
  {
    id: '5',
    title: 'Egg Curry',
    category: 'Lunch',
    price: '$1',
    img: 'https://i.ndtvimg.com/i/2017-11/goan-egg-curry_620x350_41511515276.jpg',
    desc: 'Eggs are a versatile food that can be cooked for any meal of the day. From breakfast to dinner, it can be a go-to food. Here is a mildly-spiced egg curry made with garlic, onions, a whole lot of kasuri methi, fresh cream, yogurt, and fresh coriander.',
  },
  {
    id: '6',
    title: 'Paneer Aachari',
    category: 'Lunch',
    price: '$1',
    img: 'https://i.ndtvimg.com/i/2015-04/paneer_625x350_61429707960.jpg',
    desc: "Don't get intimidated by the list of ingredients because not only are already in your kitchen cabinet, but also because all they'll need is 20 minutes of your time. Chunks of cottage cheese cooked in some exciting spices, yogurt and a pinch of sugar.",
  },
  {
    id: '7',
    title: 'Fish Fry',
    category: 'Dinner',
    price: '$1',
    img: 'https://i.ndtvimg.com/i/2015-06/indian-dinner_625x350_41434360207.jpg',
    desc: 'Get your daily dose of perfect protein. Pieces of surmai fish marinated in garlic, cumin, fennel, curry leaves, and tomatoes are pan-fried in refined oil and served hot. This fish fry recipe has a host of delectable spices used for marination giving it a unique touch.',
  },
  {
    id: '8',
    title: 'Dum Alloo',
    category: 'Dinner',
    price: '$1',
    img: 'https://i.ndtvimg.com/i/2015-06/indian-dinner_625x350_51434362664.jpg',
    desc: 'Your family will thank you for this fantastic bowl of dum aloo cooked Lakhnawi style. Take some potatoes, crumbled paneer, kasuri methi, butter, onions, and some ghee.',
  },
  {
    id: '9',
    title: 'Malai Kofta',
    category: 'Dinner',
    price: '$1',
    img: 'https://i.ndtvimg.com/i/2017-10/makhmali-kofte_620x350_51508918483.jpg',
    desc: 'A rich gravy made of khus khus, coconut and milk that tastes best with koftas made from khoya. This velvety and creamy recipe will leave you licking your fingers. Makhmali kofte can be your go-to dish for dinner parties as this is quite different from other kofta recipes and extremely delicious.',
  },
  {
    id: '10',
    title: 'Malai Kofta',
    category: 'Snaks',
    price: '$1',
    img: 'https://i.ndtvimg.com/i/2017-10/makhmali-kofte_620x350_51508918483.jpg',
    desc: 'A rich gravy made of khus khus, coconut and milk that tastes best with koftas made from khoya. This velvety and creamy recipe will leave you licking your fingers. Makhmali kofte can be your go-to dish for dinner parties as this is quite different from other kofta recipes and extremely delicious.',
  },
];

export default Data;
</code></pre>
<p>이러한 key-value 중에는 결과를 필터링할 때 쓰일 카테고리도 있습니다.</p>
<p>앱을 스타일링하기 위해 이 프로젝트의 CDN으로 부트스트랩(Bootstrap)을 사용할 겁니다.</p>
<p>이 튜토리얼 이후에는 리액트에 컴포넌트를 가져오는 방법, 데이터를 동적으로 사용하는 방법, 그리고 가장 중요한 부모 자식 컴포넌트 사이에 props를 전달하고 사용하는 방법에 대해 더 많이 알게 되실 겁니다.</p>
<h2 id="">리액트 컴포넌트 만드는 방법</h2>
<p>리액트 앱을 만드는 건 정말 쉽습니다. 본인이 선호하는 IDE의 작업 디렉토리로 가서 터미널에 아래의 명령어를 입력하면 됩니다.</p>
<pre><code>npx create-react-app react-filter-app
</code></pre>
<p>create-react-app 프로젝트를 만드는 방법을 잘 모르겠다면 <a href="https://create-react-app.dev/docs/getting-started/">create-react-app-dev</a>의 공식 가이드를 참고하셔도 됩니다.</p>
<p>세팅을 마친 후 리액트 앱이 호스팅 될 localhost:3000를 시작하기 위해서 같은 터미널에 <code>npm start</code>를 실행합시다. 모든 변화는 여기서 확인할 수 있습니다.</p>
<h2 id="hookdatajs">Hook을 사용해서 Data.js로부터 데이터를 가져오는 방법</h2>
<p>이제 리액트 프로젝트를 성공적으로 설정했으므로 Data.js에서 데이터를 가져와 UI에 사용할 차례입니다.</p>
<p>이를 위해 먼저 <strong>App.js</strong> 컴포넌트에 데이터를 가져온 다음 useState 훅을 사용하여 데이터 상태를 관리해야 합니다.</p>
<pre><code class="language-javascript">import React, { useState } from 'react';
import Data from './Data';
import Card from './Card';

const App = () =&gt; {
  const [item, setItem] = useState(Data);
  return (
    &lt;&gt;
      &lt;div className="container-fluid"&gt;
        &lt;div className="row"&gt;
          &lt;h1 className="col-12 text-center my-3 fw-bold"&gt;Our Menu&lt;/h1&gt;
          &lt;Card item={item} /&gt; // UI Component
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/&gt;
  );
};

export default App;
</code></pre>
<h2 id="ui">앱의 UI 부분을 구축하는 방법</h2>
<p>이제 앱에서 자유롭게 사용할 수 있는 변수에 데이터를 저장하고 UI에서 작업할 수 있습니다.</p>
<p>UI는 map 함수를 사용하여 동적으로 만들 <a href="https://getbootstrap.com/docs/5.0/components/card/">부트스트랩 카드</a>가 포함됩니다.</p>
<p>우리는 카드를 위해 다른 컴포넌트를 만들 겁니다. 위의 코드에서 볼 수 있듯이 우리는 <strong>Card.js</strong>라고 이름을 지었고 가져왔습니다. 또한, 카드 컴포넌트 안의 <strong>item</strong>에 저장된 데이터를 사용하기 위해 item을 props로 전달했습니다.</p>
<p>이 컴포넌트에서는 <strong>map 함수</strong>를 이용해 앱에서 동적으로 보여줄 모든 카드와 데이터를 포함할 겁니다.</p>
<pre><code class="language-javascript">import React from 'react';

const Card = ({ item }) =&gt; {
  // destructuring props
  return (
    &lt;&gt;
      &lt;div className="container-fluid"&gt;
        &lt;div className="row justify-content-center"&gt;
          {item.map((Val) =&gt; {
            return (
              &lt;div
                className="col-md-4 col-sm-6 card my-3 py-3 border-0"
                key={Val.id}
              &gt;
                &lt;div className="card-img-top text-center"&gt;
                  &lt;img src={Val.img} alt={Val.title} className="photo w-75" /&gt;
                &lt;/div&gt;
                &lt;div className="card-body"&gt;
                  &lt;div className="card-title fw-bold fs-4"&gt;
                    {Val.title} &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;--&amp;nbsp;&amp;nbsp;
                    {Val.price}
                  &lt;/div&gt;
                  &lt;div className="card-text"&gt;{Val.desc}&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            );
          })}
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/&gt;
  );
};

export default Card;
</code></pre>
<p>10개의 카드가 있는 앱의 모습은 다음과 같습니다.</p>
<p><img src="https://lh5.googleusercontent.com/dcZ-3eTALXbuRdMYsDgy672KsDcuN7D--QbHOl5_2xNjocun5-zAONVPFqD8txXpVvbyKNNBV6rjEi5JAIceQzMy-K-D1OOOjWkKs59EzlRzf-VBkjwz3LxY2I8E4FBL6Bn4vrf5" alt="스크롤바와 10개의 카드가 있는 음식 필터링 앱의 모습" width="600" height="400" loading="lazy"></p>
<h2 id="">필터 컴포넌트를 만드는 방법</h2>
<p>필터 컴포넌트를 사용하여 사용자가 검색 결과를 통해 얻은 데이터를 필터링할 수 있는 방법은 다양합니다. 하지만 여기서는 아침, 점심, 저녁 그리고 간식 같은 음식의 카테고리에 따라 데이터가 필터링 되도록 버튼을 만들 겁니다.</p>
<p>키 카테고리의 값만 포함하는 새로운 배열을 만들고 map 메소드로 보여줘야 합니다.</p>
<pre><code class="language-javascript">// spread operator는 데이터 카테고리 섹션의 모든 값을 보여줍니다. 하지만 Set은 각 종류의 단일 값만 표시할 수 있습니다.

const menuItems = [...new Set(Data.map((Val) =&gt; Val.category))];
</code></pre>
<p>위의 배열을 표시하여 얻은 모든 값이 동일한 UI를 갖고 10개의 카테고리를 모두 버튼으로 보여주기 위해 <strong>spread operator</strong>를 사용합니다.</p>
<p><code>Set()</code> 값을 사용하여 고유한 3개 또는 4개의 값만 표시하고 반복되는 값이 없도록 합니다.</p>
<p>map 메소드를 사용하여 동적으로 표시되는 버튼에 대한 새 컴포넌트를 만듭니다. 그러나 이번에는 새로 구성된 배열을 사용할 것입니다. 모든 카테고리가 배열에 저장되어 있고 **Set()**으로 인해 한 번만 표시되기 때문입니다.</p>
<pre><code class="language-javascript">import React from 'react';
import Data from './Data';

const Buttons = ({ setItem, menuItems }) =&gt; {
  return (
    &lt;&gt;
      &lt;div className="d-flex justify-content-center"&gt;
        {menuItems.map((Val, id) =&gt; {
          return (
            &lt;button
              className="btn-dark text-white p-1 px-2 mx-5 btn fw-bold"
              key={id}
            &gt;
              {Val}
            &lt;/button&gt;
          );
        })}
        &lt;button
          className="btn-dark text-white p-1 px-3 mx-5 fw-bold btn"
          onClick={() =&gt; setItem(Data)}
        &gt;
          All
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/&gt;
  );
};

export default Buttons;
</code></pre>
<p>버튼을 표시할 위치에 버튼 컴포넌트를 놓으세요. 우리의 경우 app.js의 카드 컴포넌트 위에 버튼을 놓았습니다.</p>
<p><img src="https://lh5.googleusercontent.com/gX2PTVbyYQIJ-6o_WvhHZVucTJwEZhQz0moqf7GZoC68fcgC2iORyLyqRILAmhQn-e_SQy172o1_BgeLMidY69Jm3UCAXtRBiP-fNwFf50VaJPj8_54SjjlngVvCun_EaOVG-DRh" alt="5개의 필터 버튼과 3개의 카드가 있는 음식 필터링 앱의 모습" width="600" height="400" loading="lazy"></p>
<p>카테고리에 따라 필터가 되도록 버튼에 필터를 추가할 시간입니다.</p>
<pre><code class="language-javascript">const filterItem = (curcat) =&gt; {
  const newItem = Data.filter((newVal) =&gt; {
    return newVal.category === curcat;
    // comparing category for displaying data
  });
  setItem(newItem);
};
</code></pre>
<p>filter 메소드는 오브젝트의 카테고리에 따라 데이터를 필터합니다.</p>
<p><code>onClick()</code> 이벤트 헨들러를 사용하면 버튼에 이 필터를 연결할 수 있습니다.</p>
<pre><code class="language-javascript">import React from 'react';
import Data from './Data';

const Buttons = ({ filterItem, setItem, menuItems }) =&gt; {
  return (
    &lt;&gt;
      &lt;div className="d-flex justify-content-center"&gt;
        {menuItems.map((Val, id) =&gt; {
          return (
            &lt;button
              className="btn-dark text-white p-1 px-2 mx-5 btn fw-bold"
              onClick={() =&gt; filterItem(Val)}
              key={id}
            &gt;
              {Val}
            &lt;/button&gt;
          );
        })}
        &lt;button
          className="btn-dark text-white p-1 px-3 mx-5 fw-bold btn"
          onClick={() =&gt; setItem(Data)}
        &gt;
          All
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/&gt;
  );
};

export default Buttons;
</code></pre>
<h2 id="">마무리하기</h2>
<p>사용자가 시간 낭비를 줄이고 앱에서 원하는 결과를 찾을 수 있도록 다양한 방법으로 필터 컴포넌트를 사용할 수 있습니다.</p>
<p>이 앱에서 배열에는 10개의 오브젝트만 있었지만 주로 API에서 데이터를 얻을 때는 수많은 데이터가 있습니다. 이럴 경우에는 한 번의 검색으로 정확한 결과를 얻기가 쉽지 않기 때문에 필터를 사용합니다.</p>
<p>여러분은 <a href="https://github.com/Ateevduggal/Filter-Menu-in-React">깃헙 레포</a>에서 전체 코드를 보실 수 있고, 앱의 <a href="https://filter-menu-in-react.vercel.app/">라이브 링크</a>에서 이 필터 버튼들이 어떻게 작동하는지를 확인할 수 있습니다.</p>
<p>저의 다른 프로젝트들도 확인할 수 있습니다.</p>
<ol>
<li><a href="https://tekolio.com/how-to-make-custom-pagination-in-react-js-with-hooks/">훅을 사용해서 Pagination 컴포넌트 만드는 방법</a></li>
<li><a href="https://tekolio.com/how-to-create-a-dictionary-app-in-react/">훅을 사용해서 리액트로 사전 앱 만드는 방법</a></li>
<li><a href="https://tekolio.com/how-to-host-a-react-app-on-github-pages-with-a-custom-domain/">커스텀 도메인으로 깃헙 페이지에 리액트 앱 호스팅하는 방법</a></li>
</ol>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ React.useEffect 훅(Hook) - 일반적인 문제와 그 해결 방법 ]]>
                </title>
                <description>
                    <![CDATA[  감수 Boyeon Ihn [https://boyeonihn.vercel.app/] React 훅(hook)은 꽤 오랫동안 사용되어 왔습니다. 개발자 대부분이 이 훅의 동작 원리와 일반적인 사용 사례에 대해 제법 익숙합니다. 하지만 많은 이들이 빠지는 useEffect 함정이 있습니다. 사용 사례 간단한 시나리오로 이야기를 시작해 보겠습니다. React App을 만들고 한 컴포넌트에 현재 사용자의 이름을 보여주기를 원한다고 하겠습니다. 우선, API로부터 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/react-useeffect-hug-hook-ilbanjeogin-munjewa-geu-haegyeol-bangbeob/</link>
                <guid isPermaLink="false">64958fc4f825440663cbe6b7</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ SeunghyunKim ]]>
                </dc:creator>
                <pubDate>Fri, 23 Jun 2023 12:57:35 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2023/06/react-main-image.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/most-common-react-useeffect-problems-and-how-to-fix-them/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">React.useEffect Hook – Common Problems and How to Fix Them</a>
      </p><p></p><h4 id="-boyeon-ihn">감수 <strong><a href="https://boyeonihn.vercel.app/">Boyeon Ihn</a></strong></h4><p></p><p>React 훅(hook)은 꽤 오랫동안 사용되어 왔습니다. 개발자 대부분이 이 훅의 동작 원리와 일반적인 사용 사례에 대해 제법 익숙합니다. 하지만 많은 이들이 빠지는 <code>useEffect</code> 함정이 있습니다.</p><h2 id="-">사용 사례</h2><p>간단한 시나리오로 이야기를 시작해 보겠습니다. React App을 만들고 한 컴포넌트에 현재 사용자의 이름을 보여주기를 원한다고 하겠습니다. 우선, API로부터 사용자 이름을 불러와야 합니다.</p><p>또한, 애플리케이션 내 다른 곳에서도 사용자 데이터가 사용될 것이기 때문에 데이터 불러오는 로직(logic)을 커스텀(custom) React 훅에 담아보겠습니다.</p><p>기본적으로 이 React 컴포넌트는 다음과 같을 것입니다.</p><pre><code class="language-js">const Component = () =&gt; {
  // useUser custom hook

  return &lt;div&gt;{user.name}&lt;/div&gt;;
};
</code></pre><p>아주 간단해 보이지 않나요?</p><h2 id="useuser-react-">useUser React 훅</h2><p>두번째 단계는 <code>useUser</code> 커스텀 훅을 만드는 것입니다.</p><pre><code class="language-js">const useUser = (user) =&gt; {
  const [userData, setUserData] = useState();
  useEffect(() =&gt; {
    if (user) {
      fetch('users.json').then((response) =&gt;
        response.json().then((users) =&gt; {
          return setUserData(users.find((item) =&gt; item.id === user.id));
        })
      );
    }
  }, []);

  return userData;
};
</code></pre><p>하나씩 자세히 살펴보겠습니다. 우선 이 훅이 사용자 객체를 받는지 확인을 합니다. 그런 다음에 <code>user.json</code>이라는 파일로부터 애플리케이션 사용자의 목록을 불러오고 필요한 사용자를 찾기 위해 아이디(id)로 목록을 걸러냅니다.</p><p>그런 다음에 필요한 데이터를 갖게 되면, 그 데이터를 훅에 있는 <code>userData</code> state에 저장합니다. 마지막으로 <code>userData</code>를 반환합니다.</p><p>주의: 위 예시는 단지 설명을 위한 것입니다. 실제로 데이터 불러오기는 이보다 훨씬 복잡합니다. 이 주제에 관해 관심이 있다면 ReactQuery, Typescript and GraphQL으로 데이터 불러오기 설정을 어떻게 하는지에 관한 <a href="https://whereisthemouse.com/graphql-requests-made-easy-with-react-query-and-typescript">제 글을 확인하시길 바랍니다</a>.</p><p>이제 훅을 React 컴포넌트에 적용하면 어떻게 되는지 확인해보겠습니다.</p><pre><code class="language-js">const Component = () =&gt; {
  const user = useUser({ id: 1 });
  return &lt;div&gt;{user?.name}&lt;/div&gt;;
};
</code></pre><p>모든 게 예상대로 작동하는 것처럼 보입니다. 잠깐만... 이게 뭐죠?</p><h2 id="eslint-exhaustive-deps-">ESLint exhaustive-deps 규칙</h2><p>훅(hook)에 ESLint 경고가 생겼습니다.</p><pre><code>React Hook useEffect has a missing dependency: 'user'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)
</code></pre><p>음, <code>useEffect</code>에 디펜던시(dependency)가 빠진 것으로 보입니다. 자, 그럼! 디펜던시를 추가해보겠습니다. 별 대수겠어요? 😂(여기서 디펜던시는 한 대상과 의존관계에 있는 다른 대상을 일컫습니다. 즉, <code>useEffect</code> 훅이 디펜던시에 영향을 받는다는 것으로 이해하시면 되겠습니다. - 역주)</p><pre><code class="language-js">const useUser = (user) =&gt; {
  const [userData, setUserData] = useState();
  useEffect(() =&gt; {
    if (user) {
      fetch('users.json').then((response) =&gt;
        response.json().then((users) =&gt; {
          return setUserData(users.find((item) =&gt; item.id === user.id));
        })
      );
    }
  }, [user]);

  return userData;
};
</code></pre><p>이런, 이제는 <code>Component</code>가 렌더링(rendering)을 멈추는 것 같지 않아 보입니다. 어떻게 된 일일까요? - (렌더링은 간단하게 화면에 보이는 요소들을 불러오는 것으로 이해하시면 되겠습니다. - 역주)<br>설명해보겠습니다.</p><h2 id="-re-render-">무한 렌더링(re-render) 문제</h2><p>컴포넌트가 무한 렌더링(re-rendering)을 하는 이유는 <code>useEffect</code>의 디펜던시가 끊임없이 변하기 때문입니다. 하지만 우리는 계속 같은 객체를 훅에 전달하고 있는데 왜 문제가 발생할까요?<br>같은 key와 value를 가진 객체를 전달하고 있는 것은 맞지만 엄밀히 말하면 같은 객체는 아닙니다. 사실 <code>Component</code>가 렌더링할 때마다 새로운 객체가 생성됩니다. 그런 다음 이 새로 생긴 객체를 <code>useUser</code> 훅의 인자(argument)로 전달됩니다.<br><code>useEffect</code>가 두 객체를 비교할 때 서로 다른 참조 값(reference) 가지고 있기 때문에 다시 사용자들을 불러오고 새로운 사용자 객체를 state에 저장합니다. 그리고 그 state의 업데이트에 의해 컴포넌트의 리렌더링을 일으키는 것입니다. 무한 반복이 되는 거죠. (Reference는 한 변수를 선언할 때 해당 변수에 생기는 참조 값입니다. 그 변수의 별명 같은 것으로 보면 됩니다. 같아 보이는 두 객체라도 각기 다른 변수에 저장이 되면 각각의 참조 값은 다른 값을 가집니다. - 역주)<br>그러면 어떻게 해결할 수 있을까요?</p><h2 id="--1">해결 방법</h2><p>이제 문제를 파악했으니 해결책을 찾을 수 있습니다.<br>첫번째, 그리고 아마 가장 확실한 방법은 <code>useEffect</code> 디펜던시 배열에서 해당 디펜던시를 제거하고 ESLint 규칙을 무시하고 그냥 넘어가는 것입니다.<br>하지만 이 방법은 잘못된 접근입니다. 이 방법은 앱에 에러와 예기치 못한 동작을 초래할 수 있습니다(그리고 분명 그럴 것입니다). <code>useEffect</code>가 어떻게 동작하는지 더 알고 싶은 분께 Dan Abramov`s <a href="https://overreacted.io/a-complete-guide-to-useeffect/">완벽 가이드</a>를 강력하게 추천합니다.</p><p>그러면 다음 해결책은 뭘까요?</p><p>이 예시 같은 경우에 가장 쉬운 해결책은 <code>{ id: 1 }</code> 객체를 컴포넌트에서 빼내는 것입니다. 이 방법은 그 객체에 동일한 참조 값을 주고 문제를 해결해 줄 것입니다.</p><pre><code class="language-js">const userObject = { id: 1 };
const Component = () =&gt; {
  const user = useUser(userObject);
  return &lt;div&gt;{user?.name}&lt;/div&gt;;
};
export default Component;
</code></pre><p>하지만 이 방법은 언제나 가능한 것은 아닙니다. 사용자 id가 컴포넌트 props 혹은 state에 의존한다고 가정해보겠습니다.</p><p>예를 들어, 사용자 id에 접근하기 위해 URL 파라미터를 사용하는 상황 같은 것일 수 있습니다. 만약 이러한 경우라면 그 객체를 기억하고 동일한 참조 값을 보장해 줄 <code>useMemo</code>를 이용하면 됩니다.</p><pre><code class="language-js">const Component = () =&gt; {
  const { userId } = useParams();
  const userObject = useMemo(() =&gt; {
    return { id: userId };
  }, [userId]); // Don't forget the dependencies here either!
  const user = useUser(userObject);
  return &lt;div&gt;{user?.name}&lt;/div&gt;;
};
export default Component;
</code></pre><p>마지막으로 <code>useUser</code> 훅에 객체 변수를 보내는 대신에 원시 값(primitive value)인 사용자 id만 보내는 게 가능합니다. 이 방법은 <code>useEffect</code> 훅에서 참조 값 동일 문제를 예방할 수 있습니다.</p><pre><code class="language-js">const useUser = (userId) =&gt; {
  const [userData, setUserData] = useState();
  useEffect(() =&gt; {
    fetch('users.json').then((response) =&gt;
      response.json().then((users) =&gt; {
        return setUserData(users.find((item) =&gt; item.id === userId));
      })
    );
  }, [userId]);
  return userData;
};
const Component = () =&gt; {
  const user = useUser(1);
  return &lt;div&gt;{user?.name}&lt;/div&gt;;
};
</code></pre><p>해결됐네요!</p><p>그리고 이 과정을 통해서 ESLint 규칙을 깨뜨릴 필요도 없었네요.</p><p>주의: 커스텀 훅에 보낸 인자가 객체가 아니라 함수라면 무한 리-렌더링을 피하기 위해 비슷한 기술을 사용했을 것입니다. 주요한 차이는 위의 예시에서 사용한 <code>useMemo</code> 대신 <code>useCallback</code>을 사용해야 합니다.</p><p>읽어 주셔서 감사합니다!</p><p>코딩에 대해 궁금하시나요? <a href="https://codesandbox.io/s/useeffect-gotcha-20jw9?file=/src/App.js">여기</a>서 맘껏 코딩해보세요.</p><p><a href="https://twitter.com/iva_kop">Twitter</a>와 개인 <a href="https://whereisthemouse.com/">블로그</a>에 더 많은 React 관련 컨텐츠가 있으니 팔로우하러 놀러오세요.</p><p>이미지 출처 <a href="https://www.freepik.com/vectors/technology">vectorjuice</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 리액트 모범 사례 - 2022 더 나은 리액트 코드 작성을 위한 팁 ]]>
                </title>
                <description>
                    <![CDATA[ > 이 년 전, 리액트를 배우기 시작했다. 그리고 지금도 여전히 소프트웨어 개발자로서 직장에서 일을 할 때나 사이드 프로젝트를 할 때 여전히 리액트를 쓴다. 그때 당시에는 정말 많은 "전형적인" 문제들을 만났다. 그러다 보니 검색해 보며 여러 모범 사례를 찾아다녔고, 작업 흐름에 녹여보곤 했다. 그 덕에 나는 물론 내 팀원의 사정까지 나아질 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/best-practices-for-react/</link>
                <guid isPermaLink="false">641fb07fa421a7066d11383f</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jeong Won Yoo ]]>
                </dc:creator>
                <pubDate>Mon, 27 Mar 2023 15:37:31 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2023/03/React-Best-Practices-Thumbnail.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/best-practices-for-react/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">React Best Practices – Tips for Writing Better React Code</a>
      </p><blockquote>이 년 전, 리액트를 배우기 시작했다. 그리고 지금도 여전히 소프트웨어 개발자로서 직장에서 일을 할 때나 사이드 프로젝트를 할 때 여전히 리액트를 쓴다.</blockquote><p>그때 당시에는 정말 많은 "전형적인" 문제들을 만났다. 그러다 보니 검색해 보며 여러 모범 사례를 찾아다녔고, 작업 흐름에 녹여보곤 했다. 그 덕에 나는 물론 내 팀원의 사정까지 나아질 수 있었다.</p><p>막상 당시에는 최고의 방법으로 해결해 낼 수 없었던 과제들도 있었는데, 나중에 더 나은 방법으로 접근하고 싶었다.</p><p>그래서 이 가이드가 탄생하게 되었다. 이 년 전 처음 시작했을 때 나 자신에게 주는 팁 모음인 셈이다.</p><h2 id="-">목차 </h2><ul><li><a>리액트 개발자가 마주하는 세 가지 주요한 도전 과제</a></li><li><a>리액트의 구성 요소 배워보기</a></li><li><a>깨끗하고 성능 좋은, 유지 보수가 쉬운 리액트 컴포넌트 만드는 법 배워보기</a></li><li><a>더 나은 리액트 코드 작성을 위한 팁 - 화룡점정</a></li><li><a>마지막</a></li></ul><p>뭐니 뭐니 해도, 모든 리액트 개발자가 마주하는 세 가지 주요한 과제에 대해 알면 좋을 것 같다. 잠재적인 도전 과제에 대해 인지하고 있다면, 모범 사례를 더 깊이 이해할 수 있으니 중요한 부분이다. 시작할 때부터 이런 태도를 견지한다면, 컴포넌트를 설계하거나 자신만의 프로젝트를 준비할 때 도움 될 것이다.</p><p>그럼, 이제 중요한 첫 번째 단계로써, 코드 예제를 통한 이론과 실무가 혼합된 <strong>세 가지 모범 사례</strong>를 소개하고자 한다. <em>hello world 문제</em>를 최소화하고 <em>실제 세계</em>에서 만났던 코드 이야기를 더 해보려고 한다.</p><h2 id="--1">리액트 개발자가 마주하는 세 가지 주요한 도전 과제</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/korean/news/content/images/2023/03/image.png" class="kg-image" alt="image" srcset="https://www.freecodecamp.org/korean/news/content/images/size/w600/2023/03/image.png 600w, https://www.freecodecamp.org/korean/news/content/images/size/w1000/2023/03/image.png 1000w, https://www.freecodecamp.org/korean/news/content/images/2023/03/image.png 1600w" sizes="(min-width: 720px) 720px" width="1600" height="1200" loading="lazy"><figcaption>메인 이미지</figcaption></figure><p>매일 같이 리액트를 사용하던 지난 이 년 간, 리액트 개발자가 앱을 제작하는 동안 마주하는 세 가지 중요 문제에 대해 알게 됐다. 이런 문제들을 무시한다면 앱이 더디게 성장한다거나 하는 힘든 시간을 겪을 수도 있다.</p><p>그러니 앱을 세심하게 조직하는 과정에서, 이 세 가지를 염두에 두고 시간과 에너지를 아낄 수 있다.</p><h3 id="--2">⚙️ 유지보수성</h3><p>유지보수성은 <em>재사용성</em>과 관련된다. 초기에는 애플리케이션과 컴포넌트는 매우 경량화된 상태로, 유지보수가 쉽다. 하지만 요구사항이 증가하기 시작하면, 컴포넌트는 복잡해지고 유지보수성이 떨어진다.</p><p>종종 각자 결과물을 대표하는 여러 가지 상황에 대한 컴포넌트를 볼 일이 있다. JSX가 조건부 렌더링(삼항 연산자나 단순히 <code>&amp;&amp;</code> 연산자를 사용해서)으로 넘치고, 조건에 따라 클래스 이름이 적용되어 있거나 컴포넌트가 거대한 <code>switch</code>문을 사용하고 있는 것이다. 잠재적인 prop과 상태 값들이 저마다 다른 결과물을 책임지는 형국이다.</p><p>여기에 쓰인 기술들이 그 자체로는 아무 문제가 없다고 생각한다. 하지만 모든 사람이 컴포넌트가 유지보수성이 떨어지기 시작하고 이런 기술이 남용되고 있을 때 대한 감각을 키워야 한다고 생각한다. 이 글에서 나중에 이런 경우를 어떻게 하면 더 잘 다룰 수 있을지에 대해 배워볼 것이다.</p><p>문제는 (나 역시도 죄책감을 느끼지만) 컴포넌트가 더 복잡해지고 더 다양해질수록(다형성), 더 유지하기가 까다롭다는 것이다.</p><p>솔직히 말해서, 근본적인 원인이 게으름이나 충분한 경험 부족인 경우가 종종 있고, 이따금 이것이 컴포넌트를 더 유지보수하기 쉽고 깨끗하게 만들기 위해 적절히 리팩토링해야 한다는 시간적 압박 때문이기도 하다.</p><p>목격했던 또 다른 핵심적인 요소는 테스팅을 아예 안 하거나 적게 한다는 것이다. 많은 개발자가 좋아하는 종류의 업무가 아닌 것은 알지만, 장기적으로 보면 테스팅은 정말로 도움이 된다. 테스팅 그 자체는 이 글에서 중요한 주제로 다루지는 않지만, 내가 작성한 다른 블로그 포스트에도 관심을 가져주길 바란다.</p><h3 id="--3">🧠 리액트를 확실히 이해하기</h3><p>리액트 개발자들이 겪는 또 다른 근본적인 문제는 실제로 리액트가 어떻게 작동하는지에 대한 확실한 이해가 없다는 것이다. 나 역시 그랬다.</p><p>많은 사람이 기초를 탄탄하게 하지 않은 상태에서 너무 빨리 중간에서 고급 단계의 개념으로 넘어가는 것을 봤다. 하지만, 이건 리액트에 국한된 이야기는 아니다. 프로그래밍에서 일반적으로 일어나는 문제긴 하다.</p><p>리액트를 제대로 이해하지 않으면 개발자로서 여러 문제를 겪을 수 있다. 다양한 생명 주기의 컴포넌트를 사용하고 싶었을 때 이걸 어떻게 사용해야 하는지 알 수 없어서 머리가 지끈거렸던 것이 기억난다. 그래서 다시 뒤로 돌아가 해당 주제에 대해 더 깊게 알 필요가 있었다.</p><p>이건 정말 중요한 문제 중 하나여서, 아래 블로그 포스트에서 한 챕터를 할애해 두었다.</p><h3 id="--4">📈 확장성</h3><p>확장성이라는 과제는 <em>유지보수성</em>과 연결이 된다. 리액트에만 적용되는 것은 아니고, 일반적으로 소프트웨어에 다 적용되는 말이다.</p><p>UX뿐만 아니라 깔끔한 코드 패턴과 현명한 설계 역시 훌륭한 소프트웨어 제작에 필요하다는 것을 알게 됐다. 나의 경우, 확장할 수 있는가에 따라 소프트웨어의 질이 결정됐다.</p><p>많은 요소가 동작하기 시작하면서 소프트웨어의 확장 가능성이 커졌다. 이 글에서 가장 중요한 팁에 관해 배울 수 있다.</p><p>컴포넌트를 조작하고 프로젝트 구조를 조직할 때 <em>유지보수성</em>과 <em>확장가능성</em>에 대해 유념한다면, 중대한 리팩토링이 필요할 정도로 코드가 지저분해지는 상황을 덜 겪을 것이다.</p><h1 id="--5">리액트를 배우는 방법</h1><p>자 이제 리액트 학습을 위해 몇 가지 모범 사례를 깊게 들여다보자.</p><h2 id="--6">리액트의 구성 요소 배워보기</h2><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/korean/news/content/images/2023/03/image-1.png" class="kg-image" alt="image-1" srcset="https://www.freecodecamp.org/korean/news/content/images/size/w600/2023/03/image-1.png 600w, https://www.freecodecamp.org/korean/news/content/images/2023/03/image-1.png 1000w" sizes="(min-width: 720px) 720px" width="1000" height="750" loading="lazy"><figcaption>Foundation의 알파벳 철자가 하나씩 나무 블럭에 적혀 있다.</figcaption></figure><p>위에서 이미 간단하게 이야기했지만, 구성 요소의 특징을 살피는 것이 리액트 학습에만 국한된 것은 아니다. 다른 기술이나 프로그래밍 언어에도 적용된다. 모랫바닥에 바로 고층 건물을 쌓고는 무너지지 않을 것이라 기대할 수는 없다.</p><p>기초를 제대로 이해하지 않고 리액트의 중간/고급 개념에 바로 뛰어든 개발자를 겪었다는 내 말이 이제 꽤 분명하게 들릴지도 모르겠다.</p><p>JavaScript에 비춰보아도 대체로 사실이다. 바닐라 JavaScript에 대한 확실한 토대 없이는 리액트가 이해되지 않는다는 말을 절대적으로 믿는 편이다.</p><p>자 그럼 이런 이야기들이 익숙하게 들리고, 리액트를 배우고자 하는 생각은 있지만 바닐라 JavaScript는 그다지 편하게 느껴지지 않는다면, JavaScript를 먼저 튼튼하게 하는 데 시간을 써야 한다. 미래에 두통을 겪을 시간을 아껴줄 것이다.</p><p>한번 읽어보고 싶다면, <a href="https://www.freecodecamp.org/news/top-javascript-concepts-to-know-before-learning-react/">리액트를 하기 전에 알아야 할 JavaScript의 가장 중요한 컨셉</a>이라는 가이드가 도움 될 것이다.</p><p>하지만 나한테는 이런 기본기를 아는 것만으로는 충분치 않았다. 리액트가 실제로 어떻게 돌아가는지 아는 것이 필수적이었다. 좋은 리액트 개발자가 되고자 한다면(이 글을 읽고 있으니, 물론 그렇겠다고 생각하겠지만), 자신이 쓰는 도구는 알아야 한다고 생각한다. 이는 개발자로서도 클라이언트에게도 모두 도움이 된다.</p><p>한편으로는 애플리케이션 디버깅에 쓰이는 시간을 절약할 수 있다. 또, 계속해서 기본만 다시 배워야 해서 뒤로 돌아가지 않아도 되니 훨씬 더 효율적이다. 무엇에 대해서 이야기하는지 기본적으로 알게 되는 것이다.</p><p>물론 모든 내용을 다 알 수는 없으니 이 주제에 너무 스트레스받지 않아도 된다. 실무적인 문제와 더 많은 프로젝트를 겪으면 겪을수록 더 많이 배우게 될 것이다. 시작부터 좋은 지식을 제대로 무장한 상태로 말이다.</p><p>자, 그럼 이해가 됐으리라 생각된다. 이제 리액트의 탄탄한 기초를 다지기 위해서는 무엇을 어떤 순서로 제대로 배워야 하는지 궁금할 것이다.</p><p>정말 최소한 리액트 공식 문서의 <a href="https://reactjs.org/docs/hello-world.html" rel="nofollow"><strong>주요 컨셉</strong>에 대한 챕터</a>의 모든 주제에 대해서는 이해할 필요가 있다.</p><p><a href="https://reactjs.org/docs/hooks-intro.html" rel="nofollow"><strong>Hook</strong>을 다룬 챕터에 대해서도 어느 정도는 익숙해질 필요가 있다.</a> Hook은 이제 관습화되어서 모든 곳에, 특히 서드파티 리액트 패키지에서 사용되고 있다.</p><p><code>useState</code>나 <code>useEffect</code> 같이 더 많이 사용하는 내용도 물론 좋지만, <code>useMemo</code>, <code>useCallback</code>, <code>useRef</code> 같은 다른 내용에 대한 이해도 필수적이다.</p><p><a href="https://reactjs.org/docs/accessibility.html" rel="nofollow"><strong>고급 가이드</strong>라 불리는 다른 챕터</a>도 있는데, 초기에는 무조건 읽어야 한다고 생각하지 않지만, 리액트를 따라가는 여정에서 꼭 한 번 읽어보기를 추천한다.</p><p>늘 그렇지만 실무 경험이 어느 정도 있는 상태에서 고급 주제를 이해하기가 훨씬 쉽지만, 더 일찍 이 지식을 알게 된다면 더 나을 것이다.</p><p>당연하게도 리액트 공식 문서만 따라가는 것으로 제한하지 않아도 된다. 구성요소를 다루는 온라인 강의를 수강하고 튜토리얼을 시청하거나 블로그 포스트를 읽는 것도 기초 쌓기에 도움 되는 활동 중 하나다. 무엇이 가장 최선인지 시험해 보자.</p><p>최소한으로 알아야 할 가장 중요한 컨셉을 고르라 한다면, 아래의 목록을 추천한다.</p><ul><li>"상태(state)"란?</li><li>클래스와 함수형 컴포넌트의 흥망성쇠</li><li>컴포넌트 리렌더링이랑 무엇이고 어떻게 동작하는가</li><li>리렌더링을 하는 방법</li><li>컴포넌트의 다양한 생명주기와 이것을 어떻게 다룰 것인가</li><li>가상 DOM</li><li>CSR(클라이언트 사이드 렌더링)과 SSR(서버 사이드 렌더링)의 일반적인 혹은 리액트에서의 장점</li><li>제어 컴포넌트 vs 비제어 컴포넌트</li><li>상태 끌어올리기</li><li>적어도 하나의 전역 상태 관리 기법에 대해서 (Context API, Redux/Toolkit, Recoil)</li><li>컴포넌트 패턴 (특히 적절한 패턴을 고르는 것에 관하여)</li></ul><h2 id="--7">깨끗하고 성능 좋은, 유지 보수가 쉬운 리액트 컴포넌트 만드는 법 배워보기</h2><p>모든 프로그래머의, 적어도 나에게는 꿈같은 일이다. 나한테는 훌륭한 프로그래머와 좋은 프로그래머를 가르는 자질이기도 하다. 언제나 배워야 하고, 개선해야 할 점이 있기 때문에, 절대로 완벽해질 수 없다는 사실이 재미있는 지점이다.</p><p>아래 모범 사례를 따라가다 보면 팀원뿐만 아니라 자기 자신도 도움받을 수 있을 것이다. <em>스타일 가이드</em>를 통해 어떻게 코드를 짜는지에 대한 중요한 내용을 정의해 둔 개발팀을 본 적이 있다. 어떻게 생각하느냐고 묻는다면 훌륭한 아이디어라고 말할 것이다.</p><p>그중 일부는 아래와 같았다.</p><ul><li>함수형 컴포넌트 사용하기 (arrow-function 같은)</li><li>인라인 스타일을 사용하지 않기</li><li>적절한 import 구조를 유지하기 (서드파티 import를 먼저 --&gt; 내부에서 쓰는 import를 마지막으로)</li><li>커밋(commit) 전에 코드 형식 맞추기</li></ul><p>등등이 있었다.</p><p>찾아보면 더 자세한 내용을 알 수 있을 것이다. 팀에 따라 다르긴 하지만, 개인적으로는 숙련된 개발자라면 모종의 자유를 가질 법하고, 그렇게까지 제한될 필요가 없다고 생각하기 때문에 지나치게 자세한 스타일 가이드는 좋아하지 않는 편이다.</p><p>하지만, 대개 스타일 가이드는 개요를 짜고, 모범 사례를 좇으며 팀 모두가 중요한 지점에 대해서 같은 생각을 공유한다는 것을 확실히 해주는 좋은 방법이라고 생각한다. 이는 팀워크와 결과물의 질을 정말 많이 올려준다.</p><p>그럼, 이제 깨끗하며, 성능이 좋고, 유지 보수하기 좋은 컴포넌트를 생성할 수 있는 모범 사례란 무엇인지 살펴보도록 하자. 편안한 마음으로, 뭔가 받아 적을 노트를 준비한 상태에서 즐겨보자!</p><h3 id="--8">📁 좋은 폴더 구조 만들기</h3><p>리액트 애플리케이션 내부에 파일과 폴더를 정리해 두는 것은 유지보수성과 확장성에 필수적이다.</p><p><strong>좋은</strong> 폴더 구조란 애플리케이션의 규모와 팀에 따라 다르므로 일반적으로 적용되는 대답은 없다. 특히 의견이 분분한 주제이다 보니 각자의 선호에 따라서도 좌지우지된다.</p><p>그럼에도 시간이 좀 지나고 나선, 다양한 규모의 애플리케이션에 적용할 만한 몇 가지 좋은 사례들이 진화했다.</p><p><a href="https://www.robinwieruch.de/react-folder-structure/" rel="nofollow">이 굉장한 블로그 포스트</a>에서는 애플리케이션의 다섯 가지 규모를 다루며 어떻게 파일과 폴더를 정리할 수 있을지에 대한 좋은 아이디어를 소개한다. 애플리케이션을 기획하고 시작할 때 이 글을 유념한다면 장기적으로 보았을 때 큰 차이를 만들 수 있을 것이다.</p><p>너무 과한 기술을 사용하느니 현재의 애플리케이션과 팀 규모에 가장 잘 맞는 적절한 설계를 유지하는 데 최선을 다하자.</p><h3 id="-import-">👇 구조적인 import 순서 유지하기</h3><p>이미 리액트에 대한 경험이 어느 정도 있다면, import 문이 넘쳐나 비대해진 파일을 본 적 있을 것이다. 서드파티 패키지 같은 외부 import부터 다른 컴포넌트나 유틸 함수, 스타일 등 많은 내부 import문이 섞여 있었을 것이다.</p><p>아래는 실제 코드의 일부를 발췌한 예시이다.</p><figure class="kg-card kg-code-card"><pre><code class="language-jsx">import React, { useState, useEffect, useCallback } from "react";
import Typography from "@material-ui/core/Typography";
import Divider from "@material-ui/core/Divider";
import Title from "../components/Title";
import Navigation from "../components/Navigation";
import DialogActions from "@material-ui/core/DialogActions"
import { getServiceURL } from '../../utils/getServiceURL";
import Grid from "@material-ui/core/Grid";
import Paragraph from "../components/Paragprah";
import { sectionTitleEnum } from "../../constants";
import { useSelector, useDispatch } from "react-redux";
import Box from "@material-ui/core/Box";
import axios from 'axios';
import { DatePicker } from "@material-ui/pickers";
import { Formik } from "formik";
import CustomButton from "../components/CustomButton";</code></pre><figcaption>현실에서는 import문이 55줄이 넘어가기도 한다.</figcaption></figure><p>뭐가 문제인지 눈치챘을 것이다. 대체 뭐가 서드파티고, 뭐가 로컬(내부) import 문인지 알기 어렵다. 그룹으로 묶이지 않아서 엉망이다.</p><p>아까 예시를 개선해 보자.</p><figure class="kg-card kg-code-card"><pre><code class="language-jsx">import React, { useState, useEffect, useCallback } from "react";
import { useSelector, useDispatch } from "react-redux";
import { Formik } from "formik";
import axios from 'axios';
import Typography from "@material-ui/core/Typography";
import Divider from "@material-ui/core/Divider";
import Box from "@material-ui/core/Box";
import DialogActions from "@material-ui/core/DialogActions";
import Grid from "@material-ui/core/Grid";
import { DatePicker } from "@material-ui/pickers";

import { getServiceURL } from '../../utils/getServiceURL";
import { sectionTitleEnum } from "../../constants";
import CustomButton from "../components/CustomButton";
import Title from "../components/Title";
import Navigation from "../components/Navigation";
import Paragraph from "../components/Paragraph";</code></pre><figcaption>내부 및 외부 import문을 분리한 코드 예제</figcaption></figure><p>구조가 훨씬 더 깨끗해졌고 외부와 내부의 import문을 구분하기 쉬워졌다. 물론 (가능하다면 :)) named import 같은 기법을 사용해 여기서 더 최적화를 시도해 보아도 좋다. 이를테면 material-ui의 모든 컴포넌트를 한 줄로 import 해볼 수도 있다.</p><p>어떤 개발자는 세 영역으로 나누어 import문을 정리하기도 한다.</p><p>('react' 같이) 내장 -&gt; 외부 (서드파티 노트 모듈) -&gt; 내부 순이다.</p><p>직접 매번 관리하거나 <strong>linter</strong>를 사용해 보라. 리액트 앱에서 적당한 import 구조를 유지하도록 linter를 설정하는 방법에 대해 <a href="https://dev.to/otamnitram/sorting-your-imports-correctly-in-react-213m" rel="nofollow">이곳의</a> 훌륭한 글을 통해 확인할 수 있다.</p><h3 id="--9">📔 다양한 컴포넌트 패턴 알아보기</h3><p>유지보수와 확장이 불가능한 스파게티 코드로 끝나버리지 않으려면, 리액트 개발자로서 더 경험이 많아질수록 다양한 컴포넌트 패턴에 대해 학습하는 것이 필요하다.</p><p>이것이 끝은 아닌 것이, 여러 가지 패턴을 알아가는 것 자체가 좋은 기초가 된다. 이것의 가장 중요한 면은 어떤 문제에 대해 어떤 패턴을 적용할지 <strong>그때</strong>를 알게 된다는 점이다.</p><p>모든 패턴이 특정 목적을 위해 쓰인다. 예를 들어 <strong>compound component pattern</strong>은 컴포넌트의 레벨이 깊어질 때 발생하는 불필요한 <em>prop-drilling</em>을 피하고자 한다. 즉, 다섯 개 정도 되는 레벨을 지나야 이 prop이 필요한 컴포넌트에 도달하는 경우가 발생한다면, 여러 가지 방식으로 컴포넌트를 조직해 보자.</p><p>여담인데 예전에 props-drilling에 대한 정말 많은 논의가 있었던 걸로 안다. 이것이 좋은지 나쁜지에 대한 매우 의견이 분분했다. 나의 경우 컴포넌트 레벨이 두 개 이상을 거쳐 prop이 전달된다면 다른 방법이나 패턴을 생각해보려고 하는 편이다.</p><p>개발자로 일할 때 훨씬 효율적일 뿐만 아니라 작성하는 컴포넌트도 훨씬 더 유지보수 가능하며 확장가능해진다. 이런 패턴을 사용할 줄 안다면 다른 개발자와 차별화된 리액트 개발자로 보일 것이다. 직접 리서치해 보기를 정말 권하는데, 나의 경우 <a href="https://www.udemy.com/course/the-complete-guide-to-advanced-react-patterns/" rel="nofollow">이 Udemy 강의</a>가 매우 도움이 되었다.</p><h3 id="-linter-">🔒 linter를 사용해서 규칙을 지키기</h3><p>linter는 의존성 패키지의 import 순서를 식별하기 쉽게 해주는 데만 필요한 것이 아니다. 일반적으로 더 나은 코드를 짜도록 도와준다.</p><p><em>create-react-app</em>을 사용할 때 보면 이미 ESLint가 설정되어 있지만, 완전히 내 식대로 새로 작성하거나 기작성된 규칙을 확장할 수도 있다.</p><p>linter란 작성하고 있는 JavaScript 코드를 관측하고 코드를 실행할 때 발생할 가능성이 높은 에러를 미리 알려준다. linter를 정말로 잘 사용하게 되기까지 시간이 좀 걸렸지만, 지금은 linter 없이 일하는 상황을 상상하기 어렵다.</p><p>linter를 설치하는 것과 규칙을 준수하는 건 별개의 일이다. linter를 끌 수도 있다. 특정 코드 한 줄이나 파일 전체에 대해서 비활성화할 수 있다. 이렇게 하는 것이 이해되는 상황이 몇 군데 있을 수 있지만, 내 경험으로 비춰보았을 때는 드물었다.</p><p>또 다른 훌륭한 이점은 스타일 점검을 조정해 볼 수 있다는 점이다. 특히 팀 단위인 경우 더 도움이 된다. 일단 코드를 어떻게 작성할지, 양식화를 어떻게 할지에 대한 관습을 합의하고 나면 ESLint를 JSPrettify 같은 것과 통합해 사용하는 것도 쉽다.</p><h3 id="--10">🧪 코드 테스트해 보기</h3><p>개발자로서 테스트가 제일 좋아하는 종류의 일이 아니란 것은 잘 안다. 나도 역시 그랬다. 처음에는 테스팅이 불필요한데다가 방해되는 일로 느껴졌다. 단기적으로는 맞는 말처럼 들릴 수도 있다. 하지만 앱이 성장한다거나 하는 장기적 관점에서는 테스팅은 필수다.</p><p>테스팅이야말로 내가 하는 일에 전문성을 더해주고 소프트웨어를 고품질로 만들어주었다.</p><p>기본적으로 사람 손으로 직접 하는 수행하는 테스팅에는 아무 문제도 없으며 완전히 이걸 하지 말아야 할 이유는 없다. 그렇지만 새로운 기능을 추가하려고 한다거나 뭔가가 망가지지 않기를 바란다고 생각해 보자. 시간이 소모되는 업무로 느껴지며 휴먼 에러를 발생시킬 가능성이 있다.</p><p>테스트 코드를 작성하는 시간에는 이미 테스트를 통과하기 위한 코드를 머릿속으로 조직화하는 과정을 거치고 있는 셈이다. 어떤 위험성이 따라올는지 인식함으로써 이를 주시할 수 있게 되는 식으로 늘 도움이 되었다.</p><p>바로 코드를 짜는 과정에 뛰어들지 않으면서도 (나라면 절대 권장하지 않을) 목표에 대해 먼저 생각하는 것이다.</p><p>예를 들어서 "이 특정한 컴포넌트는 무얼 수행해야 할까? 테스트한다면 어떤 중요한 엣지 케이스가 발생할까? 컴포넌트를 오직 한 기능만 수행하도록 좀 더 단순화할 수는 없을까? .."</p><p>작성하려고 하는 코드에 대한 관점을 가질 때 이 목표를 지킬 수 있는 날카로운 집중력이 유지된다.</p><p>테스트는 일종의 문서화 방법으로도 기능할 수 있는데, 새로운 개발자가 처음 코드를 접할 때 소프트웨어의 각기 다른 부분이 어떻게 동작할 것인지를 이해하는 데 매우 도움이 된다.</p><p>그러니 괜히 <em>잔업</em>이 는다는 생각에 테스트를 피하지 말자. 현실에서는 일단 프로젝트가 자리 잡고 나서 미래의 야근을 피하게 해 줄 테니 말이다.</p><p><a href="https://reactjs.org/docs/testing.html" rel="nofollow">리액트 공식 문서의 "테스팅" 챕터</a>를 확인해보시라. 리액트에서 테스팅에 대한 몇 가지 튜토리얼을 확인해 보고 소규모 TDD 앱을 바로 작성해 보거나 현재 진행 중인 앱에 바로 테스팅을 도입해 보라.</p><h3 id="-typescript-defaultprops-prop-type-">🧰 Typescript를 도입하기 (아니라면 적어도 defaultProps와 prop type 사용해보기)</h3><p>소프트웨어 개발자로서 처음 우리 팀이 맡게 된 리액트 프로젝트가 이미 다른 회사에서 기본적인 부분을 이미 개발해 두었었다. 그 위에서 고객사의 프로젝트를 진행해야 했고, TypeScript도 이미 추가되어 있었다.</p><p>그때까지는 나와 팀원 모두 바닐라 JavaScript만 사용하다가 TypeScript에 대한 경험이 별로 없을 때였다.</p><p>그 프로젝트를 몇 주 진행하면서 우리는 TypeScript의 이점보다는 업무 흐름을 방해하는 존재로 느꼈다. 실제로는 TypeScript 경고를 피하려고 모든 타입은 <em>any</em>로 지정해 버렸으니 TypeScript를 유용하게 사용하고 있지도 않았다.</p><p>이러고 나자 프로젝트에서 아예 TypeScript를 제거하고 우리가 잘 아는 영역인 바닐라 JavaScript를 사용하자는 결론이 났다. 처음에는 괜찮았지만, 프로젝트가 더 복잡해지자 더 타입 에러가 발생하기 시작했다. 그때야 우리는 TypeScript를 완전히 제거해 버린 결정을 엄청나게 후회했다. 이런 일은 얼마든지 발생할 수 있으므로 미래를 위한 귀중한 경험을 한 셈이었다.</p><p>이런 상황 덕에 TypeScript에 두 번째 기회를 줘보자 싶어 남는 시간에 TypeScript를 배웠다. TypeScript로 사이드 프로젝트 몇 개를 해보고 나니, 이것이 없는 경우를 상상하기 어려워졌다.</p><p>TypeScript를 사용할 때의 장점을 몇 가지 나열해 보자면, 정적 타입 검사, IDE(intellisense)에서 더 나은 코드 완성도, 개발자 경험의 향상, 코드 작성하는 동안의 타입 에러 검출 등이 있다.</p><p>반대로 몇 가지 문제점도 당연히 있다. (Java나 C# 같은) 강타입 언어를 사용하지 않았다면 처음에는 TypeScript를 이해하기가 너무 어렵기 때문이다.</p><p>하지만 그럴 만한 가치가 충분하니 TypeScript를 사용해 보고 이를 경험해 보라. <a href="https://blog.bitsrc.io/5-strong-reasons-to-use-typescript-with-react-bc987da5d907" rel="nofollow">이곳</a>에서 리액트 애플리케이션에서 TypeScript를 사용하는 것의 장단점 개요를 이해할 수 있는 좋은 글을 읽을 수 있다. <a href="https://www.freecodecamp.org/news/how-to-code-your-react-app-with-typescript/" rel="nofollow">이 튜토리얼에서는</a> TypeScript 환경에서 리액트 앱을 작성하는 방법도 배울 수 있다.</p><p>리액트 앱 내에 TypeScript를 도입하고 싶지 않은 이유가 있을 수도 있다. 그럴 수 있다. 하지만 최소한 적어도 prop으로 엉망이 되고 싶지 않다면, 컴포넌트에 <strong>prop-types</strong>나 <strong>default-props</strong>을 써보는 것을 추천한다.</p><h3 id="-lazy-loading-">💎 lazy-loading / 코드 분리 시도해 보기</h3><p>JavaScript나 React 세계에서 시간을 좀 보내셨다면, <strong>번들링(bundling)</strong> 때문에 꽤 우여곡절이 많았을 것이다. 이 용어를 처음 들어보았다면, 리액트 공식 문서에서 어떻게 이야기하는지 한 번 살펴보자.</p><blockquote>대부분 리액트 앱은 <em>Webpack, Rollup 또는 Browserify</em> 같은 도구를 사용해 파일을 "번들한" 상태다. 번들링이란 파일을 가져와 이를 "번들"이라는 하나의 파일로 병합하는 과정을 말한다. 이런 번들은 한 번에 전체 앱을 로드하는 웹 페이지에 포함될 수 있다.</blockquote><p>기본적으로는 훌륭한 기술이지만, 앱이 커지면 꽤 골치 아파지기도 한다. 번들도 함께 비대해지기 때문이다. three.js 같은 서드파티 라이브러리를 사용한다고 할 때 특히 그렇다.</p><p>한 가지 위험한 점은 사용자가 비록 코드의 한 조각만 필요하다고 하더라도 번들은 항상 로드가 끝까지 완료되어야 한다는 부분이다. 이는 앱을 로드하기까지 불필요하게 오랜 시간이 걸리기 때문에 성능 문제로 이어진다.</p><p>이를 피하려면, <strong>코드 분리하기(code splitting)</strong>라는, 사용자의 필요에 맞게 코드를 조각 단위로 쪼개는 기술이 있다. Webpack, Rollup, Browserify 같은 많이 쓰이는 번들러는 모두 코드 분리 기능을 지원한다. 제일 큰 이점이라면 여러 번들을 생성하고 이를 동적으로 로드할 수 있다는 점이다.</p><p>번들을 쪼개는 것은 사용자가 오로지 필요로 할 때만 <strong>lazy load</strong>를 할 수 있도록 도와준다.</p><p>식료품점으로 가서 바나나, 사과, 빵을 좀 집어 오고 싶어 한다고 가정해 보자. 가게에 있는 모든 종류를 사는 것이 아니라 그중 바나나, 사과, 빵을 골라 오는 상황일 것이다. 당신은 그저 한 범위의 일부분에만 흥미가 있을 뿐이다. 그러니 전부를 살 이유가 없지 않은가? 더 오래 걸리며 심지어는 더 비쌀 텐데.</p><p>앱이 거대해질수록 잠재적으로 발생할 수 있는 문제들에 대해 인지하고 이런 이슈를 제거할 수 있는 기술을 바로 손에 익히는 것이 정말 중요하다. 더 자세히 알고 싶다면 <a href="https://reactjs.org/docs/code-splitting.html" rel="nofollow">리액트 공식 문서</a>에서 더 읽어볼 수 있다.</p><h3 id="--11">🗄️ 재사용 가능한 로직을 커스텀 훅으로 분리하기</h3><p>리액트 공식 문서에 따르면,</p><blockquote>훅이란, 컴포넌트의 위계를 변경하지 않으면서도 state 변화가 들어 있는 로직(stateful logic)을 재사용하도록 해 준다.</blockquote><p>이 말은 즉, 클래스 컴포넌트와의 조합으로 이전에 사용했던 기술에 대한 더 나은 해결법이라 할 수 있다. 코드를 작성해 온 지 좀 되었다면, <strong>고차 컴포넌트(HOC, Higher Order Components)</strong>나 <strong>render props</strong>를 사용해 본 기억이 날 것이다.</p><p>이미 다른 함수형 컴포넌트에서 사용되었던, state의 변화를 포함한 로직을 동일하게 사용하는 상황이라면, 커스텀 훅을 만들어보기에 정말 좋은 기회이다. 커스텀 훅에는 이 로직이 캡슐화되어 있고, 컴포넌트 안에 함수처럼 이 훅을 호출하기만 하면 된다.</p><p>스크린 사이즈에 따라 UI가 변경되며, 브라우저의 창이 (사용자에 의해) 직접 변경될 때마다 현재 창 크기를 계속 추적하고 싶은 상황에 대한 예시를 빠르게 훑어보자.</p><figure class="kg-card kg-code-card"><pre><code class="language-jsx">const ScreenDimensions = () =&gt; {
  const [windowSize, setWindowSize] = useState({
    width: undefined,
    height: undefined,
  });
  
  useEffect(() =&gt; {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }
    window.addEventListener('resize', handleResize);
    handleResize();
    return () =&gt; window.removeEventListener('resize', handleResize);
  }, []);
  
  return (
  	&lt;&gt;
    	&lt;p&gt;Current screen width: {windowSize.width}&lt;/p&gt;
        &lt;p&gt;Current screen height: {windowSize.height}&lt;/p&gt;
    &lt;/&gt;
  )
}</code></pre><figcaption>출처: <a href="https://usehooks.com/useWindowSize/" rel="nofollow">https://usehooks.com/useWindowSize/</a></figcaption></figure><p>보시다시피 해결법이 꽤 직접적인 데다가 이렇게 정의한다고 해서 문제가 될 것은 없어 보인다.</p><p>까다로운 부분은 지금부터다. 정확히 같은 로직을 다른 컴포넌트에서도 사용하고 싶다고 가정해 보자. 이 경우에는 현재의 스크린 사이즈를 기준으로 (하나는 스마트폰, 하나는 데스크톱용으로) 다른 UI를 렌더하려고 한다.</p><p>물론 해당 로직을 복사-붙여넣기 해도 된다. 하지만, DRY 원칙(역자 주-Don't Repeat Yourself)으로부터 알 수 있듯이, 이건 그렇게 좋은 습관이 아니다.</p><p>이 로직을 조정이라도 하고 싶다면, 두 컴포넌트 모두에서 수정해야 한다. 더 많은 컴포넌트에 이 로직을 붙여 넣을 때면 더 유지보수하기 어려워지고 더 에러가 나기 쉬워진다.</p><p>그렇다면 보통 바닐라 JavaScript에서는 어떤 식으로 할까? 로직을 캡슐화한 함수를 정의해 둬서 다양한 부분들에서 사용될 수 있도록 할 것이다. 훅으로 하고자 하는 것도 바로 이것과 같다. JavaScript 함수에서 크게 더해지는 것은 없지만, 리액트 훅이기 때문에 몇 가지 리액트만의 특장점을 가질 뿐이다.</p><p>커스텀 훅은 아래처럼 보일 것이다.</p><pre><code class="language-jsx">const useWindowSize = () =&gt; {
  const [windowSize, setWindowSize] = useState({
    width: undefined,
    height: undefined,
  });
  
  useEffect(() =&gt; {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }
    window.addEventListener('resize', handleResize);
    handleResize();
    return () =&gt; window.removeEventListener('resize', handleResize);
  }, []);
  
  return windowSize;
}</code></pre><p>이제 <strong>ScreenDimensions</strong>라는 컴포넌트에 손쉽게 호출할 수 있다.</p><pre><code class="language-jsx">const ScreenDimensions = () =&gt; {
  const windowSize = useWindowSize()
  
  return (
  	&lt;&gt;
    	&lt;p&gt;Current screen width: {windowSize.width}&lt;/p&gt;
        &lt;p&gt;Current screen height: {windowSize.height}&lt;/p&gt;
    &lt;/&gt;
  )
}</code></pre><p>다른 어떤 컴포넌트에라도 커스텀 훅을 호출해 컴포넌트에서 사용하고자 하는 변수(여기에서는 현재의 window size)에 반환값을 저장할 수 있다.</p><pre><code class="language-jsx">const ResponsiveView = () =&gt; {
  const windowSize = useWindowSize()
  
  return (
  	&lt;&gt;
    	{windowSize.width &lt;= 960 ? (
          &lt;SmartphoneView /&gt;
        ) : (
          &lt;DesktopView /&gt;	
        )}
    &lt;/&gt;
  )
}</code></pre><h3 id="--12">🖥️ 효과적으로 에러 처리하기</h3><p>많은 개발자가 효과적으로 에러를 처리하기를 종종 간과하거나 과소평가하곤 한다. 다른 모범 사례들처럼 이것 역시 초기에는 나중에 생각할 것으로 여겨진다. 일단 코드가 동작하도록 만들고 싶고 에러에 대해서 생각하느라 시간을 "낭비"하고 싶지 않은 것이다.</p><p>하지만 일단 경험이 쌓이면서, 에러 처리를 더 잘해두었더라면 에너지를(당연하게도 귀중한 시간) 무척 절약할 수 있었을 법한 지저분한 상황을 겪고 나면, 장기적으로 보았을 때 애플리케이션 내에 견고하게 에러를 처리하는 것이 필수적이라는 사실을 알게 된다. 특히 애플리케이션이 프로덕션으로 배포되고 나면 더욱 그렇다.</p><p>하지만 정확히 <em>에러 처리하기</em>가 리액트 세계에서 의미하는 바가 무엇일까? 각자 수행하는 역할이 몇 가지가 있다. 하나는 <strong>catch</strong> 에러고, 또 하나는 그에 맞는 UI를 <strong>다루는 것</strong>이며 마지막으로 이를 적절히 <strong>로그</strong>하는 것이다.</p><h4 id="react-error-boundary">React Error Boundary</h4><p>React Error Boundary는 커스텀 클래스 컴포넌트로, 애플리케이션 전체를 감싸는 데 사용된다. 물론 예를 들자면 더 세부적인 UI를 렌더하기 위해서 컴포넌트 트리 깊게 내려간 컴포넌트 주위를 감싸는 ErrorBoundary 컴포넌트를 컨테이너 삼아도 된다. 기본적으로 에러가 날 법한 컴포넌트 주위로 ErrorBoundary로 에워싸는 것이 좋은 모범 사례로 본다.</p><p>수명주기 메소드인 <code>componentDidCatch()</code>를 통해 렌더링 단계나 다른 자식 컴포넌트의 수명 주기 동안 에러를 감지할 수 있다. 이 단계에서 에러가 발생한다면, 감지된 부분이 상위로 올라가 ErrorBoundary 컴포넌트에서도 걸린다.</p><p>로깅 서비스를 (무척 추천하는 방법이다) 사용하고 있다면, 이 서비스를 연결하기 좋은 지점이기도 하다.</p><p><code>getDerivedStateFromError()</code>라는 정적 함수는 렌더 단계 동안 호출되며 ErrorBoundary 컴포넌트의 상태값을 업데이트하는 데 쓰인다. 상태값에 따라 조건적으로 에러 UI를 렌더할 수 있다.</p><pre><code class="language-jsx">class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    //log the error to an error reporting service
    errorService.log({ error, errorInfo });
  }

  render() {
    if (this.state.hasError) {
      return &lt;h1&gt;Oops, something went wrong.&lt;/h1&gt;;
    }
    return this.props.children; 
  }
}</code></pre><p></p><p>이 접근법의 가장 큰 문제점은 서버 사이드 렌더링이나 이벤트 핸들러에서 발생하는 비동기 콜백 에러가 범위 바깥이라 처리할 수 없다는 점이다.</p><h4 id="-try-catch-">범위 밖의 에러 처리를 위한 try-catch 사용해보기</h4><p>이 방법은 비동기 콜백 내부에서 발생하는 에러를 잡는 데에 효과적이다. API에서 사용자의 프로필 데이터를 가져오고 있고, Profile 컴포넌트에서 이를 보여주고자 한다고 가정하자.</p><pre><code class="language-jsx">const UserProfile = ({ userId }) =&gt; {
	const [isLoading, setIsLoading] = useState(true)
	const [profileData, setProfileData] = useState({})
    
    useEffect(() =&gt; {
        // 비동기 함수로 분리하기 
        const getUserDataAsync = async () =&gt; {
        	try {
            	// API에서 사용자 데이터 가져오기 
            	const userData = await axios.get(`/users/${userId}`)

                // 사용자의 데이터가 존재하지 않는다면 (catch 블럭에서 걸리도록) 에러 발생시키기
                if (!userData) {
                	throw new Error("No user data found")
                }

                // 사용자의 데이터가 존재한다면 상태를 업데이트 하기 
                setProfileData(userData.profile)
            } catch(error) {
                // 어떤 에러든 로깅 서비스를 통해 로그하기 
            	errorService.log({ error })
                // 상태 업데이트 
                setProfileData(null)
            } finally {
                // 어떤 경우라도 로딩 상태는 초기화해 주기 
                setIsLoading(false)
            }
        }
        
        getUserDataAsync()
    }, [])
    
    if (isLoading) {
    	return &lt;div&gt;Loading ...&lt;/div&gt;
    }
    
    if (!profileData) {
    	return &lt;ErrorUI /&gt;
    }
    
    return (
    	&lt;div&gt;
        	...User Profile
        &lt;/div&gt;
    )
}</code></pre><p>컴포넌트가 마운트되고 나면, props로부터 받은 userId에 해당하는 사용자의 데이터를 받기 위해 API로 GET 요청을 시작한다.</p><p>try-catch를 사용하면 API를 호출하는 동안 발생할 수 있는 에러를 모든 감지할 수 있다. 예를 들어 이 경우에는 API로부터 404 혹은 500을 응답받게 될 것이다.</p><p>에러가 발생하면, catch 블럭으로 오게 되는데, 파라미터로써 에러를 받게 될 것이다. 로깅 시스템을 통해 에러가 로그되며, 상태를 업데이트하고 그에 맞는 커스텀 에러 UI를 보여줄 것이다.</p><h4 id="-react-error-boundary-">(개인적으로 추천하는) react-error-boundary 라이브러리 사용해보기</h4><p>이 라이브러리는 위에 소개한 두 기법이 모두 녹아 있다고 보면 된다. 리액트에서 에러 처리를 단순화하고 앞서 언급한 ErrorBoundary 컴포넌트의 한계를 극복했다.</p><pre><code class="language-jsx">import { ErrorBoundary } from 'react-error-boundary'

const ErrorComponent = ({ error, resetErrorBoundary }) =&gt; {
  
  return (
    &lt;div role="alert"&gt;
      &lt;p&gt;Something went wrong:&lt;/p&gt;
      &lt;pre&gt;{error.message}&lt;/pre&gt;
    &lt;/div&gt;
  )
}

const App = () =&gt; {
  const logError = (error, errorInfo) =&gt; {
  	errorService.log({ error, errorInfo })
  }
  

  return (
    &lt;ErrorBoundary 
       FallbackComponent={ErrorComponent}
       onError={logError}
    &gt;
       &lt;MyErrorProneComponent /&gt;
    &lt;/ErrorBoundary&gt;
  );
}</code></pre><p></p><p>이 라이브러리는 이미 살펴본 바 있는 ErrorBoundary 기능에 새로운 의미를 더한 컴포넌트로 구성되어 있다. prop으로써 <code>FallbackComponent</code>을 가지며 에러가 발생하면 렌더가 될 컴포넌트를 전달할 수 있게 한다.</p><p><code>onError</code>라는 prop은 에러가 발생하면 호출되는 콜백 함수도 제공한다. 로깅 서비스에 에러를 로그할 때 사용하기에 무척 좋다.</p><p>도움이 될 만한 다른 prop도 있다. 더 알고 싶다면, <a href="https://www.npmjs.com/package/react-error-boundary?activeTab=readme" rel="nofollow">이 문서</a>를 읽어볼 수 있다.</p><p>이 라이브러리는 <code>useErrorHandler()</code>라는 훅을 제공하는데, 비동기 코드든 서버 사이드 렌더링이든 이벤트 핸들러 같은 범위 바깥의 에러도 감지할 수 있게 해준다.</p><h4 id="--13">에러 로깅하기</h4><p>효과적으로 에러를 감지하고 처리했다면 이다음은 적절하게 로깅해보기가 있다. 애플리케이션 내에 에러 처리 방법이 구축되었다면, 일관적으로 이를 로그할 줄 알아야 한다.</p><p>가장 자주 사용되는 방법은 오래됐지만 여전히 좋은 <strong>console.log</strong>라는 방법이다. 개발 단계에서 빠르게 확인하고자 한다면 좋지만, 프로덕션으로 배포된 상황이라면 쓸모없어진다. 사용자의 브라우저 내에서만 에러가 확인되니 전혀 효과적이지 않다.</p><p>프로덕션일 때 에러를 로깅하고자 한다면, 개발자인 <strong>여러분</strong>은 이를 고치기 위해 특정한 장소에서 에러를 확인하고 싶을 것이다.</p><p>이런 이유로 직접 로깅 서비스를 만들거나 서드파티 라이브러리를 사용할 필요가 생긴다.</p><p>서드파티 로깅 서비스를 사용한다면 개인적으로는 단연코 <strong>Sentry</strong>를 추천한다. 한 번쯤은 꼭 확인하길 바란다.</p><h3 id="-key-prop-">☝️ 앱 전체에서 key prop을 고유하게 유지하기</h3><p>데이터의 렌더를 위해 배열을 매핑할 때, 요소마다 항상 <strong>key </strong>속성을 정의해주어야 한다. key prop으로 각 요소의 <strong>index</strong>를 사용하는 것이 나도 그렇고 다른 사람들도 간단하고 흔하게 사용하는 방법으로 보인다.</p><p>리액트가 정확히 어떤 요소가 바뀌는지, 추가되는지, 혹은 삭제되는지 알 수 있도록 하므로 key prop 사용은 중요하다. 컴포넌트의 상태가 변하면서 UI가 새로운 상태를 가지고 리렌더링 되는 상황을 상상해 보자. 리액트가 업데이트되려면 이전 UI와 새로운 UI 간의 차이를 알아야 한다.</p><p>"무슨 요소가 추가/삭제 혹은 변화되었나?"</p><p>그러므로 key prop은 고유해야 한다. 현재 요소의 index를 사용해 특정 map 함수에서 오직 고유한 key prop을 사용하도록 하는 것이다.</p><p>현 시즌의 풋볼팀의 점수 기록을 보여주고 싶다고 할 때, 아래처럼 해볼 수 있을 것이다.</p><pre><code class="language-jsx">const SeasonScores = ({ seasonScoresData }) =&gt; {
	
    return (
    	&lt;&gt;
        	&lt;h3&gt;Our scores in this season:&lt;h3&gt;
        	{seasonScoresData.map((score, index) =&gt; (
    			&lt;div key={index}&gt;
        			&lt;p&gt;{score.oponennt}&lt;/p&gt;
        			&lt;p&gt;{score.value}&lt;/p&gt;
        		&lt;/div&gt;
    		))}
        &lt;/&gt;
    )
}</code></pre><p>만약에 이 map 함수에서만 고유하다고 한다면, 나중에는 문제가 발생할 수 있다. 리액트 애플리케이션이나 심지어 한 컴포넌트 안에서도 map 함수를 하나 이상 갖는 상황이 꽤 흔하기 때문이다.</p><p>현재 선수 명단을 보여주는 컴포넌트 내부에 또 다른 map 함수가 있다고 해보자.</p><pre><code class="language-jsx">const SeasonScores = ({ seasonScoresData, currentRoster }) =&gt; {
	
    return (
    	&lt;&gt;
        	&lt;h3&gt;Our scores in this season:&lt;h3&gt;
        	{seasonScoresData.map((score, index) =&gt; (
    			&lt;div key={index}&gt;
        			&lt;p&gt;{score.oponennt}&lt;/p&gt;
        			&lt;p&gt;{score.value}&lt;/p&gt;
        		&lt;/div&gt;
    		))}
            &lt;/br&gt;
			&lt;h3&gt;Our current roster:&lt;h3&gt;
        	{currentRoster.map((player, index) =&gt; (
            	&lt;div key={index}&gt;
                	&lt;p&gt;{player.name}&lt;/p&gt;
                    &lt;p&gt;{player.position}&lt;/p&gt;
                    &lt;p&gt;{player.jerseyNumber}&lt;/p&gt;
                    &lt;p&gt;{player.totalGoals}&lt;/p&gt;
                &lt;/div&gt;
    		))}
        &lt;/&gt;
    )
}</code></pre><p>결국 컴포넌트 내부에 많은 key가 중복되어 쓰이는 상황에 처했다. <code>seasonScoresData</code> 내부에 <strong>14</strong>개의 요소가 있고, <code>currentRoaster</code> 내부에 <strong>30</strong>개의 요소가 있다고 해보자. 0-13 숫자가 두 번씩 key prop으로 쓰였다. 고유한 key prop을 가진다는 목적을 충족하지 못한다.</p><p>이는 리액트가 오직 한 아이템에 대해서만 리렌더를 수행하고, 다른 부분을 생략할 수 있기 때문에 문제 상황으로 이어질 가능성이 있다. 혹은 UI 트리를 업데이트하는 과정에 비효율이 발생할 수도 있다. 더 자세한 예시를 확인해 보려면 이 팁 아래에 추천하는 블로그 포스트 글을 한 번 확인해 보라.</p><p>이런 원치 않는 상황을 피하려면, 늘 <strong>애플리케이션 전체에서 고유한 key</strong>를 사용해야 한다. 이상적으로는 가능하다면 배열의 각 아이템이 각자의 고유한 id를 갖는 것이리라. 하지만, 늘 이럴 수만은 없기 때문에, <strong>uuidv4</strong> 같은 외부 라이브러리를 통해 고유한 id 값을 생성시킬 수 있다.</p><p>이를 유념해 두고 두 배열의 모든 아이템이 id 속성을 갖는다는 가정을 해보자. 컴포넌트는 아래와 같을 것이다.</p><pre><code class="language-jsx">const SeasonScores = ({ seasonScoresData, currentRoster }) =&gt; {
	
    return (
    	&lt;&gt;
        	&lt;h3&gt;Our scores in this season:&lt;h3&gt;
        	{seasonScoresData.map((score, index) =&gt; (
    			&lt;div key={score.id}&gt;
        			&lt;p&gt;{score.oponennt}&lt;/p&gt;
        			&lt;p&gt;{score.value}&lt;/p&gt;
        		&lt;/div&gt;
    		))}
            &lt;/br&gt;
			&lt;h3&gt;Our current roster:&lt;h3&gt;
        	{currentRoster.map((player, index) =&gt; (
            	&lt;div key={player.id}&gt;
                	&lt;p&gt;{player.name}&lt;/p&gt;
                    &lt;p&gt;{player.position}&lt;/p&gt;
                    &lt;p&gt;{player.jerseyNumber}&lt;/p&gt;
                    &lt;p&gt;{player.totalGoals}&lt;/p&gt;
                &lt;/div&gt;
    		))}
        &lt;/&gt;
    )
}</code></pre><p>더 자세히 알고 싶다면, 해당 주제에 대해 <a href="https://medium.com/swlh/understanding-the-importance-of-the-key-prop-in-react-f2b92ce65f45" rel="nofollow">이 블로그 포스트</a>를 참고할 수 있다.</p><h2 id="--14">더 나은 리액트 코드 작성을 위한 팁 - 화룡점정</h2><figure class="kg-card kg-image-card"><img src="https://camo.githubusercontent.com/26c8f440a3340d9a31322a65ced4d850fad31abb21be347e02a505a18bd96f25/68747470733a2f2f7777772e66726565636f646563616d702e6f72672f6e6577732f636f6e74656e742f696d616765732f73697a652f77313630302f323032322f30312f6a6f616e6e612d6b6f73696e736b612d5f784e375562635a3333492d756e73706c6173682e6a7067" class="kg-image" alt="이미지" width="600" height="400" loading="lazy"></figure><p>이 가이드를 집 짓기의 과정에 비교해보고 싶다. 첫 번째로 <em>리액트의 구성 요소를 배우기</em>가 애플리케이션을 짓는 견고한 토대다. 두 번째로 <em>깨끗하고 성능 좋은, 유지 보수가 쉬운 리액트 컴포넌트 만드는 법 배워보기</em>는 벽을 세우는 과정이다.</p><p>그렇다면 이제부터는 집을 완성하기 위해 꼭대기에 지붕을 세우는 것이라 할 수 있다. <em>화룡점정</em>이라 부르는 이유다. 이번 팁은 더욱더 세세할 것이다.</p><p>대부분은 이전에 언급한 것보다는 강제성이 없지만, 그래도 이들을 제대로 사용할 줄 안다면 큰 차이를 만들어낼 수 있다.</p><h3 id="-usereducer-hook-">🪄 더 이르게 useReducer hook을 사용해 보기</h3><p>리액트에서 가장 자주 사용되는 훅 중 하나는 <strong>useState</strong>일 것이다. 컴포넌트를 생성하고 시간이 지나자 여러 상태로 가득해지곤 한다. 그러니 컴포넌트가 useState 훅으로 넘쳐나는 것도 당연하다.</p><pre><code class="language-jsx">const CustomersMap = () =&gt; {
  const [isDataLoading, setIsDataLoading] = useState(false)
  const [customersData, setCustomersData] = useState([])
  const [hasError, setHasError] = useState(false)
  const [isHovered, setIsHovered] = useState(false)
  const [hasMapLoaded, setHasMapLoaded] = useState(false)
  const [mapData, setMapData] = useState({})
  const [formData, setFormData] = useState({})
  const [isBtnDisabled, setIsBtnDisabled] = useState(false)
  
  ...
  
  return ( ... )
}</code></pre><p></p><p>여러 가지 useState 훅이 생기는 것은 규모는 물론 컴포넌트의 복잡성까지 커지고 있다는 분명한 신호다.</p><p>더 작은 컴포넌트를 만들어 상태나 JSX를 옮겨둘 수 있다면, 이렇게 하는 것이 좋다. useState 혹과 JSX를 한 번에 깨끗하게 정리할 수 있다.</p><p>위 예시에서 아래 두 state를 별개의 컴포넌트에 담아 모든 상태와 JSX가 양식(form)에 대해서만 다루도록 할 수도 있다.</p><p>하지만 도무지 말이 안 돼서, 컴포넌트 안에 이렇게 많은 state를 가지고 있어야만 하는 상황이 있을 수도 있다. 컴포넌트의 가독성을 개선하려면 <strong>useReducer</strong> 훅을 사용해 보자.</p><p>공식 리액트 문서는 이렇게 설명한다.</p><blockquote><code>useReducer</code>는 보통 여러 부차적인 값을 포함하는 복잡한 상태 로직이 있는 경우나 다른 상태가 이전의 상태를 의존하는 상황에서 <code>useState</code>보다 선호된다. useReducer는 또한 콜백 대신 dispatch를 전달하면서 깊은 업데이트를 수행하는 컴포넌트를 위해 성능을 최적화할 수도 있다.</blockquote><p>이 말을 생각해 보면, 컴포넌트는 <code>useReducer</code>를 사용하면 아래처럼 바꿔볼 수 있다.</p><pre><code class="language-jsx">// 초기 상태
const initialState = {
  isDataLoading : false
  customerData: [],
  hasError: false,
  isHovered: false,
  hasMapLoaded: false,
  mapData: {},
  formdata: {},
  isBtnDisabled: false
}

// REDUCER
const reducer = (state, action) =&gt; {
  switch (action.type) {
    case 'POPULATE_CUSTOMER_DATA':
      return {
        ...state,
        customerData: action.payload
      }
    case 'LOAD_MAP':
      return {
        ...state,
        hasMapLoaded: true
      }
    ...
    ...
    ...
    default: {
      return state
    }	
  }
}

// COMPONENT
const CustomersMap = () =&gt; {
    const [state, dispatch] = useReducer(reducer, initialState)
    ...
    
    return ( ... )
}</code></pre><p></p><p>컴포넌트 자체가 매우 깨끗해졌고, 공식 문서에서 읽은 것처럼 몇 가지 이점이 함께 눈에 띈다. Redux에 익숙하다면 reducer의 개념이나 이를 사용하는 방법이 그다지 새롭지는 않을 것이다.</p><p>나는 예를 들어 컴포넌트에 사용한 useState 훅이 네 개가 넘어가거나, state 자체가 그냥 boolean 값 이상으로 복잡해진다면 useReducer를 사용하는 규칙을 세워두고 있다. 양식에 사용되는 객체 하나가 더 깊고 복잡해진 경우가 될 수도 있겠다.</p><h3 id="-boolean-props-">🔌 boolean props에는 약어 사용해 보기</h3><p>컴포넌트에 boolean 값을 전달하는 경우가 종종 있다. 많은 개발자들은 아래처럼 사용하곤 한다.</p><pre><code class="language-jsx">&lt;RegistrationForm hasPadding={true} withError={true} /&gt;</code></pre><p>그런데 prop 자체가 참인지 (prop이 전달된다면) 거짓인지 (prop이 빠져 있다면) 둘 중 하나인 상황이므로 굳이 이렇게 작성할 필요가 없다.</p><p>아래가 훨씬 깔끔한 접근법이다.</p><pre><code class="language-jsx">&lt;RegistrationForm hasPadding withError /&gt;</code></pre><h3 id="-string-props-">👎 string props에는 중괄호를 피하기</h3><p>방금 살펴본 팁은 string prop에도 적용해 볼 수 있다.</p><pre><code class="language-jsx">&lt;Paragraph variant={"h5"} heading={"A new book"} /&gt;</code></pre><p>prop에 바로 string을 직접 쓸 수 있기 때문에 이런 경우에는 중괄호를 사용하지 않아도 된다. JSX 요소에 className을 바로 사용하고 싶다고 해도 string 형태로 직접 사용할 수 있다.</p><p>string이 아니라 JavaScript 표현식을 사용하고 싶다면 중괄호를 사용해야 한다. 가령 숫자나 객체를 사용하고 싶을 때가 있을 것이다. template string도 마찬가지다. (내가 그랬던 것처럼 헷갈리지 마시길)</p><p>단순한 string이라면 예시에서처럼 그냥 이렇게 사용하면 된다.</p><pre><code class="language-jsx">&lt;Paragraph variant="h5" heading="A new book" /&gt;</code></pre><h3 id="-props-spread-non-html-">🧹 props를 전개(spread)할 때 non-html 속성을 삭제하기</h3><p>빠르게 예제를 살펴보자.</p><pre><code class="language-jsx">const MainTitle = ({ isBold, children, ...restProps }) =&gt; {
	
  return (
    &lt;h1 
      style={{ fontWeight: isBold ? 600 : 400 }}
      {...restProps}
    &gt;
      {children}
    &lt;/h1&gt;
  )
}</code></pre><p>h1 태그를 렌더하고, prop 몇 가지를 추출하고, h1 태그에 삽입될 수 있는 다른 잠재적인 prop을 전개하는 컴포넌트를 하나 만들었다. 아직까지는 좋아 보인다.</p><p>이제, 다른 컴포넌트 안에 이를 사용해서 수동으로 h1을 굵게 할지 말지를 해볼 수도 있다.</p><pre><code class="language-jsx">// 굵은 제목
const IndexPage = () =&gt; {
	
  return (
    &lt;&gt;
      &lt;MainTitle isBold&gt;
        Welcome to our new site!
      &lt;/MainTitle&gt;
      ...
    &lt;/&gt;
  )
}</code></pre><pre><code class="language-jsx">// 굵은 제목이 아닌
const AboutPage = () =&gt; {
	
  return (
    &lt;&gt;
      &lt;MainTitle&gt;
      	Some quick lines about us!
      &lt;/MainTitle&gt;
      ...
    &lt;/&gt;
  )
}</code></pre><p>에러나 경고 메시지 없이 완벽하게 동작하는 것처럼 보인다. 흥미로운 부분은 이 h1 태그에 직접 전개된 다른 prop을 쓰면서부터다.</p><p>id, class 같은 적합한 HTML 속성을 사용한다면 에러 없이 모든 것이 잘 동작할 것이다. ("className"은 "class"가 되는 것을 명심하라)</p><pre><code class="language-jsx">const IndexPage = () =&gt; {
	
  return (
    &lt;&gt;
      &lt;MainTitle isBold id="index-main-title" className="align-left"&gt;
        Welcome to our new site!
      &lt;/MainTitle&gt;
      ...
    &lt;/&gt;
  )
}</code></pre><p>위의 모든 prop은 <strong>{...restProps}</strong>를 사용하고 있으니 h1의 속성으로써 추가될 것이다. 뭐가 됐든 여기에 추가한, 빼내지 않은 props는 h1 태그에 삽입될 것이다.</p><p>여러 상황에 훌륭하게 적용되지만, 동시에 문제가 있기도 하다.</p><pre><code class="language-jsx">// Page Component
const IndexPage = () =&gt; {
	
  return (
    &lt;&gt;
      &lt;MainTitle isBold hasPadding&gt;
        Welcome to our new site!
      &lt;/MainTitle&gt;
      ...
    &lt;/&gt;
  )
}

// MainTitle Component
const MainTitle = ({ isBold, children, ...restProps }) =&gt; {
	
  return (
    &lt;h1 
      style={{ 
        fontWeight: isBold ? 600 : 400,
        padding: restProps.hasPadding ? 16 : 0
      }}
      {...restProps}
    &gt;
      {children}
    &lt;/h1&gt;
  )
}</code></pre><p>위 코드에서 <code>MainTitle</code> 컴포넌트에 <code>hasPadding</code> 이라는 새로운 prop을 추가했고, 이는 필수 prop이 아니다. 컴포넌트 내부에는 props로부터 이를 추출하고 있지 않고, <code>restProps.hasPadding</code>을 통해 호출하고 있다.</p><p>코드는 동작하지만, 브라우저를 열어보면, h1 태그에 적용하려고 했던 <code>hasPadding</code>이 non-HTML 속성이라는 경고를 보게 될 것이다. h1 태그의 <code>{...restProps}</code>이 <code>isBold</code>처럼 추출되지 않는 <code>hasPadding</code> 때문이다.</p><p>이를 피하려면, non-HTML 요소부터 prop에서 늘 먼저 추출해, JSX 요소에 전개하려고 하는 <code>restProps</code>에 적합한 HTML 속성만 남도록 해야 한다.</p><p>예제는 이렇게 될 것이다.</p><pre><code class="language-jsx">// Page Component
const IndexPage = () =&gt; {
	
  return (
    &lt;&gt;
      &lt;MainTitle isBold hasPadding&gt;
        Welcome to our new site!
      &lt;/MainTitle&gt;
      ...
    &lt;/&gt;
  )
}

// MainTitle Component
const MainTitle = ({ isBold, children, hasPadding, ...restProps }) =&gt; {
	
  return (
    &lt;h1 
      style={{ 
        fontWeight: isBold ? 600 : 400,
        padding: hasPadding ? 16 : 0
      }}
      {...restProps}
    &gt;
      {children}
    &lt;/h1&gt;
  )
}</code></pre><p>브라우저의 콘솔에 불필요한 경고가 넘쳐난다면 너무 지저분할 것이다. 디버깅할 때라면 더더욱.</p><p>이 주제에 대해 더 정보를 알아보고 해결 방법이 궁금하다면, <a href="https://reactjs.org/warnings/unknown-prop.html" rel="nofollow">리액트 문서의 이 주제</a>를 살펴보시라.</p><h3 id="-snippet-extensions-">🔥 snippet extensions 사용하기</h3><p>비주얼 스튜디오 코드에서는 생산성을 많이 높여주는 특정 확장 프로그램이 있다. 이런 확장 프로그램 중 하나는 <strong>snippet</strong>이다.</p><p>그 많은 보일러플레이트 코드를 여러 번 작성하지 않도록 해준다는 점이 큰 장점이다. 새로운 컴포넌트를 계속 만드는데, 같은 걸 계속 타이핑한다고 생각해 보라.</p><pre><code class="language-jsx">import React from 'react'

const GoogleMap = () =&gt; {

}

export default GoogleMap</code></pre><p>snippet을 사용한다면 예를 들어 <code>rafce</code>만 작성하고 탭을 누르면, 같은 보일러 플레이트 코드가 생성된다. 시간 절약은 물론 개발 속도도 빨라진다.</p><p><strong>하지만 주의해서 사용할 것!</strong> 모든 개발자에게 권하지는 못하겠다. 내 생각에는 초보자들은 snippet을 쓰는 대신 보일러 플레이트를 일일이 손으로 타이핑해보아야 한다. 이렇게 하면 근육 기억이 생겨나 배운 것을 더욱 분명하게 해 줄 것이다.</p><p>너무 많이 타이핑해서 잠자면서도 할 수 있을 정도라면, 그래서 지겹다면 snippet을 사용하기에 알맞다.</p><p>추천하는 확장 프로그램 목록</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://camo.githubusercontent.com/129a36a094be72db3eadaf9b60c3590f2102d934a0927a44b7860af4e114bd61/68747470733a2f2f7777772e66726565636f646563616d702e6f72672f6e6577732f636f6e74656e742f696d616765732f323032322f30322f42696c6473636869726d666f746f2d323032322d30322d30312d756d2d31342e35352e30322e706e67" class="kg-image" alt="ES7+ React/Redux/React-Native snippets" width="600" height="400" loading="lazy"><figcaption>ES7+ React/Redux/React-Native snippets</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://camo.githubusercontent.com/326ef6c87073aed3efe31571a069930ed4282cdf5249eaec46b9a09d50bd37d5/68747470733a2f2f7777772e66726565636f646563616d702e6f72672f6e6577732f636f6e74656e742f696d616765732f323032322f30322f42696c6473636869726d666f746f2d323032322d30322d30312d756d2d31352e30352e30312e706e67" class="kg-image" alt="JavaScript (ES6) code snippets" width="600" height="400" loading="lazy"><figcaption>JavaScript (ES6) code snippets</figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://camo.githubusercontent.com/589959cf1c8271f17870cc2cfbfab03256ada67c5943bfe4cb7e8609b014f78f/68747470733a2f2f7777772e66726565636f646563616d702e6f72672f6e6577732f636f6e74656e742f696d616765732f323032322f30322f42696c6473636869726d666f746f2d323032322d30322d30312d756d2d31352e30362e35392e706e67" class="kg-image" alt="Mithril Emmet" width="600" height="400" loading="lazy"><figcaption>Mithrill Emmet</figcaption></figure><h3 id="-div-fragment-">❌ div가 불필요할 때는 fragment 사용하기</h3><p>리액트 컴포넌트는 최상위에서 오직 한 HTML 태그만 렌더할 수 있다. 나란히 두 요소를 렌더하고자 한다면, <strong>잘 알려진 인접한 JSX 요소는 하나의 태그로 닫혀야 한다(Adjacent JSX elements must be wrapped in an enclosing tag.)</strong>라는 에러를 만나게 될 것이다.</p><pre><code class="language-jsx">const InfoText = () =&gt; {
	
  // 에러가 날 것이다.
  return (
    &lt;h1&gt;Welcome!&lt;/h1&gt;
    &lt;p&gt;This our new page, we're glad you're are here!&lt;/p&gt;
  )
}</code></pre><p>뭘 할 수 있을까? fragment로 감싸면 된다. 리액트에서 허용되며, 브라우저에서도 추가적인 HTML 요소가 렌더되지 않는다.</p><pre><code class="language-jsx">const InfoText = () =&gt; {
	
  return (
  	&lt;&gt;
      &lt;h1&gt;Welcome!&lt;/h1&gt;
      &lt;p&gt;This our new page, we're glad you're are here!&lt;/p&gt;
    &lt;/&gt;
  )
}</code></pre><p>div 태그로도 물론 가능하다. 하지만 div 다음에 또 div를 사용하면 브라우저에서 소위 <strong>div 지옥</strong>이라고 말하는, div 태그가 중첩되는 말도 안 되는 상황을 만나게 될 것이다.</p><p>그러니 리액트에서 감싸는 역할을 할 태그가 필요하다면, 불필요하게 HTML 태그를 사용하는 대신, 간단하게 fragment를 사용하자.</p><h3 id="-children-">👈 children이 불필요할 때 셀프 클로징 태그 사용하기</h3><p>경험에서 비춰보자면, 이 팁은 종종 간과되지만, 적은 노력으로 코드가 정말 훨씬 깔끔해지게 해 준다.</p><p>리액트에서는 자식 요소를 컴포넌트에 전달할 수 있고, children 속성을 통해 여기에 접근할 수 있다. 이런 컴포넌트를 가리켜 <strong>composite component</strong>라 부른다.</p><p>이런 경우 당연히 여는 태그와 닫는 태그가 존재한다.</p><pre><code class="language-jsx">&lt;NavigationBar&gt;
  &lt;p&gt;Home&lt;/p&gt;
  &lt;p&gt;About&lt;/p&gt;
  &lt;p&gt;Projects&lt;/p&gt;
  &lt;p&gt;Contact&lt;/p&gt;
&lt;/NavigationBar&gt;</code></pre><p>하지만 자식 요소가 필요하지 않다면, 여는 태그와 닫는 태그를 사용하는 것이 이해가 안 될 것이다. 그렇지 않은가?</p><pre><code class="language-jsx">&lt;NavigationBar&gt;&lt;/NavigationBar&gt;</code></pre><p>이렇게 하는 대신, HTML에서 마찬가지로 자식 요소를 갖지 않는 input 태그같이 셀프 클로징 요소처럼 컴포넌트를 사용해 보는 것을 추천한다.</p><pre><code class="language-jsx">&lt;NavigationBar /&gt;</code></pre><p>훨씬 보기에 깔끔해졌다. 안 그런가?</p><h3 id="--15">✅ 흔한 이름 표기법 따라 하기</h3><p>이름 표기법의 진짜 의미는 다루고 있는 요소가 어떤 타입인지 쉽게 인지하고, 코드 안의 무언가들이 커뮤니티에서도 통용되도록 하기 위함이다.</p><p>내 관점에서는, 따라 하면 좋을 법한, 리액트와 JavaScript에 관련된 이름 표기법이 두 가지가 있다.</p><h4 id="-interface-type-">컴포넌트, interface, type 별칭에는 파스칼 케이스를 쓰기</h4><pre><code class="language-jsx">// React component
const LeftGridPanel = () =&gt; {
  ...
}

// Typescript interface
interface AdminUser {
  name: string;
  id: number;
  email: string;
}

// Typescript Type Alias
type TodoList = {
	todos: string[];
    id: number;
    name: string;
}</code></pre><h4 id="-javascript-">변수, 배열, 객체, 함수 등 JavaScript 데이터 타입에는 카멜 케이스를 쓰기</h4><pre><code class="language-jsx">const getLastDigit = () =&gt; { ... }

const userTypes = [ ... ]
</code></pre><p>파스칼 케이스로 리액트 컴포넌트 이름을 짓는 것은 특히 중요하다. 리액트에 맞게 linter를 설정해 둔 상태에서 카멜 케이스로 컴포넌트의 이름을 지었다면, 그리고 그 안에서 훅을 사용했다면 훅은 오직 컴포넌트 내부에서만 사용되어야 한다는 경고 메시지를 계속 만날 것이다. linter가 리액트 컴포넌트는 파스칼 케이스인지 아닌지로 인식되기 때문이다.</p><p>불편해도 이미 존재하는 이름 표기법을 잘 따라간다면 쉽게 고칠 수 있다.</p><h3 id="-xss-">🧨 XSS 공격 예방을 위해 코드를 깨끗하게 하기</h3><p>리액트에서 특정 요소에 <code>dangerouslySetInnerHTML</code> 속성을 사용해야 하는 상황이 있을 수 있다. 본질적으로 JavaScript의 <code>innerHTML</code>에 상응한다.</p><p>그러므로 이를 사용한다는 것은, 리액트로부터 직접 HTML을 작성한다는 것이다.</p><p>div에 HTML string을 렌더하는 예시를 생각해 보자. string은 이미 HTML 문법이 지정된 rich 텍스트 에디터로부터 받아온다.</p><pre><code class="language-jsx">const Markup = () =&gt; {
  const htmlString = "&lt;p&gt;This is set via dangerouslySetInnerHTML&lt;/p&gt;"
  
  return (
    &lt;div dangerouslySetInnerHTML={{ __html: htmlString }} /&gt;
  )
}</code></pre><p><em>dangerously</em>라는 용어는 일부러 붙게 됐다. 이 속성을 쓸 때면 cross-site-scripting(XSS) 공격이 들어올 수도 있기 때문이다. 그러니 이 코드를 사용하려고 한다면, 먼저 깨끗하게 만드는 것이 필수다.</p><p><a href="https://www.npmjs.com/package/dompurify" rel="nofollow">dompurify</a>라는 좋은 라이브러리가 있으니, 도움이 될 것이다.</p><h2 id="--16">마지막</h2><p>와, 정말 재미있지 않은가? 머릿속에서 지난 시간 축적된 모든 것들을 꺼내놓으려고 애썼다. 내 경험을 공유하면서, 리액트를 배우고 개발하는 누군가가 어려운 시간을 피할 수 있으면 하는 마음으로 가이드를 쓰게 됐다.</p><p>물론 여기서 놓친 더 많은 중요한 모범 사례들을 고려해 볼 수도 있다. 좋은 자세다. 이 가이드에 어떤 걸 추가하고 싶은지 듣고 싶다.</p><p>하나 기억할 것은, 늘 나에게 적합한 것을 적용하는 태도다. 무조건 다 수용하지 말고, 자신의 상황에 도움이 될 만한 것을 생각해 보라. 그런 다음 자신만의 모범 사례에 추가하는 것이다.</p><p><a href="https://www.instagram.com/jean_marc.dev/" rel="nofollow">인스타그램</a>에서 개발자로서의 삶에 대한 여정이나 유용한 통찰을 확인할 수 있다. 항상 여러분을 돕는 것과 내가 받을 수 있는 피드백에 대해 행복하게 지내고 있을 것이다. 그러니, 편히 방문해 주시길.</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 리액트 18의 신기능 - 동시성 렌더링(Concurrent Rendering), 자동 일괄 처리(Automatic Batching) 등 ]]>
                </title>
                <description>
                    <![CDATA[  > 2022년 3월에 리액트 18이 발표되었습니다. 성능 향상과 렌더링 엔진 개선에 초점이 맞춰졌습니다. 리액트 18은 향후 출시될 리액트 기능의 토대가 될 동시성 렌더링 API의 초석을 다졌습니다. 이번 튜토리얼에서는 리액트 18에 발표된 기능을 빠르게 훑으며, 동시성 렌더링과 자동 일괄 처리, 전환(transitions) 같은 몇 가지 중요한 개념을 설명하고자 합니다. 한눈에 보는 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/riaegteu-18yi-singineung-dongsiseong-rendeoring-concurrent-rendering-jadong-ilgwal-ceori-automatic-batching-deung/</link>
                <guid isPermaLink="false">63f6cff51d2b73063c4ce980</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jeong Won Yoo ]]>
                </dc:creator>
                <pubDate>Thu, 23 Feb 2023 08:26:48 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2023/02/featured.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/react-18-new-features/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">React 18 New Features – Concurrent Rendering, Automatic Batching, and More</a>
      </p><p></p><blockquote>2022년 3월에 리액트 18이 발표되었습니다. 성능 향상과 렌더링 엔진 개선에 초점이 맞춰졌습니다.</blockquote><p>리액트 18은 향후 출시될 리액트 기능의 토대가 될 동시성 렌더링 API의 초석을 다졌습니다.</p><p>이번 튜토리얼에서는 리액트 18에 발표된 기능을 빠르게 훑으며, 동시성 렌더링과 자동 일괄 처리, 전환(transitions) 같은 몇 가지 중요한 개념을 설명하고자 합니다.</p><h1 id="-18">한눈에 보는 리액트 18</h1><!--kg-card-begin: html--><table style="box-sizing: border-box; border-spacing: 0px; border-collapse: collapse; margin-top: 0px; margin-bottom: 16px; display: block; width: max-content; max-width: 100%; overflow: auto; color: rgb(36, 41, 47); font-family: -apple-system, &quot;system-ui&quot;, &quot;Segoe UI&quot;, &quot;Noto Sans&quot;, Helvetica, Arial, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><thead style="box-sizing: border-box;"><tr style="box-sizing: border-box; background-color: var(--color-canvas-default); border-top: 1px solid var(--color-border-muted);"><th style="box-sizing: border-box; padding: 6px 13px; font-weight: var(--base-text-weight-semibold, 600); border: 1px solid var(--color-border-default);">구분</th><th style="box-sizing: border-box; padding: 6px 13px; font-weight: var(--base-text-weight-semibold, 600); border: 1px solid var(--color-border-default);">기능</th></tr></thead><tbody style="box-sizing: border-box;"><tr style="box-sizing: border-box; background-color: var(--color-canvas-default); border-top: 1px solid var(--color-border-muted);"><td style="box-sizing: border-box; padding: 6px 13px; border: 1px solid var(--color-border-default);">개념</td><td style="box-sizing: border-box; padding: 6px 13px; border: 1px solid var(--color-border-default);">리액트의 동시성</td></tr><tr style="box-sizing: border-box; background-color: var(--color-canvas-subtle); border-top: 1px solid var(--color-border-muted);"><td style="box-sizing: border-box; padding: 6px 13px; border: 1px solid var(--color-border-default);">기능</td><td style="box-sizing: border-box; padding: 6px 13px; border: 1px solid var(--color-border-default);">자동 일괄 처리, 변이, 서버에서의 Suspense</td></tr><tr style="box-sizing: border-box; background-color: var(--color-canvas-default); border-top: 1px solid var(--color-border-muted);"><td style="box-sizing: border-box; padding: 6px 13px; border: 1px solid var(--color-border-default);">API</td><td style="box-sizing: border-box; padding: 6px 13px; border: 1px solid var(--color-border-default);">createRoot, hydrateRoot, renderToPipeableStream, renderToReadableStream</td></tr><tr style="box-sizing: border-box; background-color: var(--color-canvas-subtle); border-top: 1px solid var(--color-border-muted);"><td style="box-sizing: border-box; padding: 6px 13px; border: 1px solid var(--color-border-default);">Hooks</td><td style="box-sizing: border-box; padding: 6px 13px; border: 1px solid var(--color-border-default);">useId, useTransition, useDeferredValue, useSyncExternalStore, useInsertionEffect</td></tr><tr style="box-sizing: border-box; background-color: var(--color-canvas-default); border-top: 1px solid var(--color-border-muted);"><td style="box-sizing: border-box; padding: 6px 13px; border: 1px solid var(--color-border-default);">개선</td><td style="box-sizing: border-box; padding: 6px 13px; border: 1px solid var(--color-border-default);">엄격 모드(Strict mode)</td></tr><tr style="box-sizing: border-box; background-color: var(--color-canvas-subtle); border-top: 1px solid var(--color-border-muted);"><td style="box-sizing: border-box; padding: 6px 13px; border: 1px solid var(--color-border-default);">지원 중단</td><td style="box-sizing: border-box; padding: 6px 13px; border: 1px solid var(--color-border-default);">ReactDOM.render, renderToString</td></tr></tbody></table><!--kg-card-end: html--><p>이제 변경 사항을 더 자세히 살펴봅시다. 우선 리액트를 업데이트하지 않았다면, 이것부터 짚어 봅시다.</p><h1 id="-18-">리액트 18로 업그레이드하는 방법</h1><p>npm이나 yarn을 통해 리액트 18과 React DOM을 설치합니다.</p><p><code>npm install react react-dom</code></p><p>이제부터는 <code>render</code> 대신 <code>createRoot</code>를 사용하게 될 것입니다.</p><p>index.js에서 <code>ReactDOM.render</code>를 <code>ReactDOM.createRoot</code>로 변경해 루트를 생성하고, 이를 통해 앱을 렌더 합니다.</p><p>기존 리액트 17에서는 아래와 같았습니다.</p><pre><code class="language-jsx">import ReactDOM from 'react-dom';
import App from 'App';

const container = document.getElementById('app'); 

ReactDOM.render(&lt;App /&gt;, container);</code></pre><p>React 18에서는 이런 모습입니다.</p><pre><code class="language-jsx">import ReactDOM from 'react-dom';
import App from 'App'; 

const container = document.getElementById('app'); 

// 루트를 생성합니다.
const root = ReactDOM.createRoot(container); 

// 루트를 통해 앱을 렌더 합니다.
root.render(&lt;App /&gt;);</code></pre><h1 id="-18--1">리액트 18에서의 동시성</h1><p>리액트 18 작업 그룹 토론 때 등장한 Dan Abramov의 <a href="https://github.com/reactwg/react-18/discussions/46">예시</a>를 생각하며 동시성을 이해해 봅시다.</p><p>앨리스와 밥 두 사람과 통화해야 하는 상황이라고 해봅니다. 먼저 앨리스에게 전화를 걸고 통화가 끝나면 밥을 부르는 식으로, 비동시적 환경에서는 한 번에 한 명에게만 전화를 걸 수 있습니다.</p><p>통화 시간이 짧다면야 괜찮지만, 앨리스와 통화하는 데 드는 시간이 길어진다면(가령 대기 중이라거나), 엄청난 시간 낭비일 것입니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://camo.githubusercontent.com/228ddb4283b21749031691a3c715529048deaac618729893cf528d2e6143c4a0/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f696f367336346a33306474336e613679787a70342e706e67" class="kg-image" alt="시간을 나타내는 x축 위에 앨리스와 통화한 시간이 일정 부분 파란색으로 표시되어 있고, 밥과 통화한 시간이 그 이후부터 또 일정 부분으로 주황색으로 표시되어 있습니다." width="600" height="400" loading="lazy"><figcaption><em><em>그림은 전형적인 비동시적 전화 대화의 모습을 보여준다. 전화를 끝내야 전화를 새로 걸 수 있습니다.</em></em></figcaption></figure><p></p><p>동시성 모드에서는, 앨리스에게 전화를 건 후 대기 상태에 들어간다면 밥에게 전화할 수 있습니다.</p><p>이는 두 사람에게 동시에 전화를 건다는 뜻은 아닙니다. 다만 같은 시간 동안 동시에 전화를 두 번 이상 진행하면서도 이 중 어떤 전화가 더 중요한지 결정할 수 있다는 의미입니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://camo.githubusercontent.com/88ec9681189577cb98cae23d6bf9b8b6da22f7f1335713f3f3c433aed9917446/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f76347a67766175736c36676f31757237363963642e706e67" class="kg-image" alt="시간을 나타내는 x축 위에 앨리스와 통화한 시간이 일정 부분 파란색으로 표시되어 있으며, 그 내부에 밥과의 통화 시간이 겹치는 영역으로써 표시되어 있습니다." width="600" height="400" loading="lazy"><figcaption><em><em>그림은 앨리스와의 전화를 대기 상태로 두고, 밥과의 통화를 더 급하게 받음으로써 앨리스와 밥 사이의 전화 통화가 동시적으로 이루어지는 상황을 보여줍니다.</em></em></figcaption></figure><p></p><p>이와 유사하게, 리액트 18에 등장한 동시성 렌더링을 통해서 리액트는 렌더링 자체에 개입하고, 이를 중단하거나 재개하고 또는 폐기할 수 있습니다. 이로써 리액트는 무거운 렌더링 작업을 하는 동안에도 사용자와의 상호작용에 더 빨리 반응할 수 있게 되는 것이죠.</p><p>리액트 18 이전의 렌더링이란 개입할 수 없는 단 하나의 동기적 처리였기 때문에 한 번 렌더링이 시작되면 중단할 수 없었습니다.</p><p>동시성은 리액트 렌더링 메커니즘의 근본적인 개선입니다. 동시성을 통해 리액트는 렌더링에 개입합니다.</p><p>동시성 렌더링의 토대가 도입된 덕에 리액트 18에서는 suspense, 스트리밍 서버 렌더링, 변이 같은 새로운 기능이 소개되기도 했습니다.</p><h1 id="-18--2">리액트 18의 새로운 기능</h1><h2 id="-automatic-batching-">자동 일괄 처리 (Automatic Batching)</h2><p>리액트 18은 자동 일괄 처리라는 신규 기능을 도입했습니다. 위에서 살펴본 <a href="https://github.com/reactwg/react-18/discussions/46#discussioncomment-846694">작업 그룹 토론</a>에 등장한, 식료품 가게에서의 쇼핑 이야기를 통해 일괄 처리를 이해해 봅시다.</p><p>저녁으로 파스타를 만든다고 해봅시다. 식료품 쇼핑을 최적화하려면, 구입해야 할 모든 식재료를 목록화해, 단 한 번 만에 모든 재료를 구매하고자 할 것입니다.</p><p>이것이 바로 일괄 처리이며, 이렇게 하지 않는다면 요리를 시작하고 나서 재료가 필요해졌을 때 가게에 가서 재료를 사고, 다시 요리를 시작하고 다른 재료가 필요해진다면 또 가게에 가고…. 결국 미쳐버리고 말겠죠.</p><p>리액트에서 일괄 처리는 <code>setState</code>를 사용할 때마다 상태가 변하면서 생기는 렌더링 횟수를 줄이는 데 도움이 됩니다. 예를 들어, 이전에는 이벤트 핸들러의 상태 변화를 한 번에 처리했습니다.</p><pre><code class="language-jsx">const handleClick = () =&gt; {
  setCounter();
  setActive();
  setValue();
};

// 마지막에 한 번에 리렌더링 되었다.</code></pre><p>그러나 이벤트 핸들러 바깥에서 진행된 상태 업데이트는 일괄 처리되지 않았습니다. 예를 들어 promise가 있거나 네트워크 호출을 하는 상황에서 상태 업데이트는 일괄 처리되지 못했습니다.</p><pre><code class="language-jsx">fetch('/network').then(() =&gt; {
  setCounter(); // 한 번 리렌더링됨.
  setActive(); // 두 번 리렌더링됨.
  setValue(); // 세 번 리렌더링됨.
});

// 총 세 번 리렌더링 됨.</code></pre><p>이는 그다지 효율적인 방법이 아닙니다. 리액트 18은 자동 일괄 처리를 도입함으로써 promise, setTimeouts, 이벤트 콜백에서든 모든 상태 업데이트가 빠짐없이 일괄로 처리되도록 했습니다. 이는 백그라운드에서 리액트가 수행해야 할 작업의 상당수가 줄어드는 효과를 보여줍니다. 리액트는 이제 리렌더링 되기 전 아주 잠깐의 작업 시간만큼만 기다리면 됩니다.</p><p>자동 일괄 처리는 리액트에서 바로 사용할 수 있지만, 이 기능을 사용하지 않으려면 <code>flushSync</code>를 사용하면 됩니다.</p><h2 id="-transitions-">전환(Transitions)</h2><p>전환은 자원이 급하게 필요하지 않은 업데이트 상황에서 UI 변화를 표시하는 데 쓸 수 있습니다.</p><p>예를 들어, 자동 완성(typeahead) 양식에 입력하는 동안에는 두 가지 일이 일어납니다. 커서가 깜빡이면서 입력된 콘텐츠에 대한 시각적 피드백이 일어나는 동시에 백그라운드에서는 입력된 데이터에 대한 검색이 진행됩니다.</p><p>사용자에게 시각적 피드백을 주는 것은 중요하므로 긴급한 사항이라 말할 수 있습니다. 검색은 그렇게까지 중요하지 않으므로 긴급하지 않은 것으로 여길 수 있죠.</p><p>전환은 이렇게 시급하지 않은 업데이트에 대한 부분을 말합니다. UI 업데이트 사항을 긴급하지 않은, 즉 "전환(Transitions)"으로 간주함으로써, 리액트는 우선순위에 따라 업데이트를 진행할 수 있습니다. 이미 오래된 부분을 처리하니 렌더링 최적화가 더욱 쉬워집니다. (역자 주 : stale은 이미 오래된 낡은, '신선하지 않은'이라는 뜻으로, 오래된 렌더링은 긴급하지 않은, 그래서 우선순위에서 정리가 된 렌더링으로 이해할 수 있습니다.)</p><p><code>startTransition</code>을 사용하면 긴급하지 않은 업데이트를 분류할 수 있습니다. 아래는 전환을 사용해 분류한 자동완성 컴포넌트의 예시입니다.</p><pre><code class="language-jsx">import { startTransition } from 'react';

// 긴급함 : 무엇이 입력되고 있는지 보여줍니다.
setInputValue(input);

// 전환 안에 두어, 긴급하지 않은 상태 변화를 별도로 표기합니다.
startTransition(() =&gt; {
  // 전환 : 결과를 보여줍니다.
  setSearchQuery(input);
});</code></pre><h3 id="debouncing-settimeout-">debouncing, setTimeout과 전환은 어떻게 다른가?</h3><ol><li>setTimeout과 다르게 startTransition은 즉시 실행됩니다.</li><li>setTimeout을 사용하면 지연되는 것이 확실하지만, startTransition은 기기의 속도와 우선순위를 갖고 렌더링 되는 다른 부분에 따라서 지연됩니다.</li><li>startTransition은 setTimeout과 다르게 방해받을 수 있지만, 페이지를 중단시키지는 않습니다.</li><li>리액트는 startTransition을 사용할 경우 보류된 상태를 추적할 수 있습니다.</li></ol><h2 id="-suspense">서버에서의 Suspense</h2><p>리액트 18에서 도입된 두 가지 기능은 아래와 같습니다.</p><ol><li>suspense 사용이 가능한 서버에서의 코드 분리</li><li>서버 측 스트리밍 렌더링</li></ol><h3 id="-vs-">클라이언트 렌더링 vs 서버 렌더링</h3><p>클라이언트 렌더링 되는 앱이라면 페이지를 실행시키고, 상호작용하도록 하기 위해 필요한 JavaScript와 함께 HTML을 로드합니다.</p><p>그런데 만일 JavaScript 번들이 지나치게 크거나 연결이 느리다면, 이 과정에 시간이 무척 소요될 것입니다. 사용자는 페이지가 상호작용한 상태가 되거나 의미 있는 정보를 주기만을 마냥 기다릴 것이고요.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://camo.githubusercontent.com/82212326e73af07ab2781f659ff49661043d28b810019d435f0579c3c210b818/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f6363666c6c6731313775387963676e6c356d76372e706e67" class="kg-image" alt="클라이언트 렌더링에서는 1. JS를 로드하고, 2. 데이터를 받아오며, 3. 컴포넌트를 렌더링한 후에 4. 상호작용한 웹 페이지가 된다는 것을 순서에 따라 보여주고 있습니다." width="600" height="400" loading="lazy"><figcaption><em><em>클라이언트 렌더링 흐름에서는 사용자는 페이지가 상호작용해질 때까지 기다리는 데 오래 걸립니다. <a href="https://www.youtube.com/watch?v=pj5N-Khihgc&amp;ab_channel=ReactConf2021" rel="nofollow">출처</a>: React Conf 2021 Suspense 사용이 가능한 스트리밍 서버 렌더링, Shaundai Person</em></em></figcaption></figure><p>사용자 경험을 최적화하고 사용자가 빈 화면만 보고 앉아 있지 않도록 하기 위해 서버 렌더링을 사용할 수 있습니다.</p><p>서버 렌더링은 서버에서 React 컴포넌트의 HTML 출력을 렌더링하고 서버에서 HTML을 보내는 기술입니다. 서버 렌더링은 JS 번들이 로딩되고 앱이 상호작용 가능해지는 동안 사용자에게 UI의 일부를 보여줍니다.</p><p>클라이언트 렌더링과 서버 렌더링에 대한 더 자세한 설명은, <a href="https://www.youtube.com/watch?v=pj5N-Khihgc&amp;ab_channel=ReactConf2021" rel="nofollow">Shaundai Person의 React Conf 2021 강연을 통해 참고할 수 있습니다.</a></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://camo.githubusercontent.com/76964e7d2fdf40fd76690bbac2237e76c7a3a2edf2e243f9bcfe81c694127ac6/68747470733a2f2f7777772e66726565636f646563616d702e6f72672f6e6577732f636f6e74656e742f696d616765732f323032322f30342f342e6a706567" class="kg-image" alt="서버 렌더링에서는 1. 데이터를 받아오고, 2. HTML을 렌더하고 3. JS를 로드한 후 4. Hydrate 작업을 거칩니다." width="600" height="400" loading="lazy"><figcaption><em><em>서버 렌더링 흐름에서는 서버로부터 HTML을 보내면서 사용자에게 의미 있는 데이터를 더 신속하게 출력해 줍니다. <a href="https://www.youtube.com/watch?v=pj5N-Khihgc&amp;ab_channel=ReactConf2021" rel="nofollow">출처</a>: React Conf 2021 Suspense 사용이 가능한 스트리밍 서버 렌더링, Shaundai Person</em></em></figcaption></figure><p></p><p>서버 렌더링은 페이지를 로딩하고 상호작용을 위한 시간을 줄여서 사용자 경험을 훨씬 개선합니다.</p><p>앱이 매우 빠른데도, 어떠한 한 부분이 그러지 못한다면 어떨까요? 데이터가 느리게 로드되거나 상호작용을 위한 JS가 너무 큰 바람에 다운로드에 시간이 걸리는 것일 수 있습니다.</p><p>리액트 18 이전에 이런 부분은 앱의 병목을 유발해 컴포넌트가 렌더 되는 시간을 증가시켰습니다.</p><p>느린 컴포넌트 하나는 전체 페이지 로드를 늦춥니다. 서버 렌더링이란 아무것도 없거나 모두 렌더링이 된 상태로, 느린 컴포넌트의 로딩을 늦추거나 다른 컴포넌트의 HTML이라도 보내달라고 요청할 수는 없었습니다.</p><p>리액트 18에 와서 서버에서의 Suspense 지원이 등장합니다. Suspense 덕에 Suspense 컴포넌트 내부에 앱의 느린 부분을 감싸, 해당 부분의 로딩을 지연시킬 수 있게 된 것이죠. 로딩되는 동안 상태를 특정하기 위해서도 사용할 수 있습니다.</p><p>리액트 18에서는 한 컴포넌트가 느리다고 모든 앱의 렌더가 느려지지는 않습니다. Suspense를 사용한다면 로딩 바(loading spinner)와 같이 일종의 플레이스 홀더용 HTML을 먼저 보내달라고 요청할 수 있습니다. 느린 컴포넌트가 다 준비되어서 데이터가 다 받아진 상태라면, 서버 렌더러가 HTML과 같은 스트림에 들어와 동작할 것입니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://camo.githubusercontent.com/f222cb66bf74aa1e067bb87bbfd2d00bf52038e9a5c379533e0a91e99b40e79e/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f7461787268377939796c78306c363875326274782e706e67" class="kg-image" alt="서버에서의 Suspense. 서버로부터 클라이언트로 HTML을 보내는 도식 하나와 플레이스 홀더용 HTML 이미지, 로딩 바가 보여집니다." width="600" height="400" loading="lazy"><figcaption><em><em>서버에서의 suspense를 사용하면 다른 컴포넌트가 완전히 렌더링 되는 동안 느린 컴포넌트가 로딩 상태를 표시할 수 있음을 보여주는 이미지입니다.</em></em></figcaption></figure><p></p><p>이를 통해 사용자는 가능한 한 빠르게 페이지의 스켈레톤을 확인한 뒤 이후 HTML의 나머지 조각이 보내질 때마다 점진적으로 콘텐츠가 나타나는 것을 볼 수 있습니다.</p><p>이 모든 것은 페이지에 JS나 리액트가 로드되기 전 일어나기 때문에 사용자 경험과 사용자가 체감하는 지연 시간을 상당히 개선합니다.</p><h2 id="-strict-mode-">엄격 모드(Strict mode)</h2><p>리액트 18에서의 엄격 모드는 이전 상태 값을 가진 컴포넌트의 마운팅(mounting), 마운팅 해제(unmounting), remounting(재마운팅)을 시뮬레이션합니다. 이는 미래에 재사용할 수 있는 상태를 위한 토대를 마련한 것인데, 마운팅이 해제되기 전과 같은 컴포넌트 상태를 담은 트리를 재마운팅 함으로써 이전 화면을 재빨리 마운트 하는 것입니다.</p><p>엄격 모드는 컴포넌트가 여러 번 마운트 되고 해제되는 데 드는 비용이 좀 더 탄력적으로 만듭니다.</p><h1 id="-">마무리</h1><p>요약하자면, 리액트 18은 향후 출시를 위한 초석을 마련하고 사용자 경험을 개선하는 데 중점을 두었습니다.</p><p>리액트 18로 업그레이드는 직관적이며 업데이트 이후에도 기존 코드가 깨지진 않습니다. 업그레이드는 반나절 이상 소요되지 않고요.</p><p>한 번 도전해 보고, 어떻게 생각하는지 알려주면 좋을 것 같습니다!</p><p>출처</p><ol><li><a href="https://github.com/reactjs/rfcs/blob/react-18/text/0212-react-18.md">React RFC</a></li><li><a href="https://dev.to/shrutikapoor08/what-s-new-in-react-18-1713" rel="nofollow">My previous React 18 post</a></li><li><a href="https://reactjs.org/blog/2022/03/29/react-v18.html" rel="nofollow">React V18 blog</a></li><li><a href="https://www.youtube.com/watch?v=ytudH8je5ko&amp;ab_channel=ReactConf2021" rel="nofollow">React Conf 2021 - React for App developers</a></li><li><a href="https://www.youtube.com/watch?v=pj5N-Khihgc&amp;ab_channel=ReactConf2021" rel="nofollow">React Conf 2021 - Streaming Server Rendering with Suspense</a></li></ol><p>이 글이 좋았다면, ❤️를 눌러 다른 독자들 눈에 띄도록 부탁합니다.</p><ul><li><a href="https://twitter.com/shrutikapoor08" rel="nofollow">더 많은 팁은 트위터에서 더 자주 확인할 수 있습니다.</a></li><li><a href="http://tinyletter.com/shrutikapoor" rel="nofollow">메일함에서 바로 글을 읽어보고 싶다면?</a></li></ul> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 리액트에서 컴포넌트를 검색하고 필터하는 방법 ]]>
                </title>
                <description>
                    <![CDATA[ 리액트 앱을 만들다보면, 당신의 사용자가 검색을 해서 정확한 결과를 얻기를 원할겁니다. 그런데 만약 API에서 엄청난 양의 아이템을 받게 된다면 사용자들이 많은 아이템들을 쉽게 찾을 수 있는 방법을 찾아야할 거에요. 이번 튜토리얼에서는 예제로 Frontend Mentor’s free advanced API project를 사용할 것입니다. 목차 1. 시작하기 2. 리액트 셋팅하기 3. 데이터 받아오기 4. ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/search-and-filter-component-in-reactjs/</link>
                <guid isPermaLink="false">638f196137582306394d7cc1</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Yerim Kang ]]>
                </dc:creator>
                <pubDate>Wed, 07 Dec 2022 07:43:49 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2022/12/wirxeocmd6tpnn9c5oqc.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/search-and-filter-component-in-reactjs/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Search and Filter Components in React</a>
      </p><h2 id="-api-">리액트 앱을 만들다보면, 당신의 사용자가 검색을 해서 정확한 결과를 얻기를 원할겁니다. 그런데 만약 API에서 엄청난 양의 아이템을 받게 된다면 사용자들이 많은 아이템들을 쉽게 찾을 수 있는 방법을 찾아야할 거에요.</h2><p>이번 튜토리얼에서는 예제로 Frontend Mentor’s free advanced API project를 사용할 것입니다.</p><h2 id="-">목차</h2><h3 id="1-">1. 시작하기</h3><h3 id="2-">2. 리액트 셋팅하기</h3><h3 id="3-">3. 데이터 받아오기</h3><h3 id="4-api-">4. API에서 아이템 찾기</h3><h3 id="5-">5. 지역에서 아이템 필터링하기</h3><p></p><h1 id="--1">시작하기</h1><p>이번 튜토리얼을 위해서 우리는 <a href="https://apilayer.com/">Apilayer</a>가 제공하는 무료 API인 <a href="https://restcountries.eu/">REST COUNTRIES API</a>을 사용할 겁니다.</p><p>기본적으로 우리의 API의 엔드포인트인 <a href="https://restcountries.eu/rest/v2/all">https://restcountries.eu/rest/v2/all</a> 에서 데이터를 가져올 겁니다. 그리고 사용자가 읽을 수 있는 형식으로 데이터를 표기할 겁니다.</p><p>그러고 나면 우리는 특정 나라를 이름과 수도로 사용자가 쉽게 사용할 수 있는 방법을 제공해보도록 합시다. 여기 어느 나라의 응답 예시를 드릴게요.</p><pre><code class="language-json">"name": "Colombia",
"topLevelDomain": [".co"],
"alpha2Code": "CO",
"alpha3Code": "COL",
"callingCodes": ["57"],
"capital": "Bogotá",
"altSpellings": ["CO", "Republic of Colombia", "República de Colombia"],
"region": "Americas",
"subregion": "South America",
"population": 48759958,
"latlng": [4.0, -72.0],
"demonym": "Colombian",
"area": 1141748.0,
"gini": 55.9,
"timezones": ["UTC-05:00"],
"borders": ["BRA", "ECU", "PAN", "PER", "VEN"],
"nativeName": "Colombia",
"numericCode": "170",
"currencies": [{
    "code": "COP",
    "name": "Colombian peso",
    "symbol": "$"
}],
"languages": [{
    "iso639_1": "es",
    "iso639_2": "spa",
    "name": "Spanish",
    "nativeName": "Español"
}],
"translations": {
    "de": "Kolumbien",
    "es": "Colombia",
    "fr": "Colombie",
    "ja": "コロンビア",
    "it": "Colombia",
    "br": "Colômbia",
    "pt": "Colômbia"
},
"flag": "&lt;https://restcountries.eu/data/col.svg&gt;",
"regionalBlocs": [{
    "acronym": "PA",
    "name": "Pacific Alliance",
    "otherAcronyms": [],
    "otherNames": ["Alianza del Pacífico"]
}, {
    "acronym": "USAN",
    "name": "Union of South American Nations",
    "otherAcronyms": ["UNASUR", "UNASUL", "UZAN"],
    "otherNames": ["Unión de Naciones Suramericanas", "União de Nações Sul-Americanas", "Unie van Zuid-Amerikaanse Naties", "South American Union"]
}],
"cioc": "COL"
}]
</code></pre><p>이 튜토리얼이 끝날 때 쯤이면 당신은 API를 통해 검색하는 법을 배우고 리액트를 통해 요청한 결과만을 반환하는 법을 배울 수 있으면 좋겠습니다!</p><h2 id="--2">리액트 셋팅하기</h2><p>어떠한 설정 없이도 모던 빌드 환경을 제공하는 <code>create-react-app</code> 으로 우리 프로젝트를 셋팅 할 겁니다.</p><p>리액트를 셋팅하기 위해서 터미널을 실행(아니면 당신이 사용하는 시스템에서 제공하거나 VS Code와 같은 에디터에서 제공하는 터미널)하고 아래의 커맨드를 실행시키세요.</p><pre><code class="language-json">npx create-react-app my-app 
cd my-app 
npm start
</code></pre><p>만약 <code>create-react-app</code> 으로 프로젝트를 제대로 구성하는 것이 확신이 없다면 <a href="https://create-react-app.dev/docs/getting-started/">create-react-app-dev</a> 이곳에서 공식 가디으 문서를 참조해보세요.</p><p>이번 튜토리얼에선 실시간 결과를 보여드리기 위해, 우리는 코드펜을 우리의 프로젝트에도 사용할 겁니다. <a href="https://codepen.io/MrMaster">Lathryx</a>가 만든 코드펜 템플릿을 이용해보세요. 그렇게 했다면 우리는 코드펜에서 리액트 셋팅을 할 수 있습니다.</p><figure class="kg-card kg-embed-card"><iframe id="cp_embed_oNYWNjr" src="https://codepen.io/MrMaster/embed/preview/oNYWNjr?default-tabs=css%2Cresult&amp;height=300&amp;host=https%3A%2F%2Fcodepen.io&amp;slug-hash=oNYWNjr" title="Embedded content" scrolling="no" frameborder="0" height="300" allowtransparency="true" class="cp_embed_iframe" loading="lazy" style="box-sizing: inherit; margin: 0px; padding: 0px; border: 0px; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-size: 22px; vertical-align: middle; width: 720px; overflow: hidden;"></iframe></figure><p></p><h2 id="api-">API 엔드포인트에서 데이터를 가져오기</h2><p>지금 우리는 리액트 프로젝트 셋팅을 성공적으로 완료했고 이제 API에서 데이터를 가져와볼 시간입니다. 리액트에서 데이터를 가져오는 다양한 방법이 있는데 가장 유명한 두개는 Axios(프로미스 기반 HTTP 클라이언트)와 Fetch API(브라우저에 내장된 웹 API) 입니다.</p><p>우리는 브라우저에서 제공하는 Fetch API를 사용할 것이고 API 엔드포인트에서 데이터를 가져오는 것은 Ajax를 사용할 것입니다. 여기 React에서 Ajax와 API을 훅으로 사용하는 예시가 있습니다.</p><pre><code class="language-jsx">function MyComponent() {
      const [error, setError] = useState(null);
      const [isLoaded, setIsLoaded] = useState(false);
      const [items, setItems] = useState([]);

      // Note: the empty deps array [] means
      // this useEffect will run once
      // similar to componentDidMount()
      useEffect(() =&gt; {
        fetch("&lt;https://api.example.com/items&gt;")
          .then(res =&gt; res.json())
          .then(
            (result) =&gt; {
              setIsLoaded(true);
              setItems(result);
            },
            // Note: it's important to handle errors here
            // instead of a catch() block so that we don't swallow
            // exceptions from actual bugs in components.
            (error) =&gt; {
              setIsLoaded(true);
              setError(error);
            }
          )
      }, [])

      if (error) {
        return &lt;div&gt;Error: {error.message}&lt;/div&gt;;
      } else if (!isLoaded) {
        return &lt;div&gt;Loading...&lt;/div&gt;;
      } else {
        return (
          &lt;ul&gt;
            {items.map(item =&gt; (
              &lt;li key={item.id}&gt;
                {item.name} {item.price}
              &lt;/li&gt;
            ))}
          &lt;/ul&gt;
        );
      }
    }
</code></pre><p>10번째 줄에서 보면 엔드포인트에서 데이터를 가져오고 있고 데이터를 받아왔을 때 컴포넌트를 업데이트하기 위해 setState를 사용하고 있습니다.</p><p>27번째 줄에서는 에러메세지를 표시하고 있는데 이것은 우리가 API에서 데이터를 가져오기 실패했을 때 사용하기 위해서 입니다. 데이터가져오기에 실패하지 않는다면 데이터를 리스트로 보여줄 것입니다.</p><p>만약 리액트로 리스트를 표현하는 것이 익숙하지 않다면 저는 <a href="https://reactjs.org/docs/lists-and-keys.html">React Lists And Keys</a>의 가이드를 보고 올 것을 추천드립니다.</p><p>이제 이 코드를 이용해서 REST COUNTRIES API에서 데이터를 가져오고 표시해보도록 합시다.</p><p>위의 예제 코드에서 React에서 useState를 가져오고 10번재 라인을 아래와 같이 바꿔보겠습니다.</p><p><code>fetch("&lt;https://restcountries.eu/rest/v2/all&gt;")</code>‌</p><p>이것들을 모두 합치면 우리는 아래와 같은 코드를 갖게 됩니다.</p><pre><code class="language-jsx">import { useState, useEffect } from "&lt;https://cdn.skypack.dev/react&gt;";

    // Note: the empty deps array [] means
    // this useEffect will run once
    function App() {
        const [error, setError] = useState(null);
        const [isLoaded, setIsLoaded] = useState(false);
        const [items, setItems] = useState([]);

        useEffect(() =&gt; {
            fetch("&lt;https://restcountries.eu/rest/v2/all&gt;")
                .then((res) =&gt; res.json())
                .then(
                    (result) =&gt; {
                        setIsLoaded(true);
                        setItems(result);
                    },
                    // Note: it's important to handle errors here
                    // instead of a catch() block so that we don't swallow
                    // exceptions from actual bugs in components.
                    (error) =&gt; {
                        setIsLoaded(true);
                        setError(error);
                    }
                );
        }, []);
</code></pre><p>참고: <code>"[&lt;https://cdn.skypack.dev/react&gt;](&lt;https://cdn.skypack.dev/react&gt;)";</code> 로부터 useState와 useEffect를 가져오고 있습니다. 이것은 우리가 Codepen에서 리액트를 CDN을 사용해서 가져오고 있기 때문입니다. 만약 당신이 리액트를 지역적으로 셋팅했다면 <code>import { useState, useEffect } from "react";</code> 를 사용해야 할것입니다.</p><p>그리고 나서 우리는 받아온 데이터를 나라들을 목록으로 표시하려고 합니다. 최종적으로 코드는 아래와 같이 되겠지요 :</p><pre><code class="language-jsx">// Note: the empty deps array [] means
    // this useEffect will run once
    function App() {
        const [error, setError] = useState(null);
        const [isLoaded, setIsLoaded] = useState(false);
        const [items, setItems] = useState([]);

        useEffect(() =&gt; {
            fetch("&lt;https://restcountries.eu/rest/v2/all&gt;")
                .then((res) =&gt; res.json())
                .then(
                    (result) =&gt; {
                        setIsLoaded(true);
                        setItems(result);
                    },
                    // Note: it's important to handle errors here
                    // instead of a catch() block so that we don't swallow
                    // exceptions from actual bugs in components.
                    (error) =&gt; {
                        setIsLoaded(true);
                        setError(error);
                    }
                );
        }, []);

        if (error) {
            return &lt;&gt;{error.message}&lt;/&gt;;
        } else if (!isLoaded) {
            return &lt;&gt;loading...&lt;/&gt;;
        } else {
            return (
                /* here we map over the element and display each item as a card  */
                &lt;div className="wrapper"&gt;
                    &lt;ul className="card-grid"&gt;
                        {items.map((item) =&gt; (
                            &lt;li&gt;
                                &lt;article className="card" key={item.callingCodes}&gt;
                                    &lt;div className="card-image"&gt;
                                        &lt;img src={item.flag} alt={item.name} /&gt;
                                    &lt;/div&gt;
                                    &lt;div className="card-content"&gt;
                                        &lt;h2 className="card-name"&gt;{item.name}&lt;/h2&gt;
                                        &lt;ol className="card-list"&gt;
                                            &lt;li&gt;
                                                population:{" "}
                                                &lt;span&gt;{item.population}&lt;/span&gt;
                                            &lt;/li&gt;
                                            &lt;li&gt;
                                                Region: &lt;span&gt;{item.region}&lt;/span&gt;
                                            &lt;/li&gt;
                                            &lt;li&gt;
                                                Capital: &lt;span&gt;{item.capital}&lt;/span&gt;
                                            &lt;/li&gt;
                                        &lt;/ol&gt;
                                    &lt;/div&gt;
                                &lt;/article&gt;
                            &lt;/li&gt;
                        ))}
                    &lt;/ul&gt;
                &lt;/div&gt;
            );
        }
    }

    ReactDOM.render(&lt;App /&gt;, document.getElementById("root"));
</code></pre><p>여기 코드펜에서 실시간 미리보기를 참고하세요</p><figure class="kg-card kg-embed-card"><iframe id="cp_embed_ZELeWoN" src="https://codepen.io/Spruce_khalifa/embed/preview/ZELeWoN?default-tabs=js%2Cresult&amp;height=300&amp;host=https%3A%2F%2Fcodepen.io&amp;slug-hash=ZELeWoN" title="Embedded content" scrolling="no" frameborder="0" height="300" allowtransparency="true" class="cp_embed_iframe" loading="lazy" style="box-sizing: inherit; margin: 0px; padding: 0px; border: 0px; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-size: 22px; vertical-align: middle; width: 720px; overflow: hidden;"></iframe></figure><p></p><p>REST COUNTRIES API로부터 성공적으로 데이터를 받아오고 표시한 시점에서 우리는 표시되는 국가를 검색하는 데 집중할 수 있습니다.</p><p>그러나 시작하기 전에 위의 예제를 css로 스타일을 지정해보겠습니다. (왜냐면 이렇게 표시되면 이상하기 때문이죠)</p><p>위의 예제에 css를 더하게 되면 아래의 예시와 같은 것을 얻게 됩니다.</p><figure class="kg-card kg-embed-card"><iframe id="cp_embed_zYNZBBB" src="https://codepen.io/Spruce_khalifa/embed/preview/zYNZBBB?default-tabs=js%2Cresult&amp;height=300&amp;host=https%3A%2F%2Fcodepen.io&amp;slug-hash=zYNZBBB" title="Embedded content" scrolling="no" frameborder="0" height="300" allowtransparency="true" class="cp_embed_iframe" loading="lazy" style="box-sizing: inherit; margin: 0px; padding: 0px; border: 0px; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-size: 22px; vertical-align: middle; width: 720px; overflow: hidden;"></iframe></figure><p>우리가 추가한 css가 완벽하진 않더라도 이전보다 깔끔한 방식으로 국가를 표시합니다. 그렇죠?</p><h2 id="--3">검색하는 컴포넌트를 생성하는 방법</h2><p>우리는 APP 함수 내에서 쿼리 q를 빈 스트링 값으로 셋팅하기 위해 <code>useState()</code> 를 사용했습니다. 또한 우리는 setQ를 가지고 있는데 이건 검색 폼의 값을 바인딩할 때 사용할 거에요.</p><p>13번째 줄에서 useState를 사용하여 API에서 검색할 수 있는 배열의 기본값을 정의했습니다. 이것은 국가의 <code>Capital(수도)</code> 와 <code>name(이름)</code> 으로 모든 국가를 검색할 수 있기를 원한다는 것을 의미합니다. 당신의 선호에 따라서 이 배열을 더 길게 만들수 있겠습니다.</p><pre><code class="language-jsx">const [error, setError] = useState(null);
        const [isLoaded, setIsLoaded] = useState(false);
        const [items, setItems] = useState([]);

        //     set search query to empty string
        const [q, setQ] = useState("");
        //     set search parameters
        //     we only what to search countries by capital and name
        //     this list can be longer if you want
        //     you can search countries even by their population
        // just add it to this array
        const [searchParam] = useState(["capital", "name"]);

        useEffect(() =&gt; {
            // our fetch codes
        }, []);

     }
</code></pre><p>반환 함수 안에서는 검색 폼을 만들것이고 우리의 코드는 아마 아래와 같을 것입니다.</p><pre><code class="language-jsx">return &lt;&gt;{error.message}&lt;/&gt;;
        } else if (!isLoaded) {
            return &lt;&gt;loading...&lt;/&gt;;
        } else {
            return (
                &lt;div className="wrapper"&gt;
                    &lt;div className="search-wrapper"&gt;
                        &lt;label htmlFor="search-form"&gt;
                            &lt;input
                                type="search"
                                name="search-form"
                                id="search-form"
                                className="search-input"
                                placeholder="Search for..."
                                value={q}
                                /*
                                // set the value of our useState q
                                //  anytime the user types in the search box
                                */
                                onChange={(e) =&gt; setQ(e.target.value)}
                            /&gt;
                            &lt;span className="sr-only"&gt;Search countries here&lt;/span&gt;
                        &lt;/label&gt;
                    &lt;/div&gt;
                    &lt;ul className="card-grid"&gt;
                        {items.map((item) =&gt; (
                            &lt;li&gt;
                                &lt;article className="card" key={item.callingCodes}&gt;
                                    &lt;div className="card-image"&gt;
                                        &lt;img src={item.flag} alt={item.name} /&gt;
                                    &lt;/div&gt;
                                    &lt;div className="card-content"&gt;
                                        &lt;h2 className="card-name"&gt;{item.name}&lt;/h2&gt;
                                        &lt;ol className="card-list"&gt;
                                            &lt;li&gt;
                                                population:{" "}
                                                &lt;span&gt;{item.population}&lt;/span&gt;
                                            &lt;/li&gt;
                                            &lt;li&gt;
                                                Region: &lt;span&gt;{item.region}&lt;/span&gt;
                                            &lt;/li&gt;
                                            &lt;li&gt;
                                                Capital: &lt;span&gt;{item.capital}&lt;/span&gt;
                                            &lt;/li&gt;
                                        &lt;/ol&gt;
                                    &lt;/div&gt;
                                &lt;/article&gt;
                            &lt;/li&gt;
                        ))}
                    &lt;/ul&gt;
                &lt;/div&gt;
            );
        }
    }

    ReactDOM.render(&lt;App /&gt;, document.getElementById("root"));
</code></pre><p>이제 우리는 검색을 담당할 함수를 만들고 우리의 반환 함수 위에 둘 겁니다.</p><pre><code class="language-jsx">return items.filter((item) =&gt; {
                return searchParam.some((newItem) =&gt; {
                    return (
                        item[newItem]
                            .toString()
                            .toLowerCase()
                            .indexOf(q.toLowerCase()) &gt; -1
                    );
                });
            });
        }
</code></pre><p>이 함수는 응답받은 아이템들을 가져와서 <code>searchParam</code> 배열에 존재하는 &nbsp;indexOF()의 값이 -1보다 큰 모든 아이템들을 반환합니다.</p><p>함수가 만들어졌으므로 이걸 사용하기 위해 반환된 데이터를 검색 함수로 감쌉니다.</p><pre><code class="language-jsx">{serach(items).map((item) =&gt; ( &lt;li&gt; // card goes here &lt;/li&gt; ))}
</code></pre><p>useState()에 저장된 데이터는 리스트 항목에 전달되기 전에 검색 함수에서 필터링 되어 작성한 쿼리와 일치하는 항목만 반환됩니다.</p><p>다음은 코드를 모두 합쳤을 때의 코드와 Codepen의 실시간 미리보기 입니다. 아래의 검색 폼으로 국가의 이름이나 수도로 어떠한 국가든지 검색해보세요.</p><figure class="kg-card kg-embed-card"><iframe id="cp_embed_wvgJWdO" src="https://codepen.io/Spruce_khalifa/embed/preview/wvgJWdO?default-tabs=js%2Cresult&amp;height=300&amp;host=https%3A%2F%2Fcodepen.io&amp;slug-hash=wvgJWdO" title="Embedded content" scrolling="no" frameborder="0" height="300" allowtransparency="true" class="cp_embed_iframe" loading="lazy" style="box-sizing: inherit; margin: 0px; padding: 0px; border: 0px; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-size: 22px; vertical-align: middle; width: 720px; overflow: hidden;"></iframe></figure><p><br></p><h2 id="--4">지역으로 국가들을 필터링 하는 방법</h2><p>이제 지역별로 국가를 필터링 할 수 있도록 더 만들어 봅시다. 모든 국가를 표기하지 않고, 아프리카 또는 아시아에만 있는 국가들을 검색하고 노출하고 싶다고 가정해보세요. 리액트에서 useState()를 사용하여 구현해낼 수 있습니다.</p><h3 id="--5">지역:</h3><ol><li>아프리카</li><li>아메리카</li><li>아시아</li><li>유럽</li><li>오세아니아</li></ol><p>우리의 지역들을 알아보았으니 이제 필터 컴포넌트를 만듭시다. 첫번째로 필터의 useState를 아래와 같이 구성합니다.</p><pre><code class="language-jsx">const [filterParam, setFilterParam] = useState(["All"]);
</code></pre><p>지역이 지정되지 않은 경우 모든 국가를 표시하고 검색할 수 있기를 원하기 때문에 useState 기본값을 의도적으로 ALL로 설정했습니다.</p><pre><code class="language-jsx">&lt;select
    /*
    // here we create a basic select input
    // we set the value to the selected value
    // and update the setFilterParam() state every time onChange is called
    */
      onChange={(e) =&gt; {
      setFilterParam(e.target.value);
       }}
       className="custom-select"
       aria-label="Filter Countries By Region"&gt;
        &lt;option value="All"&gt;Filter By Region&lt;/option&gt;
        &lt;option value="Africa"&gt;Africa&lt;/option&gt;
        &lt;option value="Americas"&gt;America&lt;/option&gt;
        &lt;option value="Asia"&gt;Asia&lt;/option&gt;
        &lt;option value="Europe"&gt;Europe&lt;/option&gt;
        &lt;option value="Oceania"&gt;Oceania&lt;/option&gt;
        &lt;/select&gt;
        &lt;span className="focus"&gt;&lt;/span&gt;
        &lt;/div&gt;
</code></pre><p>이제 필터를 만들었으므로 남은 것은 검색 기능을 수정하는 것입니다. 기본적으로 입력한 지역을 확인하고 해당 지역이 있는 국가만 반환해야 합니다.</p><pre><code class="language-jsx">function search(items) {
       return items.filter((item) =&gt; {
    /*
    // in here we check if our region is equal to our c state
    // if it's equal to then only return the items that match
    // if not return All the countries
    */
       if (item.region == filterParam) {
           return searchParam.some((newItem) =&gt; {
             return (
               item[newItem]
                   .toString()
                   .toLowerCase()
                   .indexOf(q.toLowerCase()) &gt; -1
                        );
                    });
                } else if (filterParam == "All") {
                    return searchParam.some((newItem) =&gt; {
                        return (
                            item[newItem]
                                .toString()
                                .toLowerCase()
                                .indexOf(q.toLowerCase()) &gt; -1
                        );
                    });
                }
            });
        }
</code></pre><p>Codepen에서 전체 코드와 실시간 미리보기를 찾을 수 있습니다. 국가를 필터링하고 결과를 보십시오.</p><figure class="kg-card kg-embed-card"><iframe id="cp_embed_BapWzPe" src="https://codepen.io/Spruce_khalifa/embed/preview/BapWzPe?default-tabs=js%2Cresult&amp;height=300&amp;host=https%3A%2F%2Fcodepen.io&amp;slug-hash=BapWzPe" title="Embedded content" scrolling="no" frameborder="0" height="300" allowtransparency="true" class="cp_embed_iframe" loading="lazy" style="box-sizing: inherit; margin: 0px; padding: 0px; border: 0px; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-size: 22px; vertical-align: middle; width: 720px; overflow: hidden;"></iframe></figure><p><br>CSS를 추가하면 이제 React 앱의 최종 미리보기를 볼 수 있습니다.</p><figure class="kg-card kg-embed-card"><iframe id="cp_embed_GRrWjmR" src="https://codepen.io/Spruce_khalifa/embed/preview/GRrWjmR?default-tabs=js%2Cresult&amp;height=300&amp;host=https%3A%2F%2Fcodepen.io&amp;slug-hash=GRrWjmR" title="Embedded content" scrolling="no" frameborder="0" height="300" allowtransparency="true" class="cp_embed_iframe" loading="lazy" style="box-sizing: inherit; margin: 0px; padding: 0px; border: 0px; font-style: inherit; font-variant: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-size: 22px; vertical-align: middle; width: 720px; overflow: hidden;"></iframe></figure><p><br></p><h1 id="--6">마무리</h1><p>사용자에게 표시해야 하는 많은 양의 데이터를 처리할 때 검색 및 필터 기능을 사용하면 사용자가 중요한 정보를 빠르게 탐색하고 찾을 수 있습니다. 질문이 있는 경우 저에게 연락해 주시면 기꺼이 답변해드리겠습니다. 이 프로젝트의 전체 미리보기는 여기 earthly vercel 앱에서 찾을 수 있습니다. 트위터 @sprucekhalifa에서 저를 팔로우할 수 있습니다.</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 초보자를 위한 리액트 Context - 완벽 가이드 (2021) ]]>
                </title>
                <description>
                    <![CDATA[ 리액트 context는 모든 리액트 개발자들이 필수적으로 알아야 하는 개념입니다. context는 앱에서 state를 쉽게 공유할 수 있게 해줍니다. 이 글에서는 리액트 context가 무엇인지, 어떻게 사용하는지, 언제 사용하고 사용해서는 안 되는지 등에 대해서 알아보도록 하겠습니다. 리액트 context를 다뤄본 적이 없어도 괜찮습니다. 쉬운 단계별 예시를 통해 필요한 모든 내용을 알게 될 것입니다. 그럼 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/cobojareul-wihan-riaegteu-context-wanbyeog-gaideu-2021/</link>
                <guid isPermaLink="false">63674fe4c0cb07062cace88b</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nayoung Gu ]]>
                </dc:creator>
                <pubDate>Wed, 09 Nov 2022 08:13:37 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2022/11/react-context-for-beginners.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/react-context-for-beginners/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">React Context for Beginners – The Complete Guide (2021)</a>
      </p><!--kg-card-begin: markdown--><h4 id="contextcontextstate">리액트 context는 모든 리액트 개발자들이 필수적으로 알아야 하는 개념입니다. context는 앱에서 state를 쉽게 공유할 수 있게 해줍니다.</h4>
<p>이 글에서는 리액트 context가 무엇인지, 어떻게 사용하는지, 언제 사용하고 사용해서는 안 되는지 등에 대해서 알아보도록 하겠습니다.</p>
<p>리액트 context를 다뤄본 적이 없어도 괜찮습니다. 쉬운 단계별 예시를 통해 필요한 모든 내용을 알게 될 것입니다.</p>
<p>그럼 시작해 봅시다!</p>
<blockquote>
<p>리액트를 처음부터 끝까지 배울 수 있는 가이드가 필요하시다면 <a href="https://reactbootcamp.com/">The React Bootcamp</a>를 확인하세요.</p>
</blockquote>
<h2 id="">목차</h2>
<ul>
<li>
<p><a href="#what-is-react-context">리액트 context란</a></p>
</li>
<li>
<p><a href="#when-should-you-use-react-context">리액트 context를 사용해야 할 때</a></p>
</li>
<li>
<p><a href="#what-problems-does-react-context-solve">리액트 context가 해결해주는 문제</a></p>
</li>
<li>
<p><a href="#how-do-I-use-react-context">리액트 context 사용 방법</a></p>
</li>
<li>
<p><a href="#what-is-the-useContext-hook">useContext 훅이란</a></p>
</li>
<li>
<p><a href="#you-may-not-need-context">context가 필요 없을 때</a></p>
</li>
<li>
<p><a href="#does-react-context-replace-redux">리액트 context가 리덕스를 대체하는가</a></p>
</li>
<li>
<p><a href="#react-context-caveats">리액트 context 사용 시 주의사항</a></p>
</li>
</ul>
<h2 id="h3idwhatisreactcontextcontexth3"></h2><h3 id="what-is-react-context">리액트 context란</h3>
<p>리액트 context는 앱에서 컴포넌트에게 props를 사용하지 않고 필요한 데이터를 넘겨주며 사용할 수 있게 해줍니다.</p>
<p><em>다시 말해, 리액트 context는 컴포넌트들이 데이터(state)를 더 쉽게 공유할 수 있도록 해줍니다.</em></p>
<h2 id="h3idwhenshouldyouusereactcontextcontexth3"></h2><h3 id="when-should-you-use-react-context">리액트 context를 사용해야 할 때</h3>
<p>리액트 context는 앱의 모든 컴포넌트에서 사용할 수 있는 데이터를 전달할 때 유용합니다.</p>
<p><strong>데이터 종류는 다음과 같습니다.</strong></p>
<ul>
<li>테마 데이터 (다크 모드 혹은 라이트 모드)</li>
<li>사용자 데이터 (현재 인증된 사용자)</li>
<li>로케일 데이터 (언어 혹은 지역)</li>
</ul>
<p>데이터는 자주 업데이트할 필요가 없는 리액트 context에 위치해야 합니다.</p>
<p>왜일까요? context는 전체적인 상태 관리를 위해 만들어진 시스템이 아니기 때문입니다. context는 데이터를 쉽게 사용하기 위해 만들어졌습니다.</p>
<p><em>리액트 context는 리액트 컴포넌트를 위한 전역 변수와 같다고 생각하면 됩니다.</em></p>
<h2 id="h3idwhatproblemsdoesreactcontextsolvecontexth3"></h2><h3 id="what-problems-does-react-context-solve">리액트 context가 해결해주는 문제</h3>
<p>리액트 context는 props drilling을 막는 데 도움을 줍니다.</p>
<p><strong>Props drilling</strong>이란 중첩된 여러 계층의 컴포넌트에게 props를 전달해 주는 것을 의미합니다. 해당 props를 사용하지 않는 컴포넌트들에게까지 말이죠.</p>
<p>props drilling의 예시를 보여드리겠습니다. 이 앱에서 모든 컴포넌트에 prop으로 전달된 theme 데이터에 접근할 수 있습니다.</p>
<p>하지만 보시다시피 <code>Header</code>와 같은 <code>App</code>의 직계 자식 컴포넌트 또한 props를 사용해 테마 데이터를 내려주어야 합니다.</p>
<pre><code class="language-js">export default function App({ theme }) {
  return (
    &lt;&gt;
      &lt;Header theme={theme} /&gt;
      &lt;Main theme={theme} /&gt;
      &lt;Sidebar theme={theme} /&gt;
      &lt;Footer theme={theme} /&gt;
    &lt;/&gt;
  );
}
function Header({ theme }) {
  return (
    &lt;&gt;
      &lt;User theme={theme} /&gt;
      &lt;Login theme={theme} /&gt;
      &lt;Menu theme={theme} /&gt;
    &lt;/&gt;
  );
}
</code></pre>
<p><em>이 예제의 문제는 무엇일까요?</em></p>
<p>문제는 <code>theme</code> prop을 사용하지 않는 많은 컴포넌트들에게까지 해당 prop을 내려주고 있다는 점입니다.</p>
<p><code>Header</code> 컴포넌트는 자식 컴포넌트에게 <code>theme</code>을 내려주기만 할 뿐 직접 필요로 하지 않습니다. 다시 말해, <code>User</code>, <code>Login</code>, <code>Menu</code> 컴포넌트가 <code>theme</code> 데이터를 직접 사용하는 게 더 나을 것입니다.</p>
<p>이렇듯 모든 곳에 props를 사용하는 것을 우회할 수 있어 props drilling 문제를 피할 수 있는 것이 리액트 context의 장점입니다.</p>
<h2 id="h3idhowdoiusereactcontextcontexth3"></h2><h3 id="how-do-I-use-react-context">리액트 context 사용 방법</h3>
<p>context는 리액트 버전 16부터 사용 가능한 리액트 내장 API입니다.</p>
<p>즉, 어떤 리액트 프로젝트라도 리액트를 import하면 context를 바로 생성하고 사용할 수 있습니다.</p>
<p><strong>리액트 context를 사용하기 위한 네 단계는 다음과 같습니다.</strong></p>
<ol>
<li><code>createContext</code> 메서드를 사용해 context를 생성한다.</li>
<li>생성된 context를 가지고 context provider로 컴포넌트 트리를 감싼다.</li>
<li><code>value</code> prop을 사용해 context provider에 원하는 값을 입력한다.</li>
<li>context consumer를 통해 필요한 컴포넌트에서 그 값을 불러온다.</li>
</ol>
<p><em>조금 헷갈리나요?</em> 생각보다 간단합니다.</p>
<p>다음 예제 코드를 보면 <code>App</code>에서 context를 사용해 이름을 전달해주고 <code>User</code>라는 하위 컴포넌트에서 불러옵니다.</p>
<pre><code class="language-js">import React from 'react';
export const UserContext = React.createContext();
export default function App() {
  return (
    &lt;UserContext.Provider value="Reed"&gt;
      &lt;User /&gt;
    &lt;/UserContext.Provider&gt;
  )
}
function User() {
  return (
    &lt;UserContext.Consumer&gt;
      {value =&gt; &lt;h1&gt;{value}&lt;/h1&gt;} 
      {/* prints: Reed */}
    &lt;/UserContext.Consumer&gt;
  )
}
</code></pre>
<p>위의 코드를 한 줄씩 살펴봅시다.</p>
<ol>
<li><code>App</code> 컴포넌트에서 <code>React.createContext()</code>를 사용해 context를 만들고 <code>UserContext</code> 변수에 결과를 담아두었습니다. 대부분의 경우, 컴포넌트는 다른 파일에 있기 때문에 여기서와 같이 export해 사용할 것입니다. <code>React.createContext()</code>를 사용하면 <code>value</code> prop에 초깃값을 정해줄 수 있다는 점에 유의하세요.</li>
<li><code>App</code> 컴포넌트에서 <code>UserContext</code>를 사용하고 있습니다. 정확히 말하면 <code>UserContext.Provider</code>입니다. 이렇게 만들어진 context는 컴포넌트인 <code>Provider</code>와 <code>Consumer</code>라는 두 가지 프로퍼티를 갖는 객체입니다. 앱의 모든 컴포넌트에 값을 내려주기 위해서는 Provider 컴포넌트로 감싸주어야 합니다(위 예제의 <code>User</code> 컴포넌트).</li>
<li><code>UserContext.Provider</code>에는 컴포넌트 트리 전체에 전달해주고 싶은 값을 넣습니다. 이를 위해 <code>value</code> prop을 사용했습니다(위 예제의 Reed).</li>
<li><code>User</code> 혹은 context에서 제공하는 값을 사용하고 싶은 컴포넌트에서는 <code>UserContext.Consumer</code>라는 consumer 컴포넌트를 사용해야 합니다. 전달된 값을 사용하기 위해서는 <strong>render props 패턴</strong>을 사용해야 합니다. 이는 단지 consumer 컴포넌트가 prop처럼 전달하는 함수입니다. 함수의 결괏값으로 <code>value</code>를 반환해 사용할 수 있습니다.</li>
</ol>
<h2 id="h3idwhatistheusecontexthookusecontexth3"></h2><h3 id="what-is-the-useContext-hook">useContext 훅이란</h3>
<p>위의 예시를 보면 context를 사용하기 위해 render props 패턴을 사용하는 것이 조금 이상해 보일 수 있습니다.</p>
<p>리액트 훅이 도입된 리액트 16.8부터는 context를 사용하는 다른 방법이 등장했습니다. 이제는 <strong>useContext 훅</strong>을 사용해 context를 사용할 수 있습니다.</p>
<p>render props를 사용하는 대신, context를 사용할 때 컴포넌트 최상단에서 <code>React.useContext()</code>에 전체 context 객체를 내려줄 수 있습니다.</p>
<p>useContext 훅을 사용한 예시는 다음과 같습니다.</p>
<pre><code class="language-js">import React from 'react';
export const UserContext = React.createContext();
export default function App() {
  return (
    &lt;UserContext.Provider value="Reed"&gt;
      &lt;User /&gt;
    &lt;/UserContext.Provider&gt;
  )
}
function User() {
  const value = React.useContext(UserContext);  
    
  return &lt;h1&gt;{value}&lt;/h1&gt;;
}
</code></pre>
<p><em>useContext 훅의 장점은 컴포넌트가 더욱 간결해지고 나만의 커스텀 훅을 만들 수 있다는 점입니다.</em></p>
<p>선호하는 패턴에 따라서 consumer 컴포넌트를 직접 사용할 수도 있고 useContext 훅을 사용할 수도 있습니다.</p>
<h2 id="h3idyoumaynotneedcontextcontexth3"></h2><h3 id="you-may-not-need-context">context가 필요 없을 때</h3>
<p>많은 개발자들이 하는 실수는 여러 레벨의 컴포넌트에 props를 내려줘야 하는 경우에 매번 context를 사용하는 것입니다.</p>
<p>여기 <code>App</code> 컴포넌트의 <code>username</code>과 <code>avatarSrc</code> 두 가지 props가 필요한 <code>Avatar</code>라는 중첩 컴포넌트가 있습니다.</p>
<pre><code class="language-js">export default function App({ user }) {
  const { username, avatarSrc } = user;
  return (
    &lt;main&gt;
      &lt;Navbar username={username} avatarSrc={avatarSrc} /&gt;
    &lt;/main&gt;
  );
}
function Navbar({ username, avatarSrc }) {
  return (
    &lt;nav&gt;
      &lt;Avatar username={username} avatarSrc={avatarSrc} /&gt;
    &lt;/nav&gt;
  );
}
function Avatar({ username, avatarSrc }) {
  return &lt;img src={avatarSrc} alt={username} /&gt;;
}
</code></pre>
<p>가능하다면 props가 필요 없는 컴포넌트들에게 여러 개의 props를 굳이 전달하고 싶지 않을 것입니다.</p>
<p><em>어떻게 하면 될까요?</em></p>
<p>prop drilling 하고 있다는 이유로 바로 context에 의지하는 것보다는 컴포넌트를 더 잘 구성함으로써 해결할 수 있습니다.</p>
<p>최상위 컴포넌트인 <code>App</code>만 <code>Avatar</code> 컴포넌트에 대해 알고 있으면 되기 때문에 <code>App</code> 내에 바로 avatar를 만드는 것입니다.</p>
<p>이렇게 하면 두 개의 props를 내려주는 것 대신 <code>avatar</code>라는 하나의 prop만 내려주면 됩니다.</p>
<pre><code class="language-js">export default function App({ user }) {
  const { username, avatarSrc } = user;
  const avatar = &lt;img src={avatarSrc} alt={username} /&gt;;
  return (
    &lt;main&gt;
      &lt;Navbar avatar={avatar} /&gt;
    &lt;/main&gt;
  );
}
function Navbar({ avatar }) {
  return &lt;nav&gt;{avatar}&lt;/nav&gt;;
}
</code></pre>
<p><em>요약하자면, 바로 context를 사용하지는 마세요. prop drilling을 피하기 위해 컴포넌트를 개선하여 설계할 수 있는지를 먼저 살펴보세요.</em></p>
<h2 id="h3iddoesreactcontextreplacereduxcontexth3"></h2><h3 id="does-react-context-replace-redux">리액트 context가 리덕스를 대체하는가</h3>
<p>맞기도 하고 틀리기도 합니다.</p>
<p>많은 리액트 초보자들에게 리덕스는 데이터를 더 쉽게 전달해 주는 도구로 여겨지곤 합니다. 리덕스는 리액트 context 자체와 함께 제공되기 때문입니다.</p>
<p>하지만 state를 <em>업데이트</em>하는 것이 아니라 컴포넌트에 전달만 해주는 경우라면 리덕스와 같은 전역 상태 관리 라이브러리는 필요하지 않을 수도 있습니다.</p>
<h2 id="h3idreactcontextcaveatscontexth3"></h2><h3 id="react-context-caveats">리액트 context 사용 시 주의사항</h3>
<p><em>리액트 context가 내려주는 값을 업데이트하는 것은 왜 불가능할까요?</em></p>
<p>리액트 context를 useReducer와 같은 훅과 결합해 서드파티 라이브러리 없이 임시 상태 관리 라이브러리를 생성하는 것이 가능하지만, 보통 성능상의 문제로 권장되지 않습니다.</p>
<p>이런 접근법은 리액트 context가 재렌더링을 초래한다는 문제가 있습니다.</p>
<p>만약 리액트 context provider에 객체를 내려주고 있고 그 객체의 프로퍼티가 업데이트된다면 무슨 일이 일어날까요? <em>그 context를 사용하고 있는 모든 컴포넌트에서 재렌더링이 일어날 것입니다.</em></p>
<p>state가 거의 없는 작은 앱의 경우(위 예제의 theme 데이터), 이러한 성능 이슈가 일어나지 않을 것입니다. 테마 데이터처럼 업데이트가 자주 되지 않는 경우라면 말이죠. 하지만 트리의 많은 컴포넌트에서 state 변경이 자주 일어난다면 문제가 될 것입니다.</p>
<h2 id="">결론</h2>
<p>이 가이드가 리액트 context 사용 방법을 이해하는 데 도움이 되었으면 좋겠습니다.</p>
<p>멋진 리액트 프로젝트에서 리액트 context를 사용하는 방법을 깊게 배워보고 싶다면 <a href="https://reactbootcamp.com/">리액트 부트 캠프</a>를 확인해 보세요.</p>
<h2 id="">리액트 전문가가 되고 싶으신가요? 리액트 부트 캠프에 참여하세요</h2>
<p><a href="https://reactbootcamp.com/">리액트 부트 캠프</a>에서는 리액트를 배울 때 필요한 모든 내용을 다루고 있으며 비디오, 치트 시트, 보너스 강의를 포함한 내용을 하나의 패키지로 제공합니다.</p>
<p>벌써 <strong>100명의 개발자</strong>들이 리액트 전문가가 되어 원하는 직업을 얻고 미래를 설계하며 공부한 내용을 확인해 보세요.</p>
<p><img src="https://reedbarger.nyc3.digitaloceanspaces.com/react-bootcamp-banner.png" alt="리액트 부트 캠프" width="600" height="400" loading="lazy"></p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 리액트 배경 이미지 튜토리얼 - CSS 인라인으로 backgroundImage를 설정하는 방법 ]]>
                </title>
                <description>
                    <![CDATA[ 리액트에서 인라인 CSS 방식으로 backgroundImage 속성을 지정하는 방법은 네 가지가 있습니다. 이번 튜토리얼에서는 각각의 예제 코드와 함께 이 네 가지 방법을 소개합니다. 리액트에서 외부 url을 사용해 배경 이미지를 설정하는 방법 만약 이미지가 온라인상 어딘가에 존재한다면 다음과 같이 요소에 url을 넣어 배경 이미지를 설정할 수 있습니다. function App() {   ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/riaegteu-baegyeong-imiji-tyutorieol-css-inraineuro-backgroundimagereul-seoljeonghaneun-bangbeob/</link>
                <guid isPermaLink="false">6340f52b86d53d05fdf62452</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nayoung Gu ]]>
                </dc:creator>
                <pubDate>Mon, 10 Oct 2022 07:06:29 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2022/10/fcc-bg-image-2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/react-background-image-tutorial-how-to-set-backgroundimage-with-inline-css-style/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">React Background Image Tutorial – How to Set backgroundImage with Inline CSS Style</a>
      </p><!--kg-card-begin: markdown--><h3 id="cssbackgroundimage">리액트에서 인라인 CSS 방식으로 <code>backgroundImage</code> 속성을 지정하는 방법은 네 가지가 있습니다.</h3>
<p>이번 튜토리얼에서는 각각의 예제 코드와 함께 이 네 가지 방법을 소개합니다.</p>
<h2 id="url">리액트에서 외부 url을 사용해 배경 이미지를 설정하는 방법</h2>
<p>만약 이미지가 온라인상 어딘가에 존재한다면 다음과 같이 요소에 url을 넣어 배경 이미지를 설정할 수 있습니다.</p>
<pre><code class="language-jsx">function App() {
  return (
    &lt;div style={{ 
      backgroundImage: `url("https://via.placeholder.com/500")` 
    }}&gt;
      Hello World
    &lt;/div&gt;
  );
}
</code></pre>
<p align="center">외부 url을 사용해 배경 이미지를 설정하는 방법</p>
<p>위의 코드는 <code>background-image: url(https://via.placeholder.com/500)</code>이라는 스타일이 적용된 <code>&lt;div&gt;</code> 요소를 렌더링할 것입니다.</p>
<h2 id="src">리액트에서 /src 폴더 내 배경 이미지를 불러오는 방법</h2>
<p>만약 여러분이 Create React App을 사용해 앱을 만들어 이미지가 <code>src/</code> 폴더 내에 있는 경우라면, 이미지를 먼저 <code>import</code> 한 후에 요소의 배경으로 설정할 수 있습니다.</p>
<pre><code class="language-js">import React from "react";
import background from "./img/placeholder.png";
function App() {
  return (
    &lt;div style={{ backgroundImage: `url(${background})` }}&gt;
      Hello World
    &lt;/div&gt;
  );
}
export default App;
</code></pre>
<p align="center">import한 이미지로 배경을 설정하는 방법</p>
<p><code>npm start</code> 명령어를 입력하면, 리액트는 "컴파일 실패"라는 에러를 보여주고 이미지를 찾지 못해 빌드를 멈출 것입니다.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/React-failed-to-compile-image.png" alt="컴파일 에러" width="600" height="400" loading="lazy"></p>
<p align="center">이미지를 찾지 못해 컴파일에 실패한 리액트</p>
<p>이렇게 되면 웹 앱에 깨진 이미지 링크가 표시되지 않습니다. 위의 코드에서 <code>backgroundImg</code> 값으로 템플릿 문자열이 사용되었기 때문에 자바스크립트 표현식을 포함할 수 있습니다.</p>
<h2 id="">리액트에서 상대 경로로 배경 이미지를 불러오는 방법</h2>
<p>Create React App에서 <code>public/</code> 폴더는 리액트 앱에 사용되는 정적 assets 파일들을 담고 있습니다. 이 폴더에 넣는 모든 파일은 온라인상으로 접근 가능합니다.</p>
<p><code>public/</code> 폴더 내에 <code>image.png</code> 파일을 넣게 되면 <code>&lt;호스트 주소&gt;/image.png</code> 경로로 접근할 수 있습니다. 로컬 컴퓨터에서 리액트를 실행한 경우라면 <code>http://localhost:3000/image.png</code>에서 이미지가 보일 것입니다.</p>
<p>배경 이미지를 설정하기 위해서는 호스트 주소에 상대 경로를 지정해주면 됩니다.</p>
<pre><code class="language-js">&lt;div style={{ backgroundImage: "url(/image.png)" }}&gt;
  Hello World
&lt;/div&gt;
</code></pre>
<p align="center">상대 경로로 배경 이미지를 설정하는 방법</p>
<p>위의 예시처럼 url 경로를 <code>/image.png</code>로 설정하면 브라우저는 <code>&lt;host 주소&gt;/image.png</code>에서 배경 이미지를 찾을 것입니다.</p>
<p>이미지들을 폴더 내에 따로 정리하고 싶다면 다음과 같이 <code>public/</code> 내에 다른 폴더를 만들 수도 있습니다.</p>
<p align="center"><img src="https://www.freecodecamp.org/news/content/images/2020/12/Screen-Shot-2020-12-14-at-20.18.30.png" width="600" height="400" alt="Screen-Shot-2020-12-14-at-20.18.30" loading="lazy"></p><p>
</p><p align="center">public/ 폴더 내에 img/ 폴더 만들기</p>
<p>만약 다른 폴더를 만들었다면 <code>backgroundImage</code>의 값으로 <code>url(/img/image.png)</code>를 넣어주는 것을 잊지 마세요.</p>
<h2 id="">리액트에서 절대 경로로 배경 이미지를 불러오는 방법</h2>
<p>Create React App의 <code>PUBLIC_URL</code> 환경 변수에 절대 경로를 포함해 다음과 같이 사용할 수도 있습니다.</p>
<pre><code class="language-js">&lt;div style={{ 
  backgroundImage: `url(${process.env.PUBLIC_URL + '/image.png'})` 
}}&gt;
  Hello World
&lt;/div&gt;
</code></pre>
<p align="center">절대 경로를 사용해 배경 이미지 설정하기</p>
<p>로컬 컴퓨터에서 이 코드를 실행하면 리액트 스크립트가 <code>PUBLIC_URL</code>의 값을 처리할 것입니다. 로컬에서는 절대 경로가 아닌 상대 경로처럼 보일 것입니다.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/absolute-url-background-image-1.png" alt="절대 경로 예시" width="600" height="400" loading="lazy"></p>
<p align="center">이미지의 절대 경로는 로컬 컴퓨터에서 보이지 않음</p>
<p>절대 경로는 나중에 리액트를 실제 앱으로 배포할 때 보이게 될 것입니다.</p>
<h2 id="">추가 속성과 함께 배경 이미지를 설정하는 방법</h2>
<p>배경 이미지를 추가적으로 커스터마이즈 하고 싶다면 다음 예시와 같이 <code>backgroundImage</code> 이후에 추가 속성을 지정해 줄 수 있습니다.</p>
<pre><code class="language-js">&lt;div style={{ 
  backgroundImage: `url(${process.env.PUBLIC_URL + '/image.png'})`,
  backgroundRepeat: 'no-repeat',
  width:'250px' 
}}&gt;
  Hello World
&lt;/div&gt;
</code></pre>
<p align="center">추가 속성과 함께 배경 이미지를 설정하는 방법</p>
<p>위에서 설정한 속성들은 <code>&lt;div&gt;</code> 요소에 배경 이미지를 넣고 <code>background-repeat: no-repeat</code>과 <code>width: 250px</code>을 지정하고 있습니다.</p>
<p>읽어주셔서 감사드리고, 이 글이 도움이 되었으면 좋겠습니다. 혹시 질문이 있으시면 제 <a href="https://twitter.com/nsebhastian">트위터</a>를 찾아주세요. 개발자들을 위한 짧은 팁들도 종종 올리겠습니다. 🙂</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
