<?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[ Jeong Won Yoo - 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[ Jeong Won Yoo - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/korean/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 19 May 2026 10:02:14 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/korean/news/author/jeongwon-yoo/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <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[ 리눅스 Symlink 튜토리얼 - 심볼릭 링크(Symbolic Link)를 생성하고 삭제하는 방법 ]]>
                </title>
                <description>
                    <![CDATA[ 심링크(symlink) 또는 심볼릭 링크(symbolic link)는 리눅스의 파일의 한 종류로, 컴퓨터의 다른 파일이나 폴더를 가리킵니다. 심링크는 윈도우 운영체제의 '바로가기'와 유사합니다. > 심링크는 리눅스(Linux)/유닉스(UNIX) 시스템의 링크의 한 종류인 "소프트 링크(soft link)"라고도 불리는데, 이는 "하드 링크(hard link)"의 반대 개념입니다. 소프트 링크와 하드 링크의 차이 소프트 링크는 '바로가기'와 유사하며 어떤 파일 시스템에서든 이미 생성되어 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/rinugseu-symlink-tyutorieol-simbolrig-ringkeu-symbolic-link-reul-saengseonghago-sagjehaneun-bangbeob/</link>
                <guid isPermaLink="false">63d7bd7d41a99b065fb5aaca</guid>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jeong Won Yoo ]]>
                </dc:creator>
                <pubDate>Wed, 01 Feb 2023 09:24:31 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2023/01/5f9c9b4f740569d1a4ca2b02.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/symlink-tutorial-in-linux-how-to-create-and-remove-a-symbolic-link/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Symlink Tutorial in Linux – How to Create and Remove a Symbolic Link</a>
      </p><p>심링크(symlink) 또는 심볼릭 링크(symbolic link)는 리눅스의 파일의 한 종류로, 컴퓨터의 다른 파일이나 폴더를 가리킵니다. 심링크는 윈도우 운영체제의 '바로가기'와 유사합니다.</p><blockquote>심링크는 리눅스(Linux)/유닉스(UNIX) 시스템의 링크의 한 종류인 "소프트 링크(soft link)"라고도 불리는데, 이는 "하드 링크(hard link)"의 반대 개념입니다.</blockquote><p></p><h1 id="-">소프트 링크와 하드 링크의 차이</h1><p>소프트 링크는 '바로가기'와 유사하며 어떤 파일 시스템에서든 이미 생성되어 있는 다른 파일이나 디렉터리를 가리킬 수 있습니다.</p><p>하드 링크 역시 파일이나 폴더의 '바로가기' 역할을 하지만, 다른 파일 시스템에서는 만들어질 수 없습니다.</p><p>심링크를 생성하고 삭제하는 과정을 살펴봅시다. 깨진 링크가 무엇인지, 어떻게 이를 삭제하는지도 살펴보려고 합니다.</p><h1 id="--1">심링크를 생성하는 방법</h1><p>심링크를 생성하는 문법은 아래와 같습니다.</p><p><code>ln -s &lt;연결하고자 하는 원본 파일/폴더의 경로&gt; &lt;새로 생성하는 링크의 경로&gt;</code></p><p><code>ln</code>은 링크 명령어입니다. <code>-s</code> 옵션은 해당 링크를 소프트 링크로 만들어줍니다. <code>-s</code> 옵션은 <code>-symbolic</code>으로도 대체할 수 있습니다.</p><p>기본적으로 <code>ln</code> 명령어는 하드 링크를 생성합니다. 다음 인자(argument)는 연결하려고 하는 <code>파일이나 폴더의 경로</code>입니다. (즉, '바로가기'를 하려고 하는 파일이나 폴더를 말합니다.)</p><p>마지막 인자는 <code>링크 경로</code> 그 자체로, '바로가기'를 말합니다.</p><h1 id="--2">파일의 심링크를 생성하는 방법 - 예시 명령어</h1><p><code>ln -s /home/james/transactions.txt trans.txt</code></p><p>위 명령어를 실행하고 나면, <code>trans.txt</code>로 <code>/home/james/transactions.txt</code>에 접근 가능합니다. <code>trans.txt</code>를 수정해도 그대로 원본 파일에 반영됩니다.</p><p>위 명령어는 링크 파일인 <code>trans.txt</code>을 현재 디렉터리 내부에 생성한다는 점을 기억해 두세요. 연결된 파일을 폴더 링크 안에 생성할 수도 있습니다.</p><p><code>ln -s /home/james/transactions.txt my-stuffs/trans.txt</code></p><p>현재 디렉터리에 "my-stuffs"라는 폴더가 이미 존재해야 합니다. 그렇지 않으면 명령어는 에러를 낼 것입니다.</p><h1 id="--3">폴더의 심링크를 생성하는 방법 - 예시 명령어</h1><p>위와 유사하며, 아래와 같이 사용합니다.</p><p><code>ln -s /home/james james</code></p><p>이는 <code>/home/james</code>의 내부 콘텐츠를 포함한 심링크된 폴더인 'james'를 생성합니다. 연결된 폴더에 대한 어떤 변경 사항이든 원본 폴더에 영향을 줍니다.</p><h1 id="--4">심링크를 삭제하는 방법</h1><p>심링크를 제거하기 전에, 파일들을 괜히 손대기 전에 파일이나 폴더가 심링크인지 확인하고 싶을 것입니다.</p><p>아래 명령어로 실행해 볼 수 있습니다.</p><p><code>ls -l &lt;심링크라고 생각되는 경로&gt;</code></p><p>터미널에 이 명령어를 실행하면 파일의 속성들이 표시될 것입니다. 그 결과로 나타나는 첫 문자가 소문자 L("l")이라면 파일/폴더가 심링크라는 의미입니다.</p><p>심링크가 가리키는 파일/폴더를 가리키는 화살표(-&gt;)를 확인할 수도 있습니다.</p><p>심링크를 삭제하는 두 가지 방법이 있습니다.</p><h2 id="-unlink-">언링크(unlink)를 사용해 심링크를 삭제하는 방법</h2><p>문법은 아래와 같습니다.</p><p><code>unlink &lt;심링크 경로&gt;</code></p><p>위 과정이 성공적으로 끝나면 심링크가 삭제됩니다.</p><p>폴더 형태의 심링크라 하더라도, '/'를 넣지 마세요. 리눅스는 이것이 디렉터리라고 간주하며, <code>unlink</code>는 디렉터리를 삭제하지 못합니다.</p><h2 id="rm-">rm을 사용해 심링크를 삭제하는 방법</h2><p>위에서 본 것과 같이 심링크는 원본 파일/폴더를 가리키는 또 다른 파일/폴더입니다. 이를 관계를 지우고 싶다면, 연결된 파일을 삭제하면 됩니다.</p><p>따라서 문법은 아래와 같습니다.</p><p><code>rm &lt;심링크 경로&gt;</code></p><p>예를 들어,</p><p>rm trans.txt rm james</p><p>리눅스는 'james/'를 디렉터리로 간주해 <code>r</code>과 <code>f</code> 같은 옵션이 필요하므로 <code>rm james/</code>를 실행한다면 에러가 날 것입니다. 이런 에러를 방지하기 위해서는 심링크가 폴더의 형태라도, (<code>/</code> 없이) 폴더의 이름만 써주면 됩니다.</p><p><code>unlink</code>보다 <code>rm</code>이 좋은 이유는 마치 파일처럼 한 번에 여러 심링크를 삭제할 수 있다는 것입니다.</p><h1 id="-broken-link-">깨진 링크(Broken Link)를 찾고 삭제하는 방법</h1><p>깨진 링크는 심링크가 가리키던 파일이나 폴더의 경로가 변경되거나 삭제될 때 생깁니다.</p><p>예를 들어보자면, 'transactions.txt'가 <code>/home/james</code>에서 <code>/home/james/personal</code>로 이동할 경우 'trans.txt' 링크는 깨집니다. 파일에 접근하려고 하면 '해당 파일이나 디렉터리가 존재하지 않습니다'라는 에러를 낼 것입니다. 링크가 가지고 있는 정보가 없기 때문입니다.</p><p>깨진 링크를 발견하면, 파일을 쉽게 삭제할 수 있는데, 깨진 링크를 찾는 가장 쉬운 방법은 아래와 같습니다.</p><p><code>find /home/james -xtype l</code></p><p>이 명령어는 파일에서부터 디렉터리, 그 서브 디렉터리까지 돌면서 <code>james</code> 디렉터리의 모든 깨진 링크를 출력합니다.</p><p><code>-delete</code> 옵션을 전달하면 아래처럼 해당 파일들을 삭제합니다.</p><p><code>find /home/james -xtype l -delete</code></p><h1 id="--5">마무리</h1><p>심볼릭 링크는 리눅스와 유닉스 시스템의 흥미로운 기능입니다.</p><p>바로 접근하기에 불편한 파일 또는 폴더를 쉽게 열 수 있게 해주는 심링크를 생성할 수 있습니다. 조금만 연습하면 직관적인 수준에서 작동 원리를 이해할 수 있을뿐더러 파일 시스템을 더 효율적으로 관리할 수 있을 것입니다.</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ SCP 리눅스 명령 - 원격에서 로컬로 SSH 파일을 전송하는 방법 ]]>
                </title>
                <description>
                    <![CDATA[  > 컴퓨터나 저장 능력이 있는 어떤 전자 기기로 작업한다면, 여러 방식으로 정보나 파일을 나누고 공유하게 될 것입니다. 자주 공유되는 파일로는 오디오 파일, 이미지, 비디오, PDF나 어떤 형태의 워드 문서가 포함됩니다. 대부분의 경우 공유되는 정보는 특정 무리나 개인을 위한 사적이고 기밀인 경우가 많아 이를 보호하는 것이 필수적입니다. 모바일 휴대폰 같은 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/scp-rinugseu-myeongryeong-weongyeogeseo-rokeolro-ssh-paileul-jeonsonghaneun-bangbeob/</link>
                <guid isPermaLink="false">63d3a7e07a7b73070e79724f</guid>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jeong Won Yoo ]]>
                </dc:creator>
                <pubDate>Sat, 28 Jan 2023 22:48:06 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2023/01/uide-to-writting-a-good-readme-file--3-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/scp-linux-command-example-how-to-ssh-file-transfer-from-remote-to-local/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">SCP Linux Command – How to SSH File Transfer from Remote to Local</a>
      </p><h4></h4><blockquote>컴퓨터나 저장 능력이 있는 어떤 전자 기기로 작업한다면, 여러 방식으로 정보나 파일을 나누고 공유하게 될 것입니다.</blockquote><p>자주 공유되는 파일로는 오디오 파일, 이미지, 비디오, PDF나 어떤 형태의 워드 문서가 포함됩니다.</p><p>대부분의 경우 공유되는 정보는 특정 무리나 개인을 위한 사적이고 기밀인 경우가 많아 이를 보호하는 것이 필수적입니다.</p><p>모바일 휴대폰 같은 경우라면, xender, appshare 같은 앱과 심지어 블루투스의 기능으로도 파일 전송이 가능합니다. 자 이제 컴퓨터의 경우도 마찬가지입니다. 소프트웨어나 때로는 사이트가 이를 똑같이 가능하게 합니다.</p><p>리눅스 같은 운영 체제 안에서의 데이터 공유라 한다면, 정보를 공유하고자 할 때 선택할 수 있는 다양한 명령이 있습니다. 하지만, 오늘은 <strong>SCP</strong> 명령에 집중해보고자 합니다. 이 명령어를 통해 안전하고 쉽게 파일과 데이터를 공유할 수 있습니다.</p><p>오늘날 시장에선 리눅스 기술을 가지는 것이 매우 중요하며, 여러분이 시스템 관리자라면 이는 더욱더 도움이 됩니다. 시스템 관리자로서 데이터 공유는 매일 일상적인 행위가 될 텐데 이때 데이터가 안전하게 공유될 필요가 있습니다. SCP 명령을 통해 이것이 가능합니다.</p><p>시작하기 전에, 대체 SCP가 무엇인지 이해해보고 파일 전송에 쓸 수 있는 몇 가지 명령어를 배워봅시다.</p><h2 id="scp-">SCP 명령어가 뭔가요?</h2><p>SCP는 Secure Copy Protocol의 머리글자입니다. 이 명령행 유틸리티를 이용해 사용자는 주로 유닉스(unix)나 리눅스(linux) 두 위치 사이에서 파일이나 디렉터리를 안전하게 복사할 수 있습니다.</p><p>이 프로토콜은 악의를 가진 어떤 이의 민감성 정보 취득을 방지하기 위해 파일 전송의 암호화를 보장합니다.</p><p>간단하게 말해서 SCP가 <code>cp</code> <em>(copy)</em> 명령을 위한 더 안전한 선택지라는 것입니다.</p><p>SCP가 SSH (Secure Shell) 연결을 통한 암호화를 이용한다는 것, 그래서 수상한 공격으로부터 전송 중인 데이터가 보호된다는 점이 중요하단 것을 알아두세요.</p><h2 id="scp--1">SCP 문법</h2><p>터미널에서 쓰이는 다른 명령어처럼, SCP도 성공적으로 실행되기 위한 일종의 형식이 있습니다. 이 문법을 이해하면 더 쉽게 명령어를 작성할 수 있게 됩니다.</p><pre><code class="language-bash">scp [OPTIONS] [[user@]src_host:]fil1 [[user@]dest_host:]file2
</code></pre><ul><li><code>scp</code> - 명령을 초기화하고 시큐어 셸이 준비되도록 합니다.</li><li><code>OPTIONS</code> - 어떻게 쓰이는지에 따라 다른 권한을 부여합니다. 가장 많이 쓰이는 옵션은 아래와 같습니다.</li><li><strong>P</strong>(대문자) - 원격 호스트와 연결하기 위해 포트를 특정합니다.</li><li><strong>p</strong>(소문자) - 수정과 열람의 편의성을 위해 타임 스탬프를 보존합니다.</li><li><strong>r</strong> - 디렉터리 전체를 재귀적으로 복사합니다.</li><li><strong>q</strong> - 진행 메시지를 표시하지 않고 조용히 파일을 복사합니다. 콰이어트 모드라고도 합니다.</li><li><strong>C</strong> - 전송 중 데이터 압축을 위한 옵션입니다. OPTIONS에 관해서 더 알아보고 싶다면 <a href="https://linux.die.net/man/1/scp">scp 옵션들</a>을 읽어보세요.</li><li><code>src_host</code> - 파일이 호스트 되는 곳입니다. 소스(source)는 파일의 위치에 따라 클라이언트나 서버 중 하나가 될 수 있습니다.</li><li><code>dest_host</code> - 파일이 복사되어 가는 곳입니다.</li></ul><p>파일을 전송하려면, 컴퓨터가 한 대 이상 있어야 합니다. 아래의 경우에서 SCP 명령어를 사용할 수 있습니다.</p><ul><li>같은 컴퓨터 내에서 파일 복사하기</li><li>로컬 호스트에서 원격 호스트로 또는 그 반대의 경우에서 파일 복사하기</li><li>서로 다른 두 원격 서버 사이에서 파일 복사하기</li></ul><p>이쯤에서, SCP 명령어를 사용하기 전 필요한 몇 가지를 짚고 가야겠습니다.</p><ul><li>클라이언트와 서버 컴퓨터에 모두에 SSH가 설치되어 있어야 합니다.</li><li>클라이언트와 서버 컴퓨터에 모두 Root 권한 접근이 가능해야 합니다.</li></ul><p>이 두 가지가 준비되었다면, 명령어를 확인하러 가봅시다.</p><h1 id="-scp-">대표적인 SCP 명령어</h1><h2 id="-">로컬 호스트에서 원격 서버로 파일 복제하기</h2><p>파일 복사 시에 파일 및 데이터를 로컬 저장소에서 원격 서버로 전송할 수 있다는 것은 매우 중요합니다. 이것이 가능해지려면 SCP 명령어를 사용할 때 몇 가지를 명시해주어야 합니다.</p><p>원천(source)으로서의 파일 경로와 복제되어 가는 원격 호스트의 경로를 명시해야 합니다.</p><p><code>test.txt</code>라는 파일을 원격 서버로 복제하는 상황을 생각해봅시다. 명령어는 아래와 같을 것입니다.</p><p><code>scp test.txt userbravo@destination:/location2</code></p><p>복제하고자 하는 파일 수에는 제한이 없습니다. 데스크톱 안에 web이라는 폴더 속 <code>.php</code> 확장자를 가진 파일이 있고, 이를 원격 서버의 홈 디렉터리로 복사하고자 한다고 해봅시다. 명령어는 아래와 같을 것입니다.</p><p><code>scp *.php userbravo@destinatino_host:/~/</code></p><p><strong>*.php</strong> - 현재 명시된 폴더 내 .php 확장자를 가진 모든 파일을 복사합니다.<br><strong>/~/</strong> - 이를 홈 디렉터리로 복사한다는 의미입니다.</p><p>이번에는 port 옵션 중 하나를 사용해 test.txt 파일을 복사해서 원격 서버에 다른 이름으로 저장하고자 한다면, 명령어는 아래와 같을 것입니다.</p><p><code>scp -P 8080 test.txt userbravo@destination_host:/user/home/test2.txt</code></p><p>방금 예시에서는 port 8080을 이용해 로컬의 test.txt 파일을 원격으로 전송해 test2.txt로 저장해보았습니다.</p><h2 id="--1">원격에서 로컬로 파일 복사하기</h2><p>예시를 활용하면 더 이해하기 쉽습니다. 원격 시스템에서 파일을 복사해오는 상황이라고 생각해봅시다. 파일을 복사하려면 SCP로 시작해 원격의 유저이름@IP 주소와 파일의 경로를 나열합니다.</p><p>경로를 지정하지 않으면, 사용자의 홈 디렉터리를 기본값으로 간주하며, 파일이 로컬로 저장되는 경로를 따릅니다.</p><h3 id="--2">문법</h3><p><code>scp file &lt;원격의_유저이름&gt;@&lt;IP_또는_호스트&gt;:&lt;파일의_경로&gt; &lt;로컬_파일_경로&gt;</code></p><p>*192.168.1.100.*이라는 주소의 원격 기기에서 <em>linuxcheatsheet</em>이라는 이름의 파일을 복사한다고 가정해봅시다.</p><p><em>linuxcheatsheet</em> 파일은 kali라는 사용자의 홈 디렉터리에 저장되어 있으며, 이 사용자를 인증해야 합니다. 그러니 콜론 뒤에, 이미 홈 디렉터리라는 기본값으로 설정이 되어 있으니 별도의 경로를 설정하지는 않아도 되며, 파일 이름("linuxcheatsheet")만 작성해줍니다. 이제 파일을 저장하기 위한 로컬 파일 경로로써 점(.)을 타이핑해 현재 디렉터리를 표시해줍니다.</p><p><code>scp linuxcheatsheet lary@192.168.1.100: .</code></p><h2 id="--3">원격 호스트로부터 다른 원격 호스트로 파일을 복사하기</h2><p>파일 전송에서 SCP를 사용하는 것의 장점은 로컬 컴퓨터 간의 연결뿐만 아니라 원격 서버 간의 연결도 가능하다는 것입니다.</p><p>test.txt라는 파일을 다른 원격 서버로 복사하려고 한다면, 명령어는 아래와 같을 것입니다.</p><p><code>scp user1@host1.com:/files/test.txt user2@user2.com:/files</code></p><p>이 명령어가 하는 일은 user1의 files 폴더에서 test.txt를 복사하고, <em>host2.com</em> 위에서 실행되는 user2에 files 폴더로 해당 복사본을 생성하는 것입니다.</p><h2 id="--4">파일 여러 개를 복사하기</h2><p>여러 파일을 복사할 때는 원본 경로로써 파일 이름을 지정해주기만 하면 됩니다. 예를 들어 봅시다.</p><h3 id="--5">문법</h3><p><code>scp file1 file2 ... user@&lt;사용자의_IP_주소&gt;: 도착지</code></p><p>files 1, 2, 3, 4를 복사한다고 해봅시다. 명령어는 아래와 같을 것입니다.</p><p><code>scp file1.txt file2.txt file3.txt file4.txt user1@host1.com:/home/user/Desktop</code></p><h1 id="--6">핵심 포인트</h1><ul><li>파일을 복사하려면, 원본 파일의 읽기 권한이, 목표 시스템에는 쓰기 권한이 있어야 합니다.</li><li>SCP 명령어는 안전한 데이터 전송을 위해 SSH에 의존하기 때문에 원격 시스템에 인증하기 위해 암호를 설정해야 합니다.</li><li>같은 이름과 위치에서 파일을 복사할 때는 SCP 명령어에서 별도로 경고하지 않고 덮어쓰기 할 수 있으므로 주의해야 합니다.</li><li>로컬과 원격 위치를 구분할 때는 쌍점(:)을 사용합니다.</li></ul><h1 id="--7">마무리</h1><p>기술지원이든, 시스템 관리자든, 혹은 저처럼 리눅스를 사용하며 성장하고 있거나 배우고 싶은 개발자라면 언젠가는 파일 전송이 필요한 때가 올 것입니다. 이 SCP 명령어를 알고 있으면 도움이 될 것입니다.</p><p>이 글에서는, SCP를 사용하려고 할 때 만날 수 있는 가장 흔한 경우를 다뤄보았는데, 뭔가 새로운 것을 얻어가셨기를 바랍니다.</p><p>즐거운 코딩 되시길 ❤</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ C++에서 String을 Integer로 변환하는 방법 예시 ]]>
                </title>
                <description>
                    <![CDATA[  > C++로 코드를 작성할 때, 어떤 데이터 타입을 다른 타입으로 바꿔야 할 때가 있을 것입니다. 이 글에서는 가장 잘 알려진 두 가지 방법을 통해 C++에서 string을 integer로 바꾸는 방법을 배울 수 있습니다. 그럼 시작해 봅시다! C++ 데이터 타입 C++ 프로그래밍 언어는 아래와 같은 내장 데이터 타입을 갖습니다.  * ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/c-eseo-stringeul-integerro-byeonhwanhaneun-bangbeob-yesi-2/</link>
                <guid isPermaLink="false">63d3a6537a7b73070e79722d</guid>
                
                    <category>
                        <![CDATA[ C++ ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jeong Won Yoo ]]>
                </dc:creator>
                <pubDate>Sat, 28 Jan 2023 22:47:08 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2023/01/nick-hillier-yD5rv8_WzxA-unsplash.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/string-to-int-in-c-how-to-convert-a-string-to-an-integer-example/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">String to Int in C++ – How to Convert a String to an Integer Example</a>
      </p><p></p><blockquote>C++로 코드를 작성할 때, 어떤 데이터 타입을 다른 타입으로 바꿔야 할 때가 있을 것입니다.</blockquote><p>이 글에서는 가장 잘 알려진 두 가지 방법을 통해 C++에서 string을 integer로 바꾸는 방법을 배울 수 있습니다.</p><p>그럼 시작해 봅시다!</p><h2 id="c-">C++ 데이터 타입</h2><p>C++ 프로그래밍 언어는 아래와 같은 내장 데이터 타입을 갖습니다.</p><ul><li>정수형을 위한 <code>int</code> (예, 10, 150)</li><li>부동 소수점을 위한 <code>double</code> (예, 5.0이나 4.5)</li><li>단일 문자를 위한 <code>char</code> (예, 'D'나 '!')</li><li>문자열을 위한 <code>string</code> (예, 'Hello')</li><li>boolean 값을 위한 <code>bool</code> (예, true나 false)</li></ul><p>C++는 강타입 프로그래밍 언어로, 변수를 하나 생성할 때 이 변수에 어떤 값이 저장될지 그 타입을 명확하게 명시해야 한다는 뜻입니다.</p><h2 id="c-int-">C++에서 <code>int</code>를 선언하고 초기화하는 방법</h2><p>C++에서 <code>int</code> 타입 변수를 <em>선언하려면</em> 변수의 데이터 타입을 먼저 작성해야 합니다. 이 경우에는 <code>int</code>가 되겠지요. 변수가 어떤 종류의 값을 저장할 수 있는지 컴파일러에 알려 필요한 행동을 취하도록 합니다.</p><p>다음으로, 변수에 이름을 지어줍니다.</p><p>마지막으로, 문장 끝에 세미콜론을 잊지 마세요!</p><pre><code class="language-c++">#include &lt;iostream&gt;

int main() {
  int age;
}
</code></pre><p>아래처럼 생성한 변수에 값을 줄 수도 있습니다.</p><pre><code class="language-c++">#include &lt;iostream&gt;

int main() {
  int age;
  age = 28;
}
</code></pre><p>이를 각각 별개의 작업으로 진행하는 대신, 변수를 <em>초기화하고</em> 결과를 출력해보는 과정을 합칠 수도 있습니다.</p><pre><code class="language-c++">// 이 헤더 파일을 통해 cout 같은 정보를 출력하기 위한 함수나 cin 같은 정보를 입력받는 함수를 사용할 수 있습니다.

#include &lt;iostream&gt;

// 이름공간 문장(statement)을 사용하면 std:: 접두사를 쓸 필요 없습니다.
using namespace std;

int main() { // 프로그램의 메인 함수의 시작입니다.
  int age = 28;
  // 변수를 초기화합니다.
  // 초기화란 타입과 이름, 값을 한 번에 제공하는 과정입니다.

  // &lt;&lt; 을 사용해 콘솔에 연결해 출력합니다. "My age is 28"
  cout &lt;&lt; "My age is: " &lt;&lt; age &lt;&lt; endl;
} // 메인 함수의 끝입니다.
</code></pre><h2 id="c-string-">C++에서 <code>string</code>을 선언하고 초기화하는 방법</h2><p>String은 개별 문자 집합입니다.</p><p>C++에서 String 선언은 위에서 본 바와 같이 <code>int</code>의 선언과 초기화와 매우 유사합니다.</p><p>C++ 표준 라이브러리는 <code>string</code> 클래스를 제공합니다. string 데이터 타입을 사용하려면 파일의 가장 상단, <code>#include &lt;ionstream&gt;</code> 다음 <code>&lt;string&gt;</code> 헤더 라이브러리를 추가합니다.</p><p>헤더 파일을 포함해준 뒤, 이미 확인한 바처럼 <code>using namespace std;</code>를 추가할 수 있습니다.</p><p>무엇보다도, 이 한 줄을 추가해준 다음엔 string 변수를 생성할 때마다 <code>std::string</code>를 사용하지 않아도 되어서, <code>string</code>만으로 충분합니다.</p><pre><code class="language-c++">#include &lt;ionstream&gt;
#include &lt;string&gt;

int main() {
  // string 변수를 선언합니다.

  string greeting;
  greeting = "Hello";
  // `=`는 할당 연산자로, 값을 변수에 할당합니다.
}
</code></pre><p>또는 string 변수를 초기화하고 이를 콘솔에 출력해볼 수 있습니다.</p><pre><code class="language-c++">#include &lt;ionstream&gt;
#include &lt;string&gt;
using namespace std;

int main() {
  // string 변수를 초기화합니다.

  string greetig = "Hello";

  // "Hello"를 콘솔에 출력합니다.
  cout &lt;&lt; greeting &lt;&lt; endl;
}
</code></pre><h2 id="string-ineger-">string을 ineger로 변환하는 방법</h2><p>이전에 언급한 것처럼, C++는 강타입 언어입니다.</p><p>데이터 타입과 맞지 않은 값을 제공하고자 할 때 에러를 만납니다.</p><p>또한, string을 integer로 변환하는 것은 <code>double</code>을 <code>int</code>로 변환시키는 타입 캐스팅(type casting)을 쓰는 것처럼 간단하지는 않습니다.</p><p>아래는 사용할 수 없는 코드 예시입니다.</p><pre><code class="language-c++">#include &lt;iostream&gt;
#include &lt;string&gt;
using namespace std;

int main() {
  string str = "7";
  int num;

  num = (int) str;
}
</code></pre><p>컴파일링 후 나오는 에러는 아래와 같습니다.</p><pre><code>hellp.cpp:9:10: error: no matching conversion for C-style cast from 'std::__1::string' (aka
      'basic_string&lt;char, char_traits&lt;char&gt;, allocator&lt;char&gt; &gt;') to 'int'
   num = (int) str;
         ^~~~~~~~~
/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1/string:875:5: note: candidate function
    operator __self_view() const _NOEXCEPT { return __self_view(data(), size()); }
    ^
1 error generated.
</code></pre><p>string을 int로 바꾸는 여러 가지 방법 중 아래에서는 두 가지를 소개하려고 합니다.</p><h3 id="stoi-string-int-"><code>stoi()</code> 함수를 사용해 string을 int로 변환하는 방법.</h3><p>string 객체를 numeral int로 변환하는 가장 효과적인 방법은 <code>stoi()</code> 함수를 사용하는 것입니다.</p><p>이 방법은 C++11에 도입된 새로운 버전의 C++에서 흔하게 사용됩니다.</p><p>string 값을 입력으로 받고, 이의 integer를 출력으로 반환합니다.</p><pre><code class="language-c++">#include &lt;iostream&gt;
#include &lt;string&gt;
using namespace std;

int main() {
  // str이라고 변수명을 지은 string 값
  string str = "7";
  // 콘솔에 출력
  count &lt;&lt; "I am a string" &lt;&lt; str &lt;&lt; endl;

  // string str 변수를 int 값을 갖도록 변환
  // num으로 이름 지은 새로운 변수가 int 값을 갖도록 새로운 값을 놓습니다.
  int num = stoi(str);

  // 콘솔에 출력
  cout &lt;&lt; "I am an int" &lt;&lt; num &lt;&lt; endl;
}
</code></pre><p>출력</p><pre><code>I am a string 7
I am an int 7
</code></pre><h3 id="stringstream-string-int-"><code>stringstream</code> 클래스를 사용해 string을 int로 변환하는 방법</h3><p><code>stringstream</code> 클래스는 이전 버전 C++에서 주로 사용되었습니다. string에 입력과 출력을 수행합니다.</p><p>이를 사용하려면, <code>#include &lt;sstream&gt;</code>라는 코드를 추가해 <code>sstream</code> 라이브러리를 프로그램 상단에 포함시킵니다.</p><p>이후 <code>stringstream</code>을 추가한 뒤, <code>stringstream</code> 객체를 생성합니다. 이는 int로 변환하고자 하는 string 값을 가지며 int로 변환하는 동안에 사용됩니다.</p><p>string 값에서 string을 추출하려면 <code>&lt;&lt;</code> 연산자를 사용해야 합니다.</p><p>마지막으로 int 변수에 새로 변환된 int 값을 입력하기 위해 <code>&gt;&gt;</code> 연산자를 사용합니다.</p><pre><code class="language-c++">#include &lt;iostream&gt;
#include &lt;string&gt;
#include &lt;sstream&gt; // 이제 프로그램 내에서 stringstream을 사용할 수 있습니다.

using namespace std;

int main() {
   // string을 입력 및 출력하기 위해 stringstream 객체를 생성합니다.
   stringstream ss;

   // str이라는 변수는 string 데이터 타입을 갖습니다.
   string str = "7";

   // num이라는 변수는 int 데이터 타입을 갖습니다.
   int num;

   // str 변수에서 string 값을 추출합니다. (스트림에 string 값을 입력합니다.)
   ss &lt;&lt; str;

   // 변환된 값을 int 변수에 넣습니다.
   ss &gt;&gt; num;

   // 콘솔에 출력합니다.
   cout &lt;&lt; num &lt;&lt; endl; // intiger 값 7을 출력합니다.
}
</code></pre><h2 id="-">마무리</h2><p>아주 쉽죠! C++에서 가장 쉽게 string을 integer로 변환하는 두 가지 방법을 확인했습니다.</p><p>C++ 프로그래밍 언어를 더 알고 싶다면, freeCodeCamp 유튜브 채널의 이 <a href="https://www.youtube.com/watch?v=vLnPwxZdW4Y&amp;t=3485s&amp;ab_channel=freeCodeCamp.org">4시간짜리 코스</a>를 확인해 보세요.</p><p>읽어주셔서 감사합니다. 즐겁게 배우시길 바랍니다 😊</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Debounce(디바운스) - JavaScript에서 함수를 지연시키는 방법 (JS ES6 예제) ]]>
                </title>
                <description>
                    <![CDATA[ JavaScript에서 debounce(디바운스)는 유저가 입력할 때마다 코드를 오직 한 번씩만 실행되도록 해주는 함수입니다. 검색 박스의 제안, 텍스트 필드의 자동 저장, 버튼의 더블 클릭의 제거가 모두 debounce를 이용하는 사례입니다. 이번 튜토리얼에서는 JavaScript에서 어떻게 debounce 함수를 만들어보는지 배워볼 것입니다. debounce가 뭔가요? debounce는 전자 공학에서 온 용어입니다. 버튼을 누를 때, 예를 들어 TV 리모컨이라고 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/debounce-dibaunseu-javascripteseo-hamsureul-jiyeonsikineun-bangbeob-js-es6-yeje/</link>
                <guid isPermaLink="false">63988198387939063fcf5568</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jeong Won Yoo ]]>
                </dc:creator>
                <pubDate>Tue, 20 Dec 2022 18:59:39 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2022/12/teaser.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/javascript-debounce-example/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Debounce – How to Delay a Function in JavaScript (JS ES6 Example)</a>
      </p><p>JavaScript에서 debounce(디바운스)는 유저가 입력할 때마다 코드를 오직 한 번씩만 실행되도록 해주는 함수입니다. 검색 박스의 제안, 텍스트 필드의 자동 저장, 버튼의 더블 클릭의 제거가 모두 debounce를 이용하는 사례입니다.</p><p>이번 튜토리얼에서는 JavaScript에서 어떻게 debounce 함수를 만들어보는지 배워볼 것입니다.</p><h2 id="debounce-">debounce가 뭔가요?</h2><p><strong>debounce</strong>는 전자 공학에서 온 용어입니다. 버튼을 누를 때, 예를 들어 TV 리모컨이라고 해본다면, 버튼에서 손을 떼려고 하기도 전에 신호는 아주 빠르게 리모컨의 마이크로칩으로 흐르고, 이것이 디바운스(debounce) 되어 마이크로칩은 이렇게 여러 번 '클릭'한 것을 등록해버립니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/news/content/images/2021/01/debounce-button.png" class="kg-image" alt="버튼이 클릭되었을 때 신호가 흐르는 양상을 보여주는 그림으로, 고점과 저점 사이 신호가 무시되는 지점이 파란색 칠이 되어 있다. 신호가 고점과 저점을 여러 번 반복하는 지점이 무시되는 지점으로 설명되고 있다. " width="600" height="400" loading="lazy"><figcaption>버튼이 클릭되었을 때 신호가 흐르는 양상을 보여주는 그림으로, 고점과 저점 사이 신호가 무시되는 지점이 파란색 칠이 되어 있다. 신호가 고점과 저점을 여러 번 반복하는 지점이 무시되는 지점으로 설명되고 있다.</figcaption></figure><p>이 과정을 줄이려면, 일단 버튼으로부터 신호를 받았다면, 마이크로칩은 물리적으로 다시 버튼을 누르는 것이 불가능한, 약 몇 마이크로초 동안 버튼으로부터 온 신호를 처리하지 않습니다.</p><h2 id="javascript-debounce">JavaScript에서의 Debounce</h2><p>JavaScript에서의 사용 예도 비슷합니다. 사용 사례에 따라 오직 한 번만 함수를 실행하고 싶습니다.</p><p>방문자가 타이핑을 끝내고 난 뒤에만 검색 질의(query)에 대한 제안 옵션을 보여주고 싶다고 해봅시다.</p><p>아니면 양식의 내용을 저장하고자 하지만, 매번 '저장'이 발생한다면 데이터베이스를 거쳐야 하니, 사용자가 해당 내용을 적극적으로 변경하지 않을 때만 이를 하고 싶다고 해봅시다.</p><p>어떤 분들은 Window 95에 너무 익숙해진 나머지 이제 모든 곳에 더블 클릭하는, 제가 정말 좋아하는 이야기도 있고 말이죠. 😁</p><p>간단하게 debounce 함수를 구현해보았습니다. <a href="https://codepen.io/ondrabus/pen/WNGaVZN">CodePen은 이곳에서</a> 확인해보세요. </p><pre><code class="language-js">function debounce(func, timeout = 300) {
  let timer;
  return (...args) =&gt; {
    clearTimeout(timer);
    timer = setTimeout(() =&gt; {
      func.apply(this, args);
    }, timeout);
  };
}
function saveInput() {
  console.log('Saving data');
}
const processChange = debounce(() =&gt; saveInput());
</code></pre><p>입력창에서는 이렇게 쓰입니다.</p><pre><code class="language-html">&lt;input type="text" onkeyup="processChange()" /&gt;
</code></pre><p>버튼에서는 이렇게 쓰입니다.</p><pre><code class="language-html">&lt;button onclick="processChange()"&gt;Click me&lt;/button&gt;
</code></pre><p>window 이벤트</p><pre><code class="language-js">window.addEventListener('scroll', processChange);
</code></pre><p>간단한 JS 함수 같은 요소에도 쓸 수 있습니다.</p><p>자 그럼 어떤 일이 일어날까요? <code>debounce</code>는 특별한 함수로, 아래 두 가지 일을 처리합니다.</p><ul><li><em>timer</em> 값의 스코프(scope)를 할당하고</li><li>함수가 지정된 시간에 작동되도록 스케줄링합니다.</li></ul><p>가장 먼저 다룬 텍스트 입력창의 사례로 어떻게 이것이 작동하는지 설명해보도록 하겠습니다.</p><p>방문자가 첫 글자를 입력하고 키에서 손을 뗄 때, <code>debounce</code>는 우선 <code>clearTimeout(timer)</code>을 가지고 timer를 재설정합니다. 이때, 스케줄에 잡은 것이 아무것도 없으니, 이 단계가 꼭 필수적이진 않습니다. 이후 주어진 함수인 <code>saveInput()</code>을 300 밀리세컨드마다 실행되도록 스케줄 합니다.</p><p>그런데 만일 방문자가 계속 작성하는 동안 키에서 손을 뗄 때마다 <code>debounce</code>가 한 번 더 실행된다고 해봅시다. 매번 timer를 재설정한다는 것은 다시 말해 <code>saveInput()</code>과 함께 직전에 예정한 것을 취소하고 이다음의 300 밀리세컨드라는 새로운 시간으로 다시 스케줄 한다는 것입니다. 이는 방문자가 300 밀리세컨드 이내에 계속 키를 입력하는 한 지속됩니다.</p><p>마지막 스케줄은 제거되지 않으므로, 마침내 <code>saveInput()</code>이 호출됩니다.</p><h2 id="-">반대의 경우 - 뒤이은 이벤트를 무시하는 방법</h2><p>방금까지의 방법은 자동 저장이나 제안 옵션을 보여줄 때 좋습니다. 하지만 버튼 하나를 여러 번 클릭하는 사례에서는 어떨까요? 우리는 마지막 클릭까지 기다리는 대신 첫 번째 클릭에서 이를 등록하고, 나머지를 무시하고자 할 것입니다. <a href="https://codepen.io/ondrabus/pen/bGwmXjN">CodePen은 이곳에서</a></p><pre><code class="language-js">function debounce_leading(func, timeout = 300) {
  let timer;
  return (...args) =&gt; {
    if (!timer) {
      func.apply(this, args);
    }
    clearTimeout(timer);
    timer = setTimeout(() =&gt; {
      timer = undefined;
    }, timeout);
  };
}
</code></pre><p>여기에서는 첫 번째로 버튼이 클릭 되었을 때 발생한 첫 번째 <code>debounce_leading</code> 호출에서 <code>saveInput()</code> 함수가 동작합니다. timer는 300 밀리세컨드 이후 사라지도록 설정했습니다. 이 시간 내 뒤이은 모든 버튼 클릭은 이미 지정된 timer가 있으므로, timer가 사라지기까지 남은 시간을 계속해서 300 밀리세컨드로 초기화합니다.</p><h2 id="-debounce">라이브러리에서 구현된 Debounce</h2><p>이 글에서는 여러분이 어떻게 JavaScript에서 debounce를 구현하고, 웹사이트 요소에서 발생하는 이벤트를 잘 debounce 하기 위한 사용 방법을 보여드렸습니다.</p><p>그러나 여러분이 원하지 않는다면 직접 프로젝트에서 <em>debounce</em> 구현하지 않아도 됩니다. 이미 널리 쓰이는 JS 라이브러리에 구현되어 있습니다. 아래는 몇 가지 예시입니다.</p><!--kg-card-begin: html--><table>
<thead>
<tr>
<th>라이브러리</th>
<th>사용 예</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="http://benalman.com/projects/jquery-throttle-debounce-plugin/">jQuery (라이브러리를 통한)</a></td>
<td><code>$.debounce(300, saveInput);</code></td>
</tr>
<tr>
<td><a href="https://lodash.com/docs/4.17.15#debounce">Lodash</a></td>
<td><code>_.debounce(saveInput, 300)</code></td>
</tr>
<tr>
<td><a href="https://underscorejs.org/#debounce">Underscore</a></td>
<td><code>_.debounce(saveInput, 300);</code></td>
</tr>
</tbody>
</table><!--kg-card-end: html--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ CSS와 HTML에서 SVG 이미지 사용하는 방법 - 입문자용 튜토리얼 ]]>
                </title>
                <description>
                    <![CDATA[ > SVG는 확장할 수 있는 벡터 그래픽(Scalable Vector Graphics)이라고 합니다. 확장 가능 마크업 언어(Extensible Markup Language; XML)로 쓰인 벡터 기반의 독특한 유형의 이미지 포맷입니다. 이번 튜토리얼에서는 SVG 이미지의 장점으로는 어떤 것들이 있는지, 어떻게 이를 CSS와 HTML 안에서 사용할 수 있는지 설명하려고 합니다. 왜 SVG 이미지를 써야 할까요? SVG 이미지를 사용해야 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/csswa-htmleseo-svg-imiji-sayonghaneun-bangbeob-ibmunjayong-tyutorieol/</link>
                <guid isPermaLink="false">6398760b387939063fcf551e</guid>
                
                    <category>
                        <![CDATA[ SVG ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jeong Won Yoo ]]>
                </dc:creator>
                <pubDate>Tue, 20 Dec 2022 18:56:07 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2022/12/Screen-Shot-2020-11-15-at-3.59.07-PM.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/use-svg-images-in-css-html/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Use SVG Images in CSS and HTML – A Tutorial for Beginners</a>
      </p><blockquote>SVG는 확장할 수 있는 벡터 그래픽(Scalable Vector Graphics)이라고 합니다. 확장 가능 마크업 언어(Extensible Markup Language; XML)로 쓰인 벡터 기반의 독특한 유형의 이미지 포맷입니다.</blockquote><p>이번 튜토리얼에서는 SVG 이미지의 장점으로는 어떤 것들이 있는지, 어떻게 이를 CSS와 HTML 안에서 사용할 수 있는지 설명하려고 합니다.</p><h1 id="-svg-">왜 SVG 이미지를 써야 할까요?</h1><p></p><p>SVG 이미지를 사용해야 하는 이유야 많지만, 몇 가지를 꼽아보자면,</p><ul><li>줌을 하거나 크기를 변경할 때 SVG 이미지의 질은 떨어지지 않습니다.</li><li>IDE나 텍스트 편집기에서 SVG 이미지를 작성하고 수정할 수 있습니다.</li><li>SVG 이미지는 이해하기 쉬우며 애니메이션이 가능합니다.</li><li>파일 크기는 작지만, 확장이 매우 용이합니다.</li><li>SVG 이미지를 검색하고 색인화할 수 있으며 스크립트 처리나 압축도 가능합니다.</li></ul><p>이제 정말로 SVG 이미지를 어떻게 사용할 수 있는지 알아봅시다.</p><h1 id="-svg--1">해당 튜토리얼에서 사용된 SVG 이미지를 다운로드하는 방법</h1><p>이 튜토리얼에서 사용된 SVG 이미지를 사용하고 싶다면, 아래 단계(와 도표)를 참고해 다운로드받아주세요.</p><ul><li><a href="https://undraw.co/">unDraw</a>에 접속합니다.</li><li>노란색으로 배경색을 변경해보세요.</li><li>검색창에서 <strong>happy</strong>라는 단어로 검색해보세요.</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://i.imgur.com/ncSY7Rn.png" class="kg-image" alt="unDraw 페이지에 대한 설명. 상단 오른쪽에 색상을 바꾸는 버튼이 있다. 가운데 검색창이 있으며 아래 SVG 이미지 목록이 뜬다." width="1836" height="888" loading="lazy"><figcaption>unDraw 페이지에 대한 설명. 상단 오른쪽에 색상을 바꾸는 버튼이 있다. 가운데 검색창이 있으며 아래 SVG 이미지 목록이 뜬다.</figcaption></figure><ul><li><strong>Happy news</strong>라는 이름의 이미지를 클릭하세요.</li><li>팝업창에서 Download SVG to your projects라고 쓰인 버튼을 클릭하세요.</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://i.imgur.com/qGrT73n.png" class="kg-image" alt="Happy news라는 이름의 SVG 일러스트와 이를 다운로드할 수 있는 버튼이 오른쪽에 위치해 있다." width="1336" height="581" loading="lazy"><figcaption>Happy news라는 이름의 SVG 일러스트와 이를 다운로드할 수 있는 버튼이 오른쪽에 위치해 있다.</figcaption></figure><p>올바르게 위 단계를 진행했다면, SVG 이미지는 이제 컴퓨터에 있을 것입니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://i.imgur.com/3uCGy6B.png" class="kg-image" alt="크롬 파일 다운로드 창" width="1003" height="183" loading="lazy"><figcaption>크롬 파일 다운로드 창</figcaption></figure><p>이제 당신이 좋아하는 IDE나 텍스트 편집기에서 다운로드한 SVG 이미지를 열어 봅니다. <strong>happy.svg</strong>나 마음에 드는 이름으로 파일명을 변경하세요.</p><h1 id="css-html-svg-">CSS와 HTML에서 SVG 이미지를 사용하는 방법</h1><p>CSS와 HTML 내에서 SVG 이미지를 사용하는 방법은 아주 다양합니다. 이번 튜토리얼에서는 여섯 가지 종류에 대해 알아봅시다.</p><h2 id="1-img-svg-">1. <code>&lt;img&gt;</code>로 SVG를 사용하기</h2><p>이 방법은 웹페이지에 SVG 이미지를 삽입하는 가장 간단한 방법입니다. 이렇게 쓰려면 HTML 문서에 <code>&lt;img&gt;</code> 태그를 추가하고 아래처럼 <code>src</code> 속성이 SVG 이미지를 참조하도록 합니다:</p><pre><code class="language-html">
&lt;img src="happy.svg" alt="My Happy SVG" /&gt;

</code></pre><p>unDraw에서 SVG를 다운로드했고 <strong>happy.svg</strong>라고 파일명을 바꿨다고 한다면, 이제 여러분의 HTML 문서로 위 코드 예시를 추가해보세요.</p><p>모두 제대로 완료했다면, 여러분의 웹페이지는 아래 예시와 똑같아 보일 것입니다. 👀</p><figure class="kg-card kg-embed-card"><iframe width="1000" height="500" src="https://codesandbox.io/embed/svg-demo-mppxs?fontsize=14&amp;hidenavigation=1&amp;theme=dark" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin" title="포함된 컨텐츠" loading="lazy"></iframe></figure><p>크기를 지정하지 않은 경우 <code>&lt;img&gt;</code> 태그를 통해 SVG 이미지를 추가한다면 원본 SVG 파일 크기로 상정됩니다.</p><p>예를 들어, 위 예시에선 SVG 이미지 크기를 수정하지 않았으므로 원본 크기로 간주했습니다. (가로 <code>915.11162 px</code>과 세로 <code>600.53015 px</code>.)</p><p><strong>노트:</strong> 원본 크기를 변경하고 싶다면, 아래 예시에서처럼 CSS를 이용해 <code>width</code>와 <code>height</code>를 정해줍니다. 직접 원본의 <code>width</code>와 <code>height</code>를 변경할 수도 있습니다.</p><figure class="kg-card kg-embed-card"><iframe width="1000" height="500" src="https://codesandbox.io/embed/svg-demo-1-ey5me?fontsize=14&amp;hidenavigation=1&amp;theme=dark" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin" title="포함된 컨텐츠" loading="lazy"></iframe></figure><p><code>&lt;img&gt;</code> 태그를 통해서 추가된 SVG 이미지의 크기를 변경할 수 있게 되었지만, SVG 이미지에 주요 스타일을 적용하려고 한다면 여전히 많은 제약이 있습니다.</p><h2 id="2-background-image-svg-">2. <code>background-image</code>로 SVG를 사용하기</h2><p>이 방법은 HTML 문서에 <code>&lt;img&gt;</code> 태그를 이용해 SVG를 추가하는 것과 비슷합니다. 하지만, 이번에는 아래 코드 예시에서처럼 HTML 대신 CSS를 사용해 진행해보겠습니다.</p><pre><code class="language-css">body {
	background-image: url(happy.svg);
}</code></pre><p>CSS 백그라운드 이미지로 SVG를 사용한다면, <code>&lt;img&gt;</code> 태그를 사용하는 것과 비슷하게 제약 사항이 있지만, 좀 더 여러분의 입맛에 맞출 수 있는 여지가 있습니다.</p><p>아래의 예시를 살펴보고, CSS를 이용해 자유롭게 수정해보세요.</p><figure class="kg-card kg-embed-card"><iframe width="1000" height="500" src="https://codesandbox.io/embed/svg-demo-2-ftn6n?fontsize=14&amp;hidenavigation=1&amp;theme=dark" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin" title="포함된 컨텐츠" loading="lazy"></iframe></figure><h2 id="3-inline-svg-">3. 인라인(inline) SVG 이미지 사용하는 방법</h2><p>SVG 이미지는 <code>&lt;svg&gt;&lt;/svg&gt;</code> 태그로 직접 HTML 문서에 삽입해 작성할 수도 있습니다.</p><p>VS code나 선호하는 IDE에서 SVG 이미지를 열고, 코드를 복사해 HTML 문서 <code>&lt;body&gt;</code> 요소 안에 붙여 넣으세요.</p><pre><code class="language-html">&lt;body&gt;
	// Paste the SVG code here. 
&lt;/body&gt;</code></pre><p>제대로 다 작성했다면, 웹페이지는 아래 예시처럼 보일 것입니다.</p><figure class="kg-card kg-embed-card"><iframe width="1000" height="500" src="https://codesandbox.io/embed/svg-demo-3-zunkd?fontsize=14&amp;hidenavigation=1&amp;theme=dark" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin" title="포함된 컨텐츠" loading="lazy"></iframe></figure><p>HTML 문서 내 인라인 SVG를 사용하면 HTTP 요청에 따라 제공되므로 로드되는 시간이 절약됩니다.</p><p>해당 방법을 사용하면 <code>&lt;img&gt;</code> 태그나 <code>background-image</code>를 사용하는 것과 다르게 더 다양하게 커스터마이징 할 수 있습니다.</p><h2 id="4-object-svg-">4. <code>&lt;object&gt;</code>로 SVG 사용하는 방법</h2><p>아래 코드 문법을 사용하면, HTML <code>&lt;object&gt;</code> 요소를 이용해 웹페이지에 SVG 이미지를 추가할 수 있습니다.</p><pre><code class="language-html">
&lt;object data="happy.svg" width="300" height="300"&gt;&lt;/object&gt;

</code></pre><p>사용하려고 하는 대상인 리소스의 URL을 <code>data</code>라는 속성을 이용해 지정 가능한데, 우리의 경우는 이것이 SVG 이미지가 됩니다.</p><p>SVG 이미지의 크기를 지정하기 위해 <code>width</code>와 <code>height</code>를 사용할 수 있어요.</p><p>마찬가지로, 더 탐구해보고 싶다면 아래 예시를 참고해보세요 😃</p><figure class="kg-card kg-embed-card"><iframe width="1000" height="500" src="https://codesandbox.io/embed/svg-demo-4-3ge0n?fontsize=14&amp;hidenavigation=1&amp;theme=dark" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin" title="포함된 컨텐츠" loading="lazy"></iframe></figure><h2 id="5-iframe-svg-">5. <code>&lt;iframe&gt;</code>으로 SVG 사용하는 방법</h2><p>권장하지는 않는 방식이지만, 아래 예시에서 보이는 것처럼 <code>&lt;iframe&gt;</code>을 이용해 SVG 이미지를 추가할 수도 있습니다.</p><figure class="kg-card kg-embed-card"><iframe width="1000" height="500" src="https://codesandbox.io/embed/svg-demo-5-co3hg?fontsize=14&amp;hidenavigation=1&amp;theme=dark" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin" title="포함된 컨텐츠" loading="lazy"></iframe></figure><p>다만 <code>&lt;iframe&gt;</code>은 유지보수가 어렵고, 검색 엔진 최적화(Search Engine Optimization; SEO)에 안 좋다는 것을 명심하세요.</p><p>또한, <code>&lt;iframe&gt;</code> 포맷에 SVG 이미지가 삽입된다면 확장 가능하지 않으므로, 확장할 수 있는 벡터 그래픽이라는 이름에서 *확장할 수 있는(Scalable)*이라는 목적을 무색하게 합니다.</p><h2 id="6-embed-svg-">6. <code>&lt;embed&gt;</code>로 SVG 사용하는 방법</h2><p>HTML <code>&lt;embed&gt;</code> 요소는 HTML과 CSS에서 SVG 이미지를 사용하는 또 다른 방법으로, 이런 문법을 사용합니다: <code>&lt;embed src="happy.svg" /&gt;</code>.</p><p>하지만, 이 방법 역시 제한적이라는 사실을 알아둡시다. MDN에 따르면, 대부분의 모던 브라우저들은 브라우저 플러그인에 대한 지원을 중단하거나 삭제해왔습니다. 즉, 여러분의 사이트가 일반 사용자의 브라우저에서 작동되길 바란다면, <code>&lt;embed&gt;</code>에 기대는 것은 일반적으로 현명하지는 않다는 것입니다.</p><p>아래는 HTML <code>&lt;embed&gt;</code> 요소를 사용해 SVG 이미지를 추가한 예시입니다.</p><figure class="kg-card kg-embed-card"><iframe width="1000" height="500" src="https://codesandbox.io/embed/svg-demo-6-iwy0s?fontsize=14&amp;hidenavigation=1&amp;theme=dark" style="width:1000px; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin" title="포함된 컨텐츠" loading="lazy"></iframe></figure><h1 id="-">결론</h1><p>이 튜토리얼을 통해 CSS와 HTML에 SVG를 사용하는 다양한 방법을 배웠길 바랍니다. 이 글이 웹사이트에 SVG 이미지를 추가하고자 할 때 맞는 방법을 선택할 수 있도록 도움이 되었으면 좋겠습니다.</p><p>질문이 있다면, 제게 <a href="https://twitter.com/Didicodes">트위터</a>를 통해 메시지를 보내주세요. 기쁜 마음으로 하나하나 답변해드리겠습니다.</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript에서 가장 잘 알려진 HTTP 요청 방법 ]]>
                </title>
                <description>
                    <![CDATA[ JavaScript는 서버 단의 자원으로부터 데이터를 전송받거나 전송할 수 있는 HTTP 요청을 만들 수 있는 훌륭한 모듈(modules)과 메서드(method)가 있습니다. 이 글에서는, JavaScript 내에서 잘 알려진 여러 HTTP 요청 방법을 살펴보려고 합니다. Ajax Ajax는 전통적인 방법으로 비동기(asynchronous) HTTP 요청을 보냅니다. HTTP POST를 통해 데이터를 보낼 수 있고, HTTP GET을 통해 데이터를 전송받을 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/javascripteseo-gajang-jal-alryeojin-http-yoceong-bangbeob-2/</link>
                <guid isPermaLink="false">637ec5e74d426206f9a4fc8a</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jeong Won Yoo ]]>
                </dc:creator>
                <pubDate>Wed, 07 Dec 2022 07:01:57 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2022/11/1_gqHgCNubMncv7EwWNdArGQ.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/here-is-the-most-popular-ways-to-make-an-http-request-in-javascript-954ce8c95aaa/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Here are the most popular ways to make an HTTP request in JavaScript</a>
      </p><p>JavaScript는 서버 단의 자원으로부터 데이터를 전송받거나 전송할 수 있는 HTTP 요청을 만들 수 있는 훌륭한 모듈(modules)과 메서드(method)가 있습니다. 이 글에서는, JavaScript 내에서 잘 알려진 여러 HTTP 요청 방법을 살펴보려고 합니다.</p><h1 id="ajax">Ajax</h1><p>Ajax는 전통적인 방법으로 비동기(asynchronous) HTTP 요청을 보냅니다. HTTP POST를 통해 데이터를 보낼 수 있고, HTTP GET을 통해 데이터를 전송받을 수 있습니다. 한 번 살펴보고 <code>GET</code> 요청을 보내봅시다. 저는 무작위 데이터를 JSON 형태로 반환하는 JSONPlaceholder라는 개발자를 위한 무료 온라인 REST API를 사용하려고 합니다.</p><p>Ajax로 HTTP 호출을 보내기 위해서는 새로운 <code>XMLHttpRequest()</code> 메서드를 초기화하고, URL 엔드포인트(endpoint)와 HTTP 메서드(이 경우에는 GET이겠죠)를 지정해야 합니다. 마지막으로, <code>open()</code> 메서드를 사용해 HTTP 메서드와 URL 엔드포인트를 연결하고 <code>send()</code> 메서드로 요청을 실행합니다.</p><p>HTTP 응답(response)을 콘솔로 찍어볼 수 있는데, <code>readystatechanged</code> 이벤트가 발생했을 때 호출한 이벤트 핸들러를 포함한 <code>XMLHttpRequest.onreadystatechange</code> 속성을 사용해봅시다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/1*zXtlRe4yRF3tZkFFvBhZeA.png" class="kg-image" alt="위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다." width="800" height="349" loading="lazy"><figcaption>위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다.</figcaption></figure><pre><code class="language-js">
const Http = new XMLHttpRequest();

const url = 'https://jsonplaceholder.typicode.com/posts';

Http.open('GET', url);
Http.send();
Http.onreadystatechange = (e) =&gt; {
	console.log(Http.responseText);
};</code></pre><p>브라우저 콘솔을 보면, 반환된 JSON 형식의 배열 데이터를 확인할 수 있습니다. 하지만, 어떻게 요청이 완료되었는지 알 수 있을까요? 다른 말로 하자면, Ajax를 통해 응답을 어떻게 처리할 수 있는 걸까요?</p><p><code>onreadystatechange</code> 속성은 두 가지 메서드, <code>readyState</code>와 <code>status</code>로 우리가 보낸 요청의 상태를 확인하도록 해 줍니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/1*UfZf6qaZwNh5Mptft4WIZA.png" class="kg-image" alt="위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다." width="800" height="376" loading="lazy"><figcaption>위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다.</figcaption></figure><pre><code class="language-js">
// 역자 주 : 이하 일부 이미지와 함께 제공되는 코드는 원글에는 존재하지 않지만, 번역 과정에서 대체 텍스트를 제공할 목적으로 추가되었습니다.

const Http = new XHMLHttpRequest();
const url = 'https://jsonplaceholder.typicode.com/posts';

Http.open('GET', url);
Http.send();

Http.onreadystatechange = function () {
	if (this.readyState == 4 &amp;&amp; this.status == 200) {
		console.log(Http.responseText);
	}
};</code></pre><p>만일 <code>readyState</code>가 4와 동일하다면, 이는 요청이 완료되었다는 의미입니다. <code>readyState</code> 속성에는 총 다섯 가지 응답이 있습니다. <a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState">이곳</a>에서 더 확인해보세요.</p><p>JavaScript로 직접 Ajax 호출을 하는 것 말고도, jQuery 메서드인 <code>$.Ajax</code> 같은 HTTP 호출을 만들어내는 강력한 메서드가 존재합니다. 이제부터 이에 대해 이야기해봅시다.</p><h2 id="jquery-">jQuery 메서드</h2><p>jQuery는 HTTP 요청을 쉽게 다루게 해주는 다양한 메서드를 가지고 있습니다. 이 메서드를 사용하려면, jQuery 라이브러리를 프로젝트에 포함해야 합니다.</p><pre><code class="language-html">&lt;script src='https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js'&gt;&lt;/script&gt;</code></pre><h3 id="-ajax">$.ajax</h3><p>jQuery Ajax는 HTTP 호출을 만들어내는 가장 쉬운 방법 중 하나입니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/1*vZ4BqVQfsvtpJm_RCsCE2Q.png" class="kg-image" alt="위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다." width="800" height="429" loading="lazy"><figcaption>위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다.</figcaption></figure><pre><code class="language-js">
$(document).ready(function() {
	const Url = 'https://jsonplaceholder.typicode.com/posts';
	$('.btn').click(function() {
		$.ajax({
			url: Url,
			type: "GET",
			success: function(result) {
				console.log(result)
			},
			error:function(error) {
				console.log(`Error ${error})
			}
		})
	})
})</code></pre><p>이 $.ajax 메서드는 많은 파라미터를 받는데, 이중 일부는 필수적이고 일부는 선택적으로 사용됩니다. 이 메서드는 전달받은 응답을 다룰 수 있도록 두 가지 콜백(callback) 옵션인 <code>success</code>와 <code>error</code>를 가집니다.</p><h3 id="-get-">$.get 메서드</h3><p>$.get 메서드는 GET 요청을 실행하기 위해 사용됩니다. 엔드포인트와 콜백 함수 이렇게 두 가지 파라미터를 받습니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/1*2koN5FJuT68WIyRKTihe5w.png" class="kg-image" alt="위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다." width="797" height="287" loading="lazy"><figcaption>위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다.</figcaption></figure><pre><code class="language-js">const Url = 'https://jsonplaceholder.typicode.com/posts';

$('.btn').click(function () {
	$.get(Url, function (data, status) {
		console.log(`${data}`);
	});
});</code></pre><h3 id="-post">$.post</h3><p><code>$.post</code> 메서드는 서버에 데이터를 보내는 또 다른 방법입니다. <code>url</code>, 전송하고 싶은 데이터, 콜백 함수 이렇게 세 가지 파라미터를 받습니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/1*ql6Yp1EJfD7850GXhErwyw.png" class="kg-image" alt="위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다." width="695" height="355" loading="lazy"><figcaption>위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다.</figcaption></figure><pre><code class="language-js">
const Url = 'https://jsonplaceholder.typicode.com/posts';
const data = {
	name: 'said',
    id: 23,
};

$('.btn').click(function () {
    $.post(Url, data, function (data, status) {
	    console.log(`${data} and status is ${status}`);
	});
});</code></pre><h3 id="-getjson">$.getJSON</h3><p><code>$.getJSON</code> 메서드는 오직 JSON 형식으로만 데이터를 반환합니다. <code>url</code>과 콜백 함수 두 가지 파라미터를 받습니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/1*hdcFdVHiBiRAo1YOi_Kt0Q.png" class="kg-image" alt="위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다." width="755" height="291" loading="lazy"><figcaption>위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다.</figcaption></figure><pre><code class="language-js">
const Url = 'https://jsonplaceholder.typicode.com/posts';

$('.btn').click(function () {
    $.getJSON(Url, function (result) {
	    console.log(result);
    });
});</code></pre><p>jQuery는 요청을 하거나 원격 서버에 데이터를 보내기 위해 이 모든 메서드를 가지고 있습니다. 하지만 아래 예제에서 보시는 것처럼 이 모든 메서드를 <code>$.ajax</code> 메서드 하나로 합쳐 보낼 수도 있습니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/1*soPARjfQXMcZ5ccPK1QMmA.png" class="kg-image" alt="위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다." width="800" height="420" loading="lazy"><figcaption>위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다.</figcaption></figure><pre><code class="language-js">
const Url = 'https://jsonplaceholder.typicode.com/posts';

$('.btn').click(function () {
	const data = { name: 'said', id: 23 };

    $.ajax({
        url: Url,
        type: 'GET', // 또는 POST
        // data: data, // 만일 POST 타입을 쓴다면
        // dataType: JSON 또는 HTML, XML, TXT, jsonp
        success: function (result) {
	        console.log(result);	
        },
        error: function (error) {
	        console.log(`Error ${error}`);
        },
	});
});</code></pre><h2 id="fetch">fetch</h2><p><code>fetch</code>는 비동기 요청을 보낼 수 있도록 해주는 새롭게 등장한 강력한 web API입니다. 사실 <code>fetch</code>는 제가 가장 좋아하는, HTTP 요청을 만들 수 있는 최고의 방법 중 하나입니다. 이는 ES6의 훌륭한 기능 중 하나인 "Promise"를 반환합니다. ES6가 익숙하지 않으시다면, <a href="https://www.freecodecamp.org/news/write-less-do-more-with-javascript-es6-5fd4a8e50ee2">이 기사</a>에서 더 읽어보실 수 있습니다. Promise는 더 영리한 방법으로 비동기 요청을 다룰 수 있게 해 줍니다. <code>fetch</code>가 기술적으로 어떻게 동작하는지 한 번 살펴봅시다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/1*kz6k4VRs0RiVCasWR0pCow.png" class="kg-image" alt="fetch를 이용해 HTTP 요청을 보내는 예시. 이미지 속 내용은 아래 코드와 동일하다." width="800" height="270" loading="lazy"><figcaption>위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다.</figcaption></figure><pre><code class="language-js">
const Url = 'https://jsonplaceholder.typicode.com/posts';

fetch(Url)
.then((data) =&gt; {return data.json()})
.then((res) =&gt; {console.log(res)})</code></pre><p><code>fetch</code> 함수는 <code>엔드포인트</code> URL이라는 파라미터를 필수적으로 받습니다. 아래 예제에서 보시는 것처럼 여러 다른 선택 가능한 파라미터도 있습니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/1*QasrBgYZcU4BBFHqD2bBdg.png" class="kg-image" alt="fetch를 이용해 HTTP 요청을 보내는 예시 두 번째에서는 선택적 파라미터에 대한 코드가 적혀 있다. 이미지 속 내용은 아래 코드와 동일하다." width="800" height="497" loading="lazy"><figcaption>위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다.</figcaption></figure><pre><code class="language-js">
const Url = 'https://jsonplaceholder.typicode.com/posts';

const Data = {
    name: 'Said',
    id: 23,
};

// 선택적 파라미터

const othePram = {
    headers: {
	    'content-type': 'application/json, charset=UTF-8',
    },
    body: Data,
    method: 'POST',
};

fetch(Url, othePram)
.then((data) =&gt; {return data.json()})
.then((res) =&gt; {console.log(res)})
.catch((error) =&gt; console.log(error));</code></pre><p>여기서 보시는 것처럼, <code>fetch</code>는 HTTP 요청을 만들기 위한 다양한 이점들이 있습니다. <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch">이곳</a>에서 더 많이 알아보세요. 추가적으로, fetch 안에서는 서버 단에 요청을 보내거나 요청을 받을 수 있는 <a href="https://github.com/axios/axios">axios</a> 같은 다른 모듈이나 플러그인이 있습니다.</p><h2 id="axios">Axios</h2><p>Axios는 HTTP 요청을 만들고, 다양한 좋은 기능까지 제공하는 오픈 소스 라이브러리입니다. 어떻게 동작하는지 한 번 확인해봅시다.</p><h3 id="-">사용 방법</h3><p>우선, Axios를 가지고 있어야겠죠. 프로젝트에 Axios를 포함하는 두 가지 방법이 있습니다.</p><p>제일 먼저 npm을 이용할 수 있습니다:</p><pre><code class="language-bash">npm install axios --save</code></pre><p>이후 이를 임포트(import)합니다.</p><pre><code class="language-bash">import axios from 'axios'</code></pre><p>두 번째로 CDN을 이용해 포함하는 방법입니다.</p><pre><code class="language-html">&lt;script src='https://unpkg.com/axios/dist/axios.min.js'&gt;&lt;/script&gt;</code></pre><h3 id="axios-">axios로 요청하기</h3><p>Axios를 통해 여러분은 서버에 데이터를 받거나, 데이터를 보낼 수 있는 <code>GET</code>과 <code>POST</code>를 사용할 수 있습니다.</p><h3 id="get-">GET:</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/1*4wmqiPsSN5mdgjJiRaKVZg.png" class="kg-image" alt="Axios로 GET 요청을 보내는 예제 코드. 이미지 속 내용은 아래 코드와 동일하다." width="781" height="440" loading="lazy"><figcaption>위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다.</figcaption></figure><pre><code class="language-js">
const Url = 'https://jsonplaceholder.typicode.com/posts';

axios
.get(Url)
.then((data) =&gt; console.log(data))
.catch((err) =&gt; console.log(err));

// 파라미터와 함께

const Url = 'https://jsonplaceholder.typicode.com/posts';
const params = {
    name: 'Said',
    id: 21,
};

axios.get(Url, params)
.then((data) =&gt; console.log(data))
.catch((err) =&gt; console.log(err));</code></pre><p><code>axios</code>는 하나의 필수 파라미터를 갖고, 두 번째로 선택 가능한 파라미터를 갖기도 합니다. 이 경우는 간단한 쿼리(query)로 데이터를 받습니다.</p><h3 id="post-">POST:</h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/1*ey6-vwsrm9RAhyoU15u6xQ.png" class="kg-image" alt="Axios로 POST 요청을 보내는 예제 코드. 이미지 속 내용은 아래 코드와 동일하다." width="755" height="412" loading="lazy"><figcaption>위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다.</figcaption></figure><pre><code class="language-js">const Url = 'https://jsonplaceholder.typicode.com/posts';

const user = {
    name: 'Said',
    id: 21,
};
axios({
    method: 'post',
    url: Url,
    data: {
    user,
	},
})
.then((data) =&gt; console.log(data))
.catch((err) =&gt; console.log(err));</code></pre><p><a href="https://github.com/axios/axios">Axios</a>는 "Promise"를 반환합니다. 이에 익숙하다면, 여러분은 promise가 여러 요청을 실행할 수 있다는 것도 아실 겁니다. 여러분은 axios를 통해 동일한 작업을 할 수 있고, 동시에 여러 요청을 실행할 수 있습니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/1*40Pji4utVKPpC7-dePfC6Q.png" class="kg-image" alt="Axios의 axios.all 메서드를 통해서 두 가지 GET 요청을 보내는 예제 코드. 이미지 속 내용은 아래 코드와 동일하다." width="800" height="396" loading="lazy"><figcaption>위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다.</figcaption></figure><pre><code class="language-js">
function getUser() {
	const userUrl = 'https://jsonplaceholder.typicode.com/users';
	return axios.get(userUrl);
}

function getPost() {
    const postUrl = 'https://jsonplaceholder.typicode.com/posts';
    return axios.get(postUrl);
}

axios.all([getUser(), getPost()])
.then((users, posts) =&gt; {
    console.log(users);
    console.log(posts);
})
.catch((err) =&gt; console.log(err));</code></pre><p>Axios는 여러 다른 메서드와 옵션을 지원합니다. <a href="https://github.com/axios/axios">이곳</a>에서 더 탐구해볼 수 있습니다.</p><h2 id="angular-httpclient">Angular HttpClient</h2><p>Angular는 Angular 앱에서 동작하는 자체 HTTP 모듈을 가지고 있습니다. RxJS 라이브러리를 사용해 비동기 요청을 처리하고 많은 옵션을 제공해 HTTP 요청을 수행하도록 합니다.</p><h3 id="angular-httpclient-">Angular HttpClient를 사용해 서버로 보낼 요청 호출하기</h3><p>Angular HttpClient를 통해 요청을 발생시키려면, Angular 앱 내부에서 우리의 코드를 실행해야 합니다. 하나 만들어보았습니다. 만일 Angular에 익숙하지 않으시다면, 저의 <a href="https://medium.freecodecamp.org/learn-how-to-create-your-first-angular-app-in-20-min-146201d9b5a7">20분 안에 첫 Angular 앱 만드는 방법</a>을 확인해보세요.</p><p>제일 먼저 해야 할 일은 <code>app.module.ts</code>에서 <code>HttpClientModule</code>을 가져오는 것입니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/1*iFuW5Fbp91VR5gwQ6XNMEQ.png" class="kg-image" alt="HttpClientModule 사용 예제. 이미지 속 내용은 아래 코드와 동일하다." width="758" height="632" loading="lazy"><figcaption>위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다.</figcaption></figure><pre><code class="language-ts">import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angluar/core';
// HttpModule
import { HttpClient } from '@angular/common/http';

import { AppComponent } from './app.component';

@NgModule({
	declaration: [
    	AppComponent
    ],
	imports: [
        BrowserModule,
        // HttpModule
		HttpClientModule,
	],
    providers: [],
    bootstrap: [AppComponent],
})

export class AppModule {}</code></pre><p>그리고 요청을 처리할 서비스를 생성해야 합니다. <a href="https://angular.io/cli">Angular CLI</a>를 통해서 쉽게 이 서비스를 만들 수 있습니다.</p><pre><code class="language-js">ng g service FetchdataService</code></pre><p>이제 <code>fetchdataServce.ts</code> 서비스에서 HttpClient를 가져오고(import), 생성자(constructor) 안에 이를 주입(inject)합니다.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://cdn-media-1.freecodecamp.org/images/1*kKwELAhSSpnN8DvIgdOfcQ.png" class="kg-image" alt="HttpClientModule을 주입하는 예제. 이미지 속 내용은 아래 코드와 동일하다." width="685" height="461" loading="lazy"><figcaption>위에서 설명한 내용이 이미지로 삽입되어 있다. 이미지 속 내용은 아래 코드와 동일하다.</figcaption></figure><pre><code class="language-ts">import { Injectable } from '@angular/core';
// HttpClient를 가져옵니다.
import { HttpClient } from '@angular/common/http';

@Injectable() {
export class FetchdataService {
    constructor(private http:HttpClient) {}
    
    getData(url: string) {
    	return this.http.get(url)
	}
}</code></pre><p>그리고 <code>app.component.ts</code>에서 <code>fetchdataService</code>를 가져옵니다.</p><pre><code class="language-js">// 가져오기
import { FetchdataService } from './fetchdata.service';</code></pre><p>마지막으로 서비스를 호출하고, 실행합니다.</p><p><code>app.component.ts</code>:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://www.freecodecamp.org/korean/news/content/images/2022/11/image.png" class="kg-image" alt="app.component.ts 파일에서 서비스를 호출하고 실행하는 예제. 이미지 속 내용은 아래 코드와 동일하다" srcset="https://www.freecodecamp.org/korean/news/content/images/size/w600/2022/11/image.png 600w, https://www.freecodecamp.org/korean/news/content/images/2022/11/image.png 788w" sizes="(min-width: 720px) 720px" width="788" height="620" loading="lazy"><figcaption><a href="https://cdn-media-1.freecodecamp.org/images/1*OrRe183Yaclt19n5ZQ194Q.png">app.component.ts 파일에서 서비스를 호출하고 실행하는 예제. 이미지 속 내용은 아래 코드와 동일하다</a></figcaption></figure><pre><code class="language-ts">@Component({
    selector: 'app-post',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css'],
    providers: [FetchdataService],
})

export class AppComponent implements OnInit {
    posts;
    Url = 'https://jsonplaceholder.typicode.com/posts';

    // HttpClient를 주입하기
    constructor(private srv: FetchdataService) {}

    getPosts(): void {
        this.srv.getData(this.Url)
        	.subscribe((data) =&gt; 	console.log(data));
	}

    ngOnInit() {
	    this.getPost();
    }
}</code></pre><p><a href="https://stackblitz.com/edit/angular-httpclinent">Stackblitz</a>에서 데모 예제를 확인해볼 수 있습니다.</p><h2 id="--1">마치며</h2><p>우리는 JavaScript에서 HTTP 호출 요청을 보내는 가장 알려진 여러 방법을 살펴보았습니다.</p><p>시간을 내주셔서 감사합니다. 이 글이 좋았다면, 좋아요를 눌러주시고, <a href="https://twitter.com/SaidHYN">Twitter</a>에도 만나러 와주세요.</p><p>그나저나, 저는 최근에 저의 모바일 애플리케이션 중 하나를 위해 훌륭한 소프트웨어 엔지니어 그룹과 일했습니다. 이 조직은 훌륭했고, 제품은 그 어떤 회사나 제가 함께 일했던 프리랜서들보다도 훨씬 빠르게 출시되었습니다. 솔직히 다른 프로젝트에도 이 그룹을 추천하고 싶다고 생각했습니다. 연락이 필요하시면 이메일을 주세요 - <a><em>said@devsdata.com</em></a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 리눅스 LS 명령어 - 디렉터리 내부에서 옵션 플래그와 함께 파일을 열거하는 방법 ]]>
                </title>
                <description>
                    <![CDATA[  1970년 만들어진 이후 유닉스는 많은 운영 체제 시스템의 토대가 되었습니다. 대다수의 운영 체제 시스템은 실패했고, 그 외의 것들은 성공했죠. 리눅스는 가장 유명한 유닉스 기반 운영 체제 시스템입니다. 오픈 소스로, 다양한 산업군에 걸쳐 전 세계적으로 쓰이고 있습니다. 리눅스 운영 체제의 가장 근사한 점은 사용자가 셸(Shell)을 통해 컴퓨터와 상호작용할 수 있는 ]]>
                </description>
                <link>https://www.freecodecamp.org/korean/news/rinugseu-ls-myeongryeongeo-diregteori-naebueseo-obsyeon-peulraegeuwa-hamgge-paileul-yeolgeohaneun-bangbeob-2/</link>
                <guid isPermaLink="false">634fea9571cbd405f6d6a6de</guid>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jeong Won Yoo ]]>
                </dc:creator>
                <pubDate>Sun, 23 Oct 2022 13:07:41 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/korean/news/content/images/2022/10/article-banner-7.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>기사 원문:</strong> <a href="https://www.freecodecamp.org/news/the-linux-ls-command-how-to-list-files-in-a-directory-with-options/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">The Linux LS Command – How to List Files in a Directory + Option Flags</a>
      </p><p></p><p>1970년 만들어진 이후 유닉스는 많은 운영 체제 시스템의 토대가 되었습니다. 대다수의 운영 체제 시스템은 실패했고, 그 외의 것들은 성공했죠.</p><p>리눅스는 가장 유명한 유닉스 기반 운영 체제 시스템입니다. 오픈 소스로, 다양한 산업군에 걸쳐 전 세계적으로 쓰이고 있습니다.</p><p>리눅스 운영 체제의 가장 근사한 점은 사용자가 셸(Shell)을 통해 컴퓨터와 상호작용할 수 있는 명령 줄 인터페이스(CLI)를 제공한다는 점입니다. 리눅스 셸은, REPL(<strong>R</strong>ead, <strong>E</strong>valuate, <strong>P</strong>rint, <strong>L</strong>oop) 환경으로, 사용자가 명령을 입력하면 셸이 이를 실행 후 결과를 반환합니다.</p><p><code>ls</code> 명령어는 수많은 리눅스 명령어 중 하나인데, 사용자의 CLI에서 파일이나 디렉터리 목록을 보여줍니다.</p><p>이 기사에서는, <code>ls</code> 명령어와 일상적으로 흔히 필요하게 될 몇 가지 중요한 플래그에 대해 더 깊이 알아보고자 합니다.</p><h1 id="-">선행 조건</h1><ul><li>디렉터리와 파일이 있는 컴퓨터</li><li>리눅스 배포판이 하나 이상 설치되어야 함</li><li>CLI를 다루는 기초 수준의 지식</li><li>얼굴에 미소를 띠우기 :)</li></ul><h1 id="-ls-">리눅스 ls 명령어</h1><p>리눅스 <code>ls</code> 명령어는 리눅스 또는 다른 유닉스 기반 운영 체제 시스템 안에서 파일이나 디렉터리를 목록으로 보여주기 위해 쓰입니다.</p><p>GUI로 제공되는 <em>파일 탐색기</em>나 <em>파인더(Finder)</em>처럼, <code>ls</code> 명령어는 현재 디렉터리를 기본으로 모든 파일과 디렉터리를 나열해 보여주거나 명령 줄로 파일과 디렉터리와 더 상호작용하도록 해줍니다.</p><p>터미널을 열어서 <code>ls</code>를 입력해 실제로 어떻게 동작하는지 살펴봅시다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-20-at-9.40.29-PM.png" class="kg-image" alt="ls 명령어를 입력한 후 디렉터리와 파일 목록이 반환된 터미널 창" width="600" height="400" loading="lazy"></figure><h2 id="--1">옵션과 함께 디렉터리 내 파일 나열하기</h2><p><code>ls</code> 명령어는 또한 몇 가지 플래그(옵션이라고도 합니다)와 함께 쓰이는데, 일종의 부가 정보로 어떻게 파일과 디렉터리가 터미널에서 보일지 바꿔줍니다.</p><p>즉, 플래그는 <code>ls</code> 명령어가 동작하는 방식을 바꾼다고 할 수 있습니다.</p><pre><code class="language-shell">ls [flags] [directory]
</code></pre><blockquote>추신: 이 문서 전체에 걸쳐 <strong>내용</strong>이라는 단어는 나열된 <strong>파일과 디렉터리</strong>를 일컬으며, 실제 파일/디렉터리 내부의 내용을 말하지는 않습니다.</blockquote><h3 id="--2">현재 작업 중인 디렉터리의 파일을 목록화하기</h3><p>현재 작업 디렉터리의 내용을 목록으로 보려면 <code>ls</code> 명령어를 입력해보세요</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-20-at-9.40.29-PM.png" class="kg-image" alt="ls 명령어를 입력한 후 디렉터리와 파일 목록이 반환된 터미널 창" width="600" height="400" loading="lazy"></figure><h3 id="--3">다른 디렉터리의 파일을 목록화하기</h3><p><code>ls [디렉터리 경로]</code> 명령어는 해당 디렉터리의 내용을 나열할 때 써보세요<br></p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-20-at-10.32.52-PM.png" class="kg-image" alt="ls Desktop 명령어를 입력한 후 디렉터리와 파일 목록이 반환된 터미널 창" width="600" height="400" loading="lazy"></figure><h3 id="--4">루트 디렉터리의 파일을 목록화하기</h3><p>루트 디렉터리의 내용을 보려면 <code>ls /</code> 명령어를 입력해보세요</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-20-at-10.46.10-PM.png" class="kg-image" alt="ls / 명령어를 입력한 후 디렉터리와 파일 목록이 반환된 터미널 창" width="600" height="400" loading="lazy"></figure><h3 id="--5">부모 디렉터리의 파일을 목록화하기</h3><p><code>ls ..</code> 명령어를 입력하면 한 단계 위의 부모 디렉터리의 내용을 목록화해서 보여줍니다. <code>ls ../..</code> 명령어를 활용하면 두 단계 위의 내용이 보입니다</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-20-at-10.48.22-PM.png" class="kg-image" alt="루트 디렉터리에서 cd Downloads 명령을 실행하고 Downloads 디렉터리에서 ls .. 명령어를 실행한 후 디렉터리와 파일 목록이 반환된 터미널 창" width="600" height="400" loading="lazy"></figure><h3 id="-home-user-">사용자의 홈 디렉터리(/home/user)의 파일을 목록화하기</h3><p><code>ls ~</code> 명령어를 사용하면 사용자의 홈 디렉터리의 내용이 보입니다</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-20-at-10.51.19-PM.png" class="kg-image" alt="ls ~ 명령어 실행 후 디렉터리와 파일 목록이 반환된 터미널 창" width="600" height="400" loading="lazy"></figure><h3 id="--6">디렉터리만 보여주기</h3><p><code>ls -d */</code> 명령어는 오직 디렉터리들만 목록 결과로 보여줍니다</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-21-at-12.53.05-PM.png" class="kg-image" alt="ls -d */ 명령어 실행 후 디렉터리 목록만 반환된 터미널 창" width="600" height="400" loading="lazy"></figure><h3 id="--7">파일과 서브 디렉터리를 목록화해서 보여주기</h3><p><code>ls *</code> 명령어는 디렉터리와 해당 디렉터리의 서브 디렉터리를 목록화해서 보여줍니다</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-21-at-1.07.54-PM.png" class="kg-image" alt="ls * 명령어를 실행 후 디렉터리와 그의 서브 디렉터리가 함께 반환된 터미널 창" width="600" height="400" loading="lazy"></figure><h3 id="--8">재귀적으로 파일을 목록화하기</h3><p><code>ls -R</code> 명령어를 입력하면, 파일과 디렉터리 및 그의 서브 디렉터리들의 마지막 파일을 만날 때까지 쭉 타고 내려가 모두 목록으로 보여줍니다<br></p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/09/Screenshot-2020-09-01-at-9.04.56-AM.png" class="kg-image" alt="ls -R 명령어를 실행 후 파일, 디렉터리, 서브 디렉터리가 모두 재귀적으로 반환된 터미널 창" width="600" height="400" loading="lazy"></figure><blockquote>파일이 너무 많다면, 각 디렉터리의 파일 하나하나까지 모두 출력되기 때문에 작업이 완료되기까지 시간이 상당히 소요될 것입니다. 대신 한 디렉터리를 특정해 이 명령어를 실행할 수도 있습니다: <code>ls Downloads -R</code></blockquote><h3 id="--9">크기와 함께 파일을 목록화하기</h3><p><code>ls -s</code>(<strong>s</strong>는 소문자입니다) 명령어를 입력하면 파일 또는 디렉터리가 크기와 함께 목록화되어 보입니다:</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-21-at-12.30.19-PM.png" class="kg-image" alt="ls -s 명령어를 입력한 후 파일, 디렉터리 그리고 크기가 반환된 터미널 창" width="600" height="400" loading="lazy"></figure><h3 id="--10">긴 형식으로 파일을 목록화하기</h3><p><code>ls -l</code> 명령어를 입력하면 아래의 열을 포함한 표 형태로 디렉터리 내용을 목록화해 보여줍니다</p><ul><li>내용 권한</li><li>내용에 대한 링크의 개수</li><li>내용의 소유자</li><li>내용의 그룹 소유자</li><li>내용의 바이트 단위</li><li>내용을 마지막으로 수정한 날짜 및 시간</li><li>파일 또는 디렉터리의 이름</li></ul><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-20-at-10.52.37-PM.png" class="kg-image" alt="ls -l 명령어를 입력한 후 표 형태로 반환된 터미널 창" width="600" height="400" loading="lazy"></figure><h3 id="--11">읽을 수 있는 파일로 표현된 긴 형식의 파일 목록화하기</h3><p><code>ls -lh</code> 명령어를 입력하면, 위와 같은 표 형태로 파일과 디렉터리를 목록화해 보여주지만, 각 파일/디렉터리의 크기를 표현하는 열도 추가되었습니다<br></p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-21-at-12.14.33-PM.png" class="kg-image" alt="ls -lh 명령어를 입력한 후 표 형태로 반환된 터미널 창" width="600" height="400" loading="lazy"></figure><p>파일이나 디렉터리의 가 1,024바이트보다 클 경우 바이트(B), 메가바이트(MB), 기가바이트(GB) 또는 테라바이트(TB)로 표현됩니다.</p><h3 id="--12">숨김 파일을 포함해 파일을 목록화하기</h3><p><code>ls -a</code> 명령어를 입력하면, 숨겨진 파일 또는 디렉터리를 포함해 파일 또는 디렉터리를 목록화해 보여줍니다. 리눅스에서는 <code>.</code> 로 시작하는 이름은 숨김 파일로 간주합니다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-21-at-11.12.26-AM.png" class="kg-image" alt="ls -a 명령어를 입력한 후 숨김 파일까지 반환된 터미널 창" width="600" height="400" loading="lazy"></figure><h3 id="--13">숨김 파일을 포함해 긴 형식으로 파일을 목록화하기</h3><p><code>ls -l -a</code>, <code>ls -a -l</code>, <code>ls -la</code>, <code>ls -al</code> 명령어는 숨김 파일 또는 디렉터리를 포함한 부가 정보를 표 형태로 목록화해 보여줍니다<br></p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-21-at-12.17.01-PM.png" class="kg-image" alt="ls -la 명령어를 입력한 후 표 형태로 반환된 터미널 창" width="600" height="400" loading="lazy"></figure><h3 id="--14">날짜와 시간으로 분류해 파일을 목록화하기</h3><p><code>ls -t</code> 명령어를 입력하면 파일 또는 디렉터리를 마지막으로 수정한 날짜에 따라 내림차순으로 (숫자가 작은 것에서 큰 순으로) 분류해 목록으로 보여줍니다.</p><p><code>-r</code> 플래그를 넣어 <code>ls -tr</code>처럼 분류 순서를 반대로 할 수도 있습니다</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-21-at-12.20.09-PM.png" class="kg-image" alt="ls -t과 ls-tr 명령어를 입력한 후 파일과 디렉터리를 반환한 터미널 창" width="600" height="400" loading="lazy"></figure><h3 id="--15">파일 크기로 분류해 파일을 목록화하기</h3><p><code>ls -S</code>(<strong>S</strong>는 대문자입니다) 명령어를 입력하면 파일 또는 디렉터리를 크기에 따라 내림차순으로 (숫자가 적은 것에서 큰 순으로) 분류해 목록으로 보여줍니다.</p><p><code>-r</code> 플래그를 넣어 <code>ls -Sr</code>처럼 분류 순서를 반대로 할 수도 있습니다<br></p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/08/Screenshot-2020-08-21-at-12.20.38-PM.png" class="kg-image" alt="ls -S와 ls -Sr 명령어를 입력한 후 파일과 디렉터리를 반환한 터미널 창" width="600" height="400" loading="lazy"></figure><h3 id="--16">파일을 목록화한 결과를 파일로 생성하기</h3><p><code>ls &gt; output.txt</code> 명령어는 이전 명령어에 대한 결과를 <code>output.txt</code>에 삽입해줍니다. <code>-la</code> 같이 위에서 논의한 플래그는 전부 사용할 수 있습니다 - 여기서 중요한 점은 결과가 한 파일로 출력되지만, 명령 줄에는 기록되지 않는다는 것입니다.</p><p>원하는 대로 파일을 활용해보거나 <code>cat output.txt</code>로 파일 내용을 출력할 수도 있습니다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/09/Screenshot-2020-09-01-at-9.12.59-AM.png" class="kg-image" alt="ls > output.txt, cat output.txt 명령어를 입력한 후 파일과 디렉터리를 반환한 터미널 창" width="600" height="400" loading="lazy"></figure><h1 id="--17">결론</h1><p>필요에 따라 파일과 디렉터리를 출력해보기 위해 탐구해볼 만한 다른 수많은 명령어와 조합이 존재합니다. 기억해야 할 것은 한 번에 여러 명령어를 합칠 수 있는 능력입니다.</p><p>긴 형식으로 파일을 나열하되, 숨김 파일을 포함하고, 파일 크기에 따라 분류하고 싶다고 생각해봅시다. 명령어는 <code>ls -alS</code>로, <code>ls -l</code>, <code>ls -a</code> 그리고 <code>ls -S</code>의 조합입니다.</p><p>만일 어떤 명령어도 기억이 안 나거나 뭘 해야 할지 확실치 않다면, <code>ls --help</code>나 <code>man ls</code>를 실행해 <code>ls</code> 명령어에 대해 가능한 모든 옵션에 대한 설명서를 볼 수 있습니다.</p><figure class="kg-card kg-image-card"><img src="https://www.freecodecamp.org/news/content/images/2020/09/Screenshot-2020-09-01-at-9.57.37-AM.png" class="kg-image" alt="ls --help나 man ls 명령어를 실행한 터미널 창" width="600" height="400" loading="lazy"></figure><p>읽어주셔서 감사합니다!</p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
