<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>gyumin_2.log</title>
        <link>https://velog.io/</link>
        <description>애기 프론트 엔드 개발자</description>
        <lastBuildDate>Thu, 16 Nov 2023 03:44:05 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>gyumin_2.log</title>
            <url>https://images.velog.io/images/gyumin_2/profile/12ae2138-12e0-49c8-a70d-c761645797aa/Untitled-1.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. gyumin_2.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/gyumin_2" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Next.js - Pre-rendering and Data Fetching]]></title>
            <link>https://velog.io/@gyumin_2/Next.js-Pre-rendering-and-Data-Fetching</link>
            <guid>https://velog.io/@gyumin_2/Next.js-Pre-rendering-and-Data-Fetching</guid>
            <pubDate>Thu, 16 Nov 2023 03:44:05 GMT</pubDate>
            <description><![CDATA[<h1 id="pre-rendering">Pre-rendering</h1>
<ul>
<li>기본적으로 next.js는 모든 페이지를 pre-rendering 한다. next.js는 클라이언트 사이드 js에 의해 모든 페이지가 렌더링 되는 것 대신 사전에 모든 페이지의 html을 생성한다. pre-rendering은 더 나은 성능과 SEO를 제공한다.</li>
<li>생성된 각 HTML은 해당 페이지에 필요한 최소 JavaScript 코드와 연결됩니다. 페이지가 브라우저에 의해 로드될 때, 해당 페이지의 js가 실행되고 해당 페이지를 완전이 인터렉티브하게 만드는데, 이를 <strong>hydration</strong>이라고 합니다.</li>
</ul>
<h2 id="pre-rendering-vs-no-pre-rendering">pre-rendering vs no pre-rendering</h2>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/cc94baf8-f072-4355-92f6-b11b6c23105a/image.png" alt="">
<img src="https://velog.velcdn.com/images/gyumin_2/post/0f8a882c-fb31-452b-b6da-c62f467e7774/image.png" alt=""></p>
<ul>
<li>pre-rendering<ol>
<li>initial load : pre-rendering 된 HTML을 화면에 표시</li>
<li>JS 로드</li>
<li>hydration : 리액트 컴포넌트가 초기화되고, 앱이 인터렉티브 됨<ul>
<li>Link 컴포넌트 같은 인터랙티브 컴포넌트가 있다면, js가 로드된 후 활성화 됨</li>
</ul>
</li>
</ol>
</li>
<li>no pre-rendering<ol>
<li>initial load : 앱이 화면에 렌더링 되지 않음 </li>
<li>JS 로드</li>
<li>hydration : 리액트 컴포넌트가 초기화되고, 앱이 인터렉티브 됨</li>
</ol>
</li>
</ul>
<h1 id="pre-rendering-종류">Pre-rendering 종류</h1>
<ul>
<li>next.js는 두 가지 유형의 pre-rendering이 있다.</li>
<li>Static Generation<ul>
<li>빌드타임에 html이 생성되며 모든 요청 마다 재사용된다.</li>
<li>next.js 에서 퍼포먼스를 위해 권장하는 방식</li>
<li>정적으로 생성된 페이지들은 CDN에 캐시 가능</li>
<li>마케팅 페이지, 블로그 포스트, 이커머스 상품 리스트, 도움말 문서 등에 적합</li>
<li>유저의 요청 전에 pre-render 해야한다면 Static Generation 방식 추천</li>
</ul>
</li>
<li>Server-side Rendering<ul>
<li>요청 마다 html이 생성된다.</li>
<li>사용자의 요청 전에 pre-render 하면 안되는 경우에 적합. 가령 대시보드처럼 데이터가 자주 업데이트 되거나 페이지가 매 요청마다 달라지는 경우에 적합</li>
<li>Static Generation 방식보다 느리지만 항상 최신 정보를 보여준다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>💡 개발 모드에서 모든 페이지는 Static Generation 방식의 pre-rendering을 사용하지만 개발 편의성을 위해 모든 요청마다 pre-rendering된다.</p>
</blockquote>
<ul>
<li>next.js에서는 개발자가 두 유형의 pre-rendering 중 원하는 걸 선택해서 페이지 별로 개발할 수 있습니다. 개발자는 대부분의 페이지는 Static Generation으로 나머지 페이지는 Server-side Rendering을 사용하여 하이브리드 Next.js 앱을 만들 수 있습니다. = 대략 페이지별로 둘 다 혼용해서 쓸 수 있다는 이야기</li>
</ul>
<h1 id="fetching-data-at-build-time">Fetching Data at Build Time</h1>
<ul>
<li><p>Static Generation 방식은 서버의 데이터가 필요할 수도 있고 필요하지 않을 수도 있습니다. 그러나 대부분의 페이지는 서버의 데이터를 필요로합니다. Static Generation 방식에서 서버의 데이터를 가져와 정적 페이지를 만들고자 할 경우에는 getStaticProps 메소드를 사용합니다.</p>
</li>
<li><p>빌드타임에 파일 시스템에 접근하거나, 외부 API를 호출하거나, DB에 접근하고자 할 경우에 <code>getStaticProps</code>를 사용합니다. 여기서 가장 중요한 것은 페이지를 요청할 때가 아니라 앱 빌드 타임에 위 내용이 필요할 경우입니다.</p>
</li>
<li><p><code>getStaticProps</code> 기본 사용 법</p>
<ul>
<li><p>페이지 컴포넌트를 export 할 때 아래와 같이 async 키워드를 붙인 <code>getStaticProps</code>를 같이 export 한다</p>
<pre><code class="language-jsx">export default function Home(props) {
  return &lt;&gt;...&lt;/&gt;
}

export async function getStaticProps() {
// Get external data from the file system, API, DB, etc.
const data = ...

// The value of the `props` key will be
//  passed to the `Home` component
return {
  props: ...
}
}</code></pre>
</li>
</ul>
</li>
<li><p><code>getStaticProps</code> 함수는 빌드 타임에 실행되며 함수 내부에서 외부 데이터를 가져와 페이지에 props로 전달할 수있다.</p>
</li>
<li><p><code>getStaticProps</code> 함수는 next.js에 해당 페이지에 데이터 디펜던시가 있으니 빌드 타임 때 해당 페이지를 pre-render할 때 데이터 의존성을 먼저 해결하라고 말해준다.</p>
</li>
<li><p><code>getStaticProps</code> 함수는 오직 page에서만 export 할 수 있다. 즉 pages 하위 파일에서만 사용 가능하다. 이는 리액트는 페이지가 리렌더 되기 전에 모든 필수적인 데이터를 갖고있어야 하기 때문이다.</p>
</li>
<li><p>개발모드에서 <code>getStaticProps</code> 함수는 요청마다 실행되며, production에서는 빌드 타임에서만 실행된다. 그러나 <code>getStaticProps</code> 함수가 fallback key를 반환하면 확장될 수 있다.</p>
</li>
</ul>
<h1 id="fetching-data-at-request-time">Fetching Data at Request Time</h1>
<ul>
<li><p>빌드 타임이 아니라 요청 시 데이터를 가져오고 싶을 때는 Server-side Rendering을 사용해야한다.</p>
</li>
<li><p>Server-side Rendering 방식에서 데이터를 가져올 때는 <code>getStaticProps</code> 대신 <code>getServerSideProps</code>를 사용해야한다.</p>
</li>
<li><p>예시</p>
<pre><code class="language-jsx">  export async function getServerSideProps(context) {
    return {
      props: {
        // props for your component
      },
    };
  }</code></pre>
</li>
<li><p><code>getServerSideProps</code> 은 <code>getStaticProps</code> 과 마찬가지로 브라우저가 아닌 서버에서 실행됩니다. 때문에 <code>getServerSideProps</code> 의 파라미터은 context는 request에 대한 정보를 포함합니다.</p>
</li>
<li><p><code>getServerSideProps</code> 은 pre-render해야하는 페이지의 데이터가 요청 시 가져와야 하는 경우에만 사용해야합니다.</p>
</li>
<li><p><code>getServerSideProps</code> 은 <code>getStaticProps</code> 보다 느리고 추가 설정을 하지 않는 한 CDN에 의해 캐시되지 않습니다.</p>
</li>
<li><p>데이터를 pre-render하고 싶지 않을 경우에는 CSR방식을 사용하는 것이 좋다.</p>
</li>
</ul>
<h1 id="client-side-rendering">Client-side Rendering</h1>
<ul>
<li>대쉬보드처럼 데이터를 pre-render 하고 싶지 않을 경우 사용하는 방식</li>
<li>next build 명령어 실행 → 앱이 배포로 빌드 됨 → 외부 데이터를 필요로 하지 않는 부분을 정적으로 생성함 → js가 로드 되면 data를 fetch → 불러온 데이터로 나머지 부분을 렌더링</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js - Assets, MetaData, CSS]]></title>
            <link>https://velog.io/@gyumin_2/Next.js-Assets-MetaData-CSS</link>
            <guid>https://velog.io/@gyumin_2/Next.js-Assets-MetaData-CSS</guid>
            <pubDate>Wed, 25 Oct 2023 05:44:25 GMT</pubDate>
            <description><![CDATA[<h1 id="nextjs에서-정적-파일-불러오기">Next.js에서 정적 파일 불러오기</h1>
<ul>
<li>next.js에서는 루트 폴더에 있는 public 폴더에 이미지 파일 같은 정적파일을 보관하고, <code>/</code> 로 시작하는 url로 파일에 접근 가능합니다. 예를들어 public 폴더 내에 sample.png 파일이 있다면 코드 내에서는 <code>/sample.png</code>로 접근 가능합니다.</li>
<li>next.js에서 정적 파일 사용 시 주의 할 점<ol>
<li>정적 파일이 저장된 폴더 이름은 반드시 public 이어야 한다.<ul>
<li>public 폴더 이름은 변경되어서는 안되며 public 폴더만 유일한 정적파일 보관 폴더로 사용되어야 한다.</li>
</ul>
</li>
<li>public 폴더에 저장된 파일의 이름과, pages 폴더에 저장된 파일의 이름이 같으면 안 된다.<ul>
<li>next.js가 이를 구분 하지 못한다. 만약 파일 이름이 같을 경우 확장자라도 다른게 해야한다.</li>
</ul>
</li>
<li>public 폴더 내의 모든 파일은 next.js에 의해 빌드 타임에 제공된다. 런타임에 추가된 파일은 사용할 수가 없다.<ul>
<li>만약 런타임에 추가한 파일을 사용하고 싶을 경우 AWS S3같이 서드파티 서비스를 사용해야한다.</li>
</ul>
</li>
</ol>
</li>
</ul>
<h1 id="이미지-파일-사용하기">이미지 파일 사용하기</h1>
<ul>
<li><p>일반적인 방식인 img 태그를 사용한 이미지 불러오면 아래 사항을 수동으로 처리해야 한다.</p>
<ol>
<li>다양한 화면 크기에서 이미지가 반응하는지 확인</li>
<li>서드파티 제품이나 라이브러리로 이미지 최적화</li>
<li>화면에 표시될때만 ( 뷰포트에 들어갈 때만 ) 이미지 로드</li>
</ol>
</li>
<li><p><code>next/image</code> 의 <code>Image</code> 컴포넌트는 위 작업을 자동으로 해줍니다. Image 컴포넌트는 이미지 리사이징, 이미지 최적화 그리고 브라우저가 지원한다면 WebP같은 최신 이미지 포맷도 사용할 수 있게 해줍니다.</p>
</li>
<li><p>또한 Next.js는 빌드 시 이미지를 최적화하는 대신, 사용자가 요청할 때 만 이미지를 최적화합니다. 정적 사이트와는 달리, 10개의 이미지를 전송하든 1천만 개의 이미지를 전송하든 빌드 시간이 증가하지 않습니다.</p>
</li>
<li><p>이미지는 기본적으로 lazy loading 됩니다.(화면에 표시될 이미지만 로드 ) 즉, 뷰포트 외부의 이미지에 대해서는 페이지 속도에 영향을 미치지 않습니다.</p>
</li>
<li><p>이미지는 레이아웃 오류를 방지하여 렌더링됩니다. 사용자 예상치 못하게, 레이아웃이 움직여 잘못된 행동을 하게 되는 것을 방지합니다.</p>
</li>
<li><p>사용법</p>
<pre><code class="language-tsx">  import Image from &#39;next/image&#39;;

  const YourComponent = () =&gt; (
    &lt;Image
      src=&quot;/images/profile.jpg&quot; // Route of the image file
      height={144} // Desired size with correct aspect ratio
      width={144} // Desired size with correct aspect ratio
      alt=&quot;Your Name&quot;
    /&gt;
  );</code></pre>
</li>
</ul>
<h2 id="image-컴포넌트-속성">Image 컴포넌트 속성</h2>
<ul>
<li>src : <strong>이미지 출처값</strong>으로, 프로젝트 디렉토리 내 정적경로 혹은 외부 도메인의 URL 스트링(config 설정 필요) 을 넘겨줘야 한다.</li>
<li>width, height : 이미지의 <strong>픽셀 너비 및 높이</strong>. 단위를 제외한 정수값을 전달해줘야 한다. (<strong>정적 이미지 혹은 layout=&quot;fii&quot; 이 아닌 경우 요구</strong>된다.)<ul>
<li>Local Image는 <strong>자동으로 width, height를 계산</strong>해주지만(Cumulative Layout Shift 미발생), Remote Image는 <strong>Next.js가 빌드 시점에 파일에 접근할 수 없으므로 width, height props를 필수로 입력</strong>해줘야 한다.</li>
</ul>
</li>
<li><a href="https://abangpa1ace.tistory.com/242">기타 속성 자세히 보기</a></li>
</ul>
<h1 id="matadata-설정-하기">matadata 설정 하기</h1>
<ul>
<li><p><code>next/head</code> 모듈에 있는 <code>&lt;Head&gt;</code> 컴포넌트를 사용하면 페이지 별로 title 같은 메타 데이터를 다르게 설정할 수 있다.</p>
</li>
<li><p>예제</p>
<pre><code class="language-tsx">  // index.js
  &lt;Head&gt;
    &lt;title&gt;Create Next App&lt;/title&gt;
    &lt;link rel=&quot;icon&quot; href=&quot;/favicon.ico&quot; /&gt;
  &lt;/Head&gt;

  // first-post.js
  import Head from &#39;next/head&#39;;

  export default function FirstPost() {
    return (
      &lt;&gt;
        &lt;Head&gt;
          &lt;title&gt;First Post&lt;/title&gt;
        &lt;/Head&gt;
        &lt;h1&gt;First Post&lt;/h1&gt;
        &lt;h2&gt;
          &lt;Link href=&quot;/&quot;&gt;← Back to home&lt;/Link&gt;
        &lt;/h2&gt;
      &lt;/&gt;
    );
  }</code></pre>
</li>
</ul>
<h1 id="third-party-javascript"><strong>Third-Party JavaScript</strong></h1>
<ul>
<li><p>서드파티 js 라이브러리를 불러올 때 아래와 같이 head 태그 안에 불러 올 수 있다.</p>
<pre><code class="language-tsx">  &lt;Head&gt;
    &lt;title&gt;First Post&lt;/title&gt;
    &lt;script src=&quot;https://connect.facebook.net/en_US/sdk.js&quot; /&gt;
  &lt;/Head&gt;</code></pre>
</li>
<li><p>위 방식도 작동은 하지만 같은 페이지의 js 코드가 언제 로드 되는지에 대해 명확히 알 수 없어 권장되는 방식은 아닙니다. 특히 특정 스크립트가 렌더링을 차단중이고 페이지 콘텐츠 로드를 지연시킬 수 있는 경우, 성능이 저하될 수 있습니다. 때문에 위 방식 대신 next/script 모듈의 Script 컴포넌트를 사용하는 것이 좋습니다.</p>
<pre><code class="language-tsx">  // Script 태그 사용하기 위해 불러오기
  import Script from &quot;next/script&quot;

  export default function FirstPost() {
    return (
      &lt;&gt;
        &lt;Head&gt;
          &lt;title&gt;First Post&lt;/title&gt;
        &lt;/Head&gt;
        &lt;Script
          src=&quot;https://connect.facebook.net/en_US/sdk.js&quot;
          strategy=&quot;lazyOnload&quot;
          onLoad={() =&gt;
            console.log(`script loaded correctly, window.FB has been populated`)
          }
        /&gt;
        &lt;h1&gt;First Post&lt;/h1&gt;
        &lt;h2&gt;
          &lt;Link href=&quot;/&quot;&gt;
            &lt;a&gt;Back to home&lt;/a&gt;
          &lt;/Link&gt;
        &lt;/h2&gt;
      &lt;/&gt;
    )
  }</code></pre>
<ul>
<li>strategy : javascript를 로드해야 하는 시기를 정합니다. lazyOnload 값을 넣으면 브라우저 대기 시간 동안, 스크립트를 느리게 로드하도록 Next.js 에 지시합니다.</li>
<li>onLoad : 스크립트 로드가 완료된 직후 JavaScript 코드를 실행하는 데 사용됩니다. 위 예시에서는 스크립트가 올바르게 로드되었다는 메시지를 콘솔에 기록합니다.</li>
</ul>
</li>
</ul>
<h1 id="nextjs에서-스타일링하기">Next.js에서 스타일링하기</h1>
<ul>
<li>Next.js에서 스타일링 하는 방식은 순수 react에서 스타일링 하는 방식과 크게 다르지 않다. 다만 글로벌 스타일링을 적용하고자 할 경우 _app에서 import 하면 된다.</li>
<li><a href="https://www.notion.so/React-a32195fa55ae4f7e91e986ec702498db?pvs=21">react 에서 스타일링 하는 방식</a></li>
</ul>
<h1 id="기타-참고-자료">기타 참고 자료</h1>
<ul>
<li><a href="https://fe-developers.kakaoent.com/2022/220714-next-image/">Next/Image를 활용한 이미지 최적화</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js - 라우팅]]></title>
            <link>https://velog.io/@gyumin_2/Next.js-%EB%9D%BC%EC%9A%B0%ED%8C%85</link>
            <guid>https://velog.io/@gyumin_2/Next.js-%EB%9D%BC%EC%9A%B0%ED%8C%85</guid>
            <pubDate>Thu, 19 Oct 2023 00:36:52 GMT</pubDate>
            <description><![CDATA[<h1 id="페이지-라우팅">페이지 라우팅</h1>
<ul>
<li>그냥 리액트에서는 <code>react-route-dom</code> 라이브러러리를 통해 페이지간 라우팅을 구현할 수 있지만, next.js 에서는 pages 폴더 안에 있는 파일을 생성하면 자동으로 라우팅이 구현된다. pages 폴더 안의 파일이름이 해당 페이지의 주소가 된다.(export 이름이 아니라 파일 명임!)</li>
<li><code>pages/index.tsx</code> ⇒ <code>/</code> 경로의 파일 렌더링</li>
<li><code>pages/post.tsx</code> ⇒ <code>/posts</code> 경로</li>
<li><code>pages/posts/index.tsx</code> ⇒ <code>/posts</code></li>
<li><code>pages/posts/list.tsx</code> ⇒ <code>/posts/list</code></li>
</ul>
<h2 id="link-컴포넌트">Link 컴포넌트</h2>
<ul>
<li><p>일반적인 웹 사이트에서는 a 태그를 이용해 페이지간 이동을 한다. next.js에서는 react-route-dom과 같이 <code>Link</code> 컴포넌트를 사용해 페이지간 이동을 할 수 있다.</p>
</li>
<li><p>next.js 12.2 이전 버전에서는 <code>Link</code> 컴포넌트 사용 시 반드시 a 태그를 감싸야한다. 하지만 12.2이상 버전에서 부터는 a 태그를 안 감사도 된다.</p>
</li>
<li><p><code>Link</code> 컴포넌트는 <code>next/link</code>에서 import 할 수 있으며, 이를 이용해 <strong>client-side navigation</strong>을 수행 할 수 있다.</p>
</li>
<li><p>클라이언트 사이드 네비게이션이란 js를 이용한 페이지 전환을 의미한다. 이를 이용하면 브라우저에 의해 페이지 전환이 되는 것보다 더 빠르고 사용자에게 좋은 ux를 제공할 수 있다.</p>
</li>
<li><p>next.js는 자동으로 코드 스플리팅을 수행한다. 따라서 각 페이지는 필요할 때만 로드된다. 이는 특정 페이지가 렌더링 될 때 다른 페이지의 코드가 초기에 제공되지 않는 다는 것을 의미한다. 사용자가 해당 페이지를 요청 할 때만 그 페이지의 코드가 로딩되며, 이는 각 페이지가 독립되어있고 몇 백개의 페이지가 있어로 페이지 로딩이 빠름을 보장한다는 뜻이다.</p>
</li>
<li><p>게다가 프로덕션 환경에서 브라우저의 viewport에 Link 컴포넌트가 나타나면 next.js는 자동으로 해당 링크의 페이지를 백그라운드에서 prefetch 한다. 때문에 사용자가 해당 링크를 클릭 할 때 쯤에는 해당 페이지의 코드는 이미 백그라운드에서 로드되어 있을 것이다.</p>
</li>
<li><p>예제</p>
<pre><code class="language-jsx">  import Link from &quot;next/Link&quot;;

  export default function App() {
    return (
      &lt;div&gt;
        &lt;h2&gt;Link to &#39;tomato&#39; Page&lt;/h2&gt;
        &lt;Link href=&quot;/post&quot;&gt;
          post
        &lt;/Link&gt;
      &lt;/div&gt;
    );
  }</code></pre>
</li>
</ul>
<h2 id="userouter"><code>useRouter</code></h2>
<ul>
<li>가끔은 Link 컴포넌트를 사용하지 않고 함수나 로직 내에서 라우팅을 수행해야 할 때가 있습니다. 이처럼 프로그래밍 적으로 페이지 네비게이션을 하고자 할 경우 next/router의 useRouter 훅을 사용하면 됩니다.</li>
</ul>
<pre><code class="language-jsx">import { useRouter } from &quot;next/router&quot;;

export default function App() {
  const router = useRouter();
  return (
    &lt;div&gt;
      &lt;h2&gt;Link to &#39;tomato&#39; Page&lt;/h2&gt;
      &lt;button onClick={() =&gt; router.push(&quot;/tomato&quot;)}&gt;토마토로 가기&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre>
<ul>
<li><strong>router.back()</strong> : 직전 페이지로 돌아갑니다.</li>
<li><strong>router.push(&quot;url&quot;)</strong> : 지정한 경로로 이동하며 히스토리 스택에 URL를 추가합니다.</li>
<li><strong>router.replace(&quot;url&quot;)</strong> : 지정한 경로로 이동하나 히스토리 스택에 URL를 추가하지 않습니다.</li>
<li>SEO에 영향을 미치는 크롤러는 router.push() 가 다른 페이지로 이동하는 링크라고 인식하지 않기 때문에 이벤트 핸들러 등에 필요한 경우가 아니라면 Link 컴포넌트를 활용해 시멘틱한 구조를 유지하는 것을 권장</li>
</ul>
<h1 id="동적-라우팅">동적 라우팅</h1>
<ul>
<li><code>/note/1/2</code> 같이 url이 정적이 아닌 params가 붙은 동적 url을 라우팅하고자 할 경우, 아래처럼 대괄호 [] 로 파일명을 감싸면 해당 페이지는 동적으로 경로가 지정되는 페이지가 됩니다.</li>
</ul>
<pre><code class="language-bash">/pages
  ㄴ-- index.jsx
  ㄴ-- note
         ㄴ-- [id].jsx
         ㄴ-- [name].jsx =&gt; Error!! # 한 동적 경로에는 한 개의 동적 페이지만이 존재할 수 있습니다.</code></pre>
<ul>
<li>대괄호 [] 로 파일명을 감싸면 해당 페이지는 동적으로 경로가 지정되는 페이지가 되고, 동적 페이지가 존재하는 경로에 임의의 주소를 대입하면 대입한 주소를 쿼리명으로 갖는 페이지로 이동할 수 있습니다.</li>
<li><code>/notes/1?can=123</code> 를 요청했을 때, router 객체를 통해 query 값을 확인 할 수 있습니다.</li>
</ul>
<pre><code class="language-jsx">// App.tsx
import Link from &quot;next/Link&quot;;

export default function App() {
  return (
    &lt;div&gt;
      &lt;h2&gt;Link to &#39;potato&#39; Page&lt;/h2&gt;
      &lt;Link href=&quot;/note/1?can=123&quot;&gt;
        &lt;a&gt;move to note + id + can&lt;/a&gt;
      &lt;/Link&gt;
    &lt;/div&gt;
  );
}

// note/[id].jsx
import React from &quot;react&quot;;
import { useRouter } from &quot;next/router&quot;;

const NoteId = () =&gt; {
  const router = useRouter();
  console.log({ query: router.query, router: router })
  return (
    &lt;div&gt;Note&lt;/div&gt;
  )
}</code></pre>
<ul>
<li>위와 같이 <code>[id].jsx</code> 파일을 만듬으로 인해 <code>/notes/1</code>과 같은 주소에 대한 페이지를 받아볼 수 있었지만, <code>/notes/1/2</code>와 같이 param이 하나 더 추가 된다면, 해당 페이지는 404에러를 보여줍니다. 몇개의 params가 주소 뒤에 붙을지 모르기 때문에, 모든 params에 대한 페이지 접속을 허가하기 위해서는 <a href="https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes">catch all routes</a> 기능을 사용할 수 있습니다.</li>
<li>아래와 같이 전개 연산자를 사용해 파일이름을 지으면 여러 개의 params를 받을 수 있습니다.</li>
</ul>
<pre><code class="language-bash">/pages
  ㄴ-- index.jsx
  ㄴ-- /note
         ㄴ-- [[...params]].jsx</code></pre>
<pre><code class="language-jsx">import React from &quot;react&quot;;
import { useRouter } from &quot;next/router&quot;;

const Notes = () =&gt; {
  const router = useRouter();
  const { params } = router.query;
  console.log({ params })
  return (
    &lt;div&gt;Note&lt;/div&gt;
  )
}

export default Notes;

// /notes/1/1?can=123 접속 시
// =&gt; params: (2) [&quot;1&quot;, &quot;1&quot;]</code></pre>
<ul>
<li>params는 배열 형식으로 <code>router.query</code>를 통해 뽑아볼 수 있으며, 이름은 params가 아닌 어떤 형태로도 괜찮습니다.</li>
<li>다시 폴더구조를 살펴보면 <code>[...params]</code>가 또다른 배열인 <code>[]</code>로 감싸져 있습니다. <code>pages/notes</code>
 경로 아래에 , <code>index.jsx</code> 파일 또한 필요 없습니다. notes 경로 아래에서 일어나는 모든 페이지는 <code>[[...params]].jsx</code>파일만 수정하면 됩니다. index.js를 만들면 오류가 생기므로, catch all routes를 사용할 때는 index.js를 사용하지 않습니다.</li>
</ul>
<h1 id="api-routes">API Routes</h1>
<ul>
<li>Next.js는 API Routes라는 것을 지원합니다. API Routes는 Node.js 서버리스 함수이며, 이를 통해 API endpoint를 쉽게 만들 수 있습니다.</li>
</ul>
<h2 id="api-routes-만들기">API Routes 만들기</h2>
<ul>
<li><p>API Routes만드는 법은 간단합니다. pages/api 폴더 내에 아래와 같은 형식의 함수를 만들면 됩니다.</p>
<pre><code class="language-jsx">  // req = HTTP incoming message, res = HTTP server response
  export default function handler(req, res) {
    // ...
  }</code></pre>
</li>
<li><p>이렇게 만들어진 함수는 서버리스 함수로 배포됩니다.</p>
</li>
<li><p>예제</p>
<pre><code class="language-tsx">  // pages/api/hello.ts

  import type { NextApiRequest, NextApiResponse } from &#39;next&#39;

  type ResponseData = {
    message: string
  }

  export default function handler(
    req: NextApiRequest,
    res: NextApiResponse&lt;ResponseData&gt;
  ) {
    res.status(200).json({ message: &#39;Hello from Next.js!&#39; })
  }

  // http://localhost:3000/api/hello 호출 시 
  // { message: &#39;Hello from Next.js!&#39; } 응답 확인</code></pre>
</li>
<li><p>주의 사항</p>
<ul>
<li>API Routes는 <code>getStaticProps</code> 혹은 <code>getStaticPaths</code> 함수 내부에서 사용하면 안 됩니다. 이 방식 보다는 직접 서버 사이드 코드를 <code>getStaticProps</code> 혹은 <code>getStaticPaths</code> 함수 내부에 작성하는 것이 좋습니다.</li>
<li><code>getStaticProps</code> 와 <code>getStaticPaths</code> 함수는 오직 서버 사이드에서만 실행되며, 이 함수들은 브라우저를 위한 JS 번들에 포함되지 않기 때문에 API Routes는 이 함수들 내부에서 사용하면 안 됩니다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>💡 Serverless란?</p>
<p>서버(Server) + 리스(Less)의 합성어라 간혹 &#39;서버가 없다&#39;라고 문자 그대로 이해할 수 있지만, 절대 그렇지 않다. 서버리스(Serverless)는 클라우드 컴퓨팅의 모델 중 하나로 개발자가 서버를 직접 관리할 필요가 없는 아키텍처를 의미한다.</p>
</blockquote>
<p>예를들어, 서버의 사용자가 1000명이 될 걸 예상하고 그에 맞는 용량의 서비스를 구입했다면 실제 사용자가 1000명이든 0명이든 같은 금액을 내야 할 것이다. 이는 크던 작던 손실을 일으키기 마련이다. 하지만 서버리스는 동적으로 서버의 자원을 할당한다. 사용자가 없다면 자원을 할당하지 않고 대기하다 요청이 들어오면 그 때 자원을 할당해서 요청을 처리하고 다시 대기 상태로 들어가게 된다. 즉, 자원을 효율적으로 사용할 수 있는 것이다.</p>
<blockquote>
</blockquote>
<p>비용 또한 대기상태를 제외한 실제 사용 자원에 대해서만 청구되기 때문에 굉장히 경제적이며,
해당 서버는 클라우드 제공 기업에서 전적으로 관리하기 때문에 개발자는 스케일링, 업데이트, 백업, 보안 등 서버에 대해 일절 관리하거나 신경 쓸 필요가 없어 비즈니스 로직에 집중하여 개발을 할 수 있다.</p>
<blockquote>
</blockquote>
<p>서버리스 아키텍쳐의 대표적인 구현방식은 두 가지가 있다. 바로, FaaS (Function as a Service)와 BaaS (Backend as a Service) 다. 구현방식은 두 가지로 나뉘지만, 일반적으로 서버리스라고 하면 FaaS를 가리킨다.</p>
<blockquote>
</blockquote>
<p>FaaS는 Function 즉, 함수를 서비스로 제공한다. 사용자가 작성한 코드(백엔드)를 서버리스 제공자의 서버에 업로드하면 해당 서버는 업로드한 코드를 함수 단위로 쪼개 대기상태로 두게된다.
그러다 요청이 들어오면 서버가 대기상태에 두었던 함수를 실행시켜 처리한 후 작업이 끝나면 다시 대기상태로 만드는 구조이다. 비용은 함수 호출 횟수에 따라 청구된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React.js - code spliting]]></title>
            <link>https://velog.io/@gyumin_2/React.js-code-spliting</link>
            <guid>https://velog.io/@gyumin_2/React.js-code-spliting</guid>
            <pubDate>Mon, 05 Dec 2022 01:36:29 GMT</pubDate>
            <description><![CDATA[<h1 id="코드-스플리팅이란">코드 스플리팅이란?</h1>
<ul>
<li>리액트 프로젝트를 완성하여 사용자에게 제공할 때는 빌드 작업을 거쳐 배포해야하며, 이는 웹팩이 담당한다.</li>
<li>웹팩에 별도의 설정을 하지 않으면 프로젝트에서 사용 중인 모든 자바스크립트 파일이 하나의 파일로 합쳐지고, 모든 CSS 파일도 하나의 파일로 합쳐진다.</li>
<li>CRA(create-react-app)로 프로젝트를 빌드할 경우 최소 두 개 이상의 자바스크립트 파일이 생성된다. CRA의 기본 웹팩 설정에는 SimpleChunk라는 기능이 적용되어 node_modules에서 불러온 파일, 일정 크기 이상의 파일, 여러 파일 간에 공유된 파일을 자동적으로 따로 분리시켜서 캐싱의 효과를 제대로 누릴 수 있게 해준다.</li>
<li><code>npm run build</code> 명령어 실행 시 아래와 같이 <code>build/static</code> 디렉토리 내부에 자바스크립트 파일이 여러 개 생긴 것을 볼 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/15018538-d8ac-4f4e-be6e-e2446bf296a5/image.png" alt=""></p>
<ul>
<li>파일 이름에는 해시 값이 포함되어 있는데 이는 빌드하는 과정에서 해당 파일의 내용에 따라 생성되며, 이를 통해 브라우저가 새로운 파일을 받아야 할지 받지 말아야 할지를 알 수 있다.</li>
<li>2로 시작하는 파일에는 React, ReactDOM 등 node_modules에서 불러온 라이브러리 관련 코드가 들어있고, main으로 시작하는 파일에는 직접 프로젝트에 작성하는 App 같은 컴포넌트에 대한 코드가 들어있다.</li>
<li>웹팩의 SplitChunk라는 기능을 통해 자주 바뀌지 않는 코드들이 2로 시작하는 파일에 들어가있기 때문에 캐싱의 이점을 더 오래 누릴 수 있다.</li>
<li>이렇게 파일을 분리하는 작업을 코드 스플리팅이라고 한다. 프로젝트에 기본 탑재된 SplitChunk 기능을 통한 코드 스플리팅은 단순히 효율적인 캐싱 효과만 있을 뿐이다.</li>
<li>페이지 A, B, C로 구성된 SPA를 개발한다고 할 때, 사용자가 방문하지 않은 페이지에서 사용하는 컴포넌트 정보는 필요하지 않다. 하지만 리액트 프로젝트에서 별도로 설정하지 않으면 A, B, C 컴포넌트에 대한 코드가 모두 한 파일에(main) 저장되어 버린다. 만약 규모가 큰 애플리케이션에서 지금 당장 필요하지 않은 컴포넌트 정보도 모두 불러오면 파일 크기가 커지게 되고 그로 인해 로딩이 오래걸려 UX도 안 좋아지고 트래픽이 많이 발생하게 된다.</li>
<li>이러한 문제점을 해결할 수 있는 방법 중 하나는 코드 스플리팅 방법 중 하나인 비동기 로딩이다. 코드 비동기 로딩을 통해 자바스크립트 함수, 객체 혹은 컴포넌트를 처음에 불러오지 않고 필요한 시점에 불러와 사용할 수 있다.</li>
</ul>
<h1 id="자바스크립트-함수-비동기-로딩">자바스크립트 함수 비동기 로딩</h1>
<pre><code class="language-jsx">// notify.js - 비동기로 로딩할 파일
export default function notify() {
    alert(&#39;안녕하세요.&#39;);
}

// App.js
import React from &#39;react&#39;;
import notify from &#39;./notify&#39;;

const App = () =&gt; {
    const onClick = () =&gt; {
        notify();
    };

    return (
        &lt;div&gt;
      &lt;h1&gt;Hello React&lt;h1&gt;
            &lt;p onClick={onClick}&gt;click!&lt;/p&gt;
    &lt;/div&gt;
    )
}</code></pre>
<ul>
<li>위와 같이 코드를 작성하고 빌드하면 notify 코드가 main 파일 안에 들어간다.</li>
</ul>
<pre><code class="language-jsx">// App.js
import React from &#39;react&#39;;

const App = () =&gt; {
    const onClick = () =&gt; {
        import(&#39;./notify&#39;).then(result =&gt; result.default());
    };

    return (
        &lt;div&gt;
      &lt;h1&gt;Hello React&lt;h1&gt;
            &lt;p onClick={onClick}&gt;click!&lt;/p&gt;
    &lt;/div&gt;
    )
}</code></pre>
<ul>
<li>위 코드와 같이 import를 상단에서 하지 않고 import() 함수 형태로 메서드 안에서 사용하면 파일을 따로 분리시켜 저장한다. 그리고 실제 함수가 필요한 지점에서 파일을 불러와 함수를 사용할 수 있다.</li>
<li>import 함수를 사용하면 Promise 를 반환한다. 이렇게 import 함수를 사용하는 문법은 표준 자바스크립트 방식은 아니지만, stage-3 단계에 있는 dynamic import라는 문법이다. 현재 웹팩에서 지원하고 있으므로, 별도의 설정 없이 프로젝트에서 바로 사용 가능하다. 이 함수를 통해 모듈을 불러올 때 모듈에서 default로 내보낸 것은 result.default를 참조해야 사용할 수 있다.</li>
</ul>
<h1 id="리액트에서-컴포넌트-코드-스필리팅">리액트에서 컴포넌트 코드 스필리팅</h1>
<ul>
<li>리액트에 내장된 유틸 함수인 React.lazy와 컴포넌트인 Suspense는 리액트 16.6 버전부터 도입되었으며, 코드 스플리팅을 위해 사용된다.</li>
<li>16.6 이전 버전에서는 import 함수를 통해 모듈을 불러온 다음, 컴포넌트 자체를 state에 넣는 방식으로 구현해야 한다.</li>
</ul>
<h2 id="state를-사용한-코드-스플리팅">State를 사용한 코드 스플리팅</h2>
<ul>
<li><p>16.6 이전 버전에서 사용할 수 있는 코드 스플리팅 방식</p>
</li>
<li><p>스플리팅할 컴포넌트</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;

  const SplitMe = () =&gt; {
    return &lt;div&gt;SplitMe&lt;/div&gt;;
  };

  export default SplitMe;</code></pre>
</li>
<li><p>App.js</p>
<pre><code class="language-jsx">  import React, { Component } from &#39;react&#39;;
  import logo from &#39;./logo.svg&#39;;
  import &#39;./App.css&#39;;

  class App extends Component {
      state = {
          splitMe: null
      };

      handleClick = async () =&gt; {
          const loadedModule = await import(&#39;./SplitMe&#39;);
          this.setState({
              splitMe: loadedModule.default
          });
      };

      render() {
          const { splitMe } = this.state;
          return(
              &lt;div className=&quot;App&quot;&gt;
            &lt;header className=&quot;App-header&quot;&gt;
              &lt;img src={logo} className=&quot;App-logo&quot; alt=&quot;logo&quot; /&gt;
              &lt;p onClick={this.handleClick}&gt;
                Hello React!
              &lt;/p&gt;
              {splitMe &amp;&amp; &lt;SplitMe /&gt;}
            &lt;/header&gt;
          &lt;/div&gt;
          )
      }
  }</code></pre>
</li>
<li><p>state를 사용하여 컴포넌트 코드 스플리팅하는 방식은 어렵지 않지만 매번 state를 선언해야 한다는 점이 조금 불편하다.</p>
</li>
</ul>
<h2 id="reactlazy와-suspense를-사용한-코드-스플리팅">React.lazy와 Suspense를 사용한 코드 스플리팅</h2>
<ul>
<li>React.lazy와 Suspense를 사용하면 코드 스플리팅을 하기 위해 state를 따로 선언하지 않고도 간편하게 컴포넌트 코드스플리팅을 할 수 있다.</li>
</ul>
<h3 id="reactlazy">React.lazy</h3>
<ul>
<li><p>컴포넌트를 렌더링하는 시점에서 비동기적으로 로딩할 수 있게 해주는 유틸 함수</p>
<pre><code class="language-jsx">  const SplitMe = React.lazy(() =&gt; import(&#39;./SplitMe&#39;));</code></pre>
</li>
</ul>
<h3 id="suspense">Suspense</h3>
<ul>
<li><p>리액트 내장 컴포넌트로서 코드 스플리팅된 컴포넌트를 로딩하도록 발동시킬 수 있고, 로딩이 끝나지 않았을 때 보여줄 UI를 설정 할 수 있다.</p>
<pre><code class="language-jsx">  import React, { useState, Suspense } from &#39;react&#39;;

  (...)
  &lt;Suspense fallback={ &lt;div&gt;loading...&lt;/div&gt; }
      &lt;SplitMe /&gt;
  &lt;/Suspense&gt;

  // fallback props를 통해 로딩 중에 보여줄 JSX를 지정할 수 있다.</code></pre>
</li>
</ul>
<h3 id="reactlazy와-suspense를-사용한-예">React.lazy와 Suspense를 사용한 예</h3>
<pre><code class="language-jsx">import React, { useState } from &#39;react&#39;;
import logo from &#39;./logo.svg&#39;;
import &#39;./App.css&#39;;
const SplitMe = React.lazy(() =&gt; import(&#39;./SplitMe&#39;));

const App = () =&gt; {
    const [visible, setVisible] = useState(false);
  const onClick = () =&gt; {
    setVisible(true);
  };

  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;header className=&quot;App-header&quot;&gt;
        &lt;img src={logo} className=&quot;App-logo&quot; alt=&quot;logo&quot; /&gt;
        &lt;p onClick={onClick}&gt;
          Hello React!
        &lt;/p&gt;
        &lt;Suspense fallback={ &lt;div&gt;loading...&lt;/div&gt; }
                    { visible &amp;&amp; &lt;SplitMe /&gt; }
                &lt;/Suspense&gt;
      &lt;/header&gt;
    &lt;/div&gt;
  );
};</code></pre>
<h2 id="loadable-component를-사용한-코드-스플리팅">Loadable Component를 사용한 코드 스플리팅</h2>
<ul>
<li><p>Loadable Component는 코드 스플리팅을 편하게 하도록 도와주는 서드파티 라이브러리다. React.lazy와 Suspense는 서버사이드 렌더링을 지원하지 않는데 반해 이 라이브러리는 서버사이드 렌더링을 지원한다. 또한 렌더링 하기 전에 필요할 때 스플리팅된 파일을 미리 불러올 수 있는 기능도 있다.</p>
</li>
<li><p>사용법은 React.lazy와 비슷하며, Suspense를 사용할 필요가 없다.</p>
</li>
<li><p>설치 명령어 : <code>npm install @loadable/component</code></p>
<pre><code class="language-jsx">  import React, { useState } from &#39;react&#39;;
  import logo from &#39;./logo.svg&#39;;
  import &#39;./App.css&#39;;
  import loadable from &#39;@loadable/component&#39;;
  const SplitMe = loadable(() =&gt; import(&#39;./SplitMe&#39;));

  function App() {
    const [visible, setVisible] = useState(false);
    const onClick = () =&gt; {
      setVisible(true);
    };

    return (
      &lt;div className=&quot;App&quot;&gt;
        &lt;header className=&quot;App-header&quot;&gt;
          &lt;img src={logo} className=&quot;App-logo&quot; alt=&quot;logo&quot; /&gt;
          &lt;p onClick={onClick}&gt;
            Hello React!
          &lt;/p&gt;
          {visible &amp;&amp; &lt;SplitMe /&gt;}
        &lt;/header&gt;
      &lt;/div&gt;
    );
  }

  export default App;</code></pre>
</li>
<li><p>로딩중 다른 UI를 보여주고 싶을 때</p>
<pre><code class="language-jsx">  const SplitMe = loadable(() =&gt; import(&#39;./SplitMe&#39;), {
    fallback: &lt;div&gt;loading...&lt;/div&gt;
  });</code></pre>
</li>
<li><p>컴포넌트를 미리 불러오고 싶을 때</p>
<pre><code class="language-jsx">import React, { useState } from &#39;react&#39;;
import logo from &#39;./logo.svg&#39;;
import &#39;./App.css&#39;;
import loadable from &#39;@loadable/component&#39;;
const SplitMe = loadable(() =&gt; import(&#39;./SplitMe&#39;), {
  fallback: &lt;div&gt;loading...&lt;/div&gt;
});

function App() {
  const [visible, setVisible] = useState(false);
  const onClick = () =&gt; {
    setVisible(true);
  };
  const onMouseOver = () =&gt; { // 미리 불러오기
    SplitMe.preload();
  };
  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;header className=&quot;App-header&quot;&gt;
        &lt;img src={logo} className=&quot;App-logo&quot; alt=&quot;logo&quot; /&gt;
        &lt;p onClick={onClick} onMouseOver={onMouseOver}&gt;
          Hello React!
        &lt;/p&gt;
        {visible &amp;&amp; &lt;SplitMe /&gt;}
      &lt;/header&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React.js - redux middleware(리덕스 미들웨어, redux-thunk, redux-saga)]]></title>
            <link>https://velog.io/@gyumin_2/React.js-redux-middleware%EB%A6%AC%EB%8D%95%EC%8A%A4-%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4-redux-thunk-redux-saga</link>
            <guid>https://velog.io/@gyumin_2/React.js-redux-middleware%EB%A6%AC%EB%8D%95%EC%8A%A4-%EB%AF%B8%EB%93%A4%EC%9B%A8%EC%96%B4-redux-thunk-redux-saga</guid>
            <pubDate>Tue, 06 Sep 2022 01:48:42 GMT</pubDate>
            <description><![CDATA[<h1 id="react---redux리덕스-미들웨어">React - Redux(리덕스 미들웨어)</h1>
<h1 id="미들웨어란">미들웨어란?</h1>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/2a67e84b-b492-4ad3-9eed-1bf19e0a6955/image.png" alt=""></p>
<ul>
<li>소프트웨어 공학에서 미들웨어란 운영체제와 응용 소프트웨어 중간에서 조정과 중개의 역할을 수행하는 소프트웨어를 말한다.</li>
<li>리덕스 미들웨어는 액션을 디스패치 했을 때 리듀서에서 이를 처리하기에 앞서 사전에 지정된 작업을 실행한다. 미들웨어는 액션과 리듀서 사이의 중간자라고 볼 수 있다.</li>
<li>리듀서가 액션을 처리하기 전에 미들웨어가 할 수 있는 작업은 여러가지가 있다. 전달받은 액션을 단순히 콘솔에 기록하거나, 전달받은 액션 정보를 기반으로 액션을 아예 취소하거나, 다른 종류의 액션을 추가로 디스패치할 수 있다.</li>
</ul>
<h2 id="미들웨어를-왜-쓰는가">미들웨어를 왜 쓰는가?</h2>
<ul>
<li><p>리덕스는 액션이 발생하면 디스패치를 통해 스토어에게 상태 변화의 필요성을 알린다. 개발자는 가끔 디스 패치된 액션을 스토어로 전달하기 전에 로깅, 액션 취소, 다른 액션을 발생 시키는 등의 작업을 하고 싶을 수 있다. 미들웨어는 이러한 작업을 가능하게 해준다.</p>
</li>
<li><p>리덕스는 아래의 과정으로 동작한다.</p>
<ul>
<li>액션 객체 생성 → 디스패치가 액션 발생을 스토어에게 알림 → 정해진 로직에 따라 리듀서가 액션 처리 → 리듀서가 새로운 상태값 반환 → 스토어가 새로운 상태를 저장</li>
<li>이러한 작업은 동기적으로 동작한다.</li>
</ul>
</li>
<li><p>리덕스는 동기적으로 작동하고, 위 과정은 순식간에 실행된다. 때문에 위 과정을 시간을 딜레이 시켜 동작하게 하거나 ajax 통신 같은 비동기 작업을 넣을 수가 없다. 예를들어 아래와 같이 액션 생성 함수 내에서 ajax요청을 하고 응답값을 payload에 넣어 액션 객체를 생성하여 반환한다고 하면 에러가 나거나 정상작동하지 않는다.</p>
<pre><code class="language-jsx">  // async/await로 만든 액션 생성 함수
  export const fetchData = async () =&gt; {
    //임의의 api 주소에 async await으로 (get) 요청을 하였다.
    const response = await APIadress.get(&#39;/data&#39;)

    return {
      type: &#39;FETCH_DATA&#39;,
      payload: response
    }
  }
  // 결과 : 아래 에러 발생
  // action must be plain objects. Use custom middleware for async actions
  // 이유 : reducer로 dispatch 되는 최초의 action 형태는 
  //       plain object가 아니라 async await의 함수의 형태이기 때문

  // Promise로 만든 액션 생성 함수
  export const fetchData = async () =&gt; {
    //임의의 api 주소에 (get) 요청을 promise 형태로 하였다.
    const promise = await APIadress.get(&#39;/data&#39;)

    return {
      type: &#39;FETCH_DATA&#39;,
      payload: response
    }
  }
  // 결과 : 에러는 나지 않지만 정상 작동하지 않음
  // 이유 : Promise를 사용하면 reducer에서 
  //       &#39;반환된 데이터가 아직 없다&#39;(객체에 아무것도 없다!)라고 판단하기 때문</code></pre>
</li>
<li><p>리덕스에서는 이러한 비동기 작업을 처리하기 위한 지침을 알려주지 않고 있기 때문에 이러한 비동기 작업을 처리하는 데 있어 리덕스 미들웨어를 주로 사용한다.</p>
</li>
</ul>
<h2 id="대표적인-redux-middleware">대표적인 redux middleware</h2>
<ul>
<li>redux-logger : 액션 정보를 콘솔에 출력해주는 미들웨어.</li>
<li>redux-thunk : 비동기 작업을 처리 할 때 가장 많이 사용하는 미들웨어. 객체가 아닌 함수 형태의 액션을 디스패치할 수 있게 해준다.</li>
<li>redux-saga : redux-thunk 다음으로 가장 많이 사용되는 비동기 작업 관련 미들웨어 라이브러리. 특정 액션이 디스 패치 되었을 때 정해진 로직에 따라 다른 액션을 디스패치 시키는 규칙을 작성하여 비동기 작업을 처할 수 있게 해준다.</li>
</ul>
<h2 id="미들웨어-작동-방식-이해하기">미들웨어 작동 방식 이해하기</h2>
<ul>
<li>리덕스 미들웨어를 만들때 아래와 같은 템플릿을 사용한다.</li>
</ul>
<pre><code class="language-jsx">// 미들웨어 기본 템플릿
const middleware = store =&gt; next =&gt; action =&gt; {
    // 처리 로직
};

// 일반 함수 형식
const middleware = function middleware(store) {
    return function(next) {
        return function(action) {
            // 처리 로직
        }
    }
}
</code></pre>
<ul>
<li>미들웨어는 함수를 반환하는 함수를 반환하는 함수다.</li>
<li>함수의 첫 번째 매개변수인 <code>store</code>는 리덕스 스토어 인스턴스로, 이 안에 <code>dispatch</code>, <code>getState</code>, <code>subscribe</code> 내장함수들이 들어있다.</li>
<li>두 번째 매개변수인 <code>next</code>는 액션을 다음 미들웨어에게 전달하는 함수로 <code>store.dispatch</code>와 비슷한 역할을 한다. <code>next(action)</code> 형태로 사용하며, <code>next(action)</code> 를 호출하면 그 다음 처리해야할 미들웨어에게 액션을 넘겨주고, 만약 미들웨어가 없다면 리듀서에게 액션을 넘겨준다. 만약 미들웨어 내부에서 <code>next</code>를 사용하지 않으면 액션이 리듀서에게 전달되지 않는다. 즉 액션이 무시되는 것이다.</li>
<li><code>action</code>은 디스패치되어 현재 처리하고 있는 액션을 가리킨다.</li>
<li>미들웨어 내부에서 <code>store.dispatch</code>를 사용하면 첫번째 미들웨어부터 다시 처리한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/b855ad96-9567-4851-9533-0830e68c2ee9/image.png" alt=""></p>
<ul>
<li>미들웨어는 위와 같은 구조로 작동한다. 리덕스 스토어에는 여러 개의 미들웨어를 등록할 수 있다. 새로운 액션이 디스패치 되면 첫 번째로 등록한 미들웨어가 호출된다. 만약에 미들웨어에서 <code>next(action)</code>을 호출하게 되면 다음 미들웨어로 액션이 넘어간다. 그리고 만약 미들웨어에서 <code>store.dispatch</code> 를 사용하면 다른 액션을 추가적으로 발생시킬 수 도 있다.</li>
</ul>
<h2 id="logger-만들어-미들웨어-작동방식-이해하기">logger 만들어 미들웨어 작동방식 이해하기</h2>
<ul>
<li>실제 미들웨어를 직접 만들어서 사용할 일은 많지 않지만, 간단한 미들웨어를 직접 만들어 보면 미들웨어 작동 방식을 이해할 수 있다.</li>
</ul>
<pre><code class="language-jsx">// src/lib/loggerMiddleware.js
// 이전상태, 액션정보, 새로워진 생태를 순차적으로 콘솔에 기록하는 미들웨서
const loggerMiddleware = store =&gt; next =&gt; action =&gt; {
    // 미들웨어 기본 구조
    console.group(action &amp;&amp; action.type); // 액션 타입을 그룹명으로 설정
    console.log(&#39;이전상태&#39;, store.getState());
    console.log(&#39;action&#39;, action);
    next(action); // 다음 미들웨어 혹은 리듀서에게 전달
    // next 함수를 쓰지 않으면 리듀서에게 전달이 되지 않기 때문에 액션이 무시 됨
    // next 함수를 기준으로 이전이 이전 상태, 이후가 다음 상태
    console.log(&#39;다음 상태&#39;, store.getState());
    console.groupEnd(); // 그룹 끝
}

export default loggerMiddleware;

// src/index.js - 미들웨어 스토어에 적용
import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom&#39;;
import { applyMiddleware, createStore } from &#39;redux&#39;;
import { Provider } from &#39;react-redux&#39;;
import &#39;./index.css&#39;;
import App from &#39;./App&#39;;
import rootReducer from &#39;./modules&#39;;
import loggerMiddleware from &#39;./lib/loggerMiddleware&#39;;

// 미들웨어는 스토어를 생성하는 과정에서 applyMiddleware() 함수를 사용하여 적용한다.
const store = createStore(rootReducer, applyMiddleware(loggerMiddleware));

ReactDOM.render(
  &lt;Provider store={store}&gt;
    &lt;App /&gt;
  &lt;/Provider&gt;,
  document.getElementById(&#39;root&#39;)
);</code></pre>
<h2 id="redux-logger-사용하기">redux-logger 사용하기</h2>
<ul>
<li><p>설치 : <code>npm i redux-logger</code></p>
</li>
<li><p><code>src/index.js</code>에 적용하기</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  import ReactDOM from &#39;react-dom&#39;;
  import { applyMiddleware, createStore } from &#39;redux&#39;;
  import { Provider } from &#39;react-redux&#39;;
  import &#39;./index.css&#39;;
  import App from &#39;./App&#39;;
  import rootReducer from &#39;./modules&#39;;
  // import loggerMiddleware from &#39;./lib/loggerMiddleware&#39;;
  import { createLogger } from &#39;redux-logger&#39;;

  const logger = createLogger();
  const store = createStore(rootReducer, applyMiddleware(logger));

  ReactDOM.render(
    &lt;Provider store={store}&gt;
      &lt;App /&gt;
    &lt;/Provider&gt;,
    document.getElementById(&#39;root&#39;)
  );</code></pre>
</li>
</ul>
<h1 id="redux-thunk">redux-thunk</h1>
<ul>
<li>리덕스를 사용하는 프로젝트에서 비동기 작업을 처리할 때 가장 기본적으로 사용하는 미들웨어. 리덕스의 창시자인 댄 아브라모프가 만들었으며, 리덕스 공식 메뉴얼에서도 이 미들웨어를 사용하여 비동기 작업을 다루는 예시를 보여준다.</li>
</ul>
<h2 id="thunk란">Thunk란?</h2>
<ul>
<li><p>컴퓨터 프로그래밍에서, 썽크(Thunk)는 기존의 서브루틴에 추가적인 연산을 삽입할 때 사용되는 서브루틴이다. 썽크는 주로 연산 결과가 필요할 때까지 연산을 지연시키는 용도로 사용되거나, 기존의 다른 서브루틴들의 시작과 끝 부분에 연산을 추가시키는 용도로 사용되는데, 컴파일러 코드 생성 시와 모듈화 프로그래밍 방법론 등에서는 좀 더 다양한 형태로 활용되기도 한다.</p>
<pre><code class="language-jsx">  // thunk 예1
  // 1+2를 연산하고 싶다면 알해와 같이 코드를 작성하면 된다.
  const x = 1 + 2;

  // 아래와 같이 코드를 작성하면, 연산이 바로 실행되지 않고
  // foo() 함수가 호촐되어야 실행된다.
  const foo = () =&gt; 1 + 2;</code></pre>
</li>
<li><p>썽크(Thunk)는 &quot;고려하다&quot;라는 영어 단어인 &quot;Think&quot;의 은어 격 과거분사인 &quot;Thunk&quot;에서 파생된 단어인데, 연산이 철저하게 &quot;고려된 후&quot;, 즉 실행이 된 후에야 썽크의 값이 가용해지는 데서 유래된 것이라고 볼 수 있다.</p>
</li>
<li><p>Thunk란 특정 작업을 나중에 할 수 있도록 미루기 위해 함수 형태로 감싼 것을 칭한다.</p>
<pre><code class="language-jsx">  // thunk 예2

  // 파라미터로 전달받은 값에 1을 더해서 반환하는 함수
  const addOne = x =&gt; x + 1;
  addOne(1); // 2
  // 함수 실행 시 바로 1+1을 연산

  // 연산 작업을 1초 후애 실행하는 함수
  const addOne = x =&gt; x + 1; // 파라미터에 1을 더하는 함수

  function addOneThunk(x) { // addOne 함수를 반환하는 함수를 반환하는 함수
      const thunk = () =&gt; addOne(x);
      return thunk;
  }

  const fn = addOneThunk(1); // () =&gt; addOne(1);
  setTimeout(() =&gt; {
      const value = fn(); // fn이 실행되는 시점 - 연산이 실행됨
      console.log(value)
  }, 1000)

  // 화살표 함수로 변환
  const addOne = x =&gt; x + 1;
  const addOneThunk = x =&gt; () =&gt; addOne(x);

  const fn = addOneThunk(1); // () =&gt; addOne(1);
  setTimeout(() =&gt; {
      const value = fn(); // fn이 실행되는 시점
      console.log(value)
  }, 1000)
</code></pre>
</li>
<li><p>redux-thunk 라이브러리를 사용하면 thunk 함수를 만들어서 디스패치 할 수 있다. 그러면 리덕스 미들웨어가 그 함수를 전달받아 store의 dispatch와 getState를 파라미터로 넣어 호출해준다.</p>
<pre><code class="language-jsx">  // redux-thunk에서 사용할 수 있는 thunk 함수 예시
  const simpleThunk = () =&gt; (dispatch, getState) =&gt; {
      // 현재 상태를 참조할 수 있고, 새 액션을 디스패치할 수 있다.
  }</code></pre>
</li>
</ul>
<h2 id="redux-thunk란">redux-thunk란?</h2>
<ul>
<li><p>redux-thunk는 객체 대신 함수를 생성하는 액션 생성 함수를 작성 할 수 있게 해준다. 리덕스에서는 기본적으로 객체 형태의 액션을 디스패치합니다. 일반 액션 생성자는, 다음과 같이 파라미터를 가지고 액션 객체를 생성하는 작업만 한다.</p>
<pre><code class="language-jsx">  const actionCreator = (payload) =&gt; ({action: &#39;ACTION&#39;, payload});</code></pre>
</li>
<li><p>만약에 특정 액션이 몇 초 뒤에 실행되게 하거나, 현재 상태에 따라 아예 액션이 무시되게 하려면, 일반 액션 생성 함수로는 할 수가 없다. 하지만 redux-thunk를 사용하면 가능하다.</p>
</li>
<li><p>redux-thunk를 사용하면 객체가 아닌 함수를 디스패치할 수 있는데, 함수를 디스패치 할 때는 해당 함수는 <code>dispatch</code>와 <code>getState</code>를 파라미터로 가질 수 있다.</p>
</li>
<li><p>예제1) - 1초 뒤에 액션 디스패치</p>
<pre><code class="language-jsx">  // 액션 타입 선언
  const INCREMENT_COUNTER = &#39;INCREMENT_COUNTER&#39;;

  // 액션 생성 함수
  const increase = () =&gt; ({action: INCREMENT_COUNTER});

  // dispatch를 매개변수로 갖고 있는 함수를 리턴
  // 이 함수를 thunk 함수라고 한다.
  const increaseAsync = () =&gt; dispatch =&gt; { 
      // 1초 뒤 dispatch
      setTimeout(() =&gt; {
          dispatch(increase());
      }, 1000)
  }

  // INCREMENT_COUNTER 액션이 1초 뒤 디스패치
  store.dispatch(increaseAsync());</code></pre>
</li>
<li><p>예제2) - 조건에 따라 액션 디스패치 혹은 무시</p>
<pre><code class="language-jsx">  // dispatch와 getState를 매개변수로 갖는 함수를 리턴
  const dispatchIfOdd = () =&gt; (dispatch, getState) =&gt; {
      const { counter } = getState();

      if (counter % 2 === 0) {
        return;
      }

      dispatch(increment());
  }</code></pre>
</li>
<li><p>리턴하는 함수가 <code>dispatch, getState</code> 를 파라미터 갖는다면 스토어의 상태에도 접근 할 수 있다. 따라서, 현재의 스토어 상태의 값에 따라 액션이 <code>dispatch</code> 될 지 무시될지 정할 수 있다.</p>
</li>
<li><p>간단하게 정리를 하자면 redux-thunk 는 일반 액션 생성자에 날개를 달아준다. 보통의 액션생성자는 그냥 하나의 액션객체를 생성 할 뿐이지만 redux-thunk 를 통해 만든 액션생성자는 그 내부에서 여러가지 작업을 할 수 있다. 이 곳에서 네트워크 요청을 해도 무방하며, 이 안에서 액션을 여러번 디스패치 할 수도 있다.</p>
</li>
</ul>
<h3 id="dispatch-getstate는-어디서-받아오는가">dispatch, getState는 어디서 받아오는가?</h3>
<ul>
<li><p>redux-thunk 미들웨어에서, 전달받은 액션이 함수 형태 일 때, 그 함수에 <code>dispatch</code> 와 <code>getState</code> 를 넣어서 실행해준다. 실제로, redux-thunk 의 코드는 정말로 간단하다. 아래 그 코드를 보면 더 이해하기 쉽다.</p>
<pre><code class="language-jsx">  function createThunkMiddleware(extraArgument) {
    // store에서 dispatch와 getState를 비구조화 할당으로 전달
    return ({ dispatch, getState }) =&gt; next =&gt; action =&gt; {
      if (typeof action === &#39;function&#39;) {
        return action(dispatch, getState, extraArgument);
      }

      return next(action);
    };
  }

  const thunk = createThunkMiddleware();
  thunk.withExtraArgument = createThunkMiddleware;

  export default thunk;</code></pre>
</li>
</ul>
<h2 id="redux-thunk-사용하기api-요청-응답처리-예제">redux-thunk 사용하기(api 요청, 응답처리 예제)</h2>
<ul>
<li><p>설치 : <code>npm i redux-thunk</code></p>
</li>
<li><p>미들웨어 스토어에 적용 하기</p>
<pre><code class="language-jsx">  // src/index.js

  import React from &#39;react&#39;;
  import ReactDOM from &#39;react-dom&#39;;
  import { applyMiddleware, createStore } from &#39;redux&#39;;
  import { Provider } from &#39;react-redux&#39;;
  import &#39;./index.css&#39;;
  import App from &#39;./App&#39;;
  import rootReducer from &#39;./modules&#39;;
  import { createLogger } from &#39;redux-logger&#39;;
  import ReduxThunk from &#39;redux-thunk&#39;; // 1. import redux-thunk

  const logger = createLogger();

  // createStore의 매개 변수에 applyMiddleware() 함수 할당
  // applyMiddleware() 함수의 매개변수에 ReduxThunk를 할당
  const store = createStore(rootReducer, applyMiddleware(logger, ReduxThunk));

  ReactDOM.render(
    &lt;Provider store={store}&gt;
      &lt;App /&gt;
    &lt;/Provider&gt;,
    document.getElementById(&#39;root&#39;)
  );</code></pre>
</li>
</ul>
<h1 id="redux-saga">redux-saga</h1>
<ul>
<li>redux-thunk 다음으로 많이 사용하는 비동기 작업 관련 미들웨어.</li>
<li>redux-thunk는 함수 형태의 액션을 디스패치히여 미들웨어에서 해당 함수에 스토어의 <code>dispatch</code>, <code>getState</code>를 파라미터로 넣어 사용하는 원리다. 그래서 thunk 함수 내부에서 api 요청, 다른 액션 <code>dispatch</code>, 현재 상태 조회 등의 작업을 할 수 있다. 대부분의 경우에서는 redux-thunk로 충분히 기능을 구현할 수 있다.</li>
<li>redux-saga는 좀 더 까다로운 상황에서 유용하다. 아래의 상황에서 redux-saga를 쓰는 것이 유용하다.<ul>
<li>기존 요청을 취소 처리해야할 때(불필요한 중복요청 방지)</li>
<li>특정 액션이 발생했을 때 다른 액션을 발생시키거나, api 요청 등 리덕스와 관계없는 코드를 실행할 때</li>
<li>웹소켓을 사용할 때</li>
<li>api 요청 실패 시 재요청해야 할 때</li>
</ul>
</li>
<li>redux-saga는 제너레이터 함수 문법을 기반으로 비동기 작업을 관리해 준다. redux-saga는 디스패치하는 액션을 모니터링해서 그에 따라 필요한 작업을 따로 수행할 수 있는 미들웨어다.</li>
</ul>
<h2 id="제너레이터">제너레이터</h2>
<ul>
<li>일반 함수는 하나의 값(혹은 0개의 값)만을 반환한다. 하지만 제너레이터(generator)를 사용하면 여러 개의 값을 필요에 따라 하나씩 반환(yield)할 수 있으며, 제너레이터와 이터러블 객체를 함께 사용하면 손쉽게 데이터 스트림을 만들 수 있다.</li>
</ul>
<h3 id="제너레이터-기본-사용법">제너레이터 기본 사용법</h3>
<ul>
<li><p>제네레이터는 제네레이터 함수로 생성가능하며, 제너레이터 함수를 만들 때는 <code>function*</code> 키워드를 사용한다.</p>
<pre><code class="language-jsx">  // 일반 함수 - 아래처럼 여러 개의 값을 반환하는 것은 불가능 하다.
  function generalFunction() {
      return 1;
      return 2;
      return 3;
  }

  // 제너레이터 함수 선언 - function* 키워드 사용
  // function와 *를 띄워도 상관없지만 붙이는 것을 권장
  function* generateFunction() {
    yield 1;
    yield 2;
    return 3;
  }

  // &#39;제너레이터 함수&#39;는 &#39;제너레이터 객체&#39;를 생성한다.
  let generator = generateFunction();
  alert(generator); // [object Generator]</code></pre>
</li>
<li><p>제너레이터는 <code>next()</code> 라는 메서드를 갖고 있다. 제너레이터가 처음 만들어지면 함수의 흐름은 멈춰 있는 상태인데, <code>next()</code> 메서드를 호출하면 가장 가까운 <code>yield &lt;value&gt;</code>문을 만날 때까지 명령을 실행한다.(value를 생략할 수도 있는데, 이 경우엔 undefined가 된다) 이후, <code>yield &lt;value&gt;</code> 문을 만나면 실행을 멈추고 산출하고자 하는 값인 value가 바깥 코드에 반환된다.</p>
</li>
<li><p>제너레이터 함수를 사용하면 함수를 도중에 멈출 수 있고, 순차적으로 여러 값을 반환시킬 수 있다.</p>
</li>
<li><p><code>next()</code> 는 항상 아래 두 프로퍼티를 가진 객체를 반환합니다.</p>
<ul>
<li><code>value</code>: 산출 값</li>
<li><code>done</code>: 함수 코드 실행이 끝났으면 <code>true</code>, 아니라면 <code>false</code></li>
</ul>
</li>
</ul>
<pre><code class="language-jsx">function* generateFunction() {
    console.log(&#39;hello&#39;);
  yield 1; // 1번 실행 - 여기까지 실행 후 value: 1을 반환
    console.log(&#39;generateFunction&#39;);
  yield 2; // 2번 실행 - 여기까지 실행 후 value: 2를 반환
    console.log(&#39;end&#39;)
  return 3; // 3번 실행 - 여기까지 실행 후 value: 2를 반환, 제너레이터가 끝났음을 알림
}

let generator = generateFunction();

// 1번
console.log(generator.next());
// 결과 - hello / {value: 1, done: false}

// 2번
console.log(generator.next());
// 결과 - generateFunction / {value: 2, done: false}

// 3번
console.log(generator.next());
// 결과 - end / {value: 3, done: true}

// 4번
console.log(generator.next());
// 결과 - {value: undefined, done: true}</code></pre>
<h3 id="제너레이터와-이터러블">제너레이터와 이터러블</h3>
<ul>
<li><p><code>next()</code> 메서드를 보면 짐작할 수 있듯이, 제너레이터는 이터러블이다. 따라서 <code>for..of</code> 반복문을 사용해 값을 얻을 수 있습니다. 이 방식을 이용하면 <code>.next().value</code> 을 호출하는 것 보다 쉽게 모든 value를 얻을 수 있다.</p>
<pre><code class="language-jsx">  function* generateSequence() {
    yield 1;
    yield 2;
    return 3;
  }

  let generator = generateSequence();

  for(let value of generator) {
    alert(value); // 1, 2가 출력됨
  }</code></pre>
</li>
<li><p>다만 주의할 점은 위와 같이 코드를 짤 경우 1, 2만 출력되고 3은 출력되지 않는다. 그 이유는 <code>for..of</code> 이터레이션이 <code>done: true</code> 일 때 마지막 value를 무시하기 때문이다. 그러므로 <code>for..of</code> 를 사용했을 때 모든 값이 출력되길 원한다면 <code>yield</code> 로 값을 반환해야 한다.</p>
<pre><code class="language-jsx">  function* generateSequence() {
    yield 1;
    yield 2;
    yield 3;
  }

  let generator = generateSequence();

  for(let value of generator) {
    alert(value); // 1, 2, 3
  }</code></pre>
</li>
<li><p>제너레이터는 이터러블 객체이므로 제너레이터에도 스프레드 연산자를 사용할 수 있다.</p>
<pre><code class="language-jsx">  function* generateSequence() {
    yield 1;
    yield 2;
    yield 3;
  }

  let sequence = [0, ...generateSequence()];

  alert(sequence); // 0, 1, 2, 3</code></pre>
</li>
</ul>
<h3 id="yield를-사용해-제너레이터-안·밖으로-정보-교환하기">&#39;yield’를 사용해 제너레이터 안·밖으로 정보 교환하기</h3>
<ul>
<li><p>제너레이터는 값을 생성해주는 특수 문법을 가진 이터러블 객체와 유사해 보이지만, 사실 제너레이터는 더 강력하고 유연한 기능을 제공한다. 바로 <code>yield</code> 가 양방향 길과 같은 역할을 하기 때문이다. <code>yield</code> 는 결과를 바깥으로 전달할 뿐만 아니라 값을 제너레이터 안으로 전달하기까지 한다.</p>
</li>
<li><p><code>next()</code> 함수에 파라미터를 넣고, 제너레이터 함수에서는 <code>yield</code>를 사용하여 해당 값을 조회하여 값을 안, 밖으로 전달할 수 있다.</p>
<ul>
<li><p><code>generator.next(arg)</code> 를 호출할 때 인수 <code>arg</code> 는 <code>yield</code> 의 결과가 된다.</p>
<pre><code class="language-jsx">// 제너레이터 함수 선언
function* gen() {
// 질문을 제너레이터 밖 코드에 던지고 답을 기다린다.
let result = yield &quot;2 + 2 = ?&quot;; // (*)

alert(result);
}

//제너레이터 객체 생성
let generator = gen();

// generator.next() -&gt; { value: &quot;2 + 2 = ?&quot;, done: false }
// yield의 value를 반환한다.
let question = generator.next().value;

// next() 함수의 매개변수가 yield에 전달되고, yield는 result에 저장된다.
// 그 후 alert(result); 가 실행된다.
generator.next(4); // { value: undefined, done: true }</code></pre>
</li>
</ul>
<ol>
<li><code>generator.next()</code> 를 처음 호출할 땐 항상 매개변수가 없어야 한다. 매개변수가 있더라도, 이미 <code>yield</code> 에 <code>value</code> 가 있기 때문에 무시된다. <code>generator.next()</code> 를 호출하면 제너레이터 함수가 실행되고 첫 번째 <code>yield &quot;2+2=?&quot;</code> 의 결과가 반환된다. 이 시점에는 제너레이터가 (*)로 표시한 줄에서 실행을 잠시 멈춘다.</li>
<li><code>yield</code> 의 결과가 제너레이터를 호출하는 외부 코드에 있는 변수, <code>question</code> 에 할당된다.</li>
<li><code>generator.next(4)</code> 에서 제너레이터가 다시 시작되고 4는 <code>result</code> 에 할당된다. ( <code>let result = 4</code> )<ul>
<li>제너레이터 외부 코드에서 <code>next(4)</code> 를 즉시 호출하지 않고 있는데, 이는 제너레이터가 기다려주기 때문에 호출을 나중에 해도 문제가 되지 않는다.</li>
</ul>
</li>
</ol>
</li>
<li><p>일반 함수와 다르게 제너레이터와 외부 호출 코드는 <code>next/yield</code> 를 이용해 결과를 전달 및 교환한다.</p>
<pre><code class="language-jsx">  // 제너레이터 함수 정의
  function* gen() {
    let ask1 = yield &quot;2 + 2 = ?&quot;;

    alert(ask1); // 4

    let ask2 = yield &quot;3 * 3 = ?&quot;

    alert(ask2); // 9
  }

  // 제너레이터 생성
  let generator = gen();

  alert( generator.next().value ); // &quot;2 + 2 = ?&quot;

  alert( generator.next(4).value ); // &quot;3 * 3 = ?&quot;

  alert( generator.next(9).done ); // true</code></pre>
<ol>
<li>제너레이터 객체가 만들어지고 첫 번째 <code>next()</code>가 호출되면, 실행이 시작되고 첫 번째 <code>yield</code>에 도달한다.</li>
<li>산출 값(<code>&quot;2 + 2 = ?&quot;</code>)은 바깥 코드로 반환된다.</li>
<li>두 번째 <code>next(4)</code>는 첫 번째 <code>yield</code>의 결과가 될 <code>4</code>를 제너레이터 안으로 전달한다. 그리고 다시 실행이 이어집니다.</li>
<li>실행 흐름이 두 번째 <code>yield</code>에 다다르고, 산출 값(<code>&quot;3 * 3 = ?&quot;</code>)이 제너레이터 호출 결과가 됩니다.</li>
<li>세 번째 <code>next(9)</code>는 두 번째 <code>yield</code>의 결과가 될 <code>9</code>를 제너레이터 안으로 전달합니다. 그리고 다시 실행이 이어지는데, <code>done: true</code>이므로 제너레이터 함수는 종료된다.</li>
</ol>
</li>
</ul>
<h3 id="제너레이터로-액션-모니터링하기">제너레이터로 액션 모니터링하기</h3>
<pre><code class="language-jsx">function* watchGenerator() {
    console.log(&#39;모니터링 중...&#39;);
    let prevAction = null;
    while(true) {
        const action = yield;
        console.log(&#39;이전 액션: &#39;, prevAction);
        prevAction = action;
        if(action.type === &#39;HELLO&#39;) {
            console.log(&#39;안녕하세요!&#39;);
        }
    }
}

const watch = watchGenerator();

watch.next();
// 모니터링 중...
// { value: undefined, done: false }
watch.next({type: &#39;TEST&#39;});
// 이전 액션: null
// { value: undefined, done: false }
watch.next({type: &#39;HELLO&#39;});
// 이전 액션 : { type: &#39;TEST&#39; }
// 안녕하세요!
// { value: undefined, done: false }</code></pre>
<ul>
<li>redux-saga는 위 코드와 비슷한 원리로 액션을 모니터링 한다.</li>
</ul>
<h2 id="redux-saga로-비동기-카운터-만들기">redux-saga로 비동기 카운터 만들기</h2>
<h3 id="redux-saga로-모듈-만들기">redux-saga로 모듈 만들기</h3>
<pre><code class="language-jsx">// modules/counter.js
import { createAction, handleActions } from &quot;redux-actions&quot;;
import { delay, put, takeEvery, takeLatest } from &quot;@redux-saga/core/effects&quot;;

// 동기 액션 타입 선언
const INCREASE = &#39;counter/INCREASE&#39;;
const DECREASE = &#39;counter/DECREASE&#39;;
// 동기 액션 생성 함수
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);

// 비동기 액션 타입 선언
const INCREASE_ASYNC = &#39;counter/INCREASE_ASYNC&#39;;
const DECREASE_ASYNC = &#39;counter/DECREASE_ASYNC&#39;;
// 비동기 액션 생성 함수
// 마우스 클릭 이벤트가 payload 안에 들어가지 않도록
//() =&gt; undefined 를 두 번째 파라미터로 넣어준다.
export const increaseAsync = createAction(INCREASE_ASYNC, () =&gt; undefined);
export const defreascAsync = createAction(DECREASE_ASYNC, () =&gt; undefined);

// 제너레이터 함수 정의. 이 제너레이터 함수를 사가(saga)라고 부른다.
function* increaseSaga() {
    yield delay(1000); // 1초를 기다린다.
    yield put(increase()); // 특정 액션 디스패치
}

function* decreaseSaga() {
    yield delay(1000);
    yield put(decrease());
}

export function* counterSaga() {
    // takeEvery는 들어오는 모든 액션에 대해 특정 작업을 처리
        // 모든 INCREASE_ASYNC 액션에 대해 increaseSaga 함수 실행
    yield takeEvery(INCREASE_ASYNC, increaseSaga);
    // takeLatest는 기존에 진행 중이던 작업이 있다면 취소하고
    // 가장 마지막으로 실행된 작업만 수행
        // DECREASE_ASYNC액션에 대해서 기존에 진행 중이던 작업이 있다면 
        // 취소 처리하고 가장 마지막으로 실행된 작업에 대해서만 decreaseSaga 함수 실행
    yield takeLatest(DECREASE_ASYNC, decreaseSaga);
}

// 상태는 꼭 객체일 필요가 없다.
const initialState = 0;

const counter = handleActions(
    {
        [INCREASE]: state =&gt; state + 1,
        [DECREASE]: state =&gt; state - 1
    },
    initialState
)

export default counter;</code></pre>
<ul>
<li><code>delay(ms)</code> : 작업을 지연 시키는 메서드</li>
<li><code>put(action)</code> : 특정 액션을 디스패치 시키는 메서드</li>
<li><code>takeEvery(actionType, saga)</code> : <code>actionType</code> 을 모니터링 하고 있다가, <code>actionType</code> 이 발생하면 <code>saga</code> 를 실행시키는 메서드. <code>saga</code> 가 아직 종료되지 않았는데 <code>actionType</code> 이 발생하면, 기존 <code>saga</code> 실행을 중지하지 않고 <code>saga</code> 를 또 실행 시킨다. (새로은 <code>saga</code> 태스크 생성)</li>
<li><code>takeLatest(actionType, saga)</code> : <code>actionType</code> 을 모니터링 하고 있다가, <code>actionType</code> 이 발생하면 <code>saga</code> 를 실행시키는 메서드. <code>takeEvery()</code> 와 달리, <code>takeLatest()</code> 는 <code>saga</code> 가 아직 종료되지 않았는데 <code>actionType</code> 이 발생할 경우, 이미 실행 중인 <code>saga</code> 태스크를 종료한 후 마지막으로 발생한 <code>actionType</code> 에 대해서만 <code>saga</code> 를 실행 시킨다. 즉 <code>takeLatest()</code> 는 어느 순간에서도 단 하나의 <code>saga</code> 태스크만 실행되게 한다. (액션이 중첩되어 디스패치 됐을 경우 가장 마지막 액션만 처리)</li>
</ul>
<h3 id="루트-사가-등록">루트 사가 등록</h3>
<ul>
<li>루트 리듀서를 만드는 것처럼 루트 사가를 만들어야 한다. 추후 다른 리듀서에서도 사가를 만들어 등록할 것이기 때문이다. <code>all()</code> 함수를 이용해 여러 사가를 합칠 수 있다.</li>
<li><code>all(arr)</code> : <code>all()</code> 함수를 사용해서 제너레이터 함수를 배열의 형태로 인자로 넣어주면, 제너레이터 함수들이 병행적으로 동시에 실행되고, 전부 <code>resolve</code> 될때까지 기다린다. <code>Promise.all</code> 과 비슷하다.<ul>
<li>예 :  <code>yield all([testSaga1(), testSaga2()])</code><ul>
<li><code>testSaga1()</code> 과 <code>testSaga2()</code>가 동시에 실행되고, 모두 resolve될 때까지 기다린다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="language-jsx">// moudule/index.js
import { combineReducers } from &quot;redux&quot;;
import { all } from &quot;@redux-saga/core/effects&quot;; // import all method
import counter, { counterSaga } from &quot;./counter&quot;; // import countSaga
import sample from &quot;./sample&quot;;
import loading from &#39;./loading&#39;;

const rootReducer = combineReducers({counter, sample, loading});

// 루트 사가 생성
export function* rootSaga() {
    // all 함수는 여러 사가를 합쳐주는 역할을 한다.
    yield all([counterSaga()]);
}

export default rootReducer;</code></pre>
<h3 id="스토어에-redux-saga-적용">스토어에 redux-saga 적용</h3>
<pre><code class="language-jsx">// src/index.js

import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom&#39;;
import { applyMiddleware, createStore } from &#39;redux&#39;;
import { Provider } from &#39;react-redux&#39;;
import &#39;./index.css&#39;;
import App from &#39;./App&#39;;
import rootReducer, { rootSaga } from &#39;./modules&#39;; // import root saga
import { createLogger } from &#39;redux-logger&#39;;
import ReduxThunk from &#39;redux-thunk&#39;;
// import createSagaMiddleware
import createSagaMiddleware from &#39;@redux-saga/core&#39;;
import { composeWithDevTools } from &#39;redux-devtools-extension&#39;;

const logger = createLogger();
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
  rootReducer, 
  composeWithDevTools(applyMiddleware(logger, ReduxThunk, sagaMiddleware))
);
sagaMiddleware.run(rootSaga);

ReactDOM.render(
  &lt;Provider store={store}&gt;
    &lt;App /&gt;
  &lt;/Provider&gt;,
  document.getElementById(&#39;root&#39;)
);</code></pre>
<h2 id="redux-saga-함수saga-effect">redux-saga 함수(Saga-Effect)</h2>
<ul>
<li><code>delay(ms)</code> : <code>ms</code> 이후에 <code>resolve</code> 하는 <code>Promise</code> 객체를 리턴한다.</li>
<li><code>put(actionType)</code> : <code>actionType</code> 을 <code>dispatch</code> 하도록 한다.</li>
<li><code>takeEvery()</code> : 위 설명 참고</li>
<li><code>takeLatest()</code> : 위설명 참고</li>
<li><code>all(arr)</code> : 위 설명 참고</li>
<li><code>call(function, arg)</code> : <code>function</code> 에 매개변수로 <code>arg</code> 를 넣어 실행 시키는 함수. API를 호출해야 하는 상황에서는 사가 내부에서 직접 호출 하지 않고 <code>call()</code> 함수를 사용한다.</li>
<li><code>select(selector)</code> : 사가 내부에서 현재 상태를 참조할 때 사용하는 메서드. <code>selector</code> 는 <code>(state[, ...args]) =&gt; args</code> 형태의 함수로, 현재 스토어의 상태를 매개변수로 갖는다.<ul>
<li>예 : <code>const number = yield select(state ⇒ state.counter);</code></li>
</ul>
</li>
<li><code>throttle(ms, actionType, saga)</code> : <code>saga</code> 실행 주기를 제한하는 함수로, <code>actionType</code> 이 발생할 경우 <code>saga</code> 를 실행 시키되, <code>ms</code> 에 한 번만 실행 시킨다.<ul>
<li>예 : <code>throttle(3000, INCREASE_ASYNC, increaseSaga)</code> : <code>INCREASE_ASYNC</code> 이 발생할 경우 3초에  <code>increaseSaga</code> 를 단 한 번만 호출한다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React.js - redux2(리덕스 사용법)]]></title>
            <link>https://velog.io/@gyumin_2/React.js-redux2%EB%A6%AC%EB%8D%95%EC%8A%A4-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@gyumin_2/React.js-redux2%EB%A6%AC%EB%8D%95%EC%8A%A4-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Thu, 28 Jul 2022 02:20:24 GMT</pubDate>
            <description><![CDATA[<h1 id="리덕스-사용-패턴">리덕스 사용 패턴</h1>
<h2 id="컴포넌트-구조">컴포넌트 구조</h2>
<ul>
<li>리덕스를 사용할 때 가장 많이 사용하는 패턴은 프레젠테이셔널 컴포넌트와 컨테이너 컴포넌트를 분리하는 것이다.<ul>
<li>프레젠테이셔널 컴포넌트 : 상태관리가 이루어지지 않고, 그저 props를 전달받아 화면에 UI를 보여주기만 하는 컴포넌트</li>
<li>컨테이너 컴포넌트 : 리덕스와 연동되어 있는 컴포넌트로, 리덕스로부터 상태를 받아오기도 하고 리덕스 스토어에 액션을 디스패치라기도 한다.</li>
</ul>
</li>
<li>이 패턴을 사용할 경우 코드의 재사용성이 높아지고, 관심사의 분리가 이루어져 UI를 작성할 때 좀 더 집중 할 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/e5028d02-0954-4ce7-afb9-045238405a1a/image.png" alt=""></p>
<h2 id="폴더">폴더</h2>
<ul>
<li>일반적인 구조 : actions, constants, reducers라는 세 개의 디렉토리를 만들고 그 안에 기능별로 파일을 하나씩 만드는 방법. 코드를 종류에 따라 다른 파일에 작성하여 정리할 수 있어 편리하지만, 새로운 액션을 만들 때마가 세 종류의 파일을 모두 수정해야하는 불편함이 있다. 예전 리덕스 공식문서에서도 사용되므로 가장 기본적인 구조.(현재 리덕스 공식 문서에서는 redux tool kit 사용 권장)</li>
<li>ducks 방법 : 하나의 파일에 액션 타입, 액션 생성함수, 리듀서 함수를 기능별로 작성하는 방식.</li>
</ul>
<h1 id="리액트-애플리케이션에-리덕스-적용하기">리액트 애플리케이션에 리덕스 적용하기</h1>
<h2 id="루트-리듀서-만들기">루트 리듀서 만들기</h2>
<ul>
<li><p>스토어를 만들 때는 하나의 리듀서만 사용해야한다. 때문에 리덕스에서 제공하는 <code>combineReducers()</code> 함수를 이용해 여러 리듀서를 하나로 합쳐주어야한다.</p>
</li>
<li><p><code>modules/index.js</code></p>
<pre><code class="language-jsx">  import { combineReducers } from &quot;redux&quot;; // import combineReducers
  import counter from &quot;./Counter&quot;; // import reducers
  import todos from &quot;./Todos&quot;;

  // combineReducers 함수의 매개변수에 import한 reducer를 넣어준다.
  const rootReducer = combineReducers({
      counter,
      todos
  })

  export default rootReducer;

  // 파일 이름을 index.js로 설정하면, 
  // import 시 디렉터리 이름까지만 입력하여 import 할 수 있다.
  // 예) import rootReducer from &#39;./modules&#39;;  </code></pre>
</li>
</ul>
<h2 id="스토어-만들기">스토어 만들기</h2>
<pre><code class="language-jsx">// src/index.js
import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom&#39;;
import { createStore } from &#39;redux&#39;; // import createStore function
import &#39;./index.css&#39;;
import App from &#39;./App&#39;;
import rootReducer from &#39;./modules&#39;; // import toorReducer

// createStore 함수에 매개변수로 rootReducer를 전달하여 store 생성
const store = createStore(rootReducer); 

ReactDOM.render(
  &lt;React.StrictMode&gt;
    &lt;App /&gt;
  &lt;/React.StrictMode&gt;,
  document.getElementById(&#39;root&#39;)
);</code></pre>
<h2 id="provider-컴포넌트를-사용하여-프로젝트에-리덕스-적용하기">Provider 컴포넌트를 사용하여 프로젝트에 리덕스 적용하기</h2>
<ul>
<li>리액트 컴포넌트에서 스토어를 사용할 수 있도록 App 컴포넌트를 <code>react-redux</code>에서 제공하는 <code>Provider</code> 컴포넌트로 감싸주어야한다. <code>Provider</code> 컴포넌트를 사용할 때는 <code>store</code>를 <code>props</code>로 전달해 주어야한다.</li>
</ul>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom&#39;;
import { createStore } from &#39;redux&#39;;
import { Provider } from &#39;react-redux&#39;; // import Provider component
import &#39;./index.css&#39;;
import App from &#39;./App&#39;;
import rootReducer from &#39;./modules&#39;;

const store = createStore(rootReducer);

ReactDOM.render(
  &lt;Provider store={store}&gt;
    &lt;App /&gt;
  &lt;/Provider&gt;,
  document.getElementById(&#39;root&#39;)
);</code></pre>
<h2 id="redux-devtools-설치-및-적용">Redux DevTools 설치 및 적용</h2>
<ul>
<li><p>Redux DevTools는 리덕스 개발자 도구로 크롬 확장 프로그램을 설치하여 사용할 수 있다.</p>
<ul>
<li><a href="https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd">크롬 확장 프로그램 설치</a></li>
</ul>
</li>
<li><p>redux-devtools-extension을 설치한다.</p>
<ul>
<li><code>npm i redux-devtools-extension</code></li>
</ul>
</li>
<li><p>리덕스 스토어를 만드는 과정에서 아래와 같이 적용한다.</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  import ReactDOM from &#39;react-dom&#39;;
  import { createStore } from &#39;redux&#39;;
  import { Provider } from &#39;react-redux&#39;;
  import { composeWithDevTools } from &#39;redux-devtools-extension&#39;; // import
  import &#39;./index.css&#39;;
  import App from &#39;./App&#39;;
  import rootReducer from &#39;./modules&#39;;

  const store = createStore(rootReducer, composeWithDevTools()); // 적용

  ReactDOM.render(
    &lt;Provider store={store}&gt;
      &lt;App /&gt;
    &lt;/Provider&gt;,
    document.getElementById(&#39;root&#39;)
  );</code></pre>
</li>
</ul>
<h1 id="컨테이너-컴포넌트-만들기">컨테이너 컴포넌트 만들기</h1>
<ul>
<li><p>컴포넌트와 리덕스를 연동하려면 react-redux에서 제공하는 <code>connect</code> 함수를 사용해야한다.</p>
</li>
<li><p><code>connect(mapStateToProps, mapDispatchToProps)(연동할 컴포넌트)</code></p>
<ul>
<li><p><code>mapStateToProps</code> : 리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기 위해 설정하는 함수</p>
</li>
<li><p><code>mapDispatchToProps</code>: 액션 생성함수를 컴포넌트의 props로 넘겨주기 위해 사용하는 함수</p>
</li>
<li><p>connect 함수는 또 다른 함수를 반환하는데, 이 반환된 함수에 컴포넌트를 파라미터로 넣어주면 리덕스와 연동된 컴포넌트가 만들어진다. 위코드를 쉽게풀면 아래와 같다.</p>
<pre><code class="language-jsx">const makeContainer = connect(mapStateToProps, mapDispatchToProps);

makeContainer(연동할 컴포넌트);</code></pre>
</li>
</ul>
</li>
<li><p><code>mapStateToProps</code> 와 <code>mapDispatchToProps</code> 에서 반환하는 객체 내부의 값들은 컴포넌트의 props로 전달된다.</p>
</li>
<li><p><code>mapStateToProps</code>는 state를 파라미터로 받아오며, 이 값은 현재 스토어가 갖고 있는 상태값을 가리킨다.</p>
</li>
<li><p><code>mapDispatchToProps</code>의 경우 store의 내장함수 dispatch를 파리미터로 받아온다.</p>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  import { connect } from &#39;react-redux&#39;;
  import Counter from &#39;../components/Counter&#39;;
  import { increase, decrease } from &#39;../modules/Counter&#39;;

  // mapStateToProps, mapDispatchToProps가 반환하는 값이
  // CounterContainer의 props 자동 전달된다.(in connect 함수)
  const CounterContainer = ({number, increase, decrease}) =&gt; {
      return (
          &lt;Counter 
                      number={number} 
                      onIncrease={increase} 
                      onDecrease={decrease} 
                  /&gt;
      );
  };

  // 스토어의 state를 파라미터로 받아온다.
  const mapStateToProps = state =&gt; ({
      number: state.counter.number
  })

  // 스토어의 dispatch 함수를 파라미터로 받아온다.
  const mapDispatchToProps = dispatch =&gt; ({
      increase: () =&gt; {
                  // 액션 생성함수를 import하여 사용
          dispatch(increase());
      },
      decrease: () =&gt; {
          dispatch(decrease());
      }
  })

  // connect 함수로 컴포넌트와 리덕스 연결 =&gt; 컨테이너 컴포넌트 생성
  export default connect(
      mapStateToProps, 
      mapDispatchToProps
  )(CounterContainer);</code></pre>
</li>
</ul>
<h2 id="코드-개선">코드 개선</h2>
<ul>
<li><p>connect 함수를 사용할 때 일반적으로 위 코드와 같이 <code>mapStateToProps</code> 와 <code>mapDispatchToProps</code> 를 미리 선언하고 사용한다. 하지만 connect 내부에 익명 함수 형태로 선언하면 코드가 조금 더 깔끔해진다.</p>
<pre><code class="language-jsx">  ...

  export default connect(
      state =&gt; ({
          number: state.counter.number
      }),
      dispatch =&gt; ({
          increase: () =&gt; dispatch(increase()),
          decrease: () =&gt; dispatch(decrease())
      })
  )(CounterContainer);</code></pre>
</li>
</ul>
<h2 id="bindactioncreators">bindActionCreators</h2>
<ul>
<li><p>컨테이너 컴포넌트에서 액션을 디스패치하기 위해 각 액션 생성 함수를 호출하고 dispatch로 감싸는 작업은 번거롭다. 리덕스에서 제공하는 <code>bindActionCreators</code>함수를 사용하면 간편하게 액션을 디스패치 할 수 있다.</p>
<pre><code class="language-jsx">  ...
  import { bindActionCreators } from &#39;redux&#39;;

  export default connect(
      state =&gt; ({
          number: state.counter.number
      }),
      dispatch =&gt; bindActionCreators(
          {increase, decrease},
          dispatch
      )
  )(CounterContainer);</code></pre>
</li>
<li><p>mapDispatchtoProps에 해당하는 파라미터를 함수 형태가 아닌 액션 생성 함수로 이루어진 객체 형태로 넣어주면, connect 함수가 내부적으로 bindActionCreators 작업을 대신 해준다.</p>
<pre><code class="language-jsx">  ...

  export default connect(
      state =&gt; ({
          number: state.counter.number
      }),
      {
          increase,
          decrease
      }
  )(CounterContainer);</code></pre>
</li>
</ul>
<h1 id="리덕스-더-편하게-사용하기">리덕스 더 편하게 사용하기</h1>
<h2 id="redux-actions">redux-actions</h2>
<ul>
<li><p>redux-actions을 사용하면 액션 생성함수를 더 짧은 코드로 작성할 수 있다. 또한 리듀서 작성 시 switch 문이 아니라 redux-actions에서 재공하는 함수를 이용해 더 쉽게 리듀서를 작성할 수 있다.</p>
</li>
<li><p><code>npm i redux-actions</code> 로 설치</p>
</li>
<li><p><code>createAction(actionType[, payloadFunction])</code></p>
<ul>
<li><p>액션타입과 payload(액션에 필요한 추가 데이터)를 반환하는 함수를 전달받아 액션객체를 반환하는 함수</p>
<pre><code class="language-jsx">// import createAction
import { createAction } from &#39;redux-actions&#39;

// 액션 타입 정의
const MY_ACTION = &#39;sample/MY_ACTION&#39;;

// createAction함수로 액션 생성 함수 만들기
const myAction = createAction(MY_ACTION); 
// 결과 : { type: MY_ACTION }

// payload 추가하기
const action = myAction(&#39;hello&#39;);
// 결과 : { type: MY_ACTION, payload: &#39;hello&#39; }
</code></pre>
</li>
</ul>
<hr>
<p>  // payload 정의 함수 사용 =&gt; payload 변형 가능
  const myAction = createAction(MY_ACTION, text =&gt; <code>${text}!</code>);
  const action = myAction(&#39;hello&#39;);
  // 결과 : { type: MY_ACTION, payload: &#39;hello!&#39; }</p>
<pre><code></code></pre></li>
<li><p><code>handleActions(updateObject, initialState)</code></p>
<ul>
<li><p>리듀서 함수를 더 간단하고 가독성 높게 작성할 수 있도록 도와주는 함수</p>
</li>
<li><p><code>updateObject</code> : 액션에 따라 실행 할 함수들을 가지고있는 객체</p>
</li>
<li><p><code>initialState</code> : 기본상태값</p>
</li>
<li><p>예</p>
<pre><code class="language-jsx">const counter = handleActions(
  {
      [INCREASE]: (state, action) =&gt; ({number: state.number + 1}),
      [DECREASE]: (state, action) =&gt; ({number: state.number - 1})
  },
  initialState
)

// modules/Todos.js 리듀서 예
// 액션 생성 함수는 액션에 필요한 추가 데이터를 payload라는 이름으로 사용하기 때문에
// 모두 공통적으로 action.payload로 값을 조회하도록 구현해야한다.
const todos = handleActions(
  {
      [CHANGE_INPUT]: (state, action) =&gt; ({...state, input: action.payload}),
      [INSERT]: (state, action) =&gt; ({
          ...state,
          todos: state.todos.concat(action.payload)
      }),
      [TOGGLE]: (state, action) =&gt; ({
          ...state,
          todos: state.todos.map(todo =&gt; 
              todo.id === action.payload ? {...todo, done: !todo.done} : todo
          )
      }),
      [REMOVE]: (state, action) =&gt; ({
          ...state,
          todos: state.todos.filter(todo =&gt; todo.id !== action.payload)
      })
  },
  initialState
)

// 비구조화 할당 문법으로 action의 payload 이름을 새로 설정하면
// action.payload가 정확히 어떤 값을 의미하는지 더 쉽게 파악할 수 있다.
const todos = handleActions(
  {
      [CHANGE_INPUT]: (state, {payload: input}) =&gt; ({...state, input}),
      [INSERT]: (state, {payload: todo}) =&gt; ({
          ...state,
          todos: state.todos.concat(todo)
      }),
      [TOGGLE]: (state, {payload: id}) =&gt; ({
          ...state,
          todos: state.todos.map(todo =&gt; 
              todo.id === id ? {...todo, done: !todo.done} : todo
          )
      }),
      [REMOVE]: (state, {payload: id}) =&gt; ({
          ...state,
          todos: state.todos.filter(todo =&gt; todo.id !== id)
      })
  },
  initialState
)</code></pre>
</li>
</ul>
</li>
</ul>
<h2 id="immer-사용하기">immer 사용하기</h2>
<h3 id="immer-사용법">immer 사용법</h3>
<pre><code class="language-jsx">import produce from &#39;immer&#39;;

const nextState = produce(currentState, draft =&gt; {
    draft.somwwhere.deep.inside = 5;
})</code></pre>
<ul>
<li><code>produce(currentState, recipe)</code> : 상태 불변성을 유지하여 새로운 상태를 생성하는 함수<ul>
<li><code>currentState</code> : 수정하고 싶은 상태</li>
<li><code>recipe</code> : 첫 번째 파라미터인 <code>currentState</code>를 어떻게 업데이트할지 정의하는 함수. <code>recipe</code> 함수는 원본 데이터(draft)를 매개변수로 전달받는다. 이 함수 내부에서는 불변성에 신경 쓰지 않는 것처럼 코드를 작성하여 상태를 변경한다.</li>
<li><code>produce</code> 함수는 <code>recipe</code> 함수 내부에서 변경한 상태를 바탕으로 새로운 상태를 반환한다.</li>
</ul>
</li>
</ul>
<h3 id="immper-적용-예">immper 적용 예</h3>
<pre><code class="language-jsx">const todos = handleActions(
    {
        [CHANGE_INPUT]: (state, {payload: input}) =&gt; 
            produce(state, draft =&gt; {
                draft.input = input;
            }),
        [INSERT]: (state, {payload: todo}) =&gt; 
            produce(state, draft =&gt; {
                draft.todos.push(todo);
            }),
        [TOGGLE]: (state, {payload: id}) =&gt; 
            produce(state, draft =&gt; {
                const todo = draft.todos.find(todo =&gt; todo.id === id);
                todo.done = !todo.done;
            }),
        [REMOVE]: (state, {payload: id}) =&gt; 
            produce(state, draft =&gt; {
                const index = draft.todos.findIndex(todo =&gt; todo.id === id);
                draft.todos.splice(index, 1);
            })
    },
    initialState
)</code></pre>
<h1 id="hooks를-사용하여-컨테이너-컴포넌트-만들기">Hooks를 사용하여 컨테이너 컴포넌트 만들기</h1>
<ul>
<li>리덕스 스토어와 연동된 컨테이너 컴포넌트를 만들 때 connect 함수를 사용하는 대신 react-redux에서 제공하는 Hooks를 사용할 수도 있다.</li>
</ul>
<h2 id="useselector">useSelector</h2>
<pre><code class="language-jsx">const 결과 = useSelector(상태 선택 함수);</code></pre>
<ul>
<li><p>connect 함수를 사용하지 않고도 리덕스의 상태를 조회할 수 있게 해주는 Hook</p>
</li>
<li><p>상태 선택 함수는 connect 함수의 매개변수인 mapStateToProps와 형태가 똑같다.</p>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  import { useSelector } from &#39;react-redux&#39;;
  import Counter from &#39;../components/Counter&#39;;
  import { increase, decrease } from &#39;../modules/Counter&#39;;

  const CounterContainer = () =&gt; {
      const number = useSelector(state =&gt; state.counter.number);
      return (
          &lt;Counter number={number} /&gt;
      );
  };

  export default CounterContainer;</code></pre>
</li>
</ul>
<h2 id="usedispatch">useDispatch</h2>
<pre><code class="language-jsx">const dispatch = useDispatch();

dispatch({type: &#39;SAMPLE_ACTION&#39;});</code></pre>
<ul>
<li><p>컴포넌트 내부에서 스토어의 내장 함수인 dispatch를 사용할 수 있게 해주는 Hook</p>
</li>
<li><p>컴포넌트 최적화를 위해 useCallback과 함께 사용하는 것이 좋다.</p>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  import { useSelector, useDispatch } from &#39;react-redux&#39;;
  import Counter from &#39;../components/Counter&#39;;
  import { increase, decrease } from &#39;../modules/Counter&#39;;

  const CounterContainer = () =&gt; {
      const number = useSelector(state =&gt; state.counter.number);
      const dispatch = useDispatch();
      return (
          &lt;Counter 
              number={number} 
              onIncrease={() =&gt; dispatch(increase())} 
              onDecrease={() =&gt; dispatch(decrease())} 
          /&gt;
      );
  };

  export default CounterContainer;

  // 최적화를 위해 useCallback 사용
  const CounterContainer = () =&gt; {
      const number = useSelector(state =&gt; state.counter.number);
      const dispatch = useDispatch();
      const onIncrease = useCallback(() =&gt; dispatch(increase()), [dispatch]);
      const onDecrease = useCallback(() =&gt; dispatch(decrease()), [dispatch])
      return (
          &lt;Counter 
              number={number} 
              onIncrease={onIncrease} 
              onDecrease={onDecrease} 
          /&gt;
      );
  };</code></pre>
</li>
<li><p>useSelector, useDispatch 예</p>
<pre><code class="language-jsx">  import React, { useCallback } from &#39;react&#39;;
  import { useSelector, useDispatch } from &#39;react-redux&#39;;
  import { changeInput, insert, toggle, remove } from &#39;../modules/Todos&#39;;
  import Todos from &#39;../components/Todos&#39;

  const TodosContainers = () =&gt; {
      const { input, todos } = useSelector(({todos}) =&gt; ({
          input: todos.input,
          todos: todos.todos
      }));
      const dispatch = useDispatch();
      const onChangeInput = useCallback(input =&gt; dispatch(changeInput(input)), [dispatch]);
      const onInsert = useCallback(text =&gt; dispatch(insert(text)), [dispatch]);
      const onToggle = useCallback(id =&gt; dispatch(toggle(id)), [dispatch]);
      const onRemove = useCallback(id =&gt; dispatch(remove(id)), [dispatch]);
      return (
          &lt;Todos
              input={input}
              todos={todos}
              onChangeInput={onChangeInput}
              onInsert={onInsert}
              onToggle={onToggle}
              onRemove={onRemove}
          /&gt;
      );
  };</code></pre>
</li>
</ul>
<h2 id="usestore">useStore</h2>
<pre><code class="language-jsx">const store = useStore();
store.dispatch({type: &#39;SAMPLE_ACTION&#39;});
store.getState();</code></pre>
<ul>
<li>컴포넌트 내부에서 리덕스 스토어 객체를 직접 사용할 수 있게 해주는 Hook.</li>
<li>자주 사용하지 않는 것을 권장. 스토어에 직접 접근해야하는 상황에서만 사용할 것을 권장.</li>
</ul>
<h2 id="useactions">useActions</h2>
<ul>
<li><p><code>useACtions</code> Hook은 원래 react-redux에 내장된 상태로 릴리즈 될 계획이었으나 리덕스 개발 팀에서 꼭 필요하지 않다고 판단하여 제외되었다. 그 대신 공식 문서에서 그대로 복사하여 사용할 수 있도록 제공하고 있다.</p>
</li>
<li><p><code>useActions</code> 코드 (src/lib/useActions.js로 저장)</p>
<pre><code class="language-jsx">  import { bindActionCreators } from &#39;redux&#39;
  import { useDispatch } from &#39;react-redux&#39;
  import { useMemo } from &#39;react&#39;

  export function useActions(actions, deps) {
    const dispatch = useDispatch()
    return useMemo(
      () =&gt; {
        if (Array.isArray(actions)) {
          return actions.map(a =&gt; bindActionCreators(a, dispatch))
        }
        return bindActionCreators(actions, dispatch)
      },
      deps ? [dispatch, ...deps] : [dispatch]
    )
  }</code></pre>
</li>
<li><p><code>useAcrions</code> 사용 예제</p>
<pre><code class="language-jsx">  import React, { useCallback } from &#39;react&#39;;
  import { useSelector } from &#39;react-redux&#39;;
  import { changeInput, insert, toggle, remove } from &#39;../modules/Todos&#39;;
  import Todos from &#39;../components/Todos&#39;;
  import { useActions } from &#39;../lib/useActions&#39;;

  const TodosContainers = () =&gt; {
      const { input, todos } = useSelector(({todos}) =&gt; ({
          input: todos.input,
          todos: todos.todos
      }));

          // useActions : 액션 생성 함수로 이루어진 배열과 dependency 배열을 전달받아
          // 액션을 디스 패치하는 함수 배열을 반환하는 함수
          // dependency 배열 원소가 바뀌면 액션을 디스패치하는 함수를 새로 만든다.
      const [ onChangeInput, onInsert, onToggle, onRemove ] = useActions(
          [changeInput, insert, toggle, remove],
          []
      )

      return (
          &lt;Todos
              input={input}
              todos={todos}
              onChangeInput={onChangeInput}
              onInsert={onInsert}
              onToggle={onToggle}
              onRemove={onRemove}
          /&gt;
      );
  };</code></pre>
</li>
</ul>
<h2 id="connect-함수와-react-redux-hooks와의-차이점">connect 함수와 react-redux Hooks와의 차이점</h2>
<ul>
<li><code>connect</code> 함수를 사용하여 컨테이너 컴포너트를 만들 경우, 해당 컨테이너 컴포넌트의 부모 컴포넌트가 리랜더링 될 때 해당 컴포넌트의 props가 바뀌지 않는다면 리랜더링이 자동으로 방지 되어 성능이 최적화 된다.</li>
<li>하지만 <code>useSelector</code>를 사용하여 리덕스 상태를 조회했을 때는 이 최적화 작업이 이루어지지 않으므로, 성능 최적화를 위해 <code>React.memo</code>를 사용해야 한다.</li>
</ul>
<h1 id="정리">정리</h1>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/9184835e-d147-418c-b5ee-5b518fddfe82/image.png" alt=""></p>
<ol>
<li>모듈<ul>
<li>액션 타입 선언(상수), 액션 생섬 함수 선언(createAction from redux-actions), 초깃값 선언, 리듀서 선언(handleActions from redux-actions)</li>
<li>react-actions로 액션생성함수와 리듀서를 편하게 만들 수 있다.</li>
</ul>
</li>
<li>루트 리듀서<ul>
<li>combineReducers(from redux) 함수로 루트 리듀서 생성</li>
</ul>
</li>
<li>index.js<ul>
<li>createStore 함수(from redux)의 매개변수로 rootReducer를 전달하여 store 생성</li>
<li>Provider 컴포넌트(from react-redux)로 App 컴포넌트 감싸고, props로 store 전달</li>
</ul>
</li>
<li>프레젠테이셔널 컴포넌트</li>
<li>컨테이너 컴포넌트<ol>
<li>react-redux에서 제공하는 connect 함수로 컴포넌트와 스토어를 연결</li>
<li>react-redux에서 제공하는 Hook을 사용하여 컴포넌트의 스토어를 연결</li>
</ol>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[브라우저 동작 원리 - 주소창에 URL 입력 시 일어나는 과정 (프론트 편1)]]></title>
            <link>https://velog.io/@gyumin_2/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%A3%BC%EC%86%8C%EC%B0%BD%EC%97%90-URL-%EC%9E%85%EB%A0%A5-%EC%8B%9C-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EA%B3%BC%EC%A0%95-%ED%94%84%EB%A1%A0%ED%8A%B8-%ED%8E%B81</link>
            <guid>https://velog.io/@gyumin_2/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%A3%BC%EC%86%8C%EC%B0%BD%EC%97%90-URL-%EC%9E%85%EB%A0%A5-%EC%8B%9C-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EA%B3%BC%EC%A0%95-%ED%94%84%EB%A1%A0%ED%8A%B8-%ED%8E%B81</guid>
            <pubDate>Wed, 13 Jul 2022 05:57:32 GMT</pubDate>
            <description><![CDATA[<h1 id="브라우저의-주요-기능">브라우저의 주요 기능</h1>
<p>브라우저의 주요 기능은 사용자가 입력한 자원을 서버에 요청하고 사용자에게 보여주는 것입니다. 여기서 자원은 보톤 HTML 문서지만 PDF나 이미지 또는 다른 형태일 수도 있습니다. 자원의 주소는 URI(Uniform Resource Identifier)에 의해 정해집니다.</p>
<p>브라우저는 HTML과 CSS 명세에 따라 HTML 파일을 해석해서 표시하는데 이 명세는 웹 표준화 기구인 W3C(World Wide Web Consortium)에서 정합니다. 과거에는 브라우저들이 일부만 이 명세에 따라 구현하고 나머지는 독자적인 방법으로 확장해 웹 제작자가 심각한 호환성 문제를 겪었지만 최근에는 대부분의 브라우저가 표준 명세를 따릅니다.</p>
<p>일반적으로 브라우저의 사용자 인터페이스는 아래와 같습니다.</p>
<ul>
<li>URI를 입력할 수 있는 주소 표시 줄</li>
<li>이전 버튼과 다음 버튼</li>
<li>북마크</li>
<li>새로 고침 버튼과 현재 문서의 로드를 중단할 수 있는 정지 버튼</li>
<li>홈 버튼</li>
</ul>
<h1 id="브라우저의-기본-구조">브라우저의 기본 구조</h1>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/cac75e13-31eb-4747-b5ed-452c2b5f3396/image.png" alt=""></p>
<ul>
<li>사용자 인터페이스 : 주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등. 요청한 페이지를 보여주는 창을 제외한 나머지 부분.</li>
<li>브라우저 엔진 : 모든 웹 브라우저의 핵심 구성 요소로 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어.</li>
<li>렌더링 엔진 : 사용자가 요청한 웹 페이지를 화면에 렌더링하는 역할. 예를 들어 HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시함.</li>
<li>통신 : HTTP 요청과 같은 네트워크 호출에 사용됨되며 플랫폼 독립적인 인터페이스. 각 플랫폼 하부에서 실행됨. 인터넷 통신과 관련된 보안 문제를 처리</li>
<li>UI 백엔드 : 콤보 박스와 창 같은 기본적인 장치를 그림. 플랫폼에서 명시하지 않은 일반적인 인터페이스로서, OS 사용자 인터페이스 체계를 사용.</li>
<li>자바스크립트 해석기 : 자바스크립트 코드를 해석하고 실행.</li>
<li>자료 저장소 : 자료를 저장하는 계층으로 쿠키 등을 저장하는 웹 데이터베이스</li>
</ul>
<h1 id="렌더링-엔진">렌더링 엔진</h1>
<p>렌더링 엔진의 역할은 서버로 부터 받은 응답 내용 즉 HTML을 브라우저 화면에 표시하는 일입니다. 렌더링 엔진은 HTML 및 XML 문서와 이미지를 표시할 수 있습니다. 렌더링 엔진은 HTTP 통신을 통해 요청한 문서의 내용을 얻는 것으로 시작하는데 문서의 내용은 보통 8KB 단위로 전송됩니다. </p>
<p>모든 브라우저는 고유한 렌더링 엔진이 있습니다. 브라우저마다 렌더링 엔진이 다르기 때문에 사용자 화면에서 웹 페이지를 해석하는 방식이 다 다릅니다.(프론트 엔드 개발자가 고통 받는 이유…) 여기서 브라우저간 호환성 문제가 발생합니다. 때문에 우리 웹 개발자에게(특히 프론트 엔드) 여러 브라우저에서 기능 및 디자인 측면에서 웹 애플리케이션의 일관성을 확인하는 크로스 브라우저 테스트는 필수라고 할 수 있습니다. 대표 브라우저의 렌더링 엔진은 아래와 같습니다.</p>
<ul>
<li>파이어폭스 : 모질라의 개코(Gecko) 엔진</li>
<li>IE : 트라이던트(Trident) 엔진</li>
<li>사파리, 크롬(ios): 웹킷(Webkit) 엔진(최초 리눅스 플랫폼에서 동작하기 위해 제작된 오픈소스 엔진)</li>
<li>크롬(28버전 이후), 오페라 : 블링크(blink) 엔진</li>
</ul>
<p>렌더링 엔진이 어떻게 동작하는지만 알면 브라우저의 동작 원리를 모두 알 수 있습니다. 렌더링 엔진 기본 동작 과정은 아래와 같습니다.</p>
<ol>
<li>DOM 트리 구축을 위한 HTML 파싱 (Parsing)</li>
<li>렌더 트리 구축 (Attachment)</li>
<li>렌더 트리 배치 (Layout or Reflow)</li>
<li>렌더 트리 그리기 (Paint)</li>
</ol>
<p>이제 각 단계에 대해 자세히 알아보겠습니다.</p>
<h1 id="parsing-및-dom-트리-cssom-트리-구축">Parsing 및 DOM 트리 CSSOM 트리 구축</h1>
<h2 id="parsing이란">Parsing이란?</h2>
<p>파싱은 렌더링 엔진에서 매우 중요한 과정이기 때문에 조금 더 자세히 살펴보겠습니다. 그렇다고 너무 자세히 파보면 끝도 없기 때문에 브라우저 렌더링 과정을 이해할 정도까지만 살펴보겠습니다.</p>
<p>언어학에서 파싱은 구문 분석이라고합니다. 문장을 이루고 있는 구성 성분으로 문장을 분해하고, 구성 성분들 사이의 위계 관계를 분석하여 문장의 구조를 결정하는 것을 말합니다. 컴퓨터 과학에서 파싱은 일련의 문자열을 의미있는 토큰(token, 어휘 분석 단위)으로 분해하고 이들로 이루어진 파스 트리(parse tree)를 만드는 과정을 말합니다. 쉽게말해 파싱은 개발자가 작성한 코드를 컴퓨터가 이해할 수 있도록, 코드를 토큰이라는 단위로 쪼개고 구조화하는 과정이라고 생각하시면 됩니다.</p>
<p>브라우저는 서버로부터 HTML을 응답받으면 먼저 문서 파싱을 통해 브라우저가 HTML 코드를 이해하고 사용할 수 있는 구조로 변환하는 작업을 진행합니다. 파싱 결과는 보통 문서 구조를 나타내는 노드 트리인데 파스 트리(parse tree) 또는 문법 트리(syntax tree)라고 부릅니다.</p>
<h3 id="파서-및-파싱-문법">파서 및 파싱 문법</h3>
<p>파싱을 행하는 프로그램을 파서라고하는데, 파서는 보통 두 가지 일을 합니다.</p>
<ol>
<li>자료를 유효한 토큰으로 분해하는 어휘 분석기(토큰 변환기) 역할. 공백과 줄바꿈 같은 의미없는 문자를 제거</li>
<li>언어 구문 규칙에 따라 문서 구조를 분석하여 파싱 트리를 생성하는 파서 역할</li>
</ol>
<p>파싱은 어휘 분석기를 통해 토큰화 된 코드가 생성되고, 이를 파서가 해석하는 순서로 이루어집니다. 토큰화라는 것은 의미가 있는 최소 단위로 코드를 쪼개는 것을 의미합니다.  예를 들어 <code>&lt;div&gt;&lt;/div&gt;</code> 라는 코드를 토큰화하면 <code>[&#39;&lt;&#39;, &#39;div&#39;, &#39;&gt;&#39;, &#39;&lt;/&#39;, &#39;div&#39;, &#39;&gt;&#39;]</code> 처럼 나타낼 수 있습니다.</p>
<h3 id="파싱-구분-및-문법">파싱 구분 및 문법</h3>
<p>우리가 외국어를 배울 때 그 언어의 단어와 문법을 배우고, 그에 따라 언어를 말하고 해석할 수 있습니다. 파싱도 이와 같습니다. 파싱은 개발자가 작성한 코드가 해당 프로그램 언어의 문법(grammer)을 모두 따르는지 확인하는 과정입니다. 대부분의 프로그래밍 언어는 문맥 자유 문법이라는 것에 속하며 파서는 이 문맥 자유 문법에 따라 문서를 파싱합니다.</p>
<p>문법은 어휘(vocabulary)와 문법 규칙(syntax rule)으로 구성이 되어 있습니다. 어휘는 알파벳이나 한글처럼 사용할 수 있는 단어와 그 조합들을 의미하며, 문법 규칙은 어휘 사이에 적용될 수 있는 규칙들을 의미합니다. 가령 숫자의 계산에서 곱하기는 앞뒤로 정수가 와야 하고, 더하기보다 더 높은 우선순위를 갖고 있는 것 같은 규칙들처럼요.</p>
<p>문법은 어휘와 문법 규칙으로 구성되어 있기 때문에 파싱도 이 두 가지로 구분할 수 있습니다.</p>
<ul>
<li>어휘 분석 : 자료를 토큰으로 분해하는 과정. 토큰은 유효하게 구성된 단위의 집합체로 용어집이라고 할 수 있다. 인간이 언어로치면 사전에 등장하는 모든 단어가 이에 해당한다.</li>
<li>구문 분석 : 언어의 구문 규칙(문법 규칙)을 적용하는 과정.</li>
</ul>
<h3 id="파싱-과정">파싱 과정</h3>
<p>파싱은 아래의 과정이 반복됩니다.</p>
<ol>
<li>파서는 보통 어휘 분석기로부터 새 토큰을 받아 구문 규칙과 일치하는지 확인한다.</li>
<li>규칙에 맞으면 토큰에 해당하는 노드가 파싱 트리에 추가되고 파서는 또 다른 토큰을 요청한다.</li>
<li>규칙에 맞지 않으면 파서는 토큰을 내부적으로 저장하고 토큰과 일치하는 규칙이 발견될 때까지 요청한다.</li>
<li>맞는 규칙이 없는 경우 예외로 처리하는데 이것은 문서가 유효하지 않고 구문 오류를 포함하고 있다는 의미다.</li>
</ol>
<h3 id="변환">변환</h3>
<p>파싱 결과는 파스 트리라고 설명했습니다. 그러나 이 파스 트리는 최종 결과물이 아닙니다. 파싱은 보통 문서를 다른 문서로 변환합니다. 컴파일로 예를 들면 소스 코드를 기계 코드로 변환시키는 컴파일러는 파스 트리 생성 후 이를 기계 코드 문서로 변환합니다.</p>
<h3 id="파서의-종류">파서의 종류</h3>
<p>파서는 기본적으로 하향식 파서와 상향식 파서가 있습니다.</p>
<ul>
<li>하향식 파서<ul>
<li>구문의 상위 구조(가장 높은 수준의 규칙)로부터 일치하는 부분을 찾기 시작</li>
</ul>
</li>
<li>상향식 파서<ul>
<li>낮은 수준에서 높은 수준으로 규칙을 찾는다.</li>
<li>부분적으로 일치하는 표현식을 파서 스택(parser stack)에 쌓는다.</li>
<li>입력 값의 오른쪽으로 이동하기 때문에 이동-감소 파서라고 부른다.</li>
</ul>
</li>
</ul>
<h3 id="파서-생성기">파서 생성기</h3>
<p>파서는 파서 생성기라는 도구를 통해 만들어집니다. 이 파서 생성기에 언어의 어휘나 구문 규칙 같은 문법을 부여하면, 파서 생성기에 해당 언어에 맞는 파서를 생성해줍니다. 파서를 생성하는 것은 파싱에 대한 깊은 이해를 필요로하고, 개발자가 수동으로 파서를 최적화하여 생성하는 것은 쉬운일이 아니기 때문에 파서 생성기는 매우 유용합니다.</p>
<p>웹킷은 어휘 생성을 위한 플렉스(Flex)와 파서 생성을 위한 바이슨(Bison), 이 두 가지를 파서 생성기로 사용합니다.</p>
<h2 id="html-파싱">HTML 파싱</h2>
<p>드디어 파싱의 기본에 대한 설명이 끝났습니다. 그렇다면 이제 브라우저의 렌더링 엔진이 어떻게 HTML을 파싱하는지 알아보겠습니다.</p>
<blockquote>
<p>브라우저는 HTML, CSS, Javascript 이 세 엔어를 해석할 수 있습니다. 그 중 Javascript는 렌더링 엔진이 아니라 Javascript 해석기라는 별도의 레이어에서 해석됩니다.(브라우저 기본구조 참고) 렌더링 엔진에서는 HTML과 CSS만 파싱 되므로, 이 두 부분에 대해서만 살펴보겠습니다.</p>
</blockquote>
<h3 id="html-파서">HTML 파서</h3>
<p>HTML 파서는 HTML 마크업을 파싱 트리로 변환합니다. 그런데 이 HTML 파서는 우리가 앞서 살펴본 전통적인 파서와 다릅니다. 모든 전통적인 파서는 HTML에 적용 할 수 없습니다. 엥? 그러면 우리는 파서에 대해 괜히 살펴 본걸까요? 아니요 그렇지 않습니다. 우리가 전통적인 파서는 HTML에는 적용할 수 없지만 CSS와 자바스크립트에는 적용할 수 있습니다. 또한 전통적인 파서는 같은 마크업 언어인 XML과 HTML을 XML형태로 재구성한 XHTML에도 적용할 수 있습니다. 그렇다면 대체 왜 HTML에만 적용할 수 없는 걸까요? 이걸 이해하기 위해서는 먼저 DOM에 대해서 알아야 합니다.</p>
<h3 id="dom">DOM</h3>
<p>앞에서 브라우저는 HTML 문서를 토큰화해 파스 트리를 생성한다고 했습니다. 파스트리는 브라우저가 읽어야할 HTML 코드를 트리모양으로 구조화해 나타낸 것입니다. 그러나 이 파스 트리로는 화면을 그릴 수 없습니다. 왜냐하면 브라우저는 파스 트리를 이용해 DOM(Document Object Model) 트리를 새로 만들기 때문입니다. 그렇다면 파스 트리와 DOM 트리의 차이는 무엇일까요?</p>
<p>파스트리는 토큰화한 문자열을 단순하게 구조화한 트리입니다. 그러나 DOM은 HTML 문서의 객체 표현으로 마크업과 1:1의 관계를 갖고있습니다. DOM 트리는 우리가 실제로 상호작용할 수 있는 HTML 엘리먼트로 이루어진 트리이기 때문에 우리가 실제로 자바스크립트로 상호작용할 수 있는 부분입니다.</p>
<h2 id="왜-일반적인-파서를-html에-적용할-수-없을까">왜 일반적인 파서를 HTML에 적용할 수 없을까?</h2>
<h3 id="첫-번째-이유--html은-문맥-자유-문법이-아니다">첫 번째 이유 : HTML은 문맥 자유 문법이 아니다.</h3>
<p>앞에서 파서는 문맥 자유 문법에 따라 파싱을 한다고 말씀드렸습니다. 그런데 HTML이 문맥 자유 문법이 아닙니다. 이 말도 조금 어렵죠? 이것도 조금 더 쉽게 표현해 보겠습니다. HTML은 암묵적으로 태그에 대한 생략이 가능합니다. 개발자가 실수로 시작 혹은 종료 태그 등을 생략해도 정상 작동합니다. 전반적으로 엄격한 XML에 비해 HTML은 너그럽고 유연한 문법을 갖고 있습니다. 이게 바로 HTML이 문맥 자유 문맥이 아니라는 뜻입니다. HTML은 이러한 너그러운 문법때문에 개발하기 편하고 인기가 많지만, 공식적인 문법으로 HTML을 작성하기 어렵다라는 문제가 있습니다. 정리하자면 HTML은 파싱하기 어렵고 전통적인 구문 분석이 불가능하기 때문에 문맥 자유 문법이 아니라는 것이며 이 때문에 XML 파서로도 파싱하기 쉽지 않습니다.</p>
<p>HTML을 작성하면서 유효하지 않은 구문(invalid syntax) 관련 에러를 본적이 없을 겁니다. 이는 브라우저가 요류를 교정하기 때문입니다. 아래의 오류가 가득한 HTML이 있다고 가정해보겠습니다.</p>
<pre><code class="language-html">&lt;body&gt;
&lt;p class=highlight&gt;Hello
&lt;div&gt;&lt;span&gt;World</code></pre>
<p>하지만 이 코드를 실제로 브라우저에서 실행하면 아래와 같은 결과가 나옵니다.</p>
<pre><code class="language-html">&lt;body&gt;
  &lt;p class=&quot;highlight&quot;&gt;Hello&lt;/p&gt;
  &lt;div&gt;&lt;span&gt;World&lt;/span&gt;&lt;/div&gt;
&lt;/body&gt;</code></pre>
<p>이러한 규칙들은 HTML Document Type Definition (DTD)에 의해 정의어 있습니다. HTML 파서는 명세된 규칙들을 따르는 예외 처리를 따로 해주어야 하는데, 이는 일반적인 파서의 규칙만으로는 적용하기 어렵습니다.</p>
<h3 id="두-번째-이유--html-파싱은-중단-될-수-있다">두 번째 이유 : HTML 파싱은 중단 될 수 있다.</h3>
<p>브라우저는 HTML 파싱 도중 <code>&lt;script&gt;</code>, <code>&lt;link&gt;</code> 같은 외부 태그를 만나게 되면 HTML 파싱을 즉시 중단합니다. 그리고 해당 태그의 해석을 실행합니다. 만약 해당 태그가 외부 파일을 참조하고 있다면 다운로드를 한 후 해석을 시작합니다. 이는 네트워크를 통해 먼저 받아온 코드부터 해석을 실행할 수 있는 HTML과는 달리 외부 컨텐츠들은 증분적(Incrementally)으로 해석을 할 수 없기 때문입니다. 또 다른 이유는 <code>&lt;script&gt;</code>에 DOM을 직접 수정할 수 있는 내용이 있을 수도 있기 때문입니다. 가령 <code>document.write()</code> 같은 API를 사용하면 HTML을 파싱하고 있는 도중에도 DOM 엘리먼트를 동적으로 삽입할 수 있습니다. 이로 인해 외부 컨텐츠를 해석하고 실행하기까지 HTML의 파싱은 중단됩니다.</p>
<p>이 문제를 해결하기 위해 웹킷과 파이어폭스는 예측 파싱 기법을 이용해 별도의 쓰레드에서 외부 스크립트, 링크, 스타일 등을 불러오기도 합니다.</p>
<h3 id="세-번째-이유--html-파싱을-재시작-될-수-있다">세 번째 이유 : HTML 파싱을 재시작 될 수 있다.</h3>
<p>앞서 말씀드린 것처럼 HTML 파싱은 어떠한 외부 요인으로 인해 방해받을 수 있습니다. 파싱 중간에 외부 요인으로 인해 DOM이 추가, 변경, 삭제가 될 수 있습니다. 이 경우 HTML은 처음부터 다시 파싱을 거칩니다. 즉, 바이트를 문자로 변환하고, 토큰을 식별한 후 노드로 변환하고 DOM 트리를 빌드합니다. 이 때문에 처리해야 할 HTML이 많을 때에는 파싱 시간이 오래 걸릴 수 있습니다.</p>
<h2 id="cssom-트리-구축">CSSOM 트리 구축</h2>
<p>CSS는 HTML과 달리 문맥 자유 문법이고 공식적인 명세가 있기 때문에 일반적인 파서로 파싱이 가능합니다. 즉 HTML에 비해 파싱과정이 복잡하지 않습니다.</p>
<p>일반적으로 CSS을 링크하는 코드가 HTML 코드 내에 삽입되어 있기 때문에, HTML을 파싱하는 도중에 CSS 파싱이 시작됩니다. 네트워크를 통해 먼저 받아온 코드부터 해석을 실행할 수 있는 HTML 파서와는 달리, CSS 파서는 전체 파일을 모두 다운로드할 때까지 파싱을 시작할 수 없습니다. 전체 CSS 파일을 다운로드 한 후 CSS 파싱 과정이 끝나게 되면, 코드에서 명세한 내용과 순서를 바탕으로 DOM과 같은 트리를 구성하는데 이를 CSSOM(CSS Object Model) 트리라 부릅니다. 이 트리에는 스타일, 규칙, 선택자 등의 정보가 노드에 들어가게 됩니다.</p>
<h2 id="파싱-정리">파싱 정리</h2>
<p>앞서 한 설명을 토대로 브라우저 렌더링 엔진이 기본 동작 과정 중 파싱 단계 부분을 간단히 정리해보겠습니다.</p>
<ol>
<li>렌더링 엔진은 HTML을 토큰화하여 파스 트리를 생성합니다.</li>
<li>그리고 파스 트리를 이용해 DOM 트리를 생성합니다.</li>
<li>이때 스타일 태그를 만나면 HTML 파싱을 중단하고 CSS를 파싱하여 CSSOM 트리를 생성합니다.</li>
<li>CSS 파싱을 마치면 이전에 중지한 HTML 파싱이 중단된 지점부터 다시 파싱을 합니다.</li>
<li>스크립트 태그를 만나면 다시 해석을 중지하고 자바스크립트 엔진에게 제어 권한을 넘깁니다.</li>
<li>자바스크립트 파싱이 끝나면 중단되었던 HTML 파싱을 이어서 진행한 후 작업을 완료합니다.</li>
</ol>
<p>파싱 설명이 너무 길어서 프론트 편은 여러 편으로 나눠 설명하겠습니다.</p>
<blockquote>
<p>출처
<a href="https://d2.naver.com/helloworld/59361">https://d2.naver.com/helloworld/59361</a>
<a href="https://bythem.net/2021/04/23/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%9D%BC%EB%A9%B4-%EC%95%8C%EA%B3%A0-%EC%9E%88%EC%96%B4%EC%95%BC-%ED%95%A0-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%9D%98-2">https://bythem.net/2021/04/23/프론트엔드-개발자라면-알고-있어야-할-브라우저의-2</a>
<a href="https://na27.tistory.com/230">https://na27.tistory.com/230</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[React.js - redux1(리덕스 기본 개념)]]></title>
            <link>https://velog.io/@gyumin_2/React.js-redux1%EB%A6%AC%EB%8D%95%EC%8A%A4-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@gyumin_2/React.js-redux1%EB%A6%AC%EB%8D%95%EC%8A%A4-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Mon, 04 Jul 2022 09:25:58 GMT</pubDate>
            <description><![CDATA[<h1 id="리덕스란">리덕스란?</h1>
<ul>
<li>가장 많이 사용하는 리액트 상태관리 라이브러리</li>
<li>리덕스를 사용하면 컴포넌트의 상태 업데이트 로직을 다른 파일로 분리시켜 더욱 효율적으로 관리할 수 있다.</li>
<li>전역 상태를 관리할 때 굉장히 효과적. 단순히 전역상태 관리만 한다면 Context API를 사용하는 것으로도 충분. 하지만 프로젝트 규모가 클 경우 리덕스를 사용하는 것이 좋다. 왜냐하면 리덕스를 사용하면 상태를 더욱 체계적으로 관리할 수 있고 유지보수성을 높여주고 작업 효울도 극대화 시켜주기 때문이다.</li>
<li>추가로 편리한 개발자 도구를 지원하며 미들웨어라는 기능을 제공하여 비동이 작업을 효율적으로 관리할 수 있게 해준다.</li>
</ul>
<h1 id="리덕스-개념-미리-정리하기">리덕스 개념 미리 정리하기</h1>
<h2 id="액션action">액션(action)</h2>
<ul>
<li><p>상태에 어떤 변화가 필요하면 액션이 발생한다. 액션은 하나의 객체로 표현된다.</p>
</li>
<li><p>액션은 type 필드를 갖고 있는 자바스크립트 객체로, 어플리케이션에서 일어난 이벤트에 대한 설명이라고 생각할 수 있다. type 필드는 액션을 설명하는 이름을 지정하는 문자열이어야 한다.</p>
</li>
<li><p>type 값은 주로 domain/eventName 형식으로 작성한다. 첫 번째 부분에는 특징이나 액션이 속한 카테고리를, 두 번쨰 부분에는 상세하게 일어난 일을 적는다.</p>
</li>
<li><p>액션 객체는 다른 필드를 가질 수 있는데, 이는 상태를 업데이트 할 때 참고할 값으로 사용한다. 관례적으로 이를 payload라고 부른다.</p>
</li>
<li><p>액션 예시</p>
<pre><code class="language-jsx">  const addTodoAction = {
    type: &#39;todos/todoAdded&#39;,
    payload: &#39;Buy milk&#39; // payload
  }</code></pre>
</li>
</ul>
<h2 id="액션-생성-함수action-creator">액션 생성 함수(action creator)</h2>
<ul>
<li><p>액션 생성 함수는 액션 객체를 만들어 주는 함수다.</p>
</li>
<li><p>어떤 변화를 일으킬 때마다 액션 객체를 만들어야 하는데, 매번 액션 객체를 직접 작성하기 번거롭고 만드는 과정에서 실수로 정보를 놓칠 수 있기 때문에 이를 방지하기 위해 함수로 만들어 관리한다.</p>
</li>
<li><p>에시</p>
<pre><code class="language-jsx">  function addTodo(data){
      return {
          type: &#39;ADD_TODO&#39;,
          data
      }
  }

  // Arrow function
  const addTodo = data =&gt; {
      type: &#39;ADD_TODO&#39;,
      data
  }</code></pre>
</li>
</ul>
<h2 id="리듀서reducer">리듀서(reducer)</h2>
<ul>
<li><p>리듀서는 변화를 일으키는 함수다. 액션을 만들어 발생시키면 리듀서가 현재 상태와 전달받은 액션 객체를 파라미터로 받아온다. 그리고 두 값을 참고하여 새로운 상태를 만들어 반환해준다.</p>
</li>
<li><p>리듀서는 현재 상태와 액션을 매개변수로 전달받아 어떻게 상태를 변경할지 결정하고, 새로운 상태를 반환하는 함수다. 매개변수로 전달받은 액션의 type을 기준으로 이벤트를 처리하는 이벤트 리스너라고 생각할 수 있다.</p>
</li>
<li><p>리듀서는 전형적으로 아래의 일련 과정을 따른다.</p>
<ul>
<li>해당 리듀서가 해당 액션을 처리하는지 확인<ul>
<li>만약 그렇다면 매개변수로 전달받은 상태를 복사 → 복사한 상태 업데이트 → 반환</li>
</ul>
</li>
<li>만약 아니라면 매개변수로 전달받은 상태를 그대로 반환</li>
</ul>
</li>
<li><p>예시</p>
<pre><code class="language-jsx">  const initialState = { counter: 1 };

  function reducer(state = initialState, action){
      // 리듀서가 해당 액션을 처리하는지 확인
      switch (action.type) {
          case INCREMENT: 
              return { // 상태 복사 및 업데이트 -&gt; 반환
                  ...state, 
                  counter: state.counter + 1 
              };
          default: // 리듀서가 해당 액션 처리 담당이 아니면 전달받은 상태 반환
              return state;
      }
  }</code></pre>
</li>
</ul>
<h2 id="스토어store">스토어(store)</h2>
<ul>
<li>프로젝트에 리덕스를 적용하기 위한 스토어를 만든다. 하나의 프로젝트는 단 하나의 스토어만 가질 수 있다. 스토어 안에는 현재 애플리케이션 상태와 리듀서가 들어가 있으며, 그 외에도 몇 가지 중요한 내장 함수를 지닌다.</li>
<li>스토어는 리듀서 함수로 생성된다. 리덕스 스토어는 액션이 디스패치 될 때마다 루트 리듀서를 실행시킨다.</li>
</ul>
<h2 id="디스패치dispatch">디스패치(dispatch)</h2>
<ul>
<li>디스패치는 스토어 내장 함수 중 하나로 액션을 발생 시키는 역할을 한다. 이 함수는 <code>dispatch(action)</code> 형태로 액션 객체를 파라미터로 넣어서 호출한다.</li>
<li>이 함수가 호출되면 리듀서 함수를 실행시켜 새로운 상태를 만들어준다. 상태를 변경하는 유일한 방법은 디스패치 함수를 사용하는 것이다.</li>
</ul>
<h2 id="구독subscribe">구독(subscribe)</h2>
<ul>
<li><p>구독도 스토어 내장 함수 중 하나다. subscribe 함수 안에 리스너 함수를 파라미터로 넣어서 호출해 주면, 이 리스터 함수가 액션이 디스패치되어 상태가 업데이트될 때마다 호출된다.</p>
</li>
<li><p>예시</p>
<pre><code class="language-jsx">  const listener = () =&gt; {
      console.log(&#39;상태가 업데이트 됨&#39;);
  }

  const unsubscribe = store.subscribe(listener);

  // 추후 구독을 비활성할 때 함수를 호출
  unsubscribe();</code></pre>
</li>
</ul>
<h1 id="바닐라-자바스크립트로-리덕스-사용하기">바닐라 자바스크립트로 리덕스 사용하기</h1>
<ul>
<li>리덕스는 리액트에 종속되는 라이브러리가 아니다. 리액트에서 사용하려고 만들어졌지만 다른 UI 라이브러리/프레임워크와 함께 사용할 수 있다.</li>
<li>예제를 통해 바닐라 자바스크립트 환경에서 리덕스를 사용하여 리덕스의 핵심 기능화 작동원리를 알아보자.</li>
</ul>
<h2 id="개발환경-세팅">개발환경 세팅</h2>
<ul>
<li>리액트 없이 번들링하기 위해 Parcel를 번들링 툴로 사용</li>
</ul>
<pre><code class="language-jsx">mkdir vanilla-redux
cd vanilla-redux
npm init -y
npm i -g parcel-bundler
npm i redux</code></pre>
<h2 id="코드">코드</h2>
<pre><code class="language-jsx">import { createStore } from &quot;redux&quot;;

// 1. DOM node 선택
const divToggle = document.querySelector(&#39;.toggle&#39;);
const counter = document.querySelector(&#39;h1&#39;);
const btnIncrease = document.querySelector(&#39;#increase&#39;);
const btnDecrease = document.querySelector(&#39;#decrease&#39;);

// 2. 액션 타입(이름) 정의
// 액션 이름은 문자열 형태로, 주로 대문자로 작성하며
// 액션 이름은 고유해야한다.
const TOGGLE_SWITCH = &#39;TOGGLE_SWITCH&#39;,
    INCREASE = &#39;INCREASE&#39;,
    DECREASE = &#39;DECREASE&#39;;

// 3. 액션 이름을 사용하여 액션 객체를 만드는 액션 생성 함수 작성
// 액션 객체는 반드시 type 값을 갖고 잊어야 하며
// 참고할 값은 개발자가 원하는데로 작성 가능
const toggleSwitch = () =&gt; ({type: TOGGLE_SWITCH});
const increase = difference =&gt; ({type: INCREASE, difference});
const decrease = () =&gt; ({type: DECREASE});

// 4. 초깃값 설정
const initialState = {
    toggle: false,
    counter: 0
};

// 5. 리듀서 함수 정의
// state가 undefined일 때는 initialState를 기본값으로 사용
// 리듀서에서는 상태 불변성을 유지하면서 데이터에 변화를 일으켜야 한다.
// 때문에 스프레트 연산자를 사용하여 상태를 변화시킨다.
// 객체 구조가 복잡할 경우 스프레트 연산자로 불변성을 관리하기 힘들기 때문에
// 리덕스의 상태는 최대한 깊지 않은 구조로 진행하는 것이 좋다.
function reducer(state = initialState, action) { 
    // action.type에 따라 다른 작업 처리
    switch(action.type) {
        case TOGGLE_SWITCH :
            return {
                ...state,
                toggle: !state.toggle
            }
        case INCREASE :
            return {
                ...state,
                counter: state.counter + action.difference
            }
        case DECREASE : 
            return {
                ...state,
                counter: state.counter -1
            }
        default : 
            return state;
    }
}

// 6. 스토어 함수 만들기
// 스토어를 만들 때는 createStore 함수를 사용한다.
// 최상단에서 import 구문으로 createStore 함수를 불러와야한다.
// reateStore 함수의 파라미터에 리듀서 함수를 넣어줘야 한다.
const store = createStore(reducer);

// 7. render 함수 생성
// 상태가 업데이트 될 때마다 호출
// 리액트의 render함수와 다르게 직접 DOM 조작
const render = () =&gt; {
    const state = store.getState(); // 현재 상태를 불러온다.

    // 토글처리
    if (state.toggle) {
        divToggle.classList.add(&#39;active&#39;);
    } else {
        divToggle.classList.remove(&#39;active&#39;);
    }

    // 카운터 처리
    counter.innerText = state.counter;
}

render();

// 8. 구독하기
// 실제 리액트 프로젝트에서는 사용하지 않음
// 컴포넌트에서 리덕스 상태를 조회하는 과정에서 
// react-redux라는 라이브러리가 이 기능을 대신 하기 때문
store.subscribe(render); // 상태변경시 render 함수 호출

// 액션 발생 시키기
divToggle.onclick = () =&gt; {
    store.dispatch(toggleSwitch());
}
btnIncrease.onclick = () =&gt; {
    store.dispatch(increase(1));
}
btnDecrease.onclick = () =&gt; {
    store.dispatch(decrease());
}</code></pre>
<h1 id="리덕스-데이터-흐름">리덕스 데이터 흐름</h1>
<h2 id="초기-생성">초기 생성</h2>
<ul>
<li>루트 리듀서 함수를 사용하여 리덕스 저장소 생성</li>
<li>저장소가 루트 리듀서 함수를 한번 호출하고 반환값을 초기 상태값으로 저장한다.</li>
<li>UI가 처음 렌더링 될 때, UI 컴포넌트는 리덕스 저장소의 현재 상태에 접근한다. 그리고 그 데이터를 사용하여 화면을 그린다. 컴포넌트들은 또한 미래 스토어 변경을 구독하여 상태 변경을 알 수 있다.</li>
</ul>
<h2 id="업데이트">업데이트</h2>
<ul>
<li>클릭 같은 이벤트 발생</li>
<li>앱은 <code>dispatch({type: &#39;counter/increment&#39;})</code> 형식으로 Redux 스토어에게 액션을 보냄</li>
<li>스토어는 이전 상태와 현재 상태로 리듀서 함수를 작동 시킴. 그리고 반환 값을 새 상태로 저장한다.</li>
<li>스토어는 구독되고 있는 UI의 모든 부분에 스토어가 변경되었음을 알린다.</li>
<li>스토어의 데이터가 필요한 각 UI 컴포넌트는 필요는 상태의 일부가 변경되었는지 확인한다.</li>
<li>데이터 변경을 감지한 각 컴포넌트는 새로운 데이터로 리렌더링하여 사용자에게 새로운 화면을 보여준다.</li>
</ul>
<h1 id="리덕스-3가지-규칙">리덕스 3가지 규칙</h1>
<h2 id="1-단일-스토어">1. 단일 스토어</h2>
<ul>
<li>하나의 애플리케이션에 하나의 스토어를 사용하는 것이 좋다. 여러 개의 스토어를 만들 수 있지만 상태관리가 복잡해 질 수 있기 때문에 권장하지 않는다.</li>
</ul>
<h2 id="2-읽기-전용-상태">2. 읽기 전용 상태</h2>
<ul>
<li>리덕스틑 상태 읽기 전용이다. <code>setState</code>처럼 상태를 업데이트할 때 기존 객체를 건드리지 않고 새로운 객체를 생성해야한다.</li>
<li>리덕스에서 불변성을 유지해야하는 이유는 내부적으로 데이터가 변경되는 것을 감지하기 위해 얕은 비교 검사를 하기 때문이다.</li>
</ul>
<h2 id="3-리듀서는-순수함수">3. 리듀서는 순수함수</h2>
<ul>
<li>리듀서는 순수한 함수여야 하며, 순수함수는 다음 조건을 만족시킨다.<ul>
<li>리듀서 함수는 이전 상태와 액션 객체를 파라미터로 받는다.</li>
<li>파라미터 외에 값에는 의존하면 안된다.</li>
<li>이전 상태는 절대로 건드리지 않고, 변화를 준 새로운 상태 객체를 만들어 반환한다.</li>
<li>똑같은 파라미터로 호출된 리듀서 함수는 언제나 똑같은 결과값을 반환해야한다.</li>
</ul>
</li>
<li>순수 함수란 동일한 입력을 받았을 때 언제나 동일한 출력을 내는 함수를 말한다. 클릭 시 배경이 랜덤으로 바뀌는 로직을 작성한다면, 함수의 로직 내에서 랜덤값을 생성해야한다. 랜덤 값을 생성한다는 뜻은 결국 매번 출력 값이 바뀐다는 뜻이기 때문에 순수하지 못한 함수다. 네트워트 요청을 하는 작업도 순수하지 못한 로직 중 하나다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React.js - Context API]]></title>
            <link>https://velog.io/@gyumin_2/React.js-Context-API</link>
            <guid>https://velog.io/@gyumin_2/React.js-Context-API</guid>
            <pubDate>Tue, 28 Jun 2022 02:47:49 GMT</pubDate>
            <description><![CDATA[<h1 id="context-api란">Context API란?</h1>
<ul>
<li>Context API는 리액트 프로젝트에서 전역적으로 사용할 데이터가 있을 때 유용한 기능이다. 리덕스, 리액트 라우터, styled-components 등의 라이브러리는 Context API 기반으로 구현되어있다.</li>
<li>프로젝트 내에서 환경설정, 사용자 정보와 같은 전역적으로 필요한 상태를 관리해야할 때 유용하다.</li>
<li>Context는 React 컴포넌트 트리 안에서 전역적(global)이라고 볼 수 있는 데이터를 공유할 수 있도록 고안된 방법이다. Context는 모든 레벨의 컴포넌트 트리에 props를 통하지 않아도 데이터를 전달할 수 있는 방법을 제공해준다.</li>
</ul>
<h2 id="context-api를-사용하는-이유">Context API를 사용하는 이유</h2>
<ul>
<li>리액트 애플리케이션은 컴포넌트 간에 데이터를 props로 전달하기 때문에 컴포넌트 여기저기에서 필요한 데이터가 있을 경우 주로 최상위 컴포넌트인 App의 state에 넣어서 관리한다. 만약 뎁스가 깊은 프로젝트일 경우 루트에서 자식까지 데이터를 전달할 때 중복 코드가 발생하고, 그럴 경우 유지보수 효울성이 낮아진다.</li>
<li>하지만 context를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다.</li>
<li>때문에 리덕스나 MobX같은 상태관리 라이브러리를 사용하여 전역 상태관리 작업을 편하게 처리한다. 리액트 16.3v 업데이트 이후에는 Context API가 맣이 개선되었기 때문에 별도 라이브러이 없이 전역 상태를 손쉽게 관리할 수 있다.</li>
</ul>
<h1 id="context-api-사용하기">Context API 사용하기</h1>
<h2 id="context-객체-만들기">Context 객체 만들기</h2>
<ul>
<li><p><code>React.createContext(defaultValue)</code></p>
<ul>
<li>Context 객체를 만드는 함수. Context 객체를 구독하고 있는 컴포넌트를 렌더링할 때 React는 트리 상위에서 가장 가까이 있는 짝이 맞는 Provider로부터 현재값을 읽는다.</li>
<li>defaultValue 매개변수는 트리 안에서 적절한 Provider를 찾지 못했을 때만 쓰이는 값이다. 이 기본값은 컴포넌트를 독립적으로 테스트할 때 유용하다. Provider를 통해 undefined을 값으로 보낸다고 해도 구독 컴포넌트들이 defaultValue 를 읽지는 않는다.</li>
</ul>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  // context/color.js
  import { createContext } from &quot;react&quot;; // 1. import createContext

  // 2. create context object
  // setting defaultValue is optionable
  const ColorContext = createContext({color: &#39;black&#39;});

  // 3. export
  export default ColorContext;</code></pre>
</li>
<li><p>이렇게 만들어진 Context 객체는 Consumer 컴포넌트와 Provider 컴포넌트를 프로퍼티로 갖는다.</p>
<ul>
<li>Consumemr : Context에서 설정한 값을 읽을 때 사용</li>
<li>Provider : Context 에서 사용 할 값을 설정할 때 사용</li>
</ul>
</li>
</ul>
<h2 id="consumer-사용하기">Consumer 사용하기</h2>
<pre><code class="language-jsx">&lt;MyContext.Consumer&gt;
  {value =&gt; /* context 값을 이용한 렌더링 */}
&lt;/MyContext.Consumer&gt;</code></pre>
<ul>
<li><p>Consumer는 Context 변화를 구독하는 역할을 한다. 즉 Context에서 설정한 값을 컴포넌트 내에서 조회 때 사용하는 컴포넌트다. 이 컴포넌트를 사용하면 함수 컴포넌트안에서 context를 구독할 수 있다.</p>
</li>
<li><p>Context.Consumer의 자식은 함수여야한다. 이 함수는 context의 현재값을 매개변수로 받고 React 노드를 반환한다. 이 함수가 받는 value 매개변수 값은 해당 context의 Provider 중 상위 트리에서 가장 가까운 Provider의 value prop과 동일하다. 상위에 Provider가 없다면 value 매개변수 값은 createContext()에 보냈던 defaultValue와 동일하다.</p>
</li>
<li><p>Consumer 사용시 중요한 것은 Consumer 사이에 중괄호를 열어 그 안에 함수를 넣는 것이다. 이렇게 컴포넌트의 child가 있어야할 자리에 일반 JSX 혹은 문자열이 아닌 함수를 넣는 패턴을 Function as a child 혹은 Render Props라고 한다.</p>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  import ColorContext from &#39;../context/color&#39;;

  const ColorBox = () =&gt; {
    return (
      &lt;ColorContext.Consumer&gt;
        {
          value =&gt; (
            &lt;div style={{
                width: &#39;64px&#39;,
                height: &#39;64px&#39;,
                background: value.color
            }} /&gt;
          )
        }
      &lt;/ColorContext.Consumer&gt;
    );
  };

  export default ColorBox;</code></pre>
</li>
</ul>
<h2 id="provide-사용하기">Provide 사용하기</h2>
<pre><code class="language-jsx">&lt;MyContext.Provider value={/* 어떤 값 */}&gt;</code></pre>
<ul>
<li><p>Context를 구독하는 컴포넌트들에게 Context의 변화를 알리는 역할을 한다. Provider 컴포넌트는 <code>value</code> prop을 받아서 이 값을 하위에 있는 컴포넌트에게 전달한다. 값을 전달받을 수 있는 컴포넌트의 수에 제한은 없다. Provider 하위에 또 다른 Provider를 배치하는 것도 가능하며, 이 경우 하위 Provider의 값이 우선시된다.</p>
</li>
<li><p>Provider 하위에서 Context를 구독하는 모든 컴포넌트는 Provider의 value prop가 바뀔 때마다 다시 렌더링 된다. Provider로부터 하위 consumer(.contextType와 useContext을 포함한)로의 전파는 shouldComponentUpdate 메서드가 적용되지 않으므로, 상위 컴포넌트가 업데이트를 건너 뛰더라도 consumer가 업데이트된다.</p>
</li>
<li><p>createContext 함수를 사용할 때 기본값으로 넣어준 defaultValue 매개변수는 Provider를 사용하지 않을 때만 유효하다. Provicer를 사용했는데 value를 명시하지 않으면, 기본값을 사용하지 않기 때문에 오류가 발생한다.</p>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  import &#39;./App.css&#39;;
  import ColorBox from &#39;./components/ColorBox&#39;;
  import ColorContext from &#39;./context/color&#39;;

  function App() {
    return (
      &lt;ColorContext.Provider value={{color: &#39;red&#39;}}&gt; // value props 명시 필수!
        &lt;div className=&quot;App&quot;&gt;
          &lt;ColorBox /&gt;
        &lt;/div&gt;
      &lt;/ColorContext.Provider&gt;
    );
  }

  export default App;</code></pre>
</li>
</ul>
<h1 id="동적-context-사용하기">동적 Context 사용하기</h1>
<ul>
<li>색상선택 예제로 동적 context 사용하는 법 배우기</li>
</ul>
<h2 id="1-context-만들기">1. context 만들기</h2>
<pre><code class="language-jsx">// context.color.js : 색상관련 context 파일
import React, { createContext, useState } from &quot;react&quot;;

// 1. createContext함수로 context 생성
// 기본값은 Provider의 value에 넣을 객체의 형태와 일키시켜 주는 것이 좋다.
const ColorContext = createContext({ 
  state: {
    color: &#39;black&#39;,
    subcolor: &#39;red&#39;
  },
  actions: {
    setColor: () =&gt; {},
    setSubcolor: () =&gt; {}
  }
});

// 2. ColorContext.Provider를 렌더링하는 컴포넌트 생성
// Provider의 value에는 상태로 state를, 업데이트 함수는 actions로 묶어서 전달
// Context에서 값을 동적으로 사용할 때 반드시 묶어줄 필요는 없지만
// 이렇게 state와 actions 객체를 따로 분리해주면 나중에 다른 컴포넌트에서
// Context의 값을 사용할 때 편하다.
const ColorProvider = ({children}) =&gt; {
  const [color, setColor] = useState(&#39;black&#39;);
  const [subcolor, setSubcolor] = useState(&#39;red&#39;);

  const value = {
    state: {color, subcolor},
    actions: {setColor, setSubcolor}
  }

  return (
    &lt;ColorContext.Provider value={value}&gt;{children}&lt;/ColorContext.Provider&gt;
  )
}

// const ColorConsumer = ColorContext.Consumer 와 같은 의미
const { Consumer: ColorConsumer } = ColorContext

// 2. Provider와 Consumer export
export { ColorProvider, ColorConsumer }

export default ColorContext;</code></pre>
<h2 id="2-consumer-사용">2. Consumer 사용</h2>
<pre><code class="language-jsx">// components/ColorBox.js : color context의 state를 전달받아 보여주는 컴포넌트
import React from &#39;react&#39;;
// 1. import ColorConsumer component
import { ColorConsumer } from &#39;../context/color&#39;;

const ColorBox = () =&gt; {
    return (
      // 2. ColorConsumer 컴포넌트 사용
      // Consumer 컴포넌트의 child는 함수여야 한다.
      // child 함수의 매개변수는 비구조화할당을 이용해 간단히 value의 state만 조회한다.
      &lt;ColorConsumer&gt;
        {
          ({state}) =&gt; (
            &lt;&gt;
              &lt;div style={{
                  width: &#39;64px&#39;,
                  height: &#39;64px&#39;,
                  background: state.color
              }} /&gt;

              &lt;div style={{
                  width: &#39;32px&#39;,
                  height: &#39;32px&#39;,
                  background: state.subcolor
              }} /&gt;
            &lt;/&gt;
          )
        }
      &lt;/ColorConsumer&gt;
    );
};

export default ColorBox;</code></pre>
<h2 id="3-provider-사용">3. Provider 사용</h2>
<pre><code class="language-jsx">// components/SelectColor.js : 색상 선택 컴포넌트
// color context의 actions를 전달 받음
import React from &#39;react&#39;;
// 1. import consumer component
import { ColorConsumer } from &#39;../context/color&#39;;

// 2. 색상 배열 생성
const colors = [&#39;red&#39;, &#39;orange&#39;, &#39;yellow&#39;, &#39;green&#39;, &#39;blue&#39;, &#39;indigo&#39;, &#39;violet&#39;];

const SelectColor = () =&gt; {
  return (
    &lt;div&gt;
      &lt;h2&gt;Select color&lt;/h2&gt;
      // 3. consumer 컴포넌트 렌더링
      // child 함수의 매개변수에는 value의 actions 할당
      &lt;ColorConsumer&gt;
        {
          ({actions}) =&gt; (
            &lt;div style={{display: &#39;flex&#39;}}&gt;
              {
                colors.map(color =&gt; (
                  &lt;div
                    key={color}
                    style= {{
                      background: color,
                      width: &#39;24px&#39;,
                      height: &#39;24px&#39;,
                      cursor: &#39;pointer&#39;
                    }}
                    onClick={() =&gt; actions.setColor(color)}
                    // 우클릭 이벤트 : onContextMenu
                    onContextMenu={e =&gt; {
                      e.preventDefault();
                      actions.setSubcolor(color);
                    }}
                  /&gt;
                ))
              }
            &lt;/div&gt; 
          )
        }
      &lt;/ColorConsumer&gt;
      &lt;hr /&gt;
    &lt;/div&gt;
  );
};

export default SelectColor;</code></pre>
<pre><code class="language-jsx">// 4. App.js에서 Provider 컴포넌트를 import해 렌더링
import &#39;./App.css&#39;;
import ColorBox from &#39;./components/ColorBox&#39;;
import { ColorProvider } from &#39;./context/color&#39;;
import SelectColor from &#39;./components/SelectColor&#39;;

function App() {
  return (
    &lt;ColorProvider&gt;
      &lt;div className=&quot;App&quot;&gt;
        &lt;SelectColor /&gt;
        &lt;ColorBox /&gt;
      &lt;/div&gt;
    &lt;/ColorProvider&gt;
  );
}

export default App;</code></pre>
<h1 id="consumer-대체-문법">Consumer 대체 문법</h1>
<h2 id="함수형-컴포넌트">함수형 컴포넌트</h2>
<pre><code class="language-jsx">const value = useContext(Context);</code></pre>
<ul>
<li>함수형 컴포넌트에서 useContext Hook을 사용하면 Context를 편하게 사용할 수 있다.</li>
</ul>
<pre><code class="language-jsx">// components/ColorBox.js
// 1. import useContext
import React, {useContext} from &#39;react&#39;;
import ColorContext from &#39;../context/color&#39;;

const ColorBox = () =&gt; {
  // 2. useContext 매개변수로 context 할당
  // useContext를 사용하면 children에 함수를 전달하는 Render Props 패턴 없이
  // Context 값을 조회할 수 있다.
  const { state } = useContext(ColorContext);
  return (
    &lt;&gt;
      &lt;div style={{
          width: &#39;64px&#39;,
          height: &#39;64px&#39;,
          background: state.color
      }} /&gt;

      &lt;div style={{
          width: &#39;32px&#39;,
          height: &#39;32px&#39;,
          background: state.subcolor
      }} /&gt;
       &lt;/&gt;
  );
};

export default ColorBox;</code></pre>
<h2 id="클래스형-컴포넌트">클래스형 컴포넌트</h2>
<ul>
<li><p>클래스형 컴포넌트에서는 <code>static contextType</code>을 정의하면 Context를 쉽게 사용할 수 있다. 클래스 상단에 <code>static contextType</code>를 정의하면 <code>this.context</code>로 Context에 접근할 수 있다.</p>
</li>
<li><p>단 클래스형 컴포넌트에서는 한 클래스에서 하나의 Context 밖에 사용하지 못한다.</p>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  import React, { Component } from &#39;react&#39;;
  import ColorContext from &#39;../contexts/color&#39;;

  const colors = [&#39;red&#39;, &#39;orange&#39;, &#39;yellow&#39;, &#39;green&#39;, &#39;blue&#39;, &#39;indigo&#39;, &#39;violet&#39;];

  class SelectColors extends Component {
    // 1. static contextType 정의
    static contextType = ColorContext;

    // 2. this.context로 Context의 value에 접근
    handleSetColor = color =&gt; {
      this.context.actions.setColor(color);
    };

    handleSetSubcolor = subcolor =&gt; {
      this.context.actions.setSubcolor(subcolor);
    };

    render() {
      return (
        &lt;div&gt;
          &lt;h2&gt;색상을 선택하세요.&lt;/h2&gt;
          &lt;div style={{ display: &#39;flex&#39; }}&gt;
            {colors.map(color =&gt; (
              &lt;div
                key={color}
                style={{
                  background: color,
                  width: &#39;24px&#39;,
                  height: &#39;24px&#39;,
                  cursor: &#39;pointer&#39;
                }}
                onClick={() =&gt; this.handleSetColor(color)}
                onContextMenu={e =&gt; {
                  e.preventDefault();
                  this.handleSetSubcolor(color);
                }}
              /&gt;
            ))}
          &lt;/div&gt;
          &lt;hr /&gt;
        &lt;/div&gt;
      );
    }
  }

  export default SelectColors;</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[브라우저 동작 원리 - 주소창에 URL 입력 시 일어나는 과정 (백엔드 편)]]></title>
            <link>https://velog.io/@gyumin_2/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%A3%BC%EC%86%8C%EC%B0%BD%EC%97%90-URL-%EC%9E%85%EB%A0%A5-%EC%8B%9C-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EA%B3%BC%EC%A0%95-%EB%B0%B1%EC%97%94%EB%93%9C-%ED%8E%B8</link>
            <guid>https://velog.io/@gyumin_2/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%A3%BC%EC%86%8C%EC%B0%BD%EC%97%90-URL-%EC%9E%85%EB%A0%A5-%EC%8B%9C-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EA%B3%BC%EC%A0%95-%EB%B0%B1%EC%97%94%EB%93%9C-%ED%8E%B8</guid>
            <pubDate>Tue, 21 Jun 2022 02:13:49 GMT</pubDate>
            <description><![CDATA[<h1 id="주소창에-url-입력-시-백엔드에서는-무슨-일이-일어날까">주소창에 URL 입력 시 백엔드에서는 무슨 일이 일어날까?</h1>
<p>앞에 설명은 모두 이 부분을 이해하기 위한 배경지식 설명이었습니다. 그럼 이제 본격적으로 주소창에 url 입력 시 브라우저에서 어던 일이 벌어지는지 백엔드 관점에서 살펴보겠습니다.</p>
<h1 id="url-파싱">URL 파싱</h1>
<p>주소창에 URL 입력 시 브라우저는 먼저 입력받은 URL의 구조를 해석(Parsing)합니다. 브라우저는 어떤 프로토콜을 통해 해당 URL을 요청할 것인지, 어떤 도메인을 요청할 것인지, 어떤 포트로 요청할 것 인지 해석합니다.</p>
<h1 id="dns-조회">DNS 조회</h1>
<p>배경지식 편에서 말씀드린 것 처럼 웹 사이트에 접근하기 위해서는 웹 서버의 IP 주소가 필요합니다. 즉 원래는 주소창에 IP 주소를 입력해야 하지만 Domain Name System에 의해 IP 주소가 아닌 문자로 된 URL을 입력해도 우리는 웹 사이트에 접근 할 수 있습니다. 우리가 URL을 입력하면 웹 브라우저는 Domain Name Server에 접속해서 해당 도메인에 해당하는 IP주소를 응답받아 서버에 접속합니다.</p>
<p>우리가 중국집 사장이라고 가정해보겠습니다. 손님이 전화로 ‘여기 OO빌딩인데 짜장면 2개 배달해주세요.’ 라고 했을 때, 우리가 만약 처음 배달 가는 곳이라면 네이게이션으로 위치를 검색 후 찾아갈 것입니다. 그리고 그 위치를 기억해 놨다가 나중에 동일한 장소에서 배달 주문이 오면 네비게이션을 검색하지 않고 바로 찾아가겠죠.</p>
<p>브라우저도 이와 같은 작업을 합니다. 중국집 배달원이 이미 배달 가본 적 있어서 가는 길을 알고 있는데도 네비게이션으로 위치를 검색하고 찾아간다면 배달 시간도 길어지고 비효율적일 것입니다. 이 처럼 브라우저가 이미 방문한 적 있는 웹 사이트의 URL을 DNS에 IP 요청하게 된다면 웹 사이트 응답 시간이 길어지고 비효율적이겠죠. 때문에 브라우저는 DNS에게 응답받은 IP 주소를 캐싱(저장)합니다. 그리고 사용자가 주소창에 URL 입력 시, 브라우저는 먼저 캐싱 된 데이터 중에서 해당 도메인에 해당하는 IP 주소가 있는지를 먼저 확인합니다. 캐싱된 데이터에서 해당 IP주소를 찾으면 그 IP 주소로 바로 이동하고, 해당 IP 주소가 없으면 DNS에 IP 주소를 요청합니다. 이러한 방식은 브라우저가 좀 더 빠르게 서버에게 요청 할 수 있도록 합니다.</p>
<h2 id="ip-주소-탐색-과정">IP 주소 탐색 과정</h2>
<p>DNS 상에서 IP가 어떻게 찾아지는지 조금 더 상세하게 알아보겠습니다. 그 전에 몇 가지 알아야할 것들이 있습니다. 기본적으로 컴퓨터의 LAN선을 통해 인터넷이 연결되면, 인터넷을 사용할 수 있게 IP를 할당해주는 통신사(KT, SK, LG 등) 에 해당되는 각 통신사의 DNS서버가 등록되는데, 이것을 Local DNS라고 합니다. 그리고 배경지식 편에서 말했던 것처럼 루트 도메인이 있듯이 DNS도 루트 DNS가 있습니다. 루트 DNS는 전 세계에 13대만 존재하며, Local DNS가 IP를 조회할 때 가장 먼저 요청하는 DNS입니다.</p>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/0054f96b-b1e0-4a44-9154-5e0b1c9c8e88/image.png" alt=""></p>
<p>우리가 주소창에 <code>domain.co.kr</code> 을 입력했다고 가정할 때 아래의 과정을 통해 IP 주소가 조회됩니다.</p>
<ol>
<li>브라우저는 먼저 Local DNS에 <code>domain.co.kr</code> 에 해당하는 IP 주소를 요청합니다.</li>
<li>Local DNS에는 해당 도메인에 대한 정보가 있을 수도 있고 없을 수도 있습니다. 만약 Local DNS에 해당 도메인에 대한 IP 주소가 있다면 브라우저에게 응답하고, 없으면 Local DN는 루트 DNS에게 해당 도메인의 IP를 요청합니다.</li>
<li>루트 DNS는 해당 도메인 IP 정보를 갖고 있다면 Local DNS에 응답하고, 없다면 어떤 DNS에 요청해야 하는지를 알려줍니다. <code>domain.co.kr</code> 를 예로들면 루트 DNS는 <code>.kr</code> 도메인을 관리하는 DNS에 물어보라고 Local DNS에 응답합니다.</li>
<li>Local DNS는 <code>.kr</code> 도메인을 관리하는 DNS에 IP 주소 요청을 하고, <code>.kr</code> 도메인을 관리하는 DNS는 해당 IP 정보를 갖고 있다면 해당 IP 주소를 Local DNS에 응답하고, 없다면 <code>.co.kr</code> 도메인을 관리하는 DNS에 물어보라고 응답합니다.</li>
<li>Local DNS는 <code>.co.kr</code> 도메인을 관리하는 DNS에 IP 주소 요청을 하고, <code>.co.kr</code> 도메인을 관리하는 DNS는 해당 IP 정보를 갖고 있다면 해당 IP 주소를 Local DNS에 응답하고, 없다면 <code>domain.co.kr</code> 도메인을 관리하는 DNS에 물어보라고 응답합니다.</li>
<li>Local DNS는 <code>domain.co.kr</code> 도메인을 관리하는 DNS에 IP 주소 요청을 하고, <code>domain.co.kr</code> 도메인을 관리하는 DNS는 Local DNS에 해당 도메인의 IP 주소를 응답합니다.</li>
<li>이를 수신한 Local DNS는 <code>domain.co.kr</code> 도메인에 대한 IP 주소를 캐싱하고, IP 주소를 브라우저에게 응답합니다.</li>
</ol>
<p>이와 같이 Local DNS 서버가 루트 DNS를 시작으로 하위 도메인을 관리하는 DNS에게 요청하여 IP주소를 찾는 과정을 Recursive Query라고 부릅니다. 그리고 IP 주소를 찾는 과정이 이렇게 길고 복잡하기 때문에 브라우저는 IP 주소를 캐싱합니다.</p>
<h1 id="서버와-연결">서버와 연결</h1>
<h2 id="패킷-통신">패킷 통신</h2>
<p>패킷(paket)은 pack과 bucket의 합성어로 컴퓨터 통신에서 데이터를 주고받을 때 네트워크를 통해 전송되는 데이터 조각을 뜻합니다. 패킷 통신은 보내고자 하는 데이터를 작은 조각으로 쪼개서 보내는 방식입니다. 인터넷 통신의 대부분은 패킷 통신을 기본으로 하고 있으며, 웹도 마찬가지입니다. </p>
<h2 id="tcp--ip">TCP / IP</h2>
<p>패킷 통신을 위한 TCP/IP 인터넷 프로토콜이 있습니다. TCP/IP 프로토콜은 인터넷 프로토콜인 IP(Internet Protocol)와 전송 제어 프로토콜인 TCP(Transmission Control Protocol)로 이루어져 있습니다.</p>
<p>IP는 배경지식 편에서 말한 것처럼 인터넷상의 주소 규칙입니다. 집의 주소를 부여하는 규칙이 존재하듯이, 인터넷상에 연결된 모든 컴퓨터의 위치에도 규칙이 필요합니다. 이전에는 IPv4를 사용하고 있었지만 지금은 IPv6를 사용하고 있습니다.</p>
<p>TCP는 IP 위에서 동작하는 프로토콜로 데이터 전달을 관리하는 규칙으로, 데이터를 작게 나누어서 한쪽에서 다른쪽으로 옮기고, 이를 다시 조립하여 원래의 데이터로 만드는 규칙입니다. 아까 패킷 통신은 데이터를 패킷이라는 조각으로 쪼개서 보낸다고 했습니다. 이렇게 데이터를 잘게 쪼개서 보낼 경우 데이터 순서가 뒤바뀌거나 중간에 데이터가 빠질 수 있습니다. 하지만 이 문제는 목적지에서 데이터를 점검하여 순서를 정렬하고, 빠지거나 잘못된 데이터가 있으면 다시 받아 합치는 방식으로 간단히 해결할 수 있습니다. 이러한 해결방식이 TCP입니다. </p>
<p>IP는 패킷을 최대한 빨리 목적지로 보내는 역할을 합니다. 때문에 패킷 순서가 바뀌거나 누락되더라도 상관하지 않고 일반 최대한 빨리 목적지로 보내는 데에만 집중합니다. 반면 TCP는 IP보다는 느리지만 꼼꼼한 방식을 취합니다. 도착한 패킷을 조립하고, 손실된 패킷을 확인하여 재전송하도록 요청하는 기능을 합니다. 이 두 개는 다른 프로토콜이지만 패킷 통신을 위한 프로토콜이기 때문에 보통 두 가지를 같이 묶어서 말합니다.</p>
<h2 id="서버와-tcp-소켓-연결">서버와 TCP 소켓 연결</h2>
<p>이제 다시 돌아와서 브라우저가 도메인의 IP 주소를 응답받은 후 어떤 일이 벌어지는지 알아보겠습니다. 브라우저는 DNS에게서 IP 주소를 응답받으면 해당 IP 주소와 일치하는 서버와 연결하는데, 인터넷 프로토콜(IP)을 사용해 연결을 구축합니다. 또한 브라우저는 클라이언트와 서버와의 데이터 통신을 위해 TCP 소켓 연결을 진행합니다. 소켓 연결은 3-way-handshake라는 과정을 통해 이뤄집니다. 3-way-handshake는 TCP/IP프로토콜을 이용해서 데이터를 전송하기 전에 먼저 정확한 전송을 보장하기 위해 서버와 사전에 세션을 수립하는 과정입니다. 쉽게 말해 클라이언트와 서버가 서로 데이터를 전송하고 받을 준비를 하는 과정이라고 생각하시면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/f3d11c12-dd03-46b1-92c2-468601b76e76/image.png" alt=""></p>
<blockquote>
<p>사진 출처 : <a href="https://mindnet.tistory.com/entry/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-22%ED%8E%B8-TCP-3-WayHandshake-4-WayHandshake">https://mindnet.tistory.com/entry/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-22%ED%8E%B8-TCP-3-WayHandshake-4-WayHandshake</a></p>
</blockquote>
<p>3-way-handshake는 아래의 순서로 진행됩니다.</p>
<ol>
<li>클라이언트가 서버에 접속을 요청하는 SYN(Synchronize Sequence Number) 패킷을 보냅니다.</li>
<li>서버는 SYN 요청을 받고, 클라이언트에세 요청을 수락한다는 ACK(Acknowledgment)와 SYN flag가 설정된 패킷을 발송합니다.</li>
<li>서버에게 패킷을 전달받은 클라이언트는 서버에게 응답확인했다는 뜻으로 ACK 패킷을 보냅니다. 이제 서버와 연결이 되어 데이터가 오고 갈 수 있습니다.</li>
</ol>
<p>배경지식 편에서 브라우저와 서버 간에 데이터를 주고받기 위한 방식으로 HTTP라는 프로토콜을 사용하지만 보안상의 이유로 HTTPS를 더 많이 사용한다고 말씀드렸습니다. 3-way-handshake는 HTTP 기준이며, HTTPS를 통해 보안 연결을 설정하려면 TLS(Transport Layer Security) 협상이라고 하는 또 다른 handshake 과정을 거쳐야합니다. TLS는 인터넷에서 정보를 암호화해서 송수신하는 프로토콜입니다. HTTPS로 통신할 경우 브라우저와 서버는 암호화된 데이터를 교환하기 위한 일련의 협상과정을 거치는데 그게 바로 TLS 협상이라고 불리는 TSL Handshake(또는 SSL Handshake)입니다.</p>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/0441f311-bdfd-4b6d-b27a-00780529bb19/image.png" alt=""></p>
<blockquote>
<p>사진 출처 : <a href="https://mindnet.tistory.com/entry/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-22%ED%8E%B8-TCP-3-WayHandshake-4-WayHandshake">https://mindnet.tistory.com/entry/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-22%ED%8E%B8-TCP-3-WayHandshake-4-WayHandshake</a></p>
</blockquote>
<p>브라우저는 TCP 3-way-handshake 과정을 서버와 연결하고 데이터를 수신받았다면,  4-way-handshake를 통해 연을 종료합니다. 4-way-handshake 과정은 아래와 같습니다.</p>
<ol>
<li>클라이언트가 연결을 종료하겠다는 FIN 플래그를 서버에게 전송합니다.</li>
<li>서버는 확인 메시지(ACK)를 보내고 자신의 통신이 끝날때까지 기다립니다.</li>
<li>서버가 통신이 끝났으면 연결이 종료되었다고 클라이언에게 FIN 플래그를 전송합니다.</li>
<li>클라이언트는 확인했다는 메시지(ACK)를 서버에게 보냅니다.</li>
</ol>
<p>만약 서버에서 브라우저에게 FIN을 전송하기 전에 전송한 패킷이 여러가지 이유로 FIN 패킷보다 늦게 도착하여 데이터가 유실되는 경우를 막기위해, 브라우저는 서버로부터 FIN 패킷을 수신하더라고 일정시간 동안 세션을 남겨놓고 잉여 패킷을 기다리는 TIME_WAIT 과정을 거칩니다.</p>
<h1 id="http-요청">HTTP 요청</h1>
<p>TCP 연결이 완료되면 이제 본격적으로 데이터 전송이 시작됩니다. 브라우저는 웹 서버에서 사용자가 입력한 URL 웹페이지를 GET 방식으로 HTTP 요청 메시지를 보냅니다.</p>
<h2 id="http">HTTP</h2>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/4e233d10-c3c5-4d3d-ac57-11f280b4a1e6/image.png" alt=""></p>
<blockquote>
<p>사진 출처 : 사진 출처 : <a href="https://velog.io/@rimu/%EC%9B%B9%EA%B3%BC-%EC%84%9C%EB%B2%84%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B8%B0%EC%B4%88%EC%A7%80%EC%8B%9D-HTTP-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C">https://velog.io/@rimu/%EC%9B%B9%EA%B3%BC-%EC%84%9C%EB%B2%84%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B8%B0%EC%B4%88%EC%A7%80%EC%8B%9D-HTTP-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C</a></p>
</blockquote>
<p>HTTP는 Hypertext Transfer Protocol의 약자로 HTML 문서와 같은 리소스들을 가져올 수 있도록 해주는 프로토콜입니다. 클라이언트와 서버는 개별적인 메시지 교환에 의해 통신합니다. 브라우저인 클라이언트에 의해 전송되는 메시지를 요청(requests)이라고 부르며, 그에 대해 서버에서 응답으로 전송되는 메시지를 응답(responses)이라고 부릅니다. HTTP 프로토콜로 클라이언트와 서버가 데이터를 주고 받기 위해서는 위 사진 같이 요청을 보내고 응답을 받아야 합니다.</p>
<p>클라이언트가 서버에게 요청을 보낼 때 요청에 대한 정보를 담아 서버로 보냅니다. 이는 식당에서 주문서를 작성하는 것과 같습니다. 서버가 주문서를 받아 클라이언트가 어떤 것을 원하는지 파악할 수 있게 하는 것이죠. 서버도 응답할 때 응답에 대한 정보를 담아 클러이언트로 보냅니다. 이런 정보가 담긴 메시지를 HTTP 메시지라고 합니다. HTTP 메시지는 시작줄, 헤더, 본문으로 구성됩니다. 요청 메시지와 응답 메시지의 구조는 아래에서 좀 더 자세히 살펴보겠습니다.</p>
<h3 id="http-request-메시지-구조">HTTP Request 메시지 구조</h3>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/7b1bdff7-530c-4e25-a35b-bc18af08b6a9/image.png" alt=""></p>
<blockquote>
<p>사진 출처 : <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Overview">https://developer.mozilla.org/ko/docs/Web/HTTP/Overview</a></p>
</blockquote>
<ul>
<li>시작줄(첫 줄)<ul>
<li>Method<ul>
<li>URL을 이용하면 클라이언트에서 서버에 특정 데이터를 요청할 수 있는데, 요청하는 데이터에 특정 동작을 수행하고 싶을 때 HTTP 요청 메서드(Http Request Methods)를 이용한다.</li>
<li>일반적으로 HTTP 요청 메서드는 HTTP Verbs라고도 불리우며 GET, POST, PUT, PATCH, DELETE 같은 주요 메서드를 갖고 있다.</li>
<li>이 외에도 명사형의 HEAD와 OPTIONS 메서드도 갖고 있다.<ul>
<li>HEAD : 서버 헤더 정보를 획득. GET과 비슷하나 Response Body를 반환하지 않음</li>
<li>OPTIONS : 서버 옵션들을 확인하기 위한 요청. CORS에서 사용</li>
</ul>
</li>
</ul>
</li>
<li>Path : 가져오려는 리소스의 경로. 포트 번호를 제거한 리소스의 URL을 주로 사용</li>
<li>Version of the protocol : 프로토콜 버전. HTTP/1.1, HTTP2 등이 있다.</li>
</ul>
</li>
<li>Request Headers : 두 번째 줄 부터는 헤더. 요청에 대한 정보를 담고 있다.</li>
<li>Request Body : 헤더에서 한 줄 띄고 본문이 시작된다. 본문은 요청을 할 때 함께 보낼 데이터를 담는 부분이다.</li>
</ul>
<h3 id="http-response-메시지-구조">HTTP Response 메시지 구조</h3>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/4f7717f7-209d-461f-96d0-8c102b89a5b9/image.png" alt=""></p>
<blockquote>
<p>사진 출처 : <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Overview">https://developer.mozilla.org/ko/docs/Web/HTTP/Overview</a></p>
</blockquote>
<ul>
<li>시작줄(첫 줄)<ul>
<li>Version of the protocol : 프로토콜 버전.</li>
<li>Status Code : 요청의 성공 여부와, 그 이유를 나타내는 상태 코드.<ul>
<li>2xx - 성공<ul>
<li>200 : GET 요청에 대한 성공</li>
<li>204 : No Content. 성공했으나 응답 본문에 데이터가 없음</li>
<li>205 : Reset Content. 성공했으나 클라이언트의 화면을 새로 고침하도록 권고</li>
<li>206 : Partial Conent. 성공했으나 일부 범위의 데이터만 반환</li>
</ul>
</li>
<li>3xx - 리다이렉션<ul>
<li>301 : Moved Permanently, 요청한 자원이 새 URL에 존재</li>
<li>303 : See Other, 요청한 자원이 임시 주소에 존재</li>
<li>304 : Not Modified, 요청한 자원이 변경되지 않았으므로 클라이언트에서 캐싱된 자원을 사용하도록 권고. ETag와 같은 정보를 활용하여 변경 여부를 확인</li>
</ul>
</li>
<li>4xx - 클라이언트 에러<ul>
<li>400 : Bad Request, 잘못된 요청</li>
<li>401 : Unauthorized, 권한 없이 요청. Authorization 헤더가 잘못된 경우</li>
<li>403 : Forbidden, 서버에서 해당 자원에 대해 접근 금지</li>
<li>405 : Method Not Allowed, 허용되지 않은 요청 메서드</li>
<li>409 : Conflict, 최신 자원이 아닌데 업데이트하는 경우. ex) 파일 업로드 시 버전 충돌</li>
</ul>
</li>
<li>5xx - 서버 에러<ul>
<li>501 : Not Implemented, 요청한 동작에 대해 서버가 수행할 수 없는 경우</li>
<li>503 : Service Unavailable, 서버가 과부하 또는 유지 보수로 내려간 경우</li>
</ul>
</li>
</ul>
</li>
<li>Status Message : 상태코드에 대한 짧은 설명을 나타내는 상태 메시지</li>
</ul>
</li>
<li>Response Header : 요청 헤더와 비슷한, HTTP 헤더</li>
<li>Response Body : 보통 클라이언트에서 데이터를 요청하고, 응답 메시지에는 요청한 데이터를 담아서 보내주기 때문에 응답 메시지에는 보통 본문이 있다. 응답 메시지에 HTML이 담겨 있다. 이 HTML을 받아 브라우저가 화면에 렌더링한다.</li>
</ul>
<h1 id="웹-서버와-was의-요청-처리-및-응답">웹 서버와 WAS의 요청 처리 및 응답</h1>
<h2 id="정적-페이지static-pages와-동적-페이지dynamic-pages">정적 페이지(Static Pages)와 동적 페이지(Dynamic Pages)</h2>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/26dfe358-9789-479d-996f-572ee9002212/image.png" alt=""></p>
<p>우리가 보는 웹 페이지는 크게 정적 페이지와 동적 페이지로 나뉩니다.</p>
<ul>
<li>정적 페이지 : 언제 접속해도 같은 화면을 보여주는 페이지를 말합니다. 회사 소개 페이지나 해당 벨로그 페이지가 이에 해당합니다. 정적 웹 페이지의 경우 클라이언트가 요청을 보내면 서버는 미리 저장된 웹 페이지를 보내기만 하면됩니다. 때문에 사용자는 서버에 저장된 HTML, CSS, JS등의 파일이 변경되지 않는다면 매번 같은 웹 페이지를 보게 됩니다.</li>
<li>동적 페이지 : 클라이언트에게 요청을 받은 후 서버에 의해 추가적인 처리 과정을 거쳐 응답된 페이지를 말합니다. 때문에 매번 같은 화면을 보여주지 않습니다. 네이버의 뉴스, 날씨 등이 이에 해당합니다. 단순 HTML로는 동적 페이지를 만들 수가 없기 때문에, 동적 페이지를 만들기 위해서는 Java와 같은 프로그래밍 언어를 섞어서 사용해야합니다.(예: JSP, PHP, ASP) 웹 서버는 이렇에 Java와 같은 프로그래밍 언어로 동적인 데이터를 정립하면 이것을 다시 HTML 문서로 재조립하여 클라이언트에게 응답합니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/e702d222-3c07-4d64-b00e-4f3d82ad767a/image.png" alt=""></p>
<h2 id="웹-서버란">웹 서버란?</h2>
<p>웹 서버는 하드웨어 관점과 소프트웨어 관점으로 나눠서 볼 수 있습니다.</p>
<ul>
<li>하드웨어 관점 : 웹 서버가 있는 컴퓨터</li>
<li>소프트웨어 관점 : 클라이언트로부터 HTTP 요청을 받아 정적 콘텐츠(html, css 등)를 제공하는 컴퓨터 프로그램</li>
</ul>
<p>웹 서버는 클라이언트가 요청한 웹 페이지가 정적 페이지라면 웹 서버는 정적 페이지를 응답합니다. 그러나 클라이언트가 요청한 웹 페이지가 동적 페이지라면 WAS라는 미들웨어에게 요청 처리를 맡깁니다. 그리고 WAS에서 처리한 결과를 클라이언트에게 응답합니다. 대표적인 웹 서버로는 Apache, WebtoB, Nginx 등이 있습니다.</p>
<h2 id="was란">WAS란?</h2>
<p>WAS란 Web Application Server의 약자입니다. 웹 어플리케이션은 웹에서 실행되는 프로그램을 말합니다. 벨로그도 웹에서 실행되기 때문에 웹 어플리케이션이라고 할 수 있습니다. WAS는 웹 애플리케이션을 실행시켜 필요한 기능을 수행하고 그 결과를 웹 서버에게 전달하는 일종의 미들웨어를 말합니다.</p>
<p>WAS는 웹 서버와 웹 컨테이너가 합쳐진 형태입니다. 웹 컨테이너는 JSP, Servlet을 실행시킬 수 있는 소프트웨어입니다. 즉 WAS는 jsp, Servlet 구동 환경을 제공해줍니다. 이런 이유로 WAS는 웹 컨테이너 혹은 서블릿 컨테이너라고 불립니다.</p>
<p>만약 웹 서버 혼자서 DB를 조회하거나 비즈니스 로직을 처리하게 되면 서버에 과부하가 일어날 확률이 높습니다. WAS는 이런 서버를 돕는 조력자 역할을 합니다. WAS는 웹 서버가 단독으로 처리하기 어려운 DB조회 및 다양한 로직처리가 필요한 동적 페이지를 제공하는 역할을 합니다. 때문에 개발자는 사용자의 다양한 요구에 맞춰 웹 서비스를 제공할 수 있습니다. 대표적인 WAS로는 Tomcat, Jeus, JBoss 등이 있습니다.</p>
<h2 id="웹-서버와-was-분리">웹 서버와 WAS 분리</h2>
<p>WAS는 정적인 콘텐츠를 처리하지 못하는 것이 아니라 정적인 콘텐츠 + 동적인 콘텐츠까지 함께 처리할 수 있도록 설계된 서버입니다. 그렇기에 WAS 하나만 사용하여 웹 서비스를 제공하는 것도 가능합니다. 트래픽이 적다면 오히려 WAS하나만 사용하여 서비스하는 것이 복잡하지 않아서 유지 보수하기도 편합니다. 하지만 WAS는 DB 조회나 프로그래밍 로직 처리로 인해 많은 트래픽 처리가 힘들다는 단점이 있습니다. 때문에 트래픽이 많다면 WAS앞에 웹 서버를 하나 더 두어 정적인 요청은 웹 서버가 처리하도록 하여 트래픽을 분산시키는 것이 좋습니다. 웹 서버와 WAS를 분리하면 아래와 같은 이점이 있습니다.</p>
<ol>
<li>기능을 분리하여 서버 부하 방지</li>
<li>물리적으로 분리하여 보안 강화</li>
<li>여러 대의 WAS를 연결 가능</li>
<li>여러 웹 어플리케이션 서비스 가능</li>
</ol>
<h1 id="정리">정리</h1>
<p>오늘 설명드린 것을 토대로 주소창에 URL을 입력하면 일을 간단히 정리하면 아래와 같습니다.</p>
<ol>
<li>브라우저는 먼저 사용자가 입력한 URL을 해석합니다.</li>
<li>브라우저는 DNS에 사용자가 입력한 도메인에 해당하는 IP 주소를 요청합니다.</li>
<li>DNS는 해당 도메인에 해당하는 IP 주소를 찾아 브라우저에게 응답합니다.</li>
<li>브라우저는 IP주소를 응답받아 TCP / IP 에 따라 서버와 연결합니다.</li>
<li>서버와 연결이 되면 브라우저는 HTTP(S)로 서버에게 요청 메시지를 보냅니다.</li>
<li>웹 서버와 WAS는 브라우저의 요청을 받아 처리 후 응답 메시지를 브라우저에게 응답합니다.</li>
</ol>
<blockquote>
<p>참고 블로그
<a href="https://www.netmanias.com/ko/post/blog/5353/dns/dns-basic-operation">https://www.netmanias.com/ko/post/blog/5353/dns/dns-basic-operation</a>
<a href="https://hwan-shell.tistory.com/320">https://hwan-shell.tistory.com/320</a>
<a href="https://brunch.co.kr/@wangho/6">https://brunch.co.kr/@wangho/6</a>
<a href="https://enlqn1010.tistory.com/9">https://enlqn1010.tistory.com/9</a>
<a href="https://mindnet.tistory.com/entry/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-22%ED%8E%B8-TCP-3-WayHandshake-4-WayHandshake">https://mindnet.tistory.com/entry/네트워크-쉽게-이해하기-22편-TCP-3-WayHandshake-4-WayHandshake</a>
<a href="https://codechasseur.tistory.com/25">https://codechasseur.tistory.com/25</a>
<a href="https://coding-factory.tistory.com/741">https://coding-factory.tistory.com/741</a>
<a href="https://velog.io/@tnehd1998/%EC%A3%BC%EC%86%8C%EC%B0%BD%EC%97%90-www.google.com%EC%9D%84-%EC%9E%85%EB%A0%A5%ED%96%88%EC%9D%84-%EB%95%8C-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EA%B3%BC%EC%A0%95">https://velog.io/@tnehd1998/주소창에-www.google.com을-입력했을-때-일어나는-과정</a>
<a href="https://velog.io/@minj9_6/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%97%90-url%EC%9D%84-%EC%9E%85%EB%A0%A5%ED%95%9C-%EB%92%A4-%ED%8E%BC%EC%B3%90%EC%A7%80%EB%8A%94-%EA%B2%83%EB%93%A4#1-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%9D%98-url-%ED%8C%8C%EC%8B%B1">https://velog.io/@minj9_6/브라우저에-url을-입력한-뒤-펼쳐지는-것들#1-브라우저의-url-파싱</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[브라우저 동작 원리 - 주소창에 URL 입력 시 일어나는 과정(배경지식 편)]]></title>
            <link>https://velog.io/@gyumin_2/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%A3%BC%EC%86%8C%EC%B0%BD%EC%97%90-URL-%EC%9E%85%EB%A0%A5-%EC%8B%9C-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EA%B3%BC%EC%A0%95%EB%B0%B0%EA%B2%BD%EC%A7%80%EC%8B%9D-%ED%8E%B8</link>
            <guid>https://velog.io/@gyumin_2/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%A3%BC%EC%86%8C%EC%B0%BD%EC%97%90-URL-%EC%9E%85%EB%A0%A5-%EC%8B%9C-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EA%B3%BC%EC%A0%95%EB%B0%B0%EA%B2%BD%EC%A7%80%EC%8B%9D-%ED%8E%B8</guid>
            <pubDate>Tue, 14 Jun 2022 05:50:52 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가기-전">들어가기 전…</h1>
<p>주소창에 특정 웹페이지 주소를 입력 시 일어나는 과정은 개발자 면접에서 단골 질문입니다. 이는 백엔드와 프론트엔드 상관없이 둘 다 알아야 하는 기본이기 때문에 많은 회사에서 특히 주로 신입 개발자 분들에게 자주 물어보는 질문인 것 같습니다. 브라우저는 웹과 뗄레야 뗄 수 없고, 우리 웹 개발자들이 밥벌이 할 수 있게 해주는 소프트웨어입니다.(아마 브라우저가 존재하지 않았다면 웹 개발자는 필요하지 않았을 수도….) 때문에 우리는 브라우저에 대해 알아야하고, 브라우저에 대해 알면 알 수록 웹 개발에 도움이 될 것이라 생각합니다. 이 글은 이제 막 개발에 입문한 분들도 이해하실 수 있도록 최대한 쉽고 자세하게 작성할 예정입니다. 때문에 이 시리즈는 배경지식 편, 백엔드 펀 그리고 프론트엔드 편으로 나눠서 작성할 예정입니다. 설명 중에 알아야할 지식이 있다면 해당 지식을 설명하고 넘어가겠습니다. 너무 깊지 않고 또 너무 얇지 않으며, 너무 간결하지도 장황하지 않게… 아무튼 최대한 쉽게 작성해보겠습니다!</p>
<h1 id="웹-브라우저와-웹-서버">웹 브라우저와 웹 서버</h1>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/4e233d10-c3c5-4d3d-ac57-11f280b4a1e6/image.png" alt=""></p>
<blockquote>
<p>사진 출처 : <a href="https://velog.io/@rimu/%EC%9B%B9%EA%B3%BC-%EC%84%9C%EB%B2%84%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B8%B0%EC%B4%88%EC%A7%80%EC%8B%9D-HTTP-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C">https://velog.io/@rimu/%EC%9B%B9%EA%B3%BC-%EC%84%9C%EB%B2%84%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B8%B0%EC%B4%88%EC%A7%80%EC%8B%9D-HTTP-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C</a></p>
</blockquote>
<p>웹 브라우저는 사용자가 웹 사이트를 다운로드하고 볼 수 있도록 하는 컴퓨터 프로그램입니다. 웹 사이트를 다운로드 하려면 웹 사이트를 내려받을 수 있도록 전달해주는 무언가가 있겠죠? 우리는 그것을 서버(Server)라고 부릅니다. 그리고 웹 브라우저는 흔히 클라이언트(Client)라고 부릅니다.</p>
<p>식당을 예로들어 봅시다. 식당에서 손님이 종업원에게 특정 음식을 주문하면, 종업원은 손님에게 손님이 주문한 음식을 가져다줍니다. 여기서 손님이 클라이언트, 종업원이 서버라고 보시면됩니다. 종업원이 손님에세 음식을 주문하는 것처럼 클라이언트가 서버에게 ‘나 이 웹사이트 줘!’라고 하는 것을 요청(Request)라고하고, 종업원이 손님에게 주문한 음식을 가져다 주는 것처럼 서버가 클라이언트에게 요청한 웹 사이트를 주는 것을 응답(Response)이라고 합니다.</p>
<p>웹 브라우저에 특정 웹 페이지 주소를 입력하면 브라우저는 웹 서버에게 웹 사이트를 요청하고, 서버는 웹 사이트를 응답합니다. 브라우저는 웹 서버에게서 받은 웹 사이트를 받아 화면에 표시합니다. 이게 브라우저의 기본 동작 원리입니다. 하지만 이건 너무 간결하니까 여기서 조금 더 깊게 알아보겠습니다.</p>
<h1 id="ip-주소와-dns">IP 주소와 DNS</h1>
<h2 id="ip-주소란">IP 주소란?</h2>
<p>누군가가 어디 사냐고 물어보면 우리는 OO빌딩, OO아파트, OO오피스텔 등 정확한 주소가 아닌 내가 살고 있는 건물의 이름을 말하는 경우가 많습니다. 왜냐하면 우리의 실제 주소는 길고, 그냥 건물이름을 말하는게 더 편하기 때문입니다. 이는 웹에서도 동일합니다.</p>
<p>모든 건물에 고유한 주소가 있듯이, 모든 컴퓨터는 통신을 위해서 각각 고유한 IP(Internet Protocal) 주소를 갖고 있습니다. IP 주소는 <code>216.58.220.110</code> 와 같은 형태를 하고 있습니다. 이런 형태의 주소는 IPv4 방식인데 컴퓨터, 스마트폰, 사물인터넷 등의 보급화로 주소가 고갈되어 요즘은 IPv6 주소 방식을 사용합니다. IPv6 주소는 <code>2001:0DB8:1000:0000:0000:0000:1111:2222</code> 와 같은 형태를 하고 있습니다. 아무튼 결론은 IP주소는 길고 복잡하고 외우기 힘들다는 것입니다. </p>
<h2 id="도메인이란">도메인이란?</h2>
<p>우리가 인터넷 쇼핑몰에서 물건을 주문할 때 그냥 ‘OO아파트’라고 입력하는 것이 아니라 우리가 살고 있는 정확한 주소를 입력해야 합니다. 사실 우리는 특정 웹 사이트로 이동하기 위해서는 브라우저에 해당 웹 사이트의 IP 주소를 입력해야합니다. 하지만 IP 주소는 길고 복잡하여 외우기 힘들기 때문에 일반적으로 IP주소 대신 사람이 쉽게 기억하고 입력할 수 있도록 문자(영문, 한글 등)로 만든 인터넷 주소를 사용합니다. 우리는 이런 인터넷 주소를 도메인 혹은 도메인 네임이라고 합니다. 기본적으로 URL과 도메인은 혼용되어 사용되기도 하지만 도메인 이름은 <code>https://</code> 나 <code>www.</code>를 제외한 나머지를 말합니다. 예를들어 <code>https://www.naver.com</code> 의 경우 <code>naver.com</code> 이 도메인 네임입니다.</p>
<h2 id="도메인-체계">도메인 체계</h2>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/4de7d983-2071-4222-8101-c0ea5b7e77db/image.gif" alt=""></p>
<blockquote>
<p>사진 출처 : <a href="https://xn--3e0bx5euxnjje69i70af08bea817g.xn--3e0b707e/jsp/resources/domainInfo/domainInfo.jsp">https://한국인터넷정보센터.한국/jsp/resources/domainInfo/domainInfo.jsp</a></p>
</blockquote>
<p>도메인의 체계에서 최상위는 <code>.</code> 또는 루트(root)로써 인터넷 도메인의 시작점이 됩니다. 그리고 도메인 체계는 이 루트 도메인을 시작으로 위 그림과 같이 역트리 형태로 구성되어 있습니다. 루트 도메인은 모든 도메인의 뿌리이기 때문에 다른 도메인과 달리 이름이 없고 그냥 <code>.</code> 으로 표시되는데 일반적으로 루트 도메인 표시는 생략합니다. 루트 도메인 바로 아래의 단계를 1단계 도메인 또는 최상위 도메인(TLD, Top Level Domain)이라고 부르며, 그 다음 단계를 2단계 도메인(SLD, Second Level Domain)이라고 부릅니다. <code>.</code>을 기준으로 3차, 4차 도메인도 존재합니다. <code>domain.co.kr</code> 이라는 도메인이 있다고 가정할 때, <code>kr</code>은 최상위 도메인, <code>co</code>는 2차 도메인, <code>domain</code>은 3차 도메인입니다. </p>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/4e6f0426-70a2-4d44-a802-2ceeedcada15/image.png" alt=""></p>
<p>최상위 도메인을 아래와 같이 두 가지로 구분됩니다.</p>
<ol>
<li>국가 최상위 도메인(ccTLD, country code Top Level Domain)<ul>
<li><code>.kr</code>, <code>.us</code>와 같이 인터넷 상에서 국가를 나타내는 영문 및 자국어 도메인으로 각 국가에서 관리한다.</li>
<li><code>.tv</code>, <code>.io</code> 등 일부는 국가 밖에서도 널리 사용된다.</li>
</ul>
</li>
<li>일반 최상위 도메인(gTLD, genertic Top Level Domain)<ul>
<li><code>.com</code>, <code>.edu</code>, <code>.org</code> 및 <code>.gov</code> 와 같은 도메인으로 조직, 목적, 분류 등 명칭을 영문약자로 표현한
최상위 도메인</li>
<li>일반적으로 이 도메인에 대한 권한은 <strong>사설 조직</strong>에 있다.</li>
</ul>
</li>
</ol>
<h2 id="dns란">DNS란?</h2>
<p>이러한 도메인을 이용해 웹 사이트에 접근할 수 있도록 하는 것을 DNS(Domain Name System)이라고 합니다. DNS는 ICANN(Internet Corporation for Assigned Names and Numbers, 국제 인터넷 주소 관리 기구)에서 관리합니다. IP가 고유한 것처럼 도메인 또한 고유 해야하며, 도메인을 사용하려면 도메인을 구입해서 DNS(Domain Name Server)에 등록해야합니다. 보통 도메인 등록기관을 통해 도메인을 등록합니다.</p>
<p>브라우저 주소창에 도메인을 입력하면 웹 브라우저는 Domain Name Server에 접속해서 해당 도메인에 해당하는 IP주소를 응답받아 서버에 접속합니다.</p>
<h1 id="url">URL</h1>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/4a7497d4-9cb1-4769-b900-a30e2930637b/image.png" alt=""></p>
<blockquote>
<p>사진 출처 : <a href="https://velog.io/@rimu/%EC%9B%B9%EA%B3%BC-%EC%84%9C%EB%B2%84%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B8%B0%EC%B4%88%EC%A7%80%EC%8B%9D-HTTP-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C">https://velog.io/@rimu/%EC%9B%B9%EA%B3%BC-%EC%84%9C%EB%B2%84%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B8%B0%EC%B4%88%EC%A7%80%EC%8B%9D-HTTP-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C</a></p>
</blockquote>
<h2 id="url-구조">URL 구조</h2>
<p>우리가 주소창에 입력하는 영문 혹은 한글로 이뤄진 주소를 URL(Uniform Resource Locators)이라고 합니다. 앞서 말했던 것처럼 URL은 숫자로 이뤄진 IP 주소보다 훨씬 기억하기 쉽고 입력하기 쉽기 때문에 IP 주소보다 더 많이 사용됩니다. URL은 위 이미지 처럼 프로토콜, 호스트, 포토, 리소스 경로, 쿼리로 구성되어있습니다. 호스트에서 <code>www.</code>을 제외한 부분이 도메인입니다.</p>
<h2 id="프로토콜">프로토콜</h2>
<p>위 사진에서보면 URL은 포로토콜로 시작하며, 프로토콜 부분에 http라고 써있는 것을 볼 수 있습니다. 여기서 프로토콜은 통신규약으로 상호 간에 정의한 규칙을 의미하며 특정 기기 간에 데이터를 주고받기 위해 정의되었습니다. 통신규약을 쉽게 풀어서 설명하면 “나는 이렇게 줄 테니 넌 이렇게 받고 난 너가 준거 그렇게 받을게”라고 할 수 있습니다. </p>
<p>HTTP는 Hypertext Transfer Protocol의 약자로 HTML 문서와 같은 리소스를 전송하기 위한 통신 규약입니다. 웹에서는 브라우저와 서버 간에 데이터를 주고받기 위한 방식으로 HTTP를 프로토콜로 사용합니다. 그런데 실제로는 HTTP보다는 HTTPS(Hypertext Transfer Protocol Secure) 가장 많이 사용합니다. HTTPS는 마지막 Secure 라는 단어를 보면 알 수 있듯이 HTTP에 보안을 강화한 버전이라고 생각하시면 됩니다. </p>
<h2 id="포트">포트</h2>
<p>포트(port)는 사전적 의미로 항구를 뜻합니다. 항구나 공항이 외부와 접속할 수 있는 관문이 되는 것처럼 컴퓨터에서 포트도 비슷한 의미를 갖고있습니다. 컴퓨터에서 포트란 외부의 다른 장비와 접속하기 위한 플러그 같은 것을 의미합니다. 우리는 컴퓨터나 노트북에서 랜선을 연결하는 곳, 마우스나 키보드, usb 등 다양한 외부 장치와 연결할 수 있는 포트를 확인할 수 있습니다.</p>
<p>이러한 포트개념은 소프트웨어 상에도 있습니다. 우리가 특정 서버에 접속하려면 URL이나 IP 주소를 입력해야합니다. 그러면 인터넷 상에서 URL 또는 IP를 토대로 해당 서버가 있는 컴퓨터로 찾아갑니다. 그런데 대부분의 컴퓨터에서는 여러 개의 프로그램이 동시에 실행되고 있어서, 이 여러개의 프로그램 중 어느 프로그램이 내가 접속하려는 프로그램인지 컴퓨터에게 알려 주어야 합니다. 여기서 포트 번호는 어떤 프로그램에 접속 할 것인지 컴퓨터에게 알려줍니다. 우리가 하나의 usb 포트에 두 개의 usb를 연결 할 수 없는 것처럼, 소프트웨에서 이미 사용 중인 포트는 중복해서 사용할 수 없습니다.</p>
<blockquote>
<p>참고 사이트
<a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Overview">https://developer.mozilla.org/ko/docs/Web/HTTP/Overview</a>
<a href="https://velog.io/@rimu/%EC%9B%B9%EA%B3%BC-%EC%84%9C%EB%B2%84%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B8%B0%EC%B4%88%EC%A7%80%EC%8B%9D-HTTP-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C">https://velog.io/@rimu/%EC%9B%B9%EA%B3%BC-%EC%84%9C%EB%B2%84%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B8%B0%EC%B4%88%EC%A7%80%EC%8B%9D-HTTP-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C</a>
<a href="https://xn--3e0bx5euxnjje69i70af08bea817g.xn--3e0b707e/jsp/resources/domainInfo/domainInfo.jsp">https://xn--3e0bx5euxnjje69i70af08bea817g.xn--3e0b707e/jsp/resources/domainInfo/domainInfo.jsp</a>
<a href="https://run-it.tistory.com/19">https://run-it.tistory.com/19</a>
<a href="https://gentlysallim.com/%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%9D%B4%EB%A6%84%EA%B3%BC-%EC%A2%85%EB%A5%98-%EC%B5%9C%EC%83%81%EC%9C%84-%EB%8F%84%EB%A9%94%EC%9D%B8%EB%B6%80%ED%84%B0-%EC%84%9C%EB%B8%8C%EB%8F%84%EB%A9%94%EC%9D%B8/">https://gentlysallim.com/%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%9D%B4%EB%A6%84%EA%B3%BC-%EC%A2%85%EB%A5%98-%EC%B5%9C%EC%83%81%EC%9C%84-%EB%8F%84%EB%A9%94%EC%9D%B8%EB%B6%80%ED%84%B0-%EC%84%9C%EB%B8%8C%EB%8F%84%EB%A9%94%EC%9D%B8/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[React.js - 리액트 라우터(React Router)]]></title>
            <link>https://velog.io/@gyumin_2/React.js-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%9D%BC%EC%9A%B0%ED%84%B0React-Router</link>
            <guid>https://velog.io/@gyumin_2/React.js-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%9D%BC%EC%9A%B0%ED%84%B0React-Router</guid>
            <pubDate>Fri, 03 Jun 2022 02:33:26 GMT</pubDate>
            <description><![CDATA[<h1 id="react-router">react-router</h1>
<ul>
<li>다른 주소에서 다른 화면을 보여주는 것을 라우팅이라고 한다.</li>
<li>클라이언트 사이드에서 이루어지는 라우팅을 구현하여 SPA를 쉽게 만들어 주는 라이브러가 <code>react-router</code>다.</li>
</ul>
<h1 id="react-router-설치-및-적용">react-router 설치 및 적용</h1>
<ul>
<li><p>설치 : <code>npm install react-router-dom</code></p>
</li>
<li><p>프로젝트에 라우터 적용</p>
<ul>
<li><p>프로젝트에 리액트 라우터를 적용할 때는 <code>src/index.js</code> 파일에서 <code>react-router-dom</code>에 내장되어 있는 <code>BroswerRouter</code> 컴포넌트를 사용해서 루트 컴포넌트를 감싸면 된다.</p>
</li>
<li><p><code>BroswerRouter</code> 컴포넌트는 웹 애플리케이션에 HRML5의 History API를 사용하여 페이지를 새로고침 하지 않고도 주소를 변경하고, 현재 주소에 관련된 정보를 props로 쉽게 조회하거나 사용할 수 있도록 해준다.</p>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom&#39;;
import { BrowserRouter } from &#39;react-router-dom&#39; // 1. import react-router-dom
import &#39;./index.css&#39;;
import App from &#39;./App&#39;;

ReactDOM.render( // 2. 루트 컴포넌트를 BrowserRouter 컴포넌트로 감싸기
&lt;BrowserRouter&gt; 
  &lt;App /&gt;
&lt;/BrowserRouter&gt;,
document.getElementById(&#39;root&#39;)
);</code></pre>
</li>
</ul>
</li>
</ul>
<h2 id="route-컴포넌트">Route 컴포넌트</h2>
<ul>
<li><p>Router 컴포넌트를 사용하면 어떤 규칙을 가진 경로에 어떤 컴포넌트를 보여 줄지 정의할 수 있다.</p>
<pre><code class="language-jsx">  // Router 컴포넌트 사용 방식
  &lt;Router path=&quot;주소규칙&quot; component={보여줄 컴포넌트}/&gt;</code></pre>
</li>
<li><p>예시</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  import { Route } from &#39;react-router-dom&#39;; // Router 컴포넌트 불러오기
  import Home from &#39;./components/Home&#39;;
  import About from &#39;./components/About&#39;;

  function App() {
    return (
      &lt;div&gt;
        &lt;Route path=&quot;/&quot; component={Home} exact={true}/&gt;
        &lt;Route path=&quot;/about&quot; component={About}/&gt;
      &lt;/div&gt;
    );
  }

  export default App;</code></pre>
<ul>
<li>첫 화면에는 <code>Home</code> 컴포넌트 렌더링이 되고, <code>/about</code> 경로로 이동시 <code>Home</code> 컴포넌트와 <code>About</code> 컴포넌트가 렌더링된다. 이는 <code>/about</code> 경로가 <code>/</code> 규칙에도 일치하기 때문이다. 이를 수정하기 위해 주소가 <code>/</code>인 Router 컴포넌트에 <code>exact</code> 라는 props를 <code>true</code> 로 설정해준다.</li>
</ul>
</li>
</ul>
<h2 id="link-컴포넌트">Link 컴포넌트</h2>
<ul>
<li><p><code>Link</code> 컴포넌트는 클릭하면 다른 주소로 이동시켜주는 컴포넌트다. <code>a</code> 태그를 사용해도 페이지 전환이 되지만 그 경우 페이지를 새로 불러오기 때문에 애플리케이션이 갖고 있는 모든 상태가 초기화된다.</p>
</li>
<li><p><code>Link</code> 컴포넌트를 사용하여 페이지를 전환하면, 페이지를 새로 불러오지 않고 애플리케이션은 그대로 유지한 상태에서 HTML5 History API를 사용하여 페이지의 주소만 변경해준다. <code>Link</code> 컴포넌트 자체는 <code>a</code> 태그로 이루어져 있지만, 페이지 전환을 방지하는 기능이 내장되어 있다.</p>
<pre><code class="language-jsx">  &lt;Link to=&quot;주소&quot;&gt;내용&lt;/Link&gt;</code></pre>
</li>
<li><p>예제</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  import { Route, Link } from &#39;react-router-dom&#39;; // Link 컴포넌트 불러오기
  import Home from &#39;./components/Home&#39;;
  import About from &#39;./components/About&#39;;

  function App() {
    return (
      &lt;div&gt;
        &lt;ul&gt;
          &lt;li&gt;
            &lt;Link to=&quot;/&quot;&gt;Home&lt;/Link&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;Link to=&quot;/about&quot;&gt;About&lt;/Link&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
        &lt;Route path=&quot;/&quot; component={Home} exact={true}/&gt;
        &lt;Route path=&quot;/about&quot; component={About}/&gt;
      &lt;/div&gt;
    );
  }

  export default App;</code></pre>
</li>
</ul>
<h2 id="route-하나에-여러-개의-path-설정하기">Route 하나에 여러 개의 path 설정하기</h2>
<ul>
<li><p>Route 하나에 여러 개의 path 를 지정하는 것은 최신 버전의 리액트 라우터 v5부터 적용된 기능이다.</p>
<pre><code class="language-jsx">  // 이전 버전 - Router 두 번 사용
  &lt;Route path=&quot;/&quot; component={Home} exact={true}/&gt;
  &lt;Route path=&quot;/about&quot; component={About}/&gt;
  &lt;Route path=&quot;/info&quot; component={About}/&gt;

  // 최신 버전 - path props를 배열로 설정하면 여러 경로에 같은 컴포넌트를 보여줄 수 있다.
  &lt;Route path=&quot;/&quot; component={Home} exact={true}/&gt;
  &lt;Route path={ [&#39;/about&#39;, &#39;/info&#39;] } component={About}/&gt;</code></pre>
</li>
</ul>
<h1 id="url-파라미터와-쿼리-스트링">URL 파라미터와 쿼리 스트링</h1>
<ul>
<li>페이지 주소를 정의할 때 가끔 유동적인 값을 전달해야할 때가 있다. 이는 파라미터와 쿼리스트링으로 나뉠 수 있다.<ul>
<li>url 파리미터 예시 : <code>/profile/velopert</code></li>
<li>쿼리 스트링 예시 : <code>/about?detail=true</code></li>
</ul>
</li>
<li>라우터로 쓰인 컴포넌트는 props로 <code>match</code>, <code>location</code>, <code>history</code>를 전달받는다. <code>match</code>와 <code>history</code> 객체로 url 파리미터와 쿼리스트링을 처리할 수 있다.</li>
</ul>
<h2 id="url-파라미터">URL 파라미터</h2>
<ul>
<li><p>url 파라미터를 사용할 때는 라우트로 사용되는 컴포넌트에서는 match 객체를 props로 받아와 사용해야 한다. match 객체는 현재 컴포넌트가 어떤 경로 규칙에 의해 보이는지에 대한 정보가 들어있다. url 파라미터의 값은 match 안에 params 값에 들어있다.</p>
</li>
<li><p>url 파라미터를 받아서 처리하는 컴포넌트</p>
<pre><code class="language-jsx">  const Compnent = ({ match }) =&gt; { // match를 props로 받아옴
      const { param } = match.params; // match.params에 url 파라미터에 대한 정보다 담겨있다.
  }

  /* 
  props.match = {
      isExact: true
      params: {username: &quot;gyu&quot;}  =&gt; url 파라미터
      path: &quot;/profile/:username&quot;  =&gt; 경로 규칙
      url: &quot;/profile/gyu  =&gt; 실제 url
  }
  */&quot;</code></pre>
</li>
<li><p>라우트 정의 컴포넌트</p>
<pre><code class="language-jsx">  // Route 컴포넌트
  &lt;Route path=&quot;/주소/:변수&quot; component={컴포넌트}/&gt;

  // Link 컴포넌트
  &lt;Link to=&quot;/주소/값&quot;&gt;내용&lt;/Link&gt;

  /*
  라우트 정의 컴포넌트에서 이렇게 설정하면
  url 파라미터를 받아서 처리하는 컴포넌트에서는
  match.params = { 변수: 값 }로 받아 사용할 수 있다.
  */</code></pre>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  // Profile.js
  import React from &#39;react&#39;;

  const data = {
      gyu: {
          name: &#39;Lee&#39;,
          description: &#39;코린이...&#39;
      },
      gildong: {
          name: &#39;홍길동&#39;,
          description: &#39;홀길동도롱도롱롱롱롱&#39;
      }
  }

  const Profile = ({match}) =&gt; {
      console.log(match);
      const { username } = match.params;
      const profile = data[username];
      if(!profile) {
          return &lt;div&gt;존재하지 않는 사용자입니다.&lt;/div&gt;
      }
      return (
          &lt;div&gt;
              &lt;h3&gt;{ username }({profile.name})&lt;/h3&gt;
              &lt;p&gt;{profile.description}&lt;/p&gt;
          &lt;/div&gt;
      );
  };

  export default Profile;

  // App.js
  import React from &#39;react&#39;;
  import { Route, Link } from &#39;react-router-dom&#39;;
  import Profile from &#39;./components/Profile&#39;;

  function App() {
    return (
      &lt;div&gt;
        &lt;ul&gt;
          &lt;li&gt;
            &lt;Link to=&quot;/profile/gyu&quot;&gt;gyu&#39;s profile&lt;/Link&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;Link to=&quot;/profile/gildong&quot;&gt;gildong&#39;s profile&lt;/Link&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
        &lt;Route path=&quot;/profile/:username&quot; component={Profile}/&gt;
      &lt;/div&gt;
    );
  }</code></pre>
</li>
</ul>
<h2 id="쿼리-스트링">쿼리 스트링</h2>
<ul>
<li><p>라우터로 쓰인 컴포넌트에서 location 객체를 props로 전달을 수 있다. location 객체는 웹 어플리케이션의 현재 주소에 대한 정보를 갖고 있다.</p>
<pre><code class="language-jsx">  // location 객체 예 - /about?detail=true
  {
      hash: &quot;&quot;
      key: &quot;wq2uka&quot;
      pathname: &quot;/about&quot;
      search: &quot;?detail=true&quot;
      state: undefined
  }</code></pre>
</li>
<li><p>쿼리스트링을 확인할 때는 location 객체의 search의 값을 확인해야한다. 이 값은 문자열로 되어있는데, 쿼리 스트링의 특정 값을 확인하기 위해서는 qs 라이브러리를 사용해서 search 값을 객체로 바꿔줘야한다.</p>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  // About.js
  import React from &#39;react&#39;;
  import qs from &#39;qs&#39;; // qs 라이브러리 설치 및 불러오기

  const About = ({location}) =&gt; {
      const query = qs.parse(location.search, {
          ignoreQueryPrefix: true // search 문자열 맨 앞의 ?를 생략하는 옵션
      })
      const showDetail = query.detail === &#39;true&#39;; // 쿼리 파싱값은 문자열
      return (
          &lt;div&gt;
             &lt;h1&gt;소개&lt;/h1&gt;
             &lt;p&gt;이 프로젝트는 리액트 라우터 기초를 실습해보는 예제 프로젝트입니다.&lt;/p&gt;
             {showDetail &amp;&amp; &lt;p&gt;detail 값을 true로 설정하셨군요!&lt;/p&gt;}
          &lt;/div&gt;
      );
  };

  export default About;

  // App.js
  &lt;Link to=&quot;/about?detail=true&quot;&gt;About&lt;/Link&gt;</code></pre>
</li>
</ul>
<h1 id="서브라우트">서브라우트</h1>
<ul>
<li><p>라우트 내부에 또 라우트를 정의하는 것. 라우트로 사용되고 있는 컴포넌트 내부에 Route 컴포넌트를 사용하는 것.</p>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  // App.js
  import React from &#39;react&#39;;
  import { Route, Link } from &#39;react-router-dom&#39;;
  import ProfileWrapper from &#39;./components/ProfileWrapper&#39;;

  function App() {
    return (
      &lt;div&gt;
        &lt;ul&gt;
          &lt;li&gt;
            &lt;Link to=&quot;/profile&quot;&gt;profiles&lt;/Link&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
        &lt;Route path=&quot;/profile&quot; component={ProfileWrapper}/&gt;
      &lt;/div&gt;
    );
  }

  export default App;

  // ProfileWrapper.js
  import React from &#39;react&#39;;
  import { Route, Link } from &#39;react-router-dom&#39;;
  import Profile from &#39;./Profile&#39;

  const ProfileWrapper = () =&gt; {
      return (
          &lt;div&gt;
              &lt;h3&gt;사용자 목록 : &lt;/h3&gt;
              &lt;ul&gt;
                  &lt;li&gt;
                      &lt;Link to=&quot;/profile/gyu&quot;&gt;gyu&#39;s profile&lt;/Link&gt;
                  &lt;/li&gt;
                  &lt;li&gt;
                      &lt;Link to=&quot;/profile/gildong&quot;&gt;gildong&#39;s profile&lt;/Link&gt;
                  &lt;/li&gt;
              &lt;/ul&gt;
              &lt;Route
                  path=&quot;/profile&quot;
                  exact
                  render={()=&gt;&lt;div&gt;사용자를 선택해 주세요.&lt;/div&gt;}
              /&gt;
              &lt;Route path=&quot;/profile/:username&quot; component={Profile} /&gt;
          &lt;/div&gt;
      );
  };

  export default ProfileWrapper;

  /*
  JSX에서 props를 설정할 때 값을 생략하면 자동으로 true로 설정된다.
  때문에 exact={true}는 exact와 같다.

  Route 컴포넌트에 component 대신 render라는 props를 넣었다.
  render는 컴포넌트 자체가 아닌 보여주고 싶은 JSX를 반환하는 함수를 넣어
  컴포넌트를 만들기 애매한 상황이나, 컴포넌트에 별도의 props를 넣어주고 싶을 때 사용한다. 
  */</code></pre>
</li>
</ul>
<h1 id="리액트-라우터-부가-기능">리액트 라우터 부가 기능</h1>
<h2 id="history">History</h2>
<ul>
<li><p>history 객체는 라우르로 사용된 컴포넌트에 match, location과 함께 전달되는 props 중 하나로, 이 객체를 통해 컴포넌트 내에 구현하는 메서드에서 라우터 API를 호출 할 수 있다.</p>
</li>
<li><p>사용법</p>
<pre><code class="language-jsx">  history.goBack() // 뒤로가기
  history.push(&#39;rout&#39;) // 특정 라우트로 이동</code></pre>
</li>
</ul>
<h2 id="withrouter">withRouter</h2>
<ul>
<li><p>withRouter함수는 리액트 라우트에서 제공하는 HoC(Higher-order Component)다. 라우트로 사용된 컴포넌트가 아니어도 match, location, history 객체에 접근할 수 있게 해준다.</p>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  import { withRouter } from &#39;react-router-dom&#39;; // 1. withRouter 불러오기
  const WithRouterSample = ({ location, match, history }) =&gt; { 
    return (              // 2. props로 location, match, history 받아오기
      &lt;div&gt;
        &lt;h4&gt;location&lt;/h4&gt;
        &lt;textarea
          value={JSON.stringify(location, null, 2)}
          rows={7}
          readOnly={true}
        /&gt;
        &lt;h4&gt;match&lt;/h4&gt;
        &lt;textarea
          value={JSON.stringify(match, null, 2)}
          rows={7}
          readOnly={true}
        /&gt;
        &lt;button onClick={() =&gt; history.push(&#39;/&#39;)}&gt;홈으로&lt;/button&gt;
      &lt;/div&gt;
    );
  };

  export default withRouter(WithRouterSample); 
  // 3. 컴포넌트를 export할 때 withRouter 함수로 감싸주기

  // * JSON.stringify()함수의 두, 세번째 매개변수에 null, 2를 넣으면 
  //   JSON에 들여쓰기 된 상태로 문자열이 생성됨</code></pre>
</li>
</ul>
<blockquote>
<p>💡 고차 컴포넌트(HOC, Higher Order Component)</p>
<p>컴포넌트를 인자로 받아서 컴포넌트를 반환하는 함수로 컴포넌트 로직을 재사용하기 위한 React의 고급 기술이다.</p>
</blockquote>
<h2 id="switch">Switch</h2>
<ul>
<li><p>Switch 컴포넌트는 여러 Route 컴포넌트를 감싸서 그중 일치하는 단 하나의 라우트만을 렌더링 시켜준다. Switch 컴포넌트를 사용하면 모든 규칙에 일치하지 않을 때 보여줄 Not Found 페이지도 구현이 가능하다.</p>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  import { Route, Link, Switch } from &#39;react-router-dom&#39;;
  import About from &#39;./About&#39;;
  import Home from &#39;./Home&#39;;
  import Profiles from &#39;./Profiles&#39;;
  import HistorySample from &#39;./HistorySample&#39;;

  const App = () =&gt; {
    return (
      &lt;div&gt;
        &lt;ul&gt;
          &lt;li&gt;
            &lt;Link to=&quot;/&quot;&gt;홈&lt;/Link&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;Link to=&quot;/about&quot;&gt;소개&lt;/Link&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;Link to=&quot;/profiles&quot;&gt;프로필&lt;/Link&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;Link to=&quot;/history&quot;&gt;History 예제&lt;/Link&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
        &lt;hr /&gt;
        &lt;Switch&gt;
          &lt;Route path=&quot;/&quot; component={Home} exact={true} /&gt;
          &lt;Route path={[&#39;/about&#39;, &#39;/info&#39;]} component={About} /&gt;
          &lt;Route path=&quot;/profiles&quot; component={Profiles} /&gt;
          &lt;Route path=&quot;/history&quot; component={HistorySample} /&gt;
          &lt;Route
            // path를 따로 정의하지 않으면 모든 상황에 렌더링됨
            render={({ location }) =&gt; (
              &lt;div&gt;
                &lt;h2&gt;이 페이지는 존재하지 않습니다:&lt;/h2&gt;
                &lt;p&gt;{location.pathname}&lt;/p&gt;
              &lt;/div&gt;
            )}
          /&gt;
        &lt;/Switch&gt;
      &lt;/div&gt;
    );
  };

  export default App;</code></pre>
</li>
</ul>
<h2 id="navlink">NavLink</h2>
<ul>
<li><p>NavLink 컴포넌트는 Link 컴포넌트와 비슷하다. 현재 경로와 Link 컴포넌트에서 사용하는 경로가 일치하는 경우 특정 스타일 혹은 css 클래스를 적용할 수 있는 컴포넌트.</p>
</li>
<li><p>NavLink에서 링크가 활성화 되었을 때 스타일을 적용하고 싶다면 activeStyle을, css 클레스 이름을 적용하고 싶다면 activeClassName을 props로 넣어주면 된다.</p>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  import { NavLink, Route } from &#39;react-router-dom&#39;;
  import Profile from &#39;./Profile&#39;;

  const Profiles = () =&gt; {
    const activeStyle = {
      background: &#39;black&#39;,
      color: &#39;white&#39;
    };
    return (
      &lt;div&gt;
        &lt;h3&gt;사용자 목록:&lt;/h3&gt;
        &lt;ul&gt;
          &lt;li&gt;
            &lt;NavLink activeStyle={activeStyle} to=&quot;/profiles/velopert&quot; active&gt;
              velopert
            &lt;/NavLink&gt;
          &lt;/li&gt;
          &lt;li&gt;
            &lt;NavLink activeStyle={activeStyle} to=&quot;/profiles/gildong&quot;&gt;
              gildong
            &lt;/NavLink&gt;
          &lt;/li&gt;
        &lt;/ul&gt;

        &lt;Route
          path=&quot;/profiles&quot;
          exact
          render={() =&gt; &lt;div&gt;유저를 선택해주세요.&lt;/div&gt;}
        /&gt;
        &lt;Route path=&quot;/profiles/:username&quot; component={Profile} /&gt;
      &lt;/div&gt;
    );
  };

  export default Profiles;</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React.js - 컴포넌트 최적화]]></title>
            <link>https://velog.io/@gyumin_2/React.js-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@gyumin_2/React.js-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Wed, 25 May 2022 06:14:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 <a href="https://github.com/LGyuMin/react-todo-list">react todo-list 프로젝트</a> 기준으로 작성되었습니다.</p>
</blockquote>
<h1 id="컴포넌트가-느려지는-원인">컴포넌트가 느려지는 원인</h1>
<ul>
<li>아래의 상황에서 컴포넌트 리랜더링이 발생한다.<ol>
<li>자신이 전달받은 props가 변경될 때</li>
<li>자신의 state가 변경될 때</li>
<li>부모 컴포넌트가 리렌더링 될 때</li>
<li><code>forceUpdate()</code> 함수가 실행될 때</li>
</ol>
</li>
<li>todo list 리랜더링 과정<ul>
<li>&#39;할 일 1&#39; 항목 체크 → App 컴포넌트의 state 변경 → App 컴포넌트 리렌더링 → App 컴포넌트의 모든 자식 컴포넌트 리렌더링</li>
</ul>
</li>
<li>만약 항목이 3000개 라고 가정할 때, &#39;할 일 1&#39;을 체크하면 해당 항목은 리렌더링 되는 것이 맞지만 나머지 2999개의 항목이 리렌더링 되면 속도가 느려진다.</li>
<li>이럴 때는 컴포넌트 리렌더링 성능을 최적화해 주는 작업을 해야한다. 즉 불필요한 리렌더링을 방지하는 작업이 필요하다.</li>
</ul>
<h1 id="reactmemo-사용">React.memo 사용</h1>
<ul>
<li><p>클래스 컴포넌트에서는 <code>shouldComponentUpdate()</code> 라이프사이클을 사용하여 리렌더링을 방지할 수 있다. 그러나 함수형 컴포넌트에서는 라이프사이클 훅을 사용할 수 없다. 때문에 함수형 컴포넌트에서는 React.memo 함수를 사용한다.</p>
</li>
<li><p>React.memo 함수를 사용하면 해당 컴포넌트의 props가 바뀌지 않는다면 리렌더링하지 않도록 설정하여 함수형 컴포넌트의 리렌더링 성능을 최적화 할 수 있다.</p>
</li>
<li><p>TodoItem 컴포넌트에 React.memo 적용</p>
<pre><code class="language-jsx">  import React, { useState, useCallback } from &#39;react&#39;;
  import cn from &#39;classnames&#39;;
  import &#39;./TodoItem.scss&#39;

  const TodoItem = ({ todo, onChangeTodo, onRemoveTodo }) =&gt; {
      ...
  };

  export default React.memo(TodoItem); // 컴포넌트를 React.memo로 감싸주면, 적용 완료

  // todo, onChangeTodo, onRemoveTodo가 변경되지 않으면 리렌더링을 하지 않는다.</code></pre>
</li>
<li><p>리스트와 관련된 컴포넌트를 최적화할 때는 리스트 내부에서 사용하는 컴포넌트(TodoItem)도 최적화해야하고, 리스트로 사용되는 컴포넌트(TodoList) 자체도 최적화 시켜주는 것이 좋다.</p>
</li>
<li><p>TodoList는 todos 배열이 업데이트 될 때 뿐만 아니라 filterOption이 변경 될 때도 리렌더링을 하기 때문에 최적화가 필요하다.</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  import TodoItem from &#39;./TodoItem&#39;;

  const TodoList = ({ todos, onChangeTodo, onRemoveTodo }) =&gt; {
      ...
  };

  export default React.memo(TodoList);</code></pre>
</li>
</ul>
<h1 id="함수-최적화">함수 최적화</h1>
<ul>
<li>React.memo를 사용하는 것만으로 컴포넌트 최적화가 끝나지 않는다. 현재 프로젝트에서는 todos 배열이 업데이트되면 onChangeTodo, onRemoveTodo 함수도 새롭게 바뀌기 때문이다.</li>
<li>onChangeTodo, onRemoveTodo 함수는 todos 배열을 변경 할 때 최신 todos를 참조하기 때문에 todos가 변경될 때 마다 함수가 새로 만들어진다. 이렇게 함수가 계속 새롭게 만들어지는 상황을 방지하는 두 가지 방법이 있다.<ol>
<li><code>useState()</code> 의 함수형 업데이트 사용</li>
<li><code>useReducer()</code> 사용</li>
</ol>
</li>
</ul>
<h2 id="usestate의-함수형-업데이트-사용">useState()의 함수형 업데이트 사용</h2>
<ul>
<li><p>setState() 함수의 매개변수로 새로운 상태 값을 넣을 수 있지만, 상태 업데이트를 어떻게할지 정의해주는 업데이트 함수를 넣을 수 있다. 이를 함수형 업데이트라고 한다.( <a href="https://www.notion.so/React-state-68cbf49a9f8a4edabaef927ef8e8b30b">React - state</a> 참조)</p>
</li>
<li><p>setState() 함수의 매개변수가 함수인 경우 이전 state 값을 사용할 수 있다.</p>
<pre><code class="language-jsx">  // 함수형 업데이트 예제
  const [ number, setNumber ] = useState(0);

  const onIncrease = useCallback(
      () =&gt; setNumber(prevNumber =&gt; prevNumber + 1), // 이전값에 접근 가능
      []
  );
  /*
  setNumber(number + 1)이 아니라 위 코트처럼 어떻게 number를 업데이트할지 정의해주는
  업데이트 함수를 넣어주게 되면 useCallback을 사용할 때 두 번째 매개변수로 넣는 배열에
  number를 넣지 않아도 된다.
  */ </code></pre>
</li>
<li><p>함수형 업데이트를 onAddTodo, onChangeTodo, onRemoveTodo에 적용하기</p>
<pre><code class="language-jsx">  const onAddTodo = useCallback((text) =&gt; {
    if(text.trim() === &#39;&#39;) return;

    const newTodo = {
      id: nextId.current,
      text: text,
      isDone: false,
      isImportant: false
    }

    // const nextTodos = [...todos, newTodo];
    // setTodos(nextTodos);
    setTodos(todos =&gt; [...todos, newTodo]);
    nextId.current++;
  }, []);

  const onChangeTodo = useCallback((id, prop, value) =&gt; {
    setTodos(todos =&gt;
      todos.map(todo =&gt; { // 불변성 때문에.... 이렇게 수정해야함..
          return todo.id === id ? {...todo, [prop]: value} : todo;
        }
      )
    );
  }, []);

  const onRemoveTodo = useCallback((id) =&gt; {
    setTodos(todos =&gt; todos.filter(todo =&gt; todo.id !== id));
  }, [])</code></pre>
</li>
</ul>
<h2 id="usereducer-사용하기">useReducer 사용하기</h2>
<ul>
<li>useReducer를 사용해도 함수가 새롭게 생기는 문제를 해결할 수 있다.</li>
</ul>
<pre><code class="language-jsx">function todoReducer(todos, action) { // reducer 함수
  switch (action.type) {
    case &#39;INSERT&#39;:
      return todos.concat(action.todo);
    case &#39;UPDATE&#39;:
      return todos.map(todo =&gt; 
          todo.id === action.id ? {...todo, [action.prop]: action.value} : todo
      )
    case &#39;REMOVE&#39;: 
      return todos.filter(todo =&gt; todo.id !== action.id);
    default:
      return todos;
  }
}

const App = () =&gt; {
    const [ todos, dispatch ] = useReducer(todoReducer, [
      { id: 1, text: &#39;집가기&#39;, isDone: false, isImportant: false },
      { id: 2, text: &#39;영화 보기&#39;, isDone: false, isImportant: false },
      { id: 3, text: &#39;아무것도 안하기&#39;, isDone: true, isImportant: true },
    ]
  );

    const onAddTodo = useCallback((text) =&gt; {
    if(text.trim() === &#39;&#39;) return;

    const newTodo = {
      id: nextId.current,
      text: text,
      isDone: false,
      isImportant: false
    }

    dispatch({type: &#39;INSERT&#39;, todo: newTodo});
    nextId.current++;
  }, []);

  const onChangeTodo = useCallback((id, prop, value) =&gt; {
    dispatch({type: &#39;UPDATE&#39;, id, prop, value});
  }, []);

  const onRemoveTodo = useCallback((id) =&gt; {
    dispatch({type: &#39;REMOVE&#39;, id});
  }, [])
}</code></pre>
<h1 id="불변성의-중요성">불변성의 중요성</h1>
<ul>
<li>기존의 값을 직접 수정하지 않으면서 새로운 값을 만들어 내는 것을 &#39;불변성을 지킨다.&#39;라고 한다.</li>
<li>리액트 컴포넌트에서 상태를 업데이트 할 때 불변성을 지키는 것은 매우 중요하다. 불변성이 지켜지지 않으면 객체 내부의 값이 새로워져도 바뀐 것을 감지하지 못한다.</li>
<li>전개 연산자를 사용하여 복사할 경우 얕은 복사가 된다. immer 같은 라이브러리를 사용하면 구조가 복잡한 객체를 매우 짧은 코드로 불변성을 유지하며 업데이트할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React.js - 컴포넌트 스타일링]]></title>
            <link>https://velog.io/@gyumin_2/React.js-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81</link>
            <guid>https://velog.io/@gyumin_2/React.js-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%8A%A4%ED%83%80%EC%9D%BC%EB%A7%81</guid>
            <pubDate>Fri, 20 May 2022 03:20:55 GMT</pubDate>
            <description><![CDATA[<h1 id="리액트-컴포넌트-스타일링">리액트 컴포넌트 스타일링</h1>
<ul>
<li>리액트에서는 아래 4가지 스타일링 방식을 가장 많이 사용한다.<ol>
<li>일반 CSS : 가장 기본적인 방식</li>
<li>Sass : CSS 전처리기(pre-process) 중 하나로 확장된 CSS 문법을 사용하여 CSS코드를 더욱 쉽게 작성 할 수 있게 해준다.</li>
<li>CSS Module : 스타일을 작성할 때 CSS 클래스가 다른 CSS 클래스의 이름과 절대 충돌하지 않도록 파일마다 고유한 이름을 자동으로 생성시켜주는 옵션</li>
<li>styled-components : 스타일을 자바스크립트 파일에 내장시키는 방식으로 스타일을 작성함과 동시에 해당 스타일이 적용된 컴포넌트를 만들 수 있게 해준다.</li>
</ol>
</li>
</ul>
<h1 id="일반-css">일반 CSS</h1>
<pre><code class="language-jsx">import &#39;./App.css&#39; // css 파일 import</code></pre>
<ul>
<li>CSS를 작성할 때 가장 중요한 점은 CSS 클래스 이름을 중복되지 않게 하는 것이다. 왜냐하면 일반 CSS 방식을 사용하면 다른 컴포넌트에도 영향을 미치기 때문이다.</li>
<li>CSS 클래스 이름 중복 사용을 방지하는 2가지 방식<ol>
<li>CSS 네이밍 규칙을 설정<ul>
<li>[컴포넌트이름-클래스이름] 형식으로 이름 짓기(예 : .App-header) 등으로 클래스 이름에 컴포넌트 이름을 포함하여 다른 컴포넌트에 실수로 중복되는 클래스를 만들어 사용하는 것을 방지할 수 있다.</li>
</ul>
</li>
<li>CSS Selector<ul>
<li>CSS Selector를 사용하여 CSS 클래스가 특정 클래스 내부에 있을 때만 스타일 적용하기</li>
</ul>
</li>
</ol>
</li>
</ul>
<h1 id="sass">Sass</h1>
<ul>
<li><p>create-react-app 2버전부터 별도 추가 설정 없이 Sass를 사용할 수 있다.</p>
</li>
<li><p>Sass를 사용하기 위해서는 node-sass라는 라이브러리를 설치해야한다.</p>
</li>
<li><p>Sass 팁 : 여러 파일에서 사용될 수 있는 변수 및 믹스인을 다른 파일에 작성한 후 import 하여 사용.</p>
</li>
<li><p>예</p>
<pre><code class="language-scss">  // src/styles/utils.scss

  // 변수 사용하기
  $red: #fa5252;
  $orange: #fd7e14;
  $yellow: #fcc419;
  $green: #40c057;
  $blue: #339af0;
  $indigo: #5c7cfa;
  $violet: #7950f2;

  // 믹스인 만들기 (재사용되는 스타일 블록을 함수처럼 사용 할 수 있음)
  @mixin square($size) {
    $calculated: 32px * $size;
    width: $calculated;
    height: $calculated;
  }

  // utils.cscc를 불러와서 사용할 scss
  @import &#39;styles/utils&#39;;
  .SassComponent {
    display: flex;
    background: $red;
    .box {
      background: red;
      cursor: pointer;
      transition: all 0.3s ease-in;
      }
  }</code></pre>
</li>
</ul>
<h2 id="sass-loader-설정-커스터-마이징">sass-loader 설정 커스터 마이징</h2>
<ul>
<li><p>프로젝트 디렉토리 구조가 깊어지면 Sass 파일을 불러올 때 상위폴더로 한참 올라가야하는 불편함이 있다. 웹팩에서 Sass를 처리하는 sass-loader의 설정을 커스터마이징하면 Sass파일을 조금 더편하게 불러올 수 있다.</p>
</li>
<li><p>create-react-app으로 만든 프로젝트는 프로젝트 구조의 복잡도를 낮추기 위해 세부 설정을 모두 숨겨 놓았다. 이런 경우를 위해 npm 패키지 내에 포함된 설정파일과 스크립트를 직접 수정할 수 있게 추출(eject)해주는 명령어가 존재한다. 프로젝트 루트 디렉토리에서 <code>npm run eject</code> 라고 입력하면 세부 설정을 밖으로 꺼낼 수 있다. 명령어 입력 전에 모든 파일을 커밋해야한다.</p>
</li>
<li><p>위 명령어 입력 후 생긴 config 폴더 내에 sebpack.config.js 파일을 열어 아래와 같이 수정한다.</p>
<pre><code class="language-scss">  // sassRegex 로 검색하여 아래 부분을 찾는다.
  {
    test: sassRegex,
    exclude: sassModuleRegex,
    use: getStyleLoaders(
          {
          importLoaders: 3,
          sourceMap: isEnvProduction &amp;&amp; shouldUseSourceMap
        },
          &#39;sass-loader&#39;
      ),
    sideEffects: true
  },

  // 위 부분을 아래와 같이 수정
  {
    test: sassRegex,
    exclude: sassModuleRegex,
    use: getStyleLoaders(
          {
          importLoaders: 3,
          sourceMap: isEnvProduction &amp;&amp; shouldUseSourceMap
        }
      ).concat(
          {
          loader: require.resolve(&#39;sass-loader&#39;),
          options: {
            sassOptions: {
              includePaths: [paths.appSrc + &#39;/styles&#39;]
            },
            sourceMap: isEnvProduction &amp;&amp; shouldUseSourceMap,
            prependData: `@import &#39;utils&#39;;` 
                  // 모든 Sass 파일에 utils를 import 하는 옵션
          }
        }
      ),
    sideEffects: true
  },</code></pre>
</li>
<li><p>수정 후에는 서버를 재시작해야 한다. <code>npm run eject</code> 입력 후 서버가 재시작 되지 않으면, node_modules 디렉토리를 삭제 후 <code>npm install</code> <code>npm start</code> 명령어를 입력해야한다.</p>
</li>
<li><p>위 과정이 끝나면 Sass 파일을 불러올 때 상위 경로 입력 없이 파일 이름만 작성하여 파일을 불러올 수 있다.</p>
</li>
</ul>
<h2 id="node_modules에서-라이브러리-불러오기">node_modules에서 라이브러리 불러오기</h2>
<ul>
<li><p>Sass의 장점 중 하나는 라이브러리를 쉽게 불러와서 사용할 수 있다는 것이다. npm을 통해 설치한 라이브러리는 Sass 파일 내부에서 상대 경로를 사용하여 npde_modules 까지 들어가서 불러와야한다. 이 방식은 매우 번거롭기 때문에 물결 문자를 사용해 자동으로 node_modules에서 라이브러리 디렉토리를 탐지하여 스타일을 불러올 수 있다.</p>
<pre><code class="language-scss">  // 라이브러리는 상대경로로 불러와야 한다.
  @import &#39;../../../node_modules/library/style&#39;;

  // ~ 을 사용하면 편하게 해당 디렉토리에 접근 가능
  @import &#39;~library/style&#39;;</code></pre>
</li>
</ul>
<h2 id="craco를-사용해-절대경로-설정하기">craco를 사용해 절대경로 설정하기</h2>
<ul>
<li><p>프로젝트의 규모가 커지면 sass 파일 뿐만 아니라 컴포넌트 파일까지 많아지게 되고 그로인해 파일을 불러오는데 많은 불편함이 생기게된다. 이러한 문제를 해결하기 위해서는 cra로 만든 프로젝트에서 웹팩 설정을 변경해줘야한다. 웹팩 설정을 변경하기 위해서는 위에서 말한 eject 명령어를 사용하면 되지만, eject 명령어를 사용한 커스터마이징 방식은 아래의 두 가지 문제점을 야기한다.</p>
<ol>
<li>페이스북이 cra를 업데이트 했을 때 프로젝트가 자동으로 업데이트 되지 않기 때문에, 개발자가 직접 모든 configuration을 유지보수해야한다.</li>
<li>One Build Dependency의 장점을 읽게된다.</li>
</ol>
</li>
<li><p>때문에 eject 명령어 없이 cra의 설정을 바꿀 수 있게 해주는 라이브러리인 craco가 등장했다. craco는 Create React App Configuration Override의 약자로 eject 명령어 없이 쉽게 cra 설정을 바꿀 수 있게 해주는 라이브러리이다.</p>
</li>
<li><p>설치</p>
<pre><code class="language-bash">  npm i --save craco-alias @craco/craco</code></pre>
</li>
<li><p>package.json의 script 변경</p>
<pre><code class="language-jsx">  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;craco start&quot;,
    &quot;build&quot;: &quot;craco build&quot;,
    &quot;test&quot;: &quot;craco test&quot;,
  }</code></pre>
</li>
<li><p>root 경로에 tsconfig.paths.json 파일 생성</p>
<pre><code class="language-jsx">  // 절대경로 설정 정보를 갖고 있는 파일
  // paths 형식 : path_alias: [실제경로]
  // path_alias로 상대경로가 아닌 절대경로 사용가능
  {
    &quot;compilerOptions&quot;: {
      &quot;baseUrl&quot;: &quot;./&quot;,
      &quot;paths&quot;: {
              &quot;styles/*&quot;: [&quot;src/styles/*&quot;], // src/style 내부에 있는 파일은 이제 style로 접근 가능 
        &quot;components/*&quot;: [&quot;src/components/*&quot;]
      }
    }
  }</code></pre>
</li>
<li><p>tsconfig.json 파일 수정</p>
<pre><code class="language-jsx">  {
    &quot;extends&quot;: &quot;./tsconfig.paths.json&quot;,
    &quot;comilerOptions&quot;: {
      // 생략...
    }
    // 생략...
  }</code></pre>
</li>
<li><p>root 경로에 croco.config.js 파일 생성</p>
<pre><code class="language-jsx">  const CracoAlias = require(&#39;craco-alias&#39;);

  module.exports = {
    plugins: [
      {
        plugin: CracoAlias,
        options: {
          source: &#39;tsconfig&#39;,
          baseUrl: &#39;./&#39;, // tsconfig.paths.json에 있는 baseUrl 경로값과 동일하게 설정.
          tsConfigPath: &#39;tsconfig.paths.json&#39;,
        },
      },
    ],
  };</code></pre>
</li>
</ul>
<h1 id="css-module">CSS Module</h1>
<ul>
<li><p>리액트 프로젝트에서 컴포넌트를 스타일링 할 때 CSS Module 이라는 기술을 사용하면, CSS 클래스가 중첩되는 것을 완벽히 방지할 수 있다.</p>
</li>
<li><p>CSS Module은 CSS를 불러와서 사용할 때 클래스 이름을 <code>[파일 이름]_[클래스 이름]_[해시값]</code> 형태의 고윳값으로 만들어 클래스 이름이 중첩되는 현상을 방지한다.</p>
</li>
<li><p>create-react-app 2버전부터는 css-loader 설정이 자동으로 되어있기 때문에 따로 설정을 할 필요가 없다. 단지 파일을 <code>.module.css</code> 확장자로 저장하면 CSS Module가 적용된다.</p>
</li>
<li><p>CSS Module을 사용하면 import한 컴포넌트에만 스타일이 적용된다. 만약 특정 클래스가 웹페이지 전역적으로 사용된다면 <code>:global</code> 키워드를 앞에 입력하면 된다.</p>
</li>
<li><p>예</p>
<pre><code class="language-scss">  /* 자동으로 고유해질 것이므로 흔히 사용되는 단어를 클래스 이름으로 마음대로 사용가능*/

  .wrapper {
    background: black;
    padding: 1rem;
    color: white;
    font-size: 2rem;
  }

  /* 글로벌 CSS 를 작성하고 싶다면 */
  :global {
    // :global {} 로 감싸기
    .something {
      font-weight: 800;
      color: aqua;
    }
  }</code></pre>
</li>
</ul>
<ul>
<li><p>CSS Module이 적용된 스타일 파일을 불러오면 객체를 전달받는데, CSS Module에서 사용한 클래스 이름과 해당 이름을 고유화한 값이 키-값 형태로 들어있다.</p>
</li>
<li><p>고유화한 클래그 이름을 사용하려면 JSX 엘리먼트에 className={style.[클래스 이름]} 형태로 전달해 주면 된다. :global을 사용한 클래스의 경우 기존 방식을 사용하면 된다.</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  import styles from &#39;./CSSModule.module.scss&#39;;

  const CSSModule = () =&gt; {
    return ( // 클래스 이름을 두 개 이상 적용 시 템플릿 리터럴 사용
      &lt;div className={`${styles.wrapper} ${styles.box}`}&gt;
        안녕하세요, 저는 &lt;span className=&quot;something&quot;&gt;CSS Module!&lt;/span&gt;
      &lt;/div&gt;
    );
  };

  export default CSSModule;</code></pre>
</li>
</ul>
<h2 id="classnames-사용하기">classnames 사용하기</h2>
<ul>
<li><p>classnames는 여러 클래스를 적용하고, 클래스를 조건부로 설정할 때 유용한 라이브러리다. <code>npm install classnames</code> 로 라이브러리 설치</p>
</li>
<li><p>classNames 기본적인 사용법</p>
<pre><code class="language-jsx">  import classNames from &#39;classnames&#39;;

  // 함수의 매개변수에 원하는 클래스 이름 전달
  classNames(&#39;one&#39;, &#39;two&#39;); // one, two

  // 객체를 매개변수로 전달 - 조건부 클래스 설정
  classNames(&#39;one&#39;, { two: true}); // one, two
  classNames(&#39;one&#39;, { two: false}); // one

  // 배열을 매개변수로 전달
  classNames(&#39;one&#39;, [ &#39;two&#39;, &#39;three&#39;]); // one, two, three

  // 외부 변수를 매개변수로 전달
  const myClass = &#39;hello&#39;
  classNames(&#39;one&#39;, myClass, { myCondition: true }) // one, hello, myCondition

  // props 값에 따른 스타일링
  const MyComponent = ({ highlighted, them }) =&gt; (
      &lt;div className={ classNames(&#39;MyComponent&#39;, { highlighted }, them) }&gt;
          Hello
      &lt;/div&gt;
  )</code></pre>
</li>
</ul>
<ul>
<li><p>CSS Module과 classnames를 함께 사용하면 CSS Module 사용이 훨씬 쉬워진다. classnames에 내장되어 있는 <code>bind</code> 함수를 사용하면 클래스를 넣어줄 때 마다 <code>style.[클래스 이름]</code> 형식으로 코드를 작성하지 않아도 된다. 사전에 미리 style 객체에서 클래스를 받아 온 후 사용하게끔 설정해두고 <code>cx(&#39;클래스1&#39;, &#39;클래스2&#39;)</code> 형식으로 사용할 수 있다.</p>
<pre><code class="language-jsx">  import React from &#39;react&#39;;
  import classNames from &#39;classnames/bind&#39;;
  import styles from &#39;./CSSModule.module.scss&#39;;

  const cx = classNames.bind(styles); // 미리 styles 에서 클래스를 받아오도록 설정하고

  const CSSModule = () =&gt; {
    return (
      &lt;div className={cx(&#39;wrapper&#39;, &#39;inverted&#39;)}&gt;
        안녕하세요, 저는 &lt;span className=&quot;something&quot;&gt;CSS Module!&lt;/span&gt;
      &lt;/div&gt;
    );
  };

  export default CSSModule;</code></pre>
</li>
</ul>
<ul>
<li>Sass또한 <code>.module.scss</code> 로 저장하면 CSS Module를 사용할 수 있다.</li>
</ul>
<h1 id="styled-components">styled-components</h1>
<ul>
<li><p>컴포넌트 스타일링의 또 다른 패러다임은 자바스크립트 안에서 스타일링을 선언하는 CSS-in-JS 방식이다. 이와 관련된 라이브러리 중에서는 styled-component와 emotion이 가장 많이 사용되며, 이번 챕터에서는 styled-component에 대해서만 알아보겠다.(이 둘의 사용방식은 꽤 비슷하다.)</p>
</li>
<li><p>설치</p>
<pre><code class="language-bash">  npm i styled-components</code></pre>
</li>
</ul>
<h2 id="taggeg-템플릿-리터럴">Taggeg 템플릿 리터럴</h2>
<ul>
<li><p>styled-component에서는 Tagged 템플릿 리터럴이라는 문법을 사용한다. 그냥 템플릿 리터럴과 다른 점은 템플릿 안에 자바스크립트 객체나 함수를 전달할 때 온전히 추출할 수 있다는 것이다.</p>
</li>
<li><p>템플릿에 객체를 넣거나 함수를 넣으면 원래 형태를 잃어버린다. 객체는 <code>[object Object]</code>로 변환되고, 함수는 함수 내용이 그대로 문자열화 되어 나타난다.</p>
<pre><code class="language-jsx">  `hello ${{foo: &#39;bar&#39;}} ${() =&gt; &#39;world&#39;}`;
  // 결과 : &quot;hello [object Object] () =&gt; &#39;world&#39;&quot;</code></pre>
</li>
<li><p>만약 아래와 같이 함수를 작성하고, 해당 함수 뒤에 템플릿 리터럴을 넣어준다면, 템플릿 안에 넣은 값을 온전히 추출 할 수 있다.</p>
<pre><code class="language-jsx">  function tagged(...args) { console.log(args); }

  tagged`hello ${{foo: &#39;bar&#39;}} ${() =&gt; &#39;world&#39;}`;</code></pre>
</li>
<li><p>Tagged 템플릿 리터럴을 사용하면 이렇게 템플릿 사이에 있는 자바스크립트 객체나 함수의 원본 값을 그대로 추출 할 수 있다. styled-component는 이러한 속성을 사용하여 styled-component로 만든 컴포넌트의 props를 스타일 쪽에 쉽게 조회할 수 있도록 해준다.</p>
</li>
</ul>
<h2 id="styled-components를-사용해-엘리먼트에-스타일입히기">styled-components를 사용해 엘리먼트에 스타일입히기</h2>
<pre><code class="language-jsx">import styled from &#39;styled-components&#39;; // 1. styled 불러오기

// 2. styled.태그명 형식으로 메소드 호출
// 3. Tagged 템플릿 리터럴 문법을 통해 역따옴표 안에 스타일 작성
const MyComponent = stlyed.div`
    font-size: 2rem;
`;
// 위 스타일이 적용된 div로 이뤄진 리액트 엘리먼스 생성

// 4. 생성된 엘리먼트를 아래와 같은 형태로 사용
&lt;MyComponent&gt;Hello&lt;/MyComponent&gt;

// div가 아닌 다른 엘리먼트에 스타일을 입히고 싶다면 styled.button, styled.input 형식으로
// styled.태그명을 사용하면된다.</code></pre>
<ul>
<li><p>사용해야할 태그명이 유동적이거나 특정 컴포넌트 자체에 스타일링을 주고 싶다면 아래와 같은 형태로 구현할 수 있다.</p>
<pre><code class="language-jsx">  // 태그의 타입을 styled 함수의 인자로 전달
  const MyInput = styled(&#39;input&#39;)`
      background: gray;
  `;

  // 컴포넌트를 styled 함수의 인자로 전달
  // 이 방식을 사용할 경우 해당 컴포넌트에 className props를 최상위 DOM의 className 값으로 설정해야 한다.
  const MyComponent = ({className}) =&gt; {
      return &lt;div className={className}&gt;Hello&lt;/div&gt;;
  };

  const StyledMyComponent = styled(MyComponent)`
      color: red;
      font-size: 2rem;
  `;</code></pre>
</li>
</ul>
<h2 id="styled-compoents에서-props-사용하기">styled-compoents에서 props 사용하기</h2>
<ul>
<li><p>스타일에서 props 값 참조</p>
<pre><code class="language-jsx">  const Box = styled.div`
      /* props로 넣어준 값을 직접 전잘해줄 수 있다. */
      background: ${props =&gt; props.color || &#39;blue&#39;};
      padding: 1rem;
      display: flex;
  `;

  // JSX에서 사용 시 color 값을 props로 넣어준다.
  &lt;Box color=&quot;red&quot;&gt;&lt;/Box&gt;</code></pre>
</li>
<li><p>props에 다른 조건부 스타일링</p>
<pre><code class="language-jsx">  import styled, { css } from &#39;styled-components&#39;;
  // 단순 변수의 형태가 아니라 여러 줄의 스타일 구문을 조건부로 설정하는 경우
  // styled-components의 css를 불러와야한다.

  const Button = styled.button`
      background: black;
      color: white;
      border-radius: 4px;
      padding: 0.5rem;

      /* &amp; 를 사용하여 sass처럼 자기자신 선택 가능 */
      &amp;:hover { background: rgba(255, 255, 255, 0.9); }

      /* inverted 값이 true일 경우 특정 스타일 부여 */
      ${ props =&gt;
              props.inverted &amp;&amp;
              css`
                  background: white;
                  border: 2px solid black;
                  &amp;:hover {
                      background: white;
                      color: black;
                  }
              `
      }
  `;

  // JSX
  &lt;Button&gt;Hello&lt;/Button&gt;
  &lt;Button invertes={true}&gt;태두리만&lt;/Button&gt;

  // 스타일 코드 여러줄을 props에 따라 넣어주어야 할 경우 styled-components의 css가 필요하다.
  // css 없이 바로 아래와 같이 문자열을 넣어도 작동은한다.

  // 하지만 해당 내용이 </code></pre>
<ul>
<li><p>스타일 코드 여러줄을 props에 따라 넣어주어야 할 경우 styled-components의 css가 필요하다. css 없이 바로 아래와 같이 문자열을 넣어도 작동은한다.</p>
<pre><code class="language-jsx">  ${ props =&gt;
          props.inverted &amp;&amp;
          `
              background: none;
              border: 2px solid white;
              &amp;:hover {
                  background: white;
                  color: black;
              }
          `
      }
  `;</code></pre>
</li>
<li><p>그러나 이 방식은 아래의 2가지 문제점이 있다.</p>
<ol>
<li>해당 내용이 문자열로 취급되기 때문에 VsCode 확장프로그램에서 신텍스 하이라이팅이 이뤄지지 않음</li>
<li>Tagged 템플릿 리터럴이 아니기 때문에 함수를 받아 사용하지 못한다. 즉 해당 부분에서는 props 값을 사용하지 못한다.</li>
</ol>
</li>
<li><p>만약 조건부 스타일링을 할 때 여러 줄의 코드에서 props를 잠조하지 않는 다면 굳이 css를 불러와서 사용하지 않아도 된다. 하지만 props를 참조한다면 반드시 css로 감싸주어 Tagged 템플릿 리터럴을 사용해야 한다.</p>
</li>
</ul>
</li>
</ul>
<h2 id="반응형-디자인">반응형 디자인</h2>
<ul>
<li><p>미디어쿼리 사용</p>
<pre><code class="language-jsx">  const Box = styled.div`
      background: ${props =&gt; props.color || &#39;blue&#39;};
      padding: 1rem;
      display: flex;
      width: 1024px;
      margin: 0 auto;
      @media (max-width: 1024px) { width: 768px; }
      @medis (max-width: 768px) { width: 100%; }
  `;</code></pre>
<ul>
<li>이 방식은 일반 css와 큰 차이는 없지만, 여러 컴포넌트에서 반복해야한다면 귀찮아질 수 있다. 이 작업을 함수화하여 사용하면 간편하게 반응형 작업을 할 수 있다.</li>
</ul>
</li>
<li><p>미디어쿼리 유틸함수 만들기</p>
<pre><code class="language-jsx">  import styled, { css } from &#39;styled-components&#39;;

  const sizes = {
      desktop: 1024,
      tablet: 768
  };

  const media = Object.keys(sizes).reduce((acc, label) =&gt; {
      acc[label] = (...args) =&gt; css`
          @media (max-width: ${sizes[label] / 16}em) {
              ${css(...args)};
          }
      `;

      return acc;
  }, {});

  const Box = styled.div`
      background: ${props =&gt; props.color || &#39;blue&#39;};
      padding: 1rem;
      display: flex;
      width: 1024px;
      margin: 0 auto;
      ${media.desktop`width: 768px;`};
      ${media.tablet`width: 100%;`};
  `;</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React.js - 리액트 훅에 대해 알아보자]]></title>
            <link>https://velog.io/@gyumin_2/React.js-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@gyumin_2/React.js-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 02 May 2022 09:01:10 GMT</pubDate>
            <description><![CDATA[<h1 id="hook이란">Hook이란?</h1>
<ul>
<li>Hook은 리액트 v16.8에 새로 도입된 기능으로 함수 컴포넌트에서 React state와 생명주기 기능(lifecycle features)을 “연동(hook into)“할 수 있게 해주는 함수다. Hook은 class 안에서 동작하지 않으며, class를 사용하지 않고 React를 사용할 수 있게 해준다.</li>
</ul>
<h1 id="usestate">useState</h1>
<pre><code class="language-jsx">import React, { useState } from &#39;react&#39;; // import 구문을 통해 불러오기

const [value, setValue] = useState(&#39;초기값&#39;); 
// 비구조화 할당으로 useState() 함수의 리턴값을 변수에 저장 </code></pre>
<ul>
<li><p>가장 기본적인 Hook이며 함수형 컴포넌트에서도 가변적인 state를 지닐 수 있게 해준다. 클래스 컴포넌트의 <code>this.state</code>가 제공하는 기능과 똑같다. 일반적으로 일반 변수는 함수가 끝날 때 사라지지만, state 변수는 React에 의해 사라지지 않는다. 변수 명은 개발자가 원하는대로 지어도 된다.</p>
</li>
<li><p>state의 초깃값을 매개변수로 전달받아 배열을 반환한다. 클래스 컴포넌트에서 state 초깃값은 객체여야 하지만, <code>useState()</code> 에서 초깃값의 형태는 자유다.</p>
</li>
<li><p><code>useState()</code> 는 길이가 2인 배열을 반환한다. 배열의 첫 번째 원소는 현재 상태이고, 두 번째 원소는 상태를 설정하는 함수(setter)다.</p>
</li>
<li><p><code>useState()</code> 예제</p>
<pre><code class="language-jsx">  import React, { useState } from &#39;react&#39;;

  const Info = () =&gt; {
      // useState()를 여러개 사용할 경우
      const [ name, setName ] = useState(&#39;&#39;);
      const [ number, setNumber ] = useState(&#39;&#39;);

      const changeName = e =&gt; {
          setName(e.target.value);
      }

      const changeNumber = e =&gt; {
          setNumber(e.target.value);
      }
      return (
          &lt;div&gt;
              &lt;input value={name} onChange={changeName}/&gt;
              &lt;input value={number} onChange={changeNumber}/&gt;
              &lt;h1&gt;{name}&lt;/h1&gt;
              &lt;h1&gt;{number}&lt;/h1&gt;
          &lt;/div&gt;
      );
  };

  export default Info;</code></pre>
</li>
</ul>
<h1 id="useeffect">useEffect</h1>
<pre><code class="language-jsx">import React, { useEffect } from &#39;react&#39;;

useEffect(effects[, deps]);</code></pre>
<ul>
<li>컴포넌트가 렌더링 될 때 마다 특정 작업을 수행하도록 설정하는 Hook. <code>useEffect()</code> 는 함수 컴포넌트 내에서 이런 side effects를 수행할 수 있게 해준다. class 컴포넌트의 <code>componentDidMount</code> 나 <code>componentDidUpdate</code>, <code>componentWillUnmount</code>와 같은 목적으로 제공되지만, 하나의 API로 통합된 것이다.</li>
</ul>
<blockquote>
<p>💡 Side Effect란?</p>
<p>일반적으로 side effect는 부작용, 의도하지 않는 결과를 의미한다. 컴퓨터 공학에서의 side effect란 함수의 로컬 상태를 함수 외부에서 변경하는 것을 말한다. 함수를 입력값에 대해 일정한 출력을 하는 것으로 가정할 때, 출력값에 영향을 미치지 않는 모든 작업들, 예로들어 로그찍기, 네트워크 통신, API 호출 등을 side effect라고 할 수 있다.</p>
<p>이 개념을 react에 적용하면 컴포넌트 안에서 데이터를 가져오거나 구독하고, DOM을 직접 조작하는 작업을 “side effects”(또는 짧게 “effects”)라고 할 수 있다. 왜냐하면 이러한 작업들은 다른 컴포넌트에 영향을 줄 수도 있고, 렌더링 과정에서는 구현할 수 없는 작업이기 때문이다.</p>
</blockquote>
<ul>
<li><code>useEffect()</code> 는 effects 함수를 첫 번째 매개변수로 전달받아, 컴포넌트가 렌더링 될 때마다 effects 함수를 실행한다.</li>
<li><code>useState()</code>와 같이 중복 사용이 가능하다.</li>
<li><code>useEffect()</code>는 두 번째 매개변수로 배열을 받는다. 이 배열에는 <code>useEffect()</code> 함수가 변화를 감지할 state를 요소로 넣는다.</li>
<li>빈배열을 매개변수로 넣으면 컴포넌트가 화면에 맨 처음 렌더링 될 때만 effects 함수가 실행 된다.</li>
<li>특정 state를 배열의 요소에 넣으면, 해당 state가 변경될 때만 effects 함수가 실행된다.</li>
</ul>
<h2 id="뒷정리clean-up">뒷정리(clean-up)</h2>
<ul>
<li><p><code>useEffect()</code>는 기본적으로 렌더링되고 난 직후마다 실행되며, 두 번째 매개변수 배열에 무엇을 넣는지에 따라 실행되는 조건이 달라진다. 컴포넌트가 언마운트 되기 전이나 업데이트 직전에 어떠한 작업을 수행하고 싶다면 <code>useEffect()</code>함수에서 뒷정리(clean-up) 함수를 반환해야 한다.</p>
</li>
<li><p>뒷정리 함수는 컴포넌트가 마운트 되기 전과 업데이트 전에 실행되며, 업데이트 전에 실행될 경우 업데이트 직전 state 값에 접근할 수 있다.</p>
</li>
<li><p>뒷정리 함수는 보통 데이터 구독 취소 등 뒷정리가 필요한 경우에만 사용한다.</p>
</li>
<li><p><code>useEffect()</code> 사용 예</p>
<pre><code class="language-jsx">  import React, { useState, useEffect } from &#39;react&#39;;

  const Info = () =&gt; {
      const [ name, setName ] = useState(&#39;&#39;);

      const changeName = e =&gt; {
          setName(e.target.value);
      }

      useEffect(() =&gt; {
          console.log(&#39;effct!&#39;);
          console.log(`name : ${name}`);

          return () =&gt; { // 언마운트전, 업데이트전만 실행
              console.log(&#39;clean&#39;);
              console.log(`name : ${name}`); // 업데이트 직전 값에 접근
          }
      }, [name]) // name이 변경될 때만 effects 함수 실행

      return (
          &lt;div&gt;
              &lt;input value={name} onChange={changeName}/&gt;
              &lt;h1&gt;{name}&lt;/h1&gt;
          &lt;/div&gt;
      );
  };

  export default Info;</code></pre>
</li>
</ul>
<h2 id="useeffect-사용-시기">useEffect 사용 시기</h2>
<ul>
<li>렌더링 시(마운트 될 때, 업데이트 될 때) 특정 작업하고 싶을 때 사용</li>
<li>언 마운트 전, 업데이트 전 특정 작업을 하고 싶을 때는 뒷정리 함수 사용</li>
<li>주로 마운트 시에 하는 작업<ul>
<li><code>props</code> 로 받은 값을 컴포넌트의 로컬 상태로 설정</li>
<li>외부 API 요청 (REST API 등)</li>
<li>라이브러리 사용 (D3, Video.js 등...)</li>
<li>setInterval 을 통한 반복작업 혹은 setTimeout 을 통한 작업 예약</li>
</ul>
</li>
<li>주로 언마운트 시 하는 작업<ul>
<li>setInterval, setTimeout 을 사용하여 등록한 작업들 clear 하기 (clearInterval, clearTimeout)</li>
<li>라이브러리 인스턴스 제거</li>
</ul>
</li>
</ul>
<h1 id="usereduce">useReduce</h1>
<pre><code class="language-jsx">import React, { useReducer } from &#39;react&#39;;

const [state, dispatch] = useReducer(reducer, initialArg);</code></pre>
<ul>
<li><code>useState()</code>의 확장형 대체 함수로, 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 할 때 주로 사용하는 Hook.</li>
<li>다수의 하윗값을 포함하는 복잡한 정적 로직을 만드는 경우나 다음 state가 이전 state에 의존적인 경우, state를 다루는 경우 등 주로 사용한다.</li>
<li>reducer 함수와 초기값(initialArg)을 전달받아 상태(state)와 액션을 발생시키는 함수(dispatch)가 담긴 배열을 반환한다.</li>
</ul>
<h2 id="reducer">reducer</h2>
<pre><code class="language-jsx">(state, action) =&gt; newState</code></pre>
<ul>
<li><code>useReducer()</code> 함수의 첫 번째 매개변수. 현재 상태(state)와 업데이트를 위해 필요한 정보(action)을 매개변수로 전달받아 새로운 상태를 반환하는 함수.</li>
<li>action의 type은 상관없으며, reducer 함수는 컴포넌트 밖에서 선언이 가능하다.</li>
</ul>
<h2 id="dispatch">dispatch</h2>
<pre><code class="language-jsx">dispatch(action);</code></pre>
<ul>
<li>액션값을 매개변수로 전달받아 reducer 함수를 호출 기능의 함수.</li>
</ul>
<h2 id="usereducer-예제">useReducer 예제</h2>
<pre><code class="language-jsx">import React, { useState, useEffect } from &#39;react&#39;;

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case &#39;increment&#39;:
      return {count: state.count + 1};
    case &#39;decrement&#39;:
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    &lt;&gt;
      Count: {state.count}
      &lt;button onClick={() =&gt; dispatch({type: &#39;decrement&#39;})}&gt;-&lt;/button&gt;
      &lt;button onClick={() =&gt; dispatch({type: &#39;increment&#39;})}&gt;+&lt;/button&gt;
    &lt;/&gt;
  );
}

export default Counter;</code></pre>
<h2 id="여러-state를-다루는-usereducer-예">여러 state를 다루는 useReducer 예</h2>
<pre><code class="language-jsx">import React, { useState, useEffect, useReducer } from &#39;react&#39;;

function reducer (state, action) {
    return {
        ...state,
        [action.name]: action.value
    }
}

const Info = () =&gt; {
    const [state, dispatch] = useReducer(reducer, {
        name: &#39;&#39;,
        number: &#39;&#39;
    })

    const changeValue = (e) =&gt; {
        dispatch(e.target)
    }

    return (
        &lt;div&gt;
            &lt;input name=&quot;name&quot; value={state.name} onChange={changeValue}/&gt;
            &lt;input name=&quot;number&quot; value={state.number} onChange={changeValue}/&gt;
            &lt;h1&gt;{state.name}&lt;/h1&gt;
            &lt;h1&gt;{state.number}&lt;/h1&gt;
        &lt;/div&gt;
    );
};

export default Info;</code></pre>
<h1 id="usememo">useMemo</h1>
<pre><code class="language-jsx">import React, { useMemo } from &#39;react&#39;;

const memoizedValue = useMemo(() =&gt; fn(a, b), [a, b]);</code></pre>
<ul>
<li><p>컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술을 메모이제이션(Momoization)이라고 한다.</p>
</li>
<li><p><code>useMemo()</code>는 메모이제이션된 값을 반환하여 동일 계산의 반복 수행을 최소화하기 위한 Hook이다. <code>useMome()</code>는 특정 데이터가 변경 되었을 때에만 매개변수로 전달받은 함수를 실행한다. 이 최적화는 모든 렌더링 시의 고비용 계산을 방지하게 해준다.</p>
</li>
<li><p><code>useMemo()</code>에 전달된 함수는 렌더링 중에 실행되며, 사이드 이펙트(DOM조작, 데이터 불러오기 등)같이 렌더링 중에 할 수 없는 작업들은 함수 내에 작성하지 않는다. (사이드 이펙트를 다루는 작업은 useEffect에서 해야한다.)</p>
</li>
<li><p><code>useMemo()</code>는 렌더링하는 과정에서 특정 값이 변경되었을 때만 연산을 실행하고, 값이 변경되지 않았다면 이전에 연산했던 결과를 다시 사용한다.</p>
</li>
<li><p>의미상의 보장이 아닌 성능 최적화로 <code>useMemo()</code>를 사용할 수 있다. 리액트는 <code>useMemo()</code>의 메모이제이션 방식을 변경할 수도 있기 때문에, <code>useMemo()</code>를 사용하지 않고도 동작할 수 있도록 코드를 작성하고, 작성된 코드를 추가하여 성능을 최적화 할 것을 권장하고 있다.</p>
</li>
<li><p><code>useMemo()</code>는 첫 번째 매개변수로 값이 변경 될 때마다 실행될 함수를, 두 번째 매개변수로는 배열을 갖는다. 이 배열에는 어떤 state가 변경되었을 때 첫 번째 매개변수를 실행할 지를 명시해야한다.(리액트에서는 이를 의존성 값이라고 한다.) 두 번째 매개변수가 없는 경우 매 렌더링 때마다 새 값을 계산한다.</p>
</li>
<li><p>실행함수는 두 번째 매개변수에서 할당한 값을 매개변수로 갖는다. 위 코드에서는 a, b가 변결될 때만 <code>fn()</code> 함수가 실행된다.</p>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  import React, { useState, useMemo} from &#39;react&#39;;

  const getAverage = numbers =&gt; {
      console.log(&#39;평균값 계산중&#39;);

      const length = numbers.length;

      if(length == 0) return;

      const sum = numbers.reduce((a, b) =&gt; a + b);
      return sum/length;
  }

  const Info = () =&gt; {
      const [ list, setList ] = useState([]);
      const [ number, setNumber ] = useState(&#39;&#39;);

      const changeValue = (e) =&gt; {
          setNumber(e.target.value);
      }

      const addList = () =&gt; {
          const nextList = [...list, parseInt(number)];

          setList(nextList);
          setNumber(&#39;&#39;);
      }

      const avg = useMemo(() =&gt; getAverage(list), [list])

      return (
          &lt;div&gt;
              &lt;input name=&quot;number&quot; value={number} onChange={changeValue}/&gt;
              &lt;button onClick={addList}&gt;추가&lt;/button&gt;
              &lt;ul&gt;
                  {
                      list.map((number, index) =&gt; 
                          &lt;li key={index}&gt;{number}&lt;/li&gt;
                      )
                  }
              &lt;/ul&gt;
              &lt;div&gt;평균값 : {avg}&lt;/div&gt;
          &lt;/div&gt;
      );
  };

  export default Info;</code></pre>
</li>
</ul>
<h1 id="usecallback">useCallback</h1>
<pre><code class="language-jsx">import React, { useCallback } from &#39;react&#39;;

const memoizedCallback = useCallback(() =&gt; fn(a, b), [a, b]);</code></pre>
<ul>
<li>컴포넌트에 만들어진 함수는 컴포넌트가 리렌더링 될 때마다 새로 만들어진 함수를 사용한다. 대부분 이러한 방식은 크게 문제가 없지만, 컴포넌트의 렌더링이 자주 발생하거나 렌더링해야할 컴포넌트의 개수가 많아지면 최적화가 필요하다.</li>
<li>현재 하위 컴포넌트에 전달하는 콜백 함수를 inline 함수로 사용하고 있다거나, 컴포넌트 내에서 함수를 생성하고 있다면 (프로그래밍 구동에는 문제가 없겠지만) 새로운 함수 참조 값을 계속해서 만들고 있는 것, 다시 말해 똑같은 모양의 함수를 계속해서 만들어 메모리에 계속해서 할당하고 있다는 것을 뜻한다.</li>
<li>특히 함수를 props로 전달받는 경우, 상위 컴포넌트의 state가 변경되면 상위 컴포넌트가 리렌더링 되며 하위 컴포넌트에 넘겨주는 props가 새롭게 생성되고 call by reference에 의해 새로운 props가 이전 props와 다르다고 판단해 리렌더링을 일으킨다. 의존성에 포함된 값이 변경되지 않는다면 이전에 생성한 함수 참조 값을 반환해주는 것이 <code>useCallback()</code>이다. 즉 컴포넌트의 props로 넘겨주는 함수는 useCallback 사용해야 최적화가 된다.</li>
<li><code>useMemo()</code>는 메모이제이션된 값을 반환하여 동일 계산 반복수행을 최소화해 최적화를 한다면, <code>useCallback()</code>는 메모이제이션된 콜백을 반환하여 새로운 함수가 생성되는 것을 줄여 최적화를 한다.</li>
<li>위의 코드에서 memoizedCallback에 할당되는 값은 a, b 값이 수정될 때에만 inline callback 이 새로 생성되는 함수다. 즉 함수를 생성하는 함수인 것이다. 이때 의존성 값에 빈 배열을 주면 최초에 생성된 함수를 지속적으로 기억한다.</li>
<li>다시 말해 <code>useCallback()</code>은 최초에 혹은 특정 조건에서 생성한 함수의 참조를 기억하여 반환해주는 hook이다. 새로 생성되지 않는다함은 메모리에 새로 할당되지 않고 동일 참조 값을 사용하게 된다는 것을 의미하고, 이는 최적화된 하위 컴포넌트에서 불필요한 렌더링을 줄일 수 있다는 것을 뜻한다.</li>
</ul>
<h2 id="인라인-함수-방식의-문제점">인라인 함수 방식의 문제점</h2>
<pre><code class="language-jsx">const Parent = () =&gt; {
  return (
    &lt;&gt;
      &lt;Child onClick={() =&gt; console.log(&#39;callback&#39;)}/&gt;
      &lt;Child onClick={() =&gt; console.log(&#39;callback&#39;)}/&gt;
      &lt;Child onClick={() =&gt; console.log(&#39;callback&#39;)}/&gt;
      &lt;Child onClick={() =&gt; console.log(&#39;callback&#39;)}/&gt;
      &lt;Child onClick={() =&gt; console.log(&#39;callback&#39;)}/&gt;
      ... // 똑같은 함수가 리랜더링 될 때마다 계속해서 재 생성된다.
    &lt;/&gt;
  );
};
// 렌더되는 Child 컴포넌트의 수 만큼 인라인 함수가 생성된다. 
const Child = ({onClick}) =&gt; {
  return &lt;button onClick={onClick}&gt;Click Me!&lt;/button&gt;
};</code></pre>
<ul>
<li>위와 같이 child 컴포넌트의 클릭 이벤트로 콜백 함수를 넘겨주는 상황에서 인라인 함수를 사용하게 된다면 Child컴포넌트가 렌더되는 모든 시점에 새로 메모리에 할당되는 똑같은 모양의 인라인 함수들이 계속해서 재생성된다.</li>
</ul>
<h2 id="내부-함수-방식의-문제점">내부 함수 방식의 문제점</h2>
<pre><code class="language-jsx">const Parent = () =&gt; {
  const _onClick = () =&gt; {
    console.log(&#39;callback&#39;);
  };

  return (
    &lt;&gt;
      &lt;Child onClick={_onClick}/&gt;
      &lt;Child onClick={_onClick}/&gt;
      &lt;Child onClick={_onClick}/&gt;
      ... // 로컬 함수인 _onClick()을 하위 컴포넌트가 참조하고 있다.
    &lt;/&gt;
  );
};

// Child이 여러 번 생성되더라도 onClick props으로 전달되는 _onClick 함수는 한번만 생성된다. 
const Child = ({onClick}) =&gt; {
  return &lt;button onClick={onClick}&gt;Click Me!&lt;/button&gt;
};</code></pre>
<ul>
<li>위 코드처럼 내부 함수를 생성하여 하위 컴포넌트에 props로 내려주는 이 방식은 인라인 함수 방식보다는 나은 방식이다. 하지만 내부 함수를 감싸고 있는 함수가 리렌더링 될 때, 내부 함수 역시 새로만들어 진다. 즉 위 코드의 Parent 컴포넌트가 리랜더링 되면 내부 함수 <code>_onClick()</code> 함수가 새로 생성되는 것이다.</li>
</ul>
<h2 id="usecallback-적용">useCallback 적용</h2>
<pre><code class="language-jsx">const Root = () =&gt; {
  const [isClicked, setIsClicked] = useState(false);
  const _onClick = useCallback(() =&gt; {
    setIsClicked(true);
  }, []); 
// dependency가 없으므로 Root component가 렌더링 되는 최초에 한번만 생성되며 
// 이후에는 동일한 참조 값을 사용한다.

  return (
    &lt;&gt;
      &lt;Child onClick={_onClick}/&gt;
      &lt;Child onClick={_onClick}/&gt;
      &lt;Child onClick={_onClick}/&gt;
      ...
    &lt;/&gt;
  );
};

// Root와 Child가 여러번 렌더링 되더라도 onClick props으로 전달되는 _onClick 함수는 
// 한번만 생성되므로 계속해서 동일 참조 값을 가진다. 
const Child = ({onClick}) =&gt; {
  return &lt;button onClick={onClick}&gt;Click Me!&lt;/button&gt;
};</code></pre>
<ul>
<li><p>예</p>
<pre><code class="language-jsx">  import React, { useState, useMemo, useCallback} from &#39;react&#39;;

  const getAverage = numbers =&gt; {
      console.log(&#39;평균값 계산중&#39;);

      const length = numbers.length;

      if(length == 0) return;

      const sum = numbers.reduce((a, b) =&gt; a + b);
      return sum/length;
  }

  const Info = () =&gt; {
      const [ list, setList ] = useState([]);
      const [ number, setNumber ] = useState(&#39;&#39;);

      const changeValue = useCallback((e)=&gt;{
          setNumber(e.target.value);
      }, [])
      // 기존 값을 조회하지 않고 설정만하기 때문에
      // 해당 컴포넌트가 처음 렌더링 될 때 생성된 함수를 사용

      const addList = useCallback(()=&gt;{
          const nextList = [...list, parseInt(number)];
          setList(nextList);
          setNumber(&#39;&#39;);
      }, [number, list])
      // number, list를 조회하여 새로운 배열을 생성하기 때문에
      // 의존성 값에 number와 list를 추가한다.

      const avg = useMemo(() =&gt; getAverage(list), [list])

      return (
          &lt;div&gt;
              &lt;input name=&quot;number&quot; value={number} onChange={changeValue}/&gt;
              &lt;button onClick={addList}&gt;추가&lt;/button&gt;
              &lt;ul&gt;
                  {
                      list.map((number, index) =&gt; 
                          &lt;li key={index}&gt;{number}&lt;/li&gt;
                      )
                  }
              &lt;/ul&gt;
              &lt;div&gt;평균값 : {avg}&lt;/div&gt;
          &lt;/div&gt;
      );
  };

  export default Info;</code></pre>
</li>
</ul>
<ul>
<li>참고 : <a href="https://velog.io/@yejinh/useCallback%EA%B3%BC-React.Memo%EC%9D%84-%ED%86%B5%ED%95%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94">https://velog.io/@yejinh/useCallback과-React.Memo을-통한-렌더링-최적화</a></li>
</ul>
<h1 id="useref">useRef</h1>
<pre><code class="language-jsx">import React, { useRef } from &#39;react&#39;;

const refContainer = useRef(initialValue);</code></pre>
<ul>
<li><p><code>useRef()</code> Hook은 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있게 해준다.</p>
</li>
<li><p><code>React.createRef()</code>로 ref 객체를 생성하고 해당 객체의 current 프로퍼티를 이용해 해당 DOM을 조작하는 방식과 비슷하다.</p>
<pre><code class="language-jsx">  this.myRef = React.createRef(); // ref 생성

  return &lt;div ref={this.myRef} /&gt; // ref 어트리뷰트를 통해 React 엘리먼트에 부착

  this.myRef.current // current 프로퍼티로 DOM 조작</code></pre>
</li>
<li><p>예</p>
<pre><code class="language-jsx">  function TextInputWithFocusButton() {
    const inputEl = useRef(null); // ref 객체 생성
    const onButtonClick = () =&gt; {
      // `current` 프로퍼티는 input을 가리킨다.
      inputEl.current.focus();
    };
    return (
      &lt;&gt;
        &lt;input ref={inputEl} type=&quot;text&quot; /&gt; 
              { // ref 어트리뷰트를 통해 React 엘리먼트에 부착 }
        &lt;button onClick={onButtonClick}&gt;Focus the input&lt;/button&gt;
      &lt;/&gt;
    );
  }</code></pre>
</li>
</ul>
<h2 id="useref-로-컴포넌트-안의-변수-만들기">useRef() 로 컴포넌트 안의 변수 만들기</h2>
<ul>
<li><p><code>useRef()</code> Hook 은 DOM 을 선택하는 용도 외에도, 컴포넌트 안에서 조회 및 수정 할 수 있는 변수를 관리하는 용도로 사용할 수 있다. useRef 로 관리하는 변수는 값이 바뀐다고 해서 컴포넌트가 리렌더링 되지않는다.</p>
</li>
<li><p>리액트 컴포넌트에서의 state는 상태를 바꾸는 함수(setState 등)를 호출하고 나서 그 다음 렌더링 이후로 업데이트 된 상태를 조회 할 수 있는 반면, <code>useRef()</code> 로 관리하고 있는 변수는 설정 후 바로 조회 할 수 있다.</p>
</li>
<li><p>즉 state처럼 컴포넌트를 다루는데 필요한 데이터지만 컴포넌트 렌더링 없이 데이터를 다루고 싶을 때 사용할 수 있다.</p>
</li>
<li><p><code>useRef()</code>로 생성된 변수를 사용하여 다음과 같은 값을 관리 할 수 있다.</p>
<ul>
<li><code>setTimeout</code>, <code>setInterval</code> 을 통해서 만들어진 <code>id</code></li>
<li>외부 라이브러리를 사용하여 생성된 인스턴스</li>
<li>scroll 위치</li>
</ul>
</li>
<li><p>예) <code>useRef()</code>로 리스트 id 생성</p>
<pre><code class="language-jsx">  import React, { useState, useRef } from &#39;react&#39;;

  const List = () =&gt; {
      const [input, setInput] = useState(&#39;&#39;);
      const [items, setItems] = useState([
          {id: 1, text: &#39;눈사람&#39;},
          {id: 2, text: &#39;얼음&#39;},
          {id: 3, text: &#39;눈&#39;},
          {id: 4, text: &#39;바람&#39;},
      ]);

      const nextId = useRef(5); // 초기값을 부여해 nextId 생성

      const handleInput = e =&gt; {
          setInput(e.target.value);
      }

      const itemsList = items.map(name =&gt; {
          return &lt;li key={name.id}&gt;{name.text}&lt;/li&gt;
      });

      const handleClick = () =&gt; {
          if(input.trim() === &#39;&#39;) return;
          const nextItems = items.concat({
              id: nextId.current, // id 추가할 때 currnt 프로퍼티로 접근
              text: input
          });

          nextId.current += 1; //id 수정도 current 프로퍼티 사용
                  // 값을 변경해도 리랜더링 되지 않음

          setItems(nextItems);
          setInput(&#39;&#39;);
      }

      return (
          &lt;div&gt;
              &lt;input 
                  value={input} 
                  onChange={handleInput} 
              /&gt;
              &lt;button onClick={handleClick}&gt;추가&lt;/button&gt;
              &lt;ul&gt;
                  {itemsList}
              &lt;/ul&gt;
          &lt;/div&gt;
      );
  };

  export default List;</code></pre>
</li>
</ul>
<h1 id="커스텀-hook">커스텀 Hook</h1>
<ul>
<li><p>컴포넌트를 만들다보면 input을 관리하는 코드처럼 반복되는 로직이 자주 발생한다. 이러한 상황에서 커스텀 Hook을 만들면 반복되는 로직을 줄이고 같은 로직을 쉽게 재사용할 수 있다.</p>
</li>
<li><p>커스텀 Hook를 만들때 보통 use라는 키워드로 시작하는 파일을 만들고 그 안에 함수를 작성한다. 커스텀 Hook은 파일 내부에서 useState, useEffect, useReducer, useCallback 등 Hooks 를 사용하여 원하는 기능을 구현하고, 컴포넌트에서 사용하고 싶은 값들을 반환해주면 된다.</p>
</li>
<li><p>예)</p>
<pre><code class="language-jsx">  // src/hooks/useInputs.js
  import { useReducer } from &#39;react&#39;;

  function reducer(state, action) {
      return {
          ...state,
          [action.name]: action.value
      }
  }

  export default function useInputs(initialForm) {
      const [state, dispatch] = useReducer(reducer, initialForm);
      const onChange = (e) =&gt; {
          dispatch(e.target);
      }

      return [state, onChange];
  };

  // src/Info.js
  import React from &#39;react&#39;;
  import useInputs from &#39;./hooks/useInputs&#39;;

  const Info = () =&gt; {
      const [ state, onChange ] = useInputs({
          name: &#39;&#39;,
          number: &#39;&#39;
      })

      return (
          &lt;div&gt;
              &lt;input name=&quot;name&quot; value={state.name} onChange={onChange}/&gt;
              &lt;input name=&quot;number&quot; value={state.number} onChange={onChange}/&gt;

              &lt;h1&gt;name : {state.name}&lt;/h1&gt;
              &lt;h1&gt;number : {state.number}&lt;/h1&gt;
          &lt;/div&gt;
      );
  };

  export default Info;</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React.js - 리액트 개발환경 세팅하기 (feat. CRA)]]></title>
            <link>https://velog.io/@gyumin_2/React.js-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85%ED%95%98%EA%B8%B0-feat.-CRA</link>
            <guid>https://velog.io/@gyumin_2/React.js-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85%ED%95%98%EA%B8%B0-feat.-CRA</guid>
            <pubDate>Mon, 02 May 2022 08:51:23 GMT</pubDate>
            <description><![CDATA[<h1 id="create-react-app">create-react-app</h1>
<ul>
<li><p>리액트 프로젝트 개발환경 구축을 처음부터 설정하는 것은 굉장히 복잡하다. 그러나 create-react-app을 사용하면 쉽게 개발환경을 설정할 수 있다.</p>
</li>
<li><p>create-react-app은 페이스북에서 만든 공식적인 React 웹 개발용 Boilerplate이다. 직접 모든 개발환경을 설정하지 않아도 되고 페이스북이라는 거대한 기업에서 지속적으로 업데이트를 해주기에 많은 사람들이 사용하고 있다.</p>
</li>
<li><p>설치 방법1</p>
<pre><code class="language-jsx">  npm install -g create-react-app
  create-react-app [프로젝트 이름]
  cd [디렉토리 이름]
  npm start</code></pre>
</li>
<li><p>설치방법 2</p>
<pre><code class="language-jsx">  npx create-react-app [프로젝트 이름]
  cd [디렉토리 이름]
  npm start</code></pre>
</li>
</ul>
<blockquote>
</blockquote>
<p>💡 npx란?</p>
<blockquote>
<p>npm의 5.2.0 버전부터 추가된 도구로 npm 레지스트리에 올라가 있는 패키지를 쉽게 설치하고 관리할 수 있도록 도와주는 CLI도구다. npx는 기존 npm 설치 방법과는 다르게 일일이 설치, 실행, 제거를 할 필요 없이 일회성으로 원하는 패키지를 npm 레지스트리에 접근해서 실행&amp;설치라는 실행 도구다. 내개 패키지를 설치하고 업데이트하지 않아도 npm 레지스트리에 올라가 있는 최신 버전을 실행&amp;설치해 준다.</p>
</blockquote>
</aside>

<h1 id="create-react-app-프로젝트-구조">create-react-app 프로젝트 구조</h1>
<pre><code class="language-json">create-react-app
├── README.md
├── node_modules
├── package.json
├── public
│   └── index.html
└── src
        ├── App.css
        ├── App.js
        ├── index.css
        ├── index.js
        └── logo.svg</code></pre>
<h2 id="public">public</h2>
<ul>
<li>static resource가 위치하는 디렉토리</li>
<li>index.html : 엔트리 포인트로 이름 변경 시, 애플리케이션이 제대로 작동하지 않는다.<ul>
<li>id가 root인 div에 내용물 출력</li>
</ul>
</li>
</ul>
<h2 id="src">src</h2>
<ul>
<li><p>리액트 코드가 위치하는 디렉토리</p>
</li>
<li><p>index.js : 엔트리 포인트로 이름 변경 시, 애플리케이션이 제대로 작동하지 않는다.</p>
<pre><code class="language-jsx">  // jsx문법을 사용하기 위해 필요한 react모듈. 모든 react 컴포넌트에 필수적인 코드다.
  import React from &#39;react&#39;;
  // react-dom모듈은 react 앱을 최초 렌더링 하기위해 엔트리 포인트에서 쓰인다. 
  // 브라우저 뿐만 아니라 서버사이드용 랜더링 메소드를 지원한다.
  import ReactDOM from &#39;react-dom&#39;;
  // css파일을 import 구문으로 가져오고 있다. 
  // 이는 webpack의 css-loader를 활용한 것인데, 
  // create-react-app에서 기본적으로 세팅이 되어있다.
  import &#39;./index.css&#39;;
  // App라는 이름의 컴포넌트 가져오기
  import App from &#39;./App&#39;;
  // React의 성능을 측정하기 위한 reportWebVitals. 삭제해도 무방
  import reportWebVitals from &#39;./reportWebVitals&#39;;

  ReactDOM.render(
    &lt;React.StrictMode&gt;
      &lt;App /&gt;
    &lt;/React.StrictMode&gt;,
    document.getElementById(&#39;root&#39;)
  );

  // 애플리케이션의 성능을 측정할 때 사용하는 함수.
  // 원치 않을 경우 삭제 가능 
  reportWebVitals();</code></pre>
</li>
</ul>
<blockquote>
<p>💡 <code>&lt;React.StrictMode/&gt;</code> 란?</p>
<p><code>&lt;React.StrictMode/&gt;</code> 는 리액트 프로젝트에서 리액트 레거시 기능들을 사용하지 못하게하는 기능이다. 이를 사용하면 문자열 ref, componentWillMount 등 deprecated 된 기능을 사용할 때 경고를 출력한다.</p>
</blockquote>
<ul>
<li><p>App.js</p>
<pre><code class="language-jsx">  // 해당 컴포넌트에서 사용할 svg
  import logo from &#39;./logo.svg&#39;;
  // 해당 컴포넌트에서 사용할 css
  import &#39;./App.css&#39;;

  function App() {
    return (
      &lt;div className=&quot;App&quot;&gt;
        // 소스 코드
      &lt;/div&gt;
    );
  }

  // 해당 컴포넌트 내보내기
  export default App;</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[‘Docker란 무엇인가’가 무엇인지 모르겠다]]></title>
            <link>https://velog.io/@gyumin_2/Docker%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EC%A7%80-%EB%AA%A8%EB%A5%B4%EA%B2%A0%EB%8B%A4</link>
            <guid>https://velog.io/@gyumin_2/Docker%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EC%A7%80-%EB%AA%A8%EB%A5%B4%EA%B2%A0%EB%8B%A4</guid>
            <pubDate>Fri, 15 Apr 2022 05:55:02 GMT</pubDate>
            <description><![CDATA[<h1 id="docker란-무엇인가가-무엇인지-알겠는데-모르겠다">‘Docker란 무엇인가’가 무엇인지 알겠는데 모르겠다.</h1>
<p>몇 년 전부터 도커는 핫한 기술이었으며, 지금은 거의 업계 표준이 되면서 개발자라면 필수로 알아야할 기술이 되었습니다. <del>이직 준비를</del> 공부를 하기 위해 도커에 대해 검색 해보니, 아래의 내용들을 알 수 있었습니다.</p>
<ul>
<li>도커는 리눅스의 컨테이너라는 기술(LXC)를 이용한 소프트웨어로 지금은 자체 라이브러리인 libcontainer를 사용하고있다.</li>
<li>도커를 이용하면 개발환경에 구애받지 않고 손쉽게 내가 사용하던 개발환경을 구축할 수 있다.</li>
<li>도커는 일반적인 Hypervisor 가상화 방식과는 다르게 경량 가상화로 실행이 빠르다. 그로 인해 한 개의 서버만 있으면 여러개의 서비스 실행이 가능해지므로 불필요한 서버 확장이 필요없다.</li>
<li>도커를 이용하면 배포가 쉽고 수정도 쉽다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/6f10035c-83cf-4914-95b9-d0aeaadef7fc/image.jpeg" alt=""></p>
<p>하지만 리눅스의 ㄹ도 모르는 비전공 주니어 프론트 개발자인 저는 “컨테이너...? 가상화....? 무슨 말인지 알겠는데 무슨 말인지 모르겠다...” 였습니다. 이 글은 저와 같이 도커를 공부해보려고 하는데 도커란 무엇인가 챕터부터 이게 뭔 말인지 정확히는 모르겠고 느낌적인 느낌만 알 것 같은 분들을 위해 작성되었습니다. </p>
<h1 id="리눅스와-리눅스-컨테이너">리눅스와 리눅스 컨테이너</h1>
<p>저처럼 리눅스의 ㄹ도 모르는 분들은 도커를 알기위해서 먼저 리눅스가 뭔지 부터 알아야합니다. 왜냐하면 도커는 리눅스의 컨테이너라는 기술를 이용한 소프트웨어이기 때문입니다. 그럼 리눅스와 리눅스 컨테이너가 뭔지부터 간단히 살펴 보겠습니다.</p>
<h2 id="리눅스linux란">리눅스(Linux)란?</h2>
<p>리눅스란 가장 대표적인 오픈 소스 운영체제입니다. 운영체제(Operating System)는 크게 PC에서 돌아가는 운영체제와 서버에서 사용되는 운영체제가 있습니다. PC에서 사용되는 운영체제에는 윈도우, MacOS, 리눅스가 있고, 서버에 사용되는 운영체제로는 윈도우즈서버, 리눅스, 유닉스가 있습니다. 리눅스는 PC와 서버에서 둘 다 사용되지만 서버에서 더 많이 사용되고 있습니다.</p>
<p>리눅스는 핀란드 컴퓨터 공학과 학생이던 리누스 토발즈에 의해 개발되었습니다. 리누스 토발즈는 유닉스에 관심이 많았는데, 유닉스 운영체제를 모델로하며 유닉스 시스템의 작은 버전인 미닉스보다 좋은 운영체제를 만들자는 목표를 두고 1991년 0.01버전으로 새로운 운영체제를 개발하고 본인의 이름을 따서 리눅스라고 이름을 지었습니다. 리누스 토발즈는 리눅스 첫 공식 버전인 0.02는 1991년 10월에 발표한 후 전 세계 많은 개발자와 전문가들의 도움을 받아 지속적으로 리눅스 개발을 진행하게 되었습니다.</p>
<p>리누스 토발즈는 커널이라 불리는 리눅스 핵심 부분만 작성하여 배포했으며, 일반적으로 사람들이 이야기하는 리눅스는 리누스 토발즈가 개발한 커널에 컴파일러, 셀, 기타 응용 프로그램들이 조합된 배포판을 말합니다. 리눅스는 폐쇄적인 유닉스와 다르게 누구든지 자유롭게 프로그램을 변경하여 유동시킬 수 있는 프리웨어입니다. 때문에 다양한 회사나 개발자가 만든 다양한 배포판이 존재합니다. 한 번쯤 들어봤을 우분투, 페도라, CentOS도 수 많은 배포판 중 하나입니다. </p>
<blockquote>
<p>리눅스에 대한 자세한 설명은 <a href="https://jhnyang.tistory.com/18">여기</a>를 참고하세요.</p>
</blockquote>
<blockquote>
<p>💡 커널(Kernel)이란?
사전적 의미로&quot;알맹이, 핵심&quot;이라는 뜻으로, 컴퓨터 운영체제에서 커널은 운영체제의 핵심부로 CPU, 메모리, 파일, 네트워크, 입출력 장치 등의 컴퓨터 자원을 관리하는 프로그램을 말합니다.</p>
<p>자세한 설명은 <a href="https://medium.com/@su_bak/os-%EC%BB%A4%EB%84%90-kernel-%EC%9D%B4%EB%9E%80-b6b8aae8d0b4">여기</a>를 참고하세요.</p>
</blockquote>
<h2 id="컨테이너-탄생-배경">컨테이너 탄생 배경</h2>
<p>리눅스는 여러 명의 사용자에 의한 동시 접근을 허용하는 멀티유저(다중사용자) 운영체제입니다. 즉 리눅스에서는 많은 사람들이 동시에 한 컴퓨터를 사용하는게 가능하다는 뜻입니다.</p>
<p>리눅스 사용자는 크게 root 사용자(시스템 운영 관리자, Super User)와 일반 사용자(Normal User, Guest User)로 구분하고, 일반 사용자의 경우 로그인 가능 사용자와 로그인은 불가하지만 시스템의 필요 때문에 생성된 시스템 계정으로 나뉩니다.</p>
<p>일반적으로 리눅스 콘솔창에서 <code>&#39;ls /&#39;</code> 명령어를 입력하면 루트 디렉토리에 대한 목록이 나옵니다. 하지만 일반 사용자가 입력할 경우, 관리자가 사용자에게 어떤 권한을 주느냐에 따라 달라지게 됩니다. 접근이 될 수도 있고, 안 될 수도 있는 거죠. 만약 관리자가 사용자에게 접근을 허용하지 않게 되면, 많은 제약이 따릅니다. 이러한 제약을 좀 더 유연하면서 보안도 유지되는걸 고민해서 탄생한 것이 <code>chroot</code> 입니다. <code>chroot</code>를 사용하게 되면 일반 사용자에 한해서 기본 root디렉토리가 변경됩니다.</p>
<p>만약 관리자가 사용자에 대하 chroot로 루트 디렉토리를 <code>/home</code> 디렉토리로 변경하고 사용자가 <code>&#39;ls /&#39;</code> 명령어를 입력하면, 터미널에 다음과 같이 표시됩니다.</p>
<pre><code class="language-bash">&#39;Guest@ : ls /&#39;
/home</code></pre>
<p>즉 일반 사용자 입장에서의 root 디렉토리는 /home 디렉토리가 되는 것입니다.</p>
<p><code>chroot</code>를 사용하면 루트디렉토리만 바뀌는 것 뿐만 아니라 관리자가 <code>chroot</code>를 설정하면서 설정한 루트 디렉토리(<code>/home</code>) 아래에 사용자가 필요로 하는 참조하는 파일을 제공할 수 있게 됩니다. 이로인해 사용자는 서버의 불필요한 접근을 할 필요 없이 자신의 일만 수행하면 됩니다. 때문에 보안을 유지하면서 사용자에세 유연성을 제공하는 것이 가능해졌습니다. <code>chroot</code>는 대부분의 리눅스에서 설정이 가능하지만 설정이 까다롭습니다. 이 개념을 기반으로 2001년 VServer Project를 통해 리눅스에 격리된 공간이 구축되었으며 이것이 발전해 컨테이너로 탄생하게 됩니다.</p>
<h2 id="컨테이너란">컨테이너란?</h2>
<p>위와 같은 배경으로 만들어진 컨테이너는 리눅스의 namespace와 cgroup 이 두 가지 기능을 기반으로 만들어집니다.</p>
<h3 id="namespace">namespace</h3>
<p>namespace는 동일한 시스템에서 별개의 독립된 공간을 격리된 환경에서 운영하는 가상화 기술입니다. 아파트가 각 호실별로 격리된 주거환경을 제공하는 것과 비슷한 개념으로 생각하면 됩니다. namespace로 격리된 프로세스들은 같은 컴퓨터 안에 존재하지만 격리 되어있기 때문에 서로 간섭할 수 없습니다.</p>
<p>namespace를 이용하면 하나의 시스템에서 동일한 PID가 두 개인것처럼 프로세스를 만들 수 있습니다. 즉 PID가 같아도 서로 다른 프로세스가 되는 것이죠. </p>
<p>이렇게 격리된 프로세스는 다음 6개가 독립적으로 존재할 수 있다는 특징을 갖고있습니다.</p>
<ol>
<li>mnt (파일시스템 마운트): 호스트 파일시스템에 구애받지 않고 독립적으로 파일시스템을 마운트하거나 언마운트 가능</li>
<li>pid (프로세스): 독립적인 프로세스 공간을 할당</li>
<li>net (네트워크): namespace간에 network 충돌 방지 (중복 포트 바인딩 등)</li>
<li>ipc (SystemV IPC): 프로세스간의 독립적인 통신통로 할당</li>
<li>uts (hostname): 독립적인 hostname 할당</li>
<li>user (UID): 독립적인 사용자 할당</li>
</ol>
<h3 id="cgroup">cgroup</h3>
<p>namespace는 CPU나 메모리 등의 물리적 자원을 제한하지 않습니다. 때문에 격리된 프로세스에 하드웨어 자원을 배분하기 위해서는 cgroup이라는 기능을 사용해야 합니다.</p>
<p>cgroup은 control group의 약자로 하드웨어 자원을 배분하는 기능을 수행합니다. cgroup를 사용하면 CPU, RAM 등의 자원을 사용자가 원하는 만큼 격리된 프로세스에 할달 해줄 수 있습니다. 이렇게 할당하게되면 컴퓨터 성능을 좀 더 효율적으로 사용할 수 있으며, 추후 컨테이너에 올라가는 응용프로그램마다 필요에 따라 자원을 더 할당할 수 있습니다.</p>
<p>이렇게 namespace와 cgroup으로 만들어진 컨테이너를 LXC(LinuX Container)라고 부릅니다. LXC를 사용하면 독립된 공간의 프로세스 위에 사용자가 원라는 기능을 올릴 수 있게됩니다. 가령 사용자가 원하는 OS를 올리고 해당 OS에 JAVA서버, node.js, DB 등을 올리는 작업을 말이죠.</p>
<h1 id="docker의-탄생-배경">Docker의 탄생 배경</h1>
<h2 id="전통적인-서버-운영-방식">전통적인 서버 운영 방식</h2>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/2df13cfe-d01b-4967-afde-e04adf0a40be/image.jpeg" alt=""></p>
<p>일반적으로 서버를 관리하는 일은 매우 복잡하고 어려운 작업입니다. 사실 저는 프론트 개발자라 잘 모르지만 그렇다고 합니다. 예전에는 데이터센터나 서버실에 서버를 두고 직접 관리하던 온프레미스(On-premise) 방식으로 서버를 관리했습니다. 이러한 방식에서 서버에 OS를 설치하고 서버를 세팅하는 일은 쉬운 일이 아니었습니다. 또한 하나의 서버에 여러 개의 프로그램을 설치하는 경우 서로 사용하는 라이브러리의 버전이 다르거나 동일한 포트를 사용하는 경우 설치가 매우 까다로웠습니다. </p>
<p>때문에 전통적인 방식에서는 주로 하나의 서버에 하나의 운영체제, 하나의 프로그램을 운영했습니다. 이렇다보니 각 서버가 갖고 있는 시스템 자원 중 약 50~70% 정도만 사용할 수 있었습니다. 즉, 물리적 서버가 갖고 있는 성능을 100% 활용해서 사용할 수 없었습니다. 또한 프로그램이 늘어날 수록 서버가 늘어날 수 밖에 없었습니다. 서버를 구입하고 운영하는 데에 많은 비용이 소모되는데, 그런 서버가 늘어나는데 서버의 성능을 100% 활용하지 못한다는 것은 기업 입장에서 큰 손해였습니다. 때문에 기업은 자신들이 갖고 있는 물리적 서버를 최대한 효율적으로 사용하길 원했고, 각 서버가 100%의 성능을 발휘할 수 있길 원했습니다.</p>
<h2 id="가상화">가상화</h2>
<p>도커는 컨테이너 기반의 오픈소스 가상화 플랫폼입니다. 그런데 여기서 말하는 가상화란 정확히 뭘까요? 전통적인 서버 운영 방식을 먼저 설명드린 이유는 모두 가상화를 설명하기 위한 나름의 빅픽쳐(밑밥)이었습니다.</p>
<p>위에서 설명한 것처럼 기업은 서버 운영을 효율적으로 하기 위해 한 대의 물리적인 서버를 마치 여러 대의 서버처럼 활용하거나, 여러 서버를 하나의 서버처럼 묶어서 사용하기를 원했고 이로 인해 등장한 기술이 &quot;가상화&quot;입니다.</p>
<p>가상화는 가상화를 관리하는 소프트웨어(주로 Hypervisor)를 사용하여 프로세서, 메모리, 스토리지 등과 같은 단일 컴퓨터의 하드웨어 요소를 일반적으로 가상 머신(VM, Virtual Machine)이라고 하는 다수의 가상 컴퓨터로 분할할 수 있도록 해주는 컴퓨터 하드웨어 상의 추상화 계층을 구축하는 기술을 말합니다. 실제 기반 컴퓨터 하드웨어의 단지 일부에서만 실행됨에도 불구하고, 각각의 VM은 자체 운영체제(OS)를 실행하며 마치 독립적인 컴퓨터인 것처럼 작동합니다.</p>
<blockquote>
<p>💡 추상화란?</p>
</blockquote>
<p>일반적으로 컴퓨터를 사용할 때 여러 사용자를 등록하여 사용할 수 있다. 이렇게 여러 사용자를 등록해서 사용할 때, 각 사용자는 마치 자신이 하나의 하드웨어를 독점하여 활용하는 것처럼 느끼게된다. 이처럼 하나 뿐인 하드웨어를 마치 여러 개인 것처럼 보여지도록 하는 기술을 추상화하고 한다.</p>
<p>예를들어 각각 용도가 다른 3개의 물리 서버가 있다고 합시다. 하나는 메일 서버이고, 다른 하나는 웹 서버이고, 나머지 하나는 내부 레거시 애플리케이션을 실행하는 서버입니다. 각 서버는 잠재적인 실행 용량의 일부에 불과한 30% 용량만 사용하고 있습니다. 그러나 내부 운영을 위해서는 레거시 애플리케이션이 계속 필요하므로 레거시 애플리케이션과 이를 호스팅하는 세 번째 서버를 유지해야 합니다.</p>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/5980db67-2348-4375-a37c-fc1bd80dc231/image.png" alt=""></p>
<p>전통적으로는 위의 방식을 따릅니다. 1개의 서버와 1개의 운영 체제, 1개의 태스크와 같이 개별 서버에서 개별 태스크를 실행하는 것이 더 쉽고 안정적인 경우가 많습니다. 1개의 서버로 여러 개의 태스크를 처리하기란 쉽지 않았습니다. 그러나 가상화를 사용하면 메일 서버를 2개의 고유한 서버로 분할해 독립적인 태스크를 처리하고 레거시 애플리케이션을 마이그레이션할 수 있습니다. 마찬가지로 하드웨어도 더 효율적으로 사용할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/186b96a2-7ee8-483a-a8f0-5022c8af81cc/image.png" alt=""></p>
<p>보안을 고려하여 첫 번째 서버를 다시 분할해 다른 태스크를 처리하면 사용률을 30%에서 60% 또는 90%까지도 높일 수 있습니다. 이렇게 하고 나면 이제 빈 서버를 재사용해 다른 태스크를 처리하거나 모든 서버를 사용 중지해 냉각 및 유지관리 비용을 줄일 수 있습니다.</p>
<p>*사진 출처 : <a href="https://www.redhat.com/ko/topics/virtualization/what-is-virtualization">https://www.redhat.com/ko/topics/virtualization/what-is-virtualization</a></p>
<blockquote>
<p>가상화에 대한 자세한 설명은 <a href="https://www.ibm.com/kr-ko/cloud/learn/virtualization-a-complete-guide">여기</a>를 참고하세요.</p>
</blockquote>
<h2 id="가상화-발전-과정">가상화 발전 과정</h2>
<p>전통적인 배포방식, 가상화로된 배포방식 그리고 컨테이너 방식 이렇게 세 가지를 비교하여 가상화의 발전과정에 대해 알아보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/6c9dd59b-cbf0-4bff-86c8-e8fc6d5b3091/image.png" alt=""></p>
<h3 id="전통적인-배포-방식">전통적인 배포 방식</h3>
<ul>
<li>하나의 물리 서버에 애플리케이션을 배포하는 방식입니다.</li>
<li>애플리케이션 간 라이브러리나 미들웨어 버전의 충돌이 발생할 수 있습니다.</li>
<li>한 물리 서버에서 여러 애플리케이션의 리소스 한계를 정의할 방법이 없었기에, 리소스 할당의 문제 발생합니다.</li>
<li>물리 서버 하나에서 여러 애플리케이션을 실행하면, 리소스를 과다 사용하는 애플리케이션이 다른 애플리케이션의 성능이 저하를 유발합니다.</li>
<li>서로 다른 여러 물리 서버에서 각 애플리케이션을 실행하는 것은 리소스가 충분히 활용되지 않으며, 많은 유지 비용이 듭니다.</li>
</ul>
<h3 id="가상화를-이용한-배포-방식">가상화를 이용한 배포 방식</h3>
<ul>
<li>Host OS(실물 컴퓨터에 설치된 OS) 위에 가상화 소프트웨어(Hypervisor)를 설치해서 가상환경을 구축합니다.</li>
<li>VM간에 애플리케이션을 격리하고 애플리케이션의 정보를 다른 애플리케이션에서 자유롭게 액세스 할 수 없으므로, 일정 수준의 보안성을 제공합니다.</li>
<li>리소스를 보다 효율적으로 활용할 수 있으며, 쉽게 애플리케이션을 추가하거나 업데이트할 수 있고 하드웨어 비용을 절감할 수 있어 더 나은 확장성 제공합니다.</li>
<li>간편하게 사용할 수 있어 클라이언트 PC에서 개발환경을 구축하거나 테스트를 위해 주로 사용합니다.</li>
<li>가상환경에 Guest OS(VM에 설치된 OS)를 통째로 올입니다. OS는 용량도 많이 차지하고, 사용자가 사용하지 않는 불필요한 기능도 많습니다. 게다가 OS 자체가 무겁고 잡아먹는 자원이 많이 때문에 느리다는 치명적인 단점이 있습니다. (컨테이너와 다르게 비교적 오버헤드가 크다.)</li>
<li>OS 위에 OS를 실행하는 것이므로 리소스(CPU, 메모리 등)를 할당하는 작업이 필요합니다.</li>
</ul>
<blockquote>
<p>💡 오버헤드(overhead)란?</p>
</blockquote>
<p>어떤 처리를 하기 위해 들어가는 간접적인 처리 시간 · 메모리 등을 말한다. 예를들어, 10초 걸리는 기능이 간접적인 원인으로 20초걸린다면 오버헤드는 10초가 되는것이다. 오버헤드는 반드시 존재한다. 다만 이 오버헤드를 줄이는 것이 좋다.</p>
<h3 id="컨테이너-방식">컨테이너 방식</h3>
<ul>
<li>컨테이너는 컨테이너 실행을 담당하는 소프트웨어인 컨테이너 런타임이라는 게 존재하고, 컨테이너 런타임 위에 Guest OS 없이 애플리케이션이 올라갑니다.</li>
<li>컨테이너에서 OS가 실행되긴 하지만 사용자가 사용할 응용프로그램만 작동되도록 정말 최소단위만 올라갑니다. 때문에 컨테이너에서 실행되는 OS의 커널 크기가 작고 빠르다. 또한 Guest OS가 없기 때문에 OS 실행 없이 별도의 환경에서 애플리케이션 실행이 가능합니다.</li>
<li>서버 한대에서 여러 기능을 하는 응용프로그램들, 소위 마이크로서비스식 서버를 구축할 수 있으며, 기존 서버 커널 하나로 모든 컨테이너들의 관리가 가능하므로 관리도 쉽습니다.</li>
</ul>
<h2 id="서버-관리-방식의-변화와-docker의-등장">서버 관리 방식의 변화와 Docker의 등장</h2>
<p>시간이 흐르면서 서버의 환경은 계속 바뀌었습니다. CentoOS에 익숙해지면 우분투를 써야하는 일이 생기고, AWS에 익숙해지면 Azure를 써야하는 일이 생깁니다. 무슨 말인지 정확히 모르겠지만 어떤 기술에 익숙해질만하면 새로운 기술을 써야하는 상황이 생긴다고 이해하면 될 것 같습니다. (이건 모든 개발자들의 숙명인듯...)</p>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/59d60bbb-ff75-4d87-9c40-c06e9080206e/image.jpeg" alt=""></p>
<p>그러던 중 DevOps라는 것이 등장합니다. DevOps는 development(개발)&#39;와 &#39;operations(운영)&#39;가 합쳐진 단어어로, 개발팀과 운영팀 간의 프로세스를 자동화하고 통합하는 일련의 관행, 도구 및 문화적 철학입니다. 무슨 말인지 알겠는데 무슨 말인지 모르겠죠? 과거 설치 기반 프로그램이 대부분이던 시절에는 서비스 패치를 위해 몇 달간의 작업 후 배포하는 방식을 사용했습니다. 그런데 점점 웹기반 서비스로 바뀌고 한국인의 빨리빨리 정신이 깃든 애자일 방법론이라는 것에 관심이 많아 지면서 빈번한 배포가 필요해졌습니다. 하지만 개발팀은 서비스 개발에 매진하고, 운영팀은 보안과 안정적인 인프라 구축에 집중을 하므로 빈번한 배포가 어려웠습니다. 때문에 빈번한 배포 전략을 위해 두 팀이 병합되어 개발, 테스트, 배포, 운영에 이르는 애플리케이션 수명주기를 개발하게 되는데 이를 데브옵스라고합니다.</p>
<blockquote>
<p>DevOps에 대한 자세한 내용은 <a href="https://blog.sonim1.com/231">여기</a>를 참고하세요.</p>
</blockquote>
<p>또한 MSA(Micro Service Architecture)라는 것이 유행하기 시작합니다.(제발 그만 유행해...) MSA는 서비스간의 의존성을 없애고 기능을 쪼개는 것을 중점적으로 설계한 아키텍처입니다. 예를 들어, 은행 시스템을 하나의 통합된 프로그램으로 개발하지 않고… 입/출금 서비스, 조회 서비스, 대출 서비스 등 기능별로 작게 쪼개서 MSA 형태로 되어 있다고 가정하겠습니다. 이 때 새로운 대출 유형이 생겨 개발이 필요하면, 은행 시스템 전체를 수정할 필요 없이 대출 서비스의 수정만으로 작업을 경량화 할 수 있습니다. 이로 인해 서비스 단위 개발이 가능하고 지속적인 통합과 배포(CI/CD)를 효율적으로 할 수 있기 때문에 서비스간 결합도를 줄이고 응집도를 높일 수 있다는 장점이 있지만, 서비스들을 관리하기 복잡하다는 단점이 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/e61afca9-8cce-452b-aeea-b3d9c0d05858/image.jpeg" alt=""></p>
<p>MSA가 유행하면서 프로그램을 잘게 쪼개어 버리게 되고 이로인해 관리가 더 복잡해졌습니다. 새로운 툴은 계속해서 나오고 클라우드의 발전으로 설치해야 할 서버가 수백, 수천대에 이르는 노답 상황에서 리눅스의 컨테이너라는 기술(LXC)를 이용하며, 컨테이너 기반의 오픈소스 가상화 플랫폼인 도커가 등장합니다. 이로 인해 서버 관리 방식이 완전히 바뀌게 됩니다.</p>
<h1 id="리눅스-컨테이너와-docker-컨테이너의-차이점">리눅스 컨테이너와 Docker 컨테이너의 차이점</h1>
<p>도커는 처음에는 생성, 관리, 배포 등 컨테이너 관련 작업을 효율적으로 하기 위해 리눅스 컨테이너 기반으로 만들어졌습니다. 하지만 도커가 곧 리눅스 컨테이너가 아닙니다. 맨 처음에 언급했듯이 현재 도커는 LXC 기반이 아닌 자체 라이브러리인 libcontainer를 사용하고 있습니다. 이는 도커가 좀 더 Docker Engine에 맞게 실행되게 하기 위함입니다.</p>
<p>도커와 LXC의 차이점을 통해 도커에 대해 좀더 알아보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/addb7172-43ad-43cc-b378-2da2c94e67af/image.png" alt="">
*사진 출처 : <a href="https://www.redhat.com/ko/topics/containers/what-is-docker">https://www.redhat.com/ko/topics/containers/what-is-docker</a></p>
<h3 id="컨테이너에-실행-가능한-애플리케이션-개수">컨테이너에 실행 가능한 애플리케이션 개수</h3>
<p>LXC는 하나의 컨테이너에 여러 개의 애플리케이션을 띄울 수 있지만, 도커는 하나의 컨테이너에 하나의 애플리케이션을 사용하기를 권장하고 있습니다. 컨테이너당 애플리케이션을 하나씩 띄워 놓으면 다음과 같은 장점이 있습니다.</p>
<ul>
<li>다른 프로젝트에서 쉽게 재사용 가능</li>
<li>보안 및 격리 관점에서 더 많은 유연성을 가져올 수 있음.</li>
<li>업데이트 시 서로간의 간섭을 받지 않음</li>
<li>마이크로서비스 아키텍처를 구성하는것에 대해 효율적이다.</li>
</ul>
<h3 id="스냅샷-기능">스냅샷 기능</h3>
<p>LXC는 스냅샷 기능이 없지만, 도커는 스냅샷 기능이 있습니다. 스냅샷이란 마치 사진을 찍듯이 특정 시점에 데이터 저장 장치의 상태를 포작해 별도의 파일이나 이미지로 저장하는 기술로, 스냅샷 기능을 이용하여 데이터를 저장하면 유실된 데이터 복원과 일정 시점의 상태로 데이터를 복원할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/e3bab5b6-e64c-4a2a-961c-164f582d9893/image.webp" alt=""></p>
<p>도커에서 이 스냅샷 기능을 이용하면 구축된 환경을 이미지 파일로 저장 후 언제든지 사용할 수 있습니다. 여기서 이미지 파일은 jpg, png 같은 이미지 파일이 지칭하는 것이 아니라 도커의 최소관리 단위를 뜻하는 말로 필요한 프로그램, 라이브러리, 소스를 설치한 뒤 만든 하나의 파일을 말합니다. 이 파일은 컨테이너 실행에 필요한 모든 파일과 설정값 등을 포함하고 있으며 상태값을 가지지 않고 변하지 않습니다. 컨테이너는 이미지를 실행한 상태라고 볼 수 있고 추가되거나 변하는 값은 컨테이너에 저장됩니다. 같은 이미지에서 여러 개의 컨테이너를 생성할 수 있고 컨테이너의 상태가 바뀌거나 컨테이너가 삭제 되더라도 이미지는 변하지 않고 그대로 남아있습니다.</p>
<p>말그대로 이미지는 컨테이너를 실행하기 위한 모든 정보를 가지고 있기 때문에 더 이상 의존성 파일을 컴파일하고 이것저것 설치할 필요가 없습니다. 이제 새로운 서버가 추가되면 미리 만들어 놓은 이미지를 다운받고 컨테이너를 생성만 하면 됩니다. 한 서버에 여러 개의 컨테이너를 실행할 수 있고, 수십, 수백, 수천대의 서버도 문제없습니다. 만약 초기 셋팅을 이미지 파일로 만든 후 새로운 패키지나 업데이트를 했는데 알 수 없는 이유로 프로그램이 작동이 안된다면 초기에 만들어 놓은 이미지 파일로 쉽게 되돌아 갈 수 있습니다.</p>
<p>도커는 새로운 컨테이너를 구축할 때 이 이미지를 사용하므로 구축 프로세스가 훨씬 빠르고, 중간 변경 사항이 이미지 사이에서 공유되므로 속도, 규모, 효율성이 더 개선됩니다. 컨테이너의 시작과 종료가 수 초 밖에 되지 않기 때문에 도커는 컨테이너를 이미지화 시켜 배포를 쉽고 빠르게 할 수 있습니다.</p>
<p>게다가 도커 이미지는 깃헙같은 Docker Hub에 등록하거나 Docker Registry 저장소를 직접 만들어 관리할 수 있습니다.</p>
<h1 id="docker의-장점">Docker의 장점</h1>
<h2 id="모듈성">모듈성</h2>
<p>도커의 컨테이너 방식은 전체 애플리케이션을 분해할 필요없이, 애블리케이션의 일부를 분해하고 업데이트 또는 복구할 수 있게 해줍니다. 사용자는 MSA 기반 접근 방식 외에도 SOA(service-oriented architecture)의 작동 방식과 동일하게 멀티플 애플리케이션 사이에서 프로세스를 공유할 수 있습니다.</p>
<h2 id="레이어-저장-방식">레이어 저장 방식</h2>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/bec0b307-da19-41e1-8ebf-99bb1800506a/image.webp" alt=""></p>
<p>도커 이미지는 컨테이너를 실행하기 위한 모든 정보를 가지고 있기 때문에 보통 용량이 수백메가MB에 이릅니다. 처음 이미지를 다운받을 땐 크게 부담이 안되지만 기존 이미지에 파일 하나 추가했다고 수백메가를 다시 다운받는다면 매우 비효율적일 수 밖에 없습니다.</p>
<p>도커는 이런 문제를 해결하기 위해 레이어라는 개념을 사용하고 유니온 파일 시스템을 이용하여 여러 개의 레이어를 하나의 파일시스템으로 사용할 수 있게 해줍니다. 이미지는 여러 개의 읽기 전용read only 레이어로 구성되고 파일이 추가되거나 수정되면 새로운 레이어가 생성됩니다.</p>
<p>예를들어 a+b+c의 집합인 도커 이미지 A가 있다고 가정할 때, 이미지 A를 베이스로 만든 이미지 B는 a+b+c+B가 됩니다. 또한 도커 이미지 C를 이미지 B를 기반으로 만들었다면, 이미지C는 a+b+c+B+C 레이어로 구성됩니다. 이미지C 소스를 수정하면 a, b, c, B를 제외한 C(v2)만 레이어만 다운 받으면 되기 때문에 굉장히 효율적으로 이미지를 관리할 수 있습니다. </p>
<p>컨테이너를 생성할 때도 레이어 방식을 사용하는데 기존의 이미지 레이어 위에 읽기/쓰기 레이어를 추가합니다. 이미지 레이어를 그대로 사용하면서 컨테이너가 실행중에 생성하는 파일이나 변경된 내용은 읽기/쓰기 레이어에 저장되므로 여러개의 컨테이너를 생성해도 최소한의 용량만 사용합니다.</p>
<h2 id="신속한-배포">신속한 배포</h2>
<p>도커 이미지 설명할 때 언급했지만 도커기반 컨테이너는 배포 시간을 몇 초로 단축할 수 있습니다. 각 프로세스에 대한 컨테이너를 생성함으로써 사용자는 유사한 프로세스를 새 앱과 빠르게 공유할 수 있습니다. 또한, 컨테이너를 추가하거나 이동하기 위해 OS를 부팅할 필요가 없으므로 배포 시간이 크게 단축됩니다. 이뿐만 아니라 배포 속도가 빨라 컨테이너에서 생성된 데이터를 비용 효율적으로 쉽게 생성하고 삭제할 수 있고 사용자는 우려를 할 필요가 없습니다.</p>
<h1 id="마무리">마무리</h1>
<p>도커에서 가장 중요한 개념은 도커 컨테이너와 도커 이미지입니다. 이 두 개념을 정확히 몰라도 도커를 사용하는데 큰 문제는 없겠지만, 단순히 도커를 다룰 줄 아는 사람이 되기 보다는 도커를 알고 다룰 줄 아는 사람이 되고 싶었습니다. 그래서 ‘도커가 뭐지?’라는 질문이 ‘리눅스가 뭐지?’라는 질문이 되었고, 그 결과 이런 포스팅이 탄생했습니다. 저와 같은 분들이 분명히 계실거라 생각하고, 이 글이 그런 분들에게 많은 도움이 되었으면 좋겠습니다.</p>
<p>기회가 된다면 도커 빌드 포스팅도 작성해 보겠습니다.</p>
<blockquote>
<p>참고 블로그
<a href="https://subicura.com/2017/01/19/docker-guide-for-beginners-1.html">초보를 위한 도커 안내서 - 도커란 무엇인가?</a>
<a href="https://myjamong.tistory.com/297">Docker란 무엇인가? 왜 사용할까?</a>
<a href="https://hwan-shell.tistory.com/116">Linux) Doker와 Container의 탄생과 설명, 차이점</a>
<a href="https://futurecreator.github.io/2018/11/16/docker-container-basics/">도커 Docker 기초 확실히 다지기</a>
<a href="https://www.redhat.com/ko/topics/containers/what-is-docker">Red Hat - Docker(도커)란?</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[React.js - DOM 엘리먼트 조작(ref, portal)]]></title>
            <link>https://velog.io/@gyumin_2/React.js-DOM-%EC%97%98%EB%A6%AC%EB%A8%BC%ED%8A%B8-%EC%A1%B0%EC%9E%91ref-portal</link>
            <guid>https://velog.io/@gyumin_2/React.js-DOM-%EC%97%98%EB%A6%AC%EB%A8%BC%ED%8A%B8-%EC%A1%B0%EC%9E%91ref-portal</guid>
            <pubDate>Wed, 13 Apr 2022 02:52:01 GMT</pubDate>
            <description><![CDATA[<h1 id="react에서-dom-엘리먼트에-접근하기">React에서 DOM 엘리먼트에 접근하기</h1>
<ul>
<li>자바스크릅트에서 제공하는 API를 이용해 DOM 조작을 하는 것이 리액트 방식을 이용하는 것보다 더 쉬운 경우가 있다.</li>
<li>이를 위해 리액트는 HTML 엘리먼트에 대한 DOM API에 접근할 수 있게하는 ref라는 도구를 제공한다.</li>
<li>또한 페이지 안의 어떤 HTML 엘리먼트든 랜더링 할 수 있는 포털(portal)이라는 기능도 제공한다.</li>
</ul>
<h1 id="ref-사용하기">ref 사용하기</h1>
<ul>
<li><p>JSX는 단순히 DOM이 어떻게 보여야 할지를 기술한다. 그러나 실제 HTML을 대변하는 것은 아니다. 때문에 이를 보완하기 위해 리액트는 최종 렌더링된 HTML 엘리먼트와 JSX 사이를 연결해주는 ref(reference의 약자)라는 기능을 제공한다.</p>
</li>
<li><p>ref 속성 추가</p>
<pre><code class="language-jsx">  // 일반 함수 사용

  // 익명함수 내부에 쓰기위해 this를 변수에 할당
  const self = this;

  return (
      &lt;Element ref={
              function(el) {
                  self._input = el;
              }
          }
      /&gt;
  );

  // 화살표 함수 사용
  return (
      &lt;Element ref={
              (el) =&gt; {
                  this._input = el;
              }
          }
      /&gt;
  );</code></pre>
<ul>
<li>해당 컴포넌트가 마운트 되면 컴포넌트 내부 어디서든 self._input 혹은 this._input를 사용해 input 엘리먼트를 나타내는 HTML에 접근할 수 있다.</li>
<li>JSX 내부에 작성한 익명함수는 컴포넌트가 마운트될 때 호출되며, 최종 HTML DOM 엘리먼트에 대한 참조를 매개변수로 받는다. 위 예제에서는 el이라고 했지만 매개변수 이름은 개발자가 변경 가능하다.</li>
<li>익명함수는 단순히  컴포넌트에 _input이라는 커스텀 속성을 만들고 그 속성에 DOM 엘리먼트의 값을 지정한다.</li>
</ul>
</li>
<li><p>ref 예제</p>
<pre><code class="language-jsx">  class Welcome extends React.Component {
      constructor(props) {
          super(props);
          this.state = {
              name: &#39;&#39;
          }
          this.showWelcomeMessage = this.showWelcomeMessage.bind(this);
      }

      showWelcomeMessage() {
          alert(`환영합니다. ${this.state.name}!!`);
          this.name_input.value = &#39;&#39;;
          this.name_input.focus();
      }

      render() {
          return(
              &lt;div&gt;
                  &lt;input 
                      type=&quot;text&quot; 
                      placeholder=&quot;이름을 입력해주세요&quot; 
                      ref={
                          (el) =&gt; {
                              this.name_input = el;
                          }
                      }
                  /&gt;
                  &lt;button 
                      type=&quot;button&quot;
                      onClick={this.showWelcomeMessage}
                  &gt;
                      클릭
                  &lt;/button&gt;
              &lt;/div&gt;
          );
      }
  }

  ReactDOM.render(
      &lt;Welcome/&gt;,
      document.getElementById(&#39;app&#39;)
  )</code></pre>
</li>
</ul>
<h2 id="163에서-ref-사용하기">16.3에서 ref 사용하기</h2>
<ul>
<li><p>리액트 16.3v에서는 위 방식이 아닌 <code>React.createRef()</code>를 통해 ref를 더 손쉽게 사용할 수 있다.</p>
<pre><code class="language-jsx">  class MyComponent extends React.Component {
    constructor(props) {
      super(props);
      this.myRef = React.createRef(); // ref 생성
    }
    render() {
      return &lt;div ref={this.myRef} /&gt;; // ref 어트리뷰트를 통해 React 엘리먼트에 부착
    }
  }</code></pre>
</li>
</ul>
<h3 id="ref에-접근하기">ref에 접근하기</h3>
<pre><code class="language-jsx">const node = this.myRef.current;</code></pre>
<ul>
<li><code>render</code> 메서드 안에서 ref가 엘리먼트에게 전달되었을 때, 그 노드를 향한 참조는 ref의 current 프로퍼티에 담기게된다.</li>
<li>ref의 값은 노드의 유형에 따라 다르다.<ul>
<li>ref 어트리뷰트가 HTML 엘리먼트에 쓰였다면, 생성자에서 <code>React.createRef()</code>로 생성된 ref는 current 프로퍼티의 값으로 자신을 전달받은 DOM 엘리먼트를 받는다.</li>
<li>ref 어트리뷰트가 커스텀 클래스 컴포넌트에 쓰였다면, ref 객체는 마운트된 컴포넌트의 인스턴스를 current 프로퍼티의 값으로 받습니다.</li>
<li>함수 컴포넌트는 인스턴스가 없기 때문에 함수 컴포넌트에 ref 프로퍼티를 사용할 수 없다.</li>
</ul>
</li>
</ul>
<h1 id="포털-사용하기">포털 사용하기</h1>
<ul>
<li>리액트 컴포넌트의 랜더링 메서드가 반환하는 엘리먼트는 부모 노드에서 가장 가까운 자식 요소로 DOM에 마운트 된다. 즉 자식 요소는(컴포넌트, 엘리먼트 등) 부모 컴포넌트의 DOM 내부에 렌더링 된다</li>
<li>포탈을 사용하면 자식 요소를 부모 컴포넌트의 DOM 계층 구조 바깥에 있는 DOM 노드로 렌더링 할 수 있다. 즉 자식 요소를 개발자가 원하는 위치에 랜더링 할 수 있다.</li>
<li>포탈을 사용하면 개발자가 작성한 JSX 계층구조에 종속되지 않으면서 컴포넌트를 렌더링 할 수 있다.</li>
</ul>
<h2 id="포탈-사용법">포탈 사용법</h2>
<ul>
<li><p><code>ReactDOM.createPortal(child, container)</code></p>
<ul>
<li>child : 엘리먼트, 문자열, 혹은 fragment와 같은 어떤 종류이든 렌더링할 수 있는 React 자식</li>
<li>container : DOM 엘리먼트</li>
<li>위 두 매개변수를 전달받아 child를 container에 렌더링하는 메서드</li>
</ul>
</li>
<li><p>예1</p>
<pre><code class="language-jsx">  // 일반 코드
  render() {
    // React는 새로운 div를 마운트하고 그 안에 자식을 렌더링한다.
    return (
      &lt;div&gt;
        {this.props.children}
      &lt;/div&gt;
    );
  }

  // portal 사용 코드
  render() {
    // React는 새로운 div를 생성하지 *않고* `domNode` 안에 자식을 렌더링한다.
    // `domNode`는 DOM 노드라면 어떠한 것이든 유효하고, 그것은 DOM 내부의 어디에 있든지 상관없다.
    return ReactDOM.createPortal(
      this.props.children,
      domNode
    );
  }</code></pre>
</li>
<li><p>예2</p>
<pre><code class="language-jsx">  class Label extends React.Component {
      render() {
          return ReactDOM.createPortal(
              ` : Portal Test`,
              document.getElementById(&#39;heading&#39;)
          );
      }
  }

  class Welcome extends React.Component {
      constructor(props) {
          super(props);
          this.showWelcomeMessage = this.showWelcomeMessage.bind(this);
      }

      showWelcomeMessage() {
          alert(`환영합니다. ${this.name_input.value}!!`);
          this.name_input.value = &#39;&#39;;
          this.name_input.focus();
      }

      render() {
          return(
              &lt;div&gt;
                  &lt;input 
                      type=&quot;text&quot; 
                      placeholder=&quot;이름을 입력해주세요&quot; 
                      ref={
                          (el) =&gt; {
                              this.name_input = el;
                          }
                      }
                  /&gt;
                  &lt;button 
                      type=&quot;button&quot;
                      onClick={this.showWelcomeMessage}
                  &gt;
                      클릭
                  &lt;/button&gt;
                  &lt;Label/&gt;
              &lt;/div&gt;
          );
      }
  }

  ReactDOM.render(
      &lt;Welcome/&gt;,
      document.getElementById(&#39;app&#39;)
  )</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vue.js - 동적으로 테이블 행 병합하기, 동적 rowspan(2)]]></title>
            <link>https://velog.io/@gyumin_2/Vue.js-%EB%8F%99%EC%A0%81%EC%9C%BC%EB%A1%9C-%ED%85%8C%EC%9D%B4%EB%B8%94-%ED%96%89-%EB%B3%91%ED%95%A9%ED%95%98%EA%B8%B0-%EB%8F%99%EC%A0%81-rowspan2</link>
            <guid>https://velog.io/@gyumin_2/Vue.js-%EB%8F%99%EC%A0%81%EC%9C%BC%EB%A1%9C-%ED%85%8C%EC%9D%B4%EB%B8%94-%ED%96%89-%EB%B3%91%ED%95%A9%ED%95%98%EA%B8%B0-%EB%8F%99%EC%A0%81-rowspan2</guid>
            <pubDate>Tue, 12 Apr 2022 03:35:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://velog.io/@gyumin_2/Vue.js-%EB%8F%99%EC%A0%81%EC%9C%BC%EB%A1%9C-%ED%85%8C%EC%9D%B4%EB%B8%94-%ED%96%89-%EB%B3%91%ED%95%A9%ED%95%98%EA%B8%B0-%EB%8F%99%EC%A0%81-rowspan1">이전 글</a>과 이어집니다...</p>
</blockquote>
<h1 id="내가-구현해야-할-화면">내가 구현해야 할 화면</h1>
<p><img src="https://velog.velcdn.com/images/gyumin_2/post/e8d0e247-976a-41fa-a6ce-a7c09b293038/image.png" alt=""></p>
<ul>
<li>이전 글에서 내가 구현한 화면은 백엔드에서 데이터를 받아 같은 날짜끼리는 행이 병합되는 테이블이었다.</li>
<li>이번에는 아래의 조건을 추가된 화면을 작업해야했다.</li>
</ul>
<pre><code>1. 사용자는 검색 조건에 시간대별, 문의유형, 상담원을 선택할 수 있다.
2. 사용자는 검색 조건은 모두 선택 안 할수도, 모두 선택 할 수도 있다.
3. 같은 날짜의 같은 시간은 행이 병합되어야 한다.
4. 같은 날짜의 같은 시간의 같은 문의 유형은 행이 병합되어야 한다.</code></pre><h1 id="데이터-형식">데이터 형식</h1>
<pre><code class="language-javascript">{
    table_data: [
      {
        standard_at: &#39;2022-01-01&#39;,
        standard_time: &#39;9-10&#39;, // 시간
        inquire_type: &#39;문의유형1&#39;, // 문의유형
        counselor_nm: &#39;상담원1&#39;, // 상담원
        ...
      },
        ...
    ]
}</code></pre>
<ul>
<li>api 요청 시 백엔드에서 프론트로 위와 같은 형식의 데이터를 내려준다.</li>
<li>사용자가 해당 검색 조건을 선택했을 때만 백엔드에서 <code>standard_time</code>, <code>inquire_type</code>, <code>counselor_nm</code> 프로퍼티를 내려준다.</li>
</ul>
<h1 id="어떻게-해결해야할까">어떻게 해결해야할까...</h1>
<ul>
<li>처음에는 이전 글처럼 v-if 디렉티브와 v-bind 디렉티브에 메소드를 연결하여 구현하려고 했으나, 사용자 검색 조건 때문에 v-if 조건문이 점점 길어지고 복잡해졌다. 또한 v-for로 반복문을 돌면서 v-if 연산을하고, v-bind와 연결된 메소드를 계속 호출하는 방식이 비효율적인 것 같아 보였다.</li>
<li>그래서 api 응답 데이터를 data에 할당하기 전에 렌더링 여부와 rowspan에 할당할 값을 계산하는 방식으로 작업을 했다.<pre><code class="language-javascript">// template 영역
...
&lt;table&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th&gt;날짜&lt;/th&gt;
    &lt;th v-if=&quot;tableData[0].standard_time&quot;&gt;시간&lt;/th&gt;
    &lt;th v-if=&quot;tableData[0].inquire_type&quot;&gt;문의유형&lt;/th&gt;
    &lt;th v-if=&quot;tableData[0].counselor_nm&quot;&gt;상담원&lt;/th&gt;
    &lt;th&gt;데이터1&lt;/th&gt;
    &lt;th&gt;데이터2&lt;/th&gt;
    &lt;th&gt;데이터3&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
  &lt;tr v-for=&quot;(item, index) in tableData&quot;&gt;
    &lt;td
        v-if=&quot;item.isDateVisible&quot;
        :rowSpan=&quot;item.dateRowSpan&quot;
    &gt;
        {{ item.standard_at }} 
    &lt;/td&gt;
    &lt;td
        v-if=&quot;item.standard_time &amp;&amp; item.isTimeVisible&quot;
        :rowSpan=&quot;item.timeRowSpan&quot;
    &gt;
        {{ item.standard_time }} 
    &lt;/td&gt;
    &lt;td
        v-if=&quot;item.inquire_type &amp;&amp; item.isImquireTypeVisible&quot;
        :rowSpan=&quot;item.inquireTypeRowSpan&quot;
    &gt;
        {{ item.inquire_type }} 
    &lt;/td&gt;
    &lt;td
        v-if=&quot;item.counselor_nm&quot;
    &gt;
        {{ item.counselor_nm }} 
    &lt;/td&gt;
    &lt;td&gt; {{ item.data1 }} &lt;/td&gt;
    &lt;td&gt; {{ item.data2 }} &lt;/td&gt;
    &lt;td&gt; {{ item.data3 }} &lt;/td&gt;
  &lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
...
</code></pre>
</li>
</ul>
<p>// script 영역 - methods
...
getData() {
    axios.get(requestUrl, {config})
      .then(res =&gt; {
      this.tableData = res.data.table_data.map((item, index, arr) =&gt; {
        const param = {item, index, arr};
        this.setDateProp(param);
        if(item.standard_time) this.setTimeProp(param);
        if(item.inquire_type) this.setInquireTypeProp(param);
        return item;
      });
    })
},
// 날짜 데이터 프로퍼티 설정 메소드
setDateProp({item, index, arr}) {
    item.isDateVisible = false;
    item.dateRowSpan = 0
    // 0번째 인덱스 요소 처리
    if(index == 0) {
      item.isDateVisible = true;
      item.dateRowSpan = this.getRowSpan(arr, item, &#39;standard_at&#39;);
      return;
    }
    // 이전 인덱스 요소와 시간 비교
    if(item.standard_at != arr[index-1].standard_at) {
      item.isDateVisible = true;
      item.dateRowSpan = this.getRowSpan(arr, item, &#39;standard_at&#39;);
    }
  },
// 시간 데이터 프로퍼티 설정 메소드
setTimeProp({item, index, arr}) {
  // 초깃값 세팅
  item.isTimeVisible = false;
  item.timeRowSpan = 0;
  // 0번째 인덱스 요소 처리
  if(index == 0) {
    item.isTimeVisible = true;
    item.timeRowSpan = this.getRowSpan(arr, item, &#39;standard_time&#39;);
    return;
  }
  // 이전 인덱스와 시간, 날짜 비교
  if(item.standard_time != arr[index-1].standard_time) {
    item.isTimeVisible = true;
    item.timeRowSpan = this.getRowSpan(arr, item, &#39;standard_time&#39;);
  } else if(item.standard_at != arr[index-1].standard_at) {
    item.isTimeVisible = true;
    item.timeRowSpan = this.getRowSpan(arr, item, &#39;standard_time&#39;);
  }
},
// 문의유형 데이터 프로퍼티 설정 메소드
setInquireTypeProp({item, index, arr}) {
  // 초깃값 세팅
  item.isSkillVisible = false;
  item.skillRowSpan = 0;
  // 0번째 인덱스 요소 처리
  if(index == 0) {
    item.isSkillVisible = true;
    item.skillRowSpan = this.getRowSpan(arr, item, &#39;skill_nm&#39;);
    return;
  }
  // 이전 데이터와 문의 유형이 다른 경우
  if (arr[index-1].skill_nm != item.skill_nm) {
    item.isSkillVisible = true;
    item.skillRowSpan = this.getRowSpan(arr, item, &#39;skill_nm&#39;);
    return;
  }</p>
<p>  // 문의 유형이 같은데 날짜 혹은 시간이 다른 경우
  if (item.standard_time
      &amp;&amp; item.standard_time != arr[index-1].standard_time
     ) {
    item.isSkillVisible = true;
    item.skillRowSpan = this.getRowSpan(arr, item, &#39;skill_nm&#39;);
  } else if (item.standard_at != arr[index-1].standard_at) {
    item.isSkillVisible = true;
    item.skillRowSpan = this.getRowSpan(arr, item, &#39;skill_nm&#39;);
  }
},
getRowSpan(arr, data, prop) {
  let rowSpan = 0;</p>
<p>  if (data.standard_time &amp;&amp; prop != &#39;standard_at&#39;) {
    arr.forEach(item =&gt; {
      if(item[prop] == data[prop] 
         &amp;&amp; item.standard_at == data.standard_at 
         &amp;&amp; item.standard_time == data.standard_time) rowSpan++;
    })
    return rowSpan;
  }</p>
<p>  arr.forEach(item =&gt; {
    if(item[prop] == data[prop] 
       &amp;&amp; item.standard_at == data.standard_at) rowSpan++;
  })</p>
<p>  return rowSpan;
}
...</p>
<p>```</p>
<ul>
<li>지금 다시 보니 <code>setDateProp</code>, <code>setTimeProp</code>, <code>setInquireTypeProp</code> 메소드 형태가 비슷해서 하나의 메소드로 합칠 수 있었는데, 당시에는 정신 없이 바빠서 그런 고민할 틈이 없었나 보다...</li>
<li>이 방식보다 더 좋은 방식도 있겠지만 아무리 검색해봐도 나처럼 많은 조건이 있는 동적 행 병합 예제가 없어서 굴러가지 않은 머리를 굴려가며 코드를 어찌저찌 작성했다. 다음에는 더 좋은 방식을 고민해봐야겠다🤔</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React.js - 리액트에서 이벤트 처리하기]]></title>
            <link>https://velog.io/@gyumin_2/React.js-%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%97%90%EC%84%9C-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@gyumin_2/React.js-%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%97%90%EC%84%9C-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 11 Apr 2022 06:10:46 GMT</pubDate>
            <description><![CDATA[<h1 id="react에서-이벤트-처리하기">React에서 이벤트 처리하기</h1>
<ul>
<li>리액트도 결국에는 자바스크립트이기 때문에 이벤트를 처리하는 방식은 순수 자바스크립트와 동일하다. 먼저 이벤트를 리스닝 후 리스너를 작성하는 것이다.</li>
</ul>
<blockquote>
<p>이벤트 리스닝 : 특정 엘리먼트가 어떤 이벤트를 수신할지 결정하는 것</p>
<p>이벤트 리스너 : 이벤트 리스너란 이벤트가 발생했을 때 그 처리를 담당하는 함수를 가리키며, 이벤트 핸들러(event handler)라고도한다. 지정된 타입의 이벤트가 특정 요소에서 발생하면, 웹 브라우저는 그 요소에 등록된 이벤트 리스너를 실행시킨다.</p>
</blockquote>
<h2 id="리액트에서-이벤트-처리하는-방식">리액트에서 이벤트 처리하는 방식</h2>
<ul>
<li>React 엘리먼트에서 이벤트를 처리하는 방식은 DOM 엘리먼트에서 이벤트를 처리하는 방식과 매우 유사하지만, 몇 가지 문법 차이가 있다. 때문에 이 차이에 주의하여 이벤트를 처리해야 한다.</li>
</ul>
<h3 id="react의-이벤트는-소문자-대신-카멜-케이스camelcase를-사용">React의 이벤트는 소문자 대신 카멜 케이스(camelCase)를 사용</h3>
<ul>
<li>DOM 엘리먼트는 on+이벤트명 형식의 이벤트 속성을 가지고 있으며, 이벤트 속성은 전부 소문자로 작성을 한다. 하지만 하지만 리액트 엘리먼트에서는 전부 카멜 케이스로 이벤트 속성을 작성해야 한다.</li>
<li>DOM 엘리먼트에서 <code>onclick</code>, <code>onmouseover</code>, <code>onsubmit</code>, <code>onchange</code> 등 전부 소문자로 작성됐던 이벤트 속성들은 리액트 엘리먼트에서는 각각 <code>onClick</code>, <code>onMouseOver</code>, <code>OnSubmit</code>, <code>OnChange</code> 등 카멜 케이스로 작성되어야 한다.</li>
</ul>
<h3 id="jsx를-사용하여-문자열이-아닌-함수로-이벤트-핸들러를-전달">JSX를 사용하여 문자열이 아닌 함수로 이벤트 핸들러를 전달</h3>
<ul>
<li><p>리액트에서는 JSX 자체에 인라인 형식으로 이벤트를 리스닝한다. 리스닝할 이벤트와 호출될 이벤트 핸들러 모두 JSX 마크업 안에 인라인 형식으로 지정해야한다.</p>
<pre><code class="language-jsx">  // DOM 엘리먼트에서 이벤트 속성 작성 시 전부 소문자로 작성하며
  // 속성 값을 문자열로 작성하여 이벤트 핸들러를 전달한다. 
  &lt;button onclick=&quot;activateLasers()&quot;&gt;
    Activate Lasers
  &lt;/button&gt;

  // 리액트 엘리먼트에서 이벤트 속성 작성 시 카멜케이스로 작성하며
  // 속성 값은 문자열이 아닌 표현식을 이용해 함수로 이벤트 핸들러를 전달한다.
  &lt;button onClick={activateLasers}&gt;
    Activate Lasers
  &lt;/button&gt;</code></pre>
</li>
<li><p>React를 사용할 때 DOM 엘리먼트가 생성된 후 리스너를 추가하기 위해 <code>addEventListener</code>를 호출할 필요가 없다. 대신, 엘리먼트가 처음 렌더링될 때 리스너를 제공하면 된다.</p>
</li>
</ul>
<h3 id="이벤트-기본-동작-방지">이벤트 기본 동작 방지</h3>
<ul>
<li><p>DOM 엘리먼트의 경우 이벤트 핸들러가 <code>false</code>를 반환해도 해당 엘리먼트의 기본 동작을 방지할 수 있었지만, 리액트 엘리먼트의 경우 반드시 핸들러가 <code>preventDefault</code>를 호출해야 한다.</p>
<pre><code class="language-jsx">  // DOM 엘리먼트에서는 핸들러가 false를 반환하면 a 태그의 기본 동작이 방지됨
  &lt;a href=&quot;#&quot; onclick=&quot;console.log(&#39;The link was clicked.&#39;); return false&quot;&gt;
    Click me
  &lt;/a&gt;

  // 리액트에서는 a 태그의 기본 동작을 막기위해서 반드시 
  // preventDefault를 호출해야한다.
  function ActionLink() {
    function handleClick(e) {
      e.preventDefault();
      console.log(&#39;The link was clicked.&#39;);
    }

    return (
      &lt;a href=&quot;#&quot; onClick={handleClick}&gt;
        Click me
      &lt;/a&gt;
    );
  }</code></pre>
</li>
</ul>
<h3 id="이벤트-핸들러-내부의-this">이벤트 핸들러 내부의 this</h3>
<ul>
<li>클래스 컴포넌트의 경우 JSX 콜백 안에서 this의 의미에 대해 주의해야 한다. JavaScript에서 클래스 메서드는 기본적으로 바인딩되어 있지 않기 때문이다. 따라서 기본적으로 <code>onClick</code>등으로 전달한 이벤트에서 <code>this</code>를 호출한 경우 바인딩 되어있지 않기 때문에 undefined로 표시된다.</li>
</ul>
<blockquote>
<p>💡 이벤트 콜백에서 <code>window</code>가 아닌 <code>undefined</code>가 나오는 이유는 React가 development 모드에서는 strict mode로 검사를 하기 때문이다. production build 에서는 포함되지 않는다.</p>
</blockquote>
</aside>

<ul>
<li><p>리액트에서는 <code>onClick={this.handleClick}</code>과 같이 뒤에 ()를 사용하지 않고 메서드를 참조할 경우, 해당 메서드에 <code>this</code>를 바인딩 해야한다.</p>
</li>
<li><p>리액트에서 권장하는 이벤트 바인딩 방법</p>
<pre><code class="language-jsx">  // 1. 생성자에서 bind()를 사용하는방법
  class LoggingButton extends React.Component {
    Constructor(props) {
      super(props);
          // 콜백에서 `this`가 작동하려면 아래와 같이 바인딩해야 한다.
      this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
      console.log(&#39;this is:&#39;, this);
    }

    render() {
      return (
        &lt;button onClick={this.handleClick}&gt;
          Click me
        &lt;/button&gt;
      );
    }
  }

  // 2. 사용할 곳에서 bind()를 사용하는 방법
  class LoggingButton extends React.Component {
    handleClick() {
      console.log(&#39;this is:&#39;, this);
    }

    render() {
      return (
        &lt;button onClick={this.handleClick.bind(this)}&gt;
          Click me
        &lt;/button&gt;
      );
    }
  }

  // 3. 화살표 함수로 직접 호출하는 방법 
  // 이 방법은 LoggingButton이 랜더링 될 때마다 새로운 함수를 생성하는 문제가 있다.
  // 콜백 함수 내에서 재랜더링을 발생시키는 경우 성능 문제가 발생할 수 있다.
  class LoggingButton extends React.Component {
    handleClick() {
      console.log(&#39;this is:&#39;, this);
    }

    render() {
      return (
        // 이 문법은 `this`가 handleClick 내에서 바인딩되도록 한다.
        &lt;button onClick={(e) =&gt; this.handleClick(e)}&gt;
          Click me
        &lt;/button&gt;
      );
    }
  }

  // 4. 화살표 함수로 선언하고 바인딩하는 방법
  // 이 방법으로 이벤트를 바인딩 하는것을 권장한다.
  class LoggingButton extends React.Component {
    handleClick = () =&gt; {
      console.log(&#39;this is:&#39;, this);
    }

    render() {
      return (
        &lt;button onClick={this.handleClick}&gt;
          Click me
        &lt;/button&gt;
      );
    }
  }</code></pre>
</li>
</ul>
<h2 id="이벤트-속성">이벤트 속성</h2>
<pre><code class="language-jsx">class LoggingButton extends React.Component {
  Constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick(e) {
    console.log(&#39;this is:&#39;, this);
  }

  render() {
    return (
      &lt;button onClick={this.handleClick}&gt;
        Click me
      &lt;/button&gt;
    );
  }
}</code></pre>
<ul>
<li><code>handleClick(e)</code> 처럼 모든 이벤트 핸들러에는 해당 이벤트에 대한 정보를 담고 있는 이벤트 객체가 매개변수로 전달된다.</li>
<li>리액트에서 이벤트 핸들러에 전달된 이벤트 객체는 DOM 엘리먼트에서 전달되는 이벤트 객체와 다르다. JSX에 이벤트를 지정하는 경우 DOM 이벤트가 아닌 합성 이벤트라고 하는 리액트의 특별한 이벤트 유형인 SyntheticEvent를 다룬다.</li>
<li>즉 리액트에서 이벤트 핸들러는 네이티브 이벤트 객체가 아닌 브라우저의 네이티브 이벤트를 래핑하는 SyntheticEvent를 매개변수로 전달받는다.</li>
<li>React는 W3C 명세에 따라 SyntheticEvent를 정의하기 때문에 기존 DOM 엘리먼트에서 이벤트 핸들러가 이벤트 객체를 이용해 작업하는 것과 동일한 작업을 리액트에서도 할 수 있다. 즉 브라우저 호환성에 대해 걱정할 필요가 없다는 것이다.</li>
<li>하지만 SyntheticEvent는 기존 브라우저 이벤트를 래핑하는 것이지 일대일 대응하는 것이 아니기 때문에 DOM 이벤트 문서가 아닌 리액트 합성 이벤트 문서를 참고해야 한다. 일부 DOM 이벤트는 SyntheticEvent에 존재하지 않을 수 있다.</li>
</ul>
<p><a href="https://ko.reactjs.org/docs/events.html">리액트 합성 이벤트 문서</a></p>
<h2 id="이벤트-핸들러에-매개변수-전달하기">이벤트 핸들러에 매개변수 전달하기</h2>
<ul>
<li>이벤트 핸들러에 매개변수를 전달하는 방법 또한 기존의 방식과 조금 다르다.</li>
</ul>
<pre><code class="language-jsx">class ClickEventEx extends React.Component {
    constructor(props) {
        super(props);
        this.clickHandle = this.clickHandle.bind(this);
    }

    clickHandler(msg) {
        console.log(msg);
    }

    render() {
        return (
            &lt;button onClick={this.clickHandler(&quot;Hi&quot;)}&gt;Click&lt;/button&gt;
        );
    }
}</code></pre>
<ul>
<li><p>위와 같이 코드를 작성하면 버튼을 눌렀을 때가 아닌 화면이 렌더링 됐을 때 <code>clickHandler()</code> 함수의 명령이 실행된다.</p>
</li>
<li><p><code>onClick={함수이름}</code> 이 아닌 <code>onClick={함수이름()}</code> 으로 이벤트 핸들러를 작성할 경우 <code>clickHandler()</code> 함수를 먼저 실행 한 뒤 리턴 값을 <code>onClick</code>에 전달하게 된다.</p>
</li>
<li><p><code>clickHandler()</code> 함수를 아래와 같이 함수의 return 값을 함수로 전달하는 방식으로 수정할 경우, 버튼을 눌렀을 때 return 한 함수가 작동한다.</p>
<pre><code class="language-jsx">  clickHandle(msg) {
      console.log(msg);
      return () =&gt; {
          console.log(&quot;클릭 시 리턴한 함수가 전달된다.&quot;)
      }
  }</code></pre>
</li>
<li><p>이벤트 핸들러에 매개변수를 전달하는 방법에는 2가지가 있다.</p>
<ol>
<li><p>화살표함수를 사용하는 방법</p>
<pre><code class="language-jsx"> &lt;button onClick={(e) =&gt; this.clickHandler(&quot;Hi&quot;, e)}&gt;Click!&lt;/button&gt;</code></pre>
</li>
<li><p><code>bind()</code> 함수를 사용하는 방법</p>
<pre><code class="language-jsx"> &lt;button onClick={this.clickHandler.bind(this,&quot;Hi&quot;)}&gt;Click!&lt;/button&gt;</code></pre>
</li>
</ol>
<ul>
<li>화살표 함수를 사용할 경우 불필요한 랜더링 이슈가 있으므로 2번 방식을 사용는 것을 권장한다.</li>
<li>두 경우 모두 React 이벤트를 나타내는 <code>e</code> 인자가 두 번째 인자로 전달된다. 화살표 함수를 사용하면 명시적으로 인자를 전달해야 하지만 bind를 사용할 경우 추가 인자가 자동으로 전달된다.</li>
</ul>
</li>
</ul>
<h2 id="컴포넌트간-이벤트-통신">컴포넌트간 이벤트 통신</h2>
<ul>
<li><p>아래의 코드는 문법상의 오류는 없지만 버튼을 클릭 했을 때 아무 작동을 하지 않는다.</p>
<pre><code class="language-jsx">  class Counter extends React.Component {
      constructor(props) {
          super(props);
          this.state = {num: 0}
          this.increase = this.increase.bind(this);
      }

      increase(e) {
          this.setState({
              num: this.state.num + 1
          })
      }

      render() {
          return (
              &lt;div&gt;
                  &lt;p&gt;{this.state.num}&lt;/p&gt;
                  &lt;BtnComp onClick={this.increase}/&gt;
              &lt;/div&gt;
          )
      }
  }

  class BtnComp extends React.Component {
      constructor(props) {
          super(props);
      }

      render() {
          return(
              &lt;button&gt;Click +&lt;/button&gt;
          );
      }
  }

  ReactDOM.render(
      &lt;Counter/&gt;,
      document.getElementById(&#39;app&#39;)
  )</code></pre>
</li>
</ul>
<ul>
<li>BtnComp 컴포넌트는 HTML 엘리먼트 하나민 리턴할 뿐 클릭 시 아무 작동을 하지 않는다. 컴포넌트는 이벤트를 직접 리스닝 할 수 없다. 왜냐하면 컴포넌트는 DOM 엘리먼트를 감싸는 Wrapper이기 때문이다.</li>
<li>위 예제의 경우 onClick 이벤트를 리스닝하고 <code>increase()</code> 함수를 실행해야 하는 것은 정확히 말하면 BtnComp  컴포넌트 자체가 아니라 BtnComp의 button 태그다.</li>
<li>때문에 BtnComp의 button 태그 클릭 시 우리가 원하는 동작을 하기 위해서는 컴포넌트 안에서 DOM 엘리먼트에 이벤트를 할당하고 props로 이벤트 핸들러는 전달하면 된다.</li>
</ul>
<pre><code class="language-jsx">class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {num: 0}
        this.increase = this.increase.bind(this);
    }

    increase(e) {
        this.setState({
            num: this.state.num + 1
        })
    }

    render() {
        return (
            &lt;div&gt;
                &lt;p&gt;{this.state.num}&lt;/p&gt;
                // BtnComp에 clickHandler라는 이름으로 increase()를 props로 전달한다.
                &lt;BtnComp clickHandler={this.increase}/&gt;
            &lt;/div&gt;
        )
    }
}

class BtnComp extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        return(
          // BtnComp에서는 increase() props로 전달받아
          // onClick의 이벤트 핸들러로 설정한다. 
          &lt;button onClick={this.props.clickHandler}&gt;Click +&lt;/button&gt;
        );
    }
}</code></pre>
<ul>
<li>또 다른 예시</li>
</ul>
<pre><code class="language-jsx">const ClickCounterButton = props =&gt; {
  return (
    &lt;button onClick={props.handler}&gt;Don`t touch me with your dirty hands!&lt;/button&gt;
  );
};

class Counter extends React.Component {
  render() {
    return &lt;span&gt;Clicked {this.props.value} thies.&lt;/span&gt;;
  }
}

class Content extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
  }

  handleClick = (event) =&gt; {
    this.setState({counter: ++this.state.counter });
  }

  render() {
    return  (
      &lt;div&gt;
        &lt;ClickCounterButton handler={this.handleClick} /&gt;
        &lt;br/&gt;
        &lt;Counter value={this.state.counter}/&gt;
      &lt;/div&gt;
    )
  }
}</code></pre>
<h2 id="react가-지원하지-않는-dom-이벤트-처리하기">React가 지원하지 않는 DOM 이벤트 처리하기</h2>
<ul>
<li>모든 DOM 이벤트가 SyntheticEvent에 대응하는 것은 아니다. 가령 SyntheticEvent은 resize 이벤트를 인식하지 못한다.</li>
<li>resize나 사용자 정의 이벤트 처럼 리액트가 인식할 수 없는 이벤트의 경우, 생명주기 이벤트에서 <code>addEventListener</code>를 사용하는 전통적인 방법을 사용해야 한다.</li>
</ul>
<pre><code class="language-jsx">class Something extends React.Component {
    handleMyEvent(e) {
        // 이벤트 처리
    }

    componentDidMount() { // 이벤트 등록
        window.addEventListener(&quot;someEvent&quot;, this.handleMyEvent);
    }

    componentWillUnmount() { // 이벤트 제거
        window.removeEventListener(&quot;someEvent&quot;, this.handleMyEvent);
    }

    render() {
        return (
            &lt;div onSomeEvent={this.handleMyEvent}&gt;Some Event&lt;/div&gt;
        )
    }
}</code></pre>
<h2 id="리액트-이벤트-처리-방식이-전통적인-방식과-다른-이유">리액트 이벤트 처리 방식이 전통적인 방식과 다른 이유</h2>
<ol>
<li>브라우저 호환성<ul>
<li>낮은 버전의 브라우저의 경우 이벤트 처리가 일관되게 작동하지 않을 수 있다. 때문에 리액트는 기존 이벤트 객체를 SyntheticEvent로 래핑함으로써 호환되지 않는 환경에서도 이벤트 처리를 동일한 방법으로 할 수 있게 한다.</li>
</ul>
</li>
<li>성능 향상<ul>
<li>이벤트 핸들러는 많으면 많을 수록 많은 메모리를 차지한다.</li>
<li>리액트는 DOM 엘리먼트에 직접 이벤트 핸들러를 부착하지 않는다. 리액트는 문서 최상위에 있는 하나의 이벤트 핸들럴르 사용한다. 이 핸들러는 모든 이벤트를 리스닝하며, 이벤트 발생 시 적합한 개별 핸들러를 호출하는 역할을 한다.</li>
<li>이는 이벤트 처리 코드를 개발자가 직접 최적화 하지 않아도 되게 해준다.</li>
</ul>
</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>