<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>doeunnkimm_.log</title>
        <link>https://velog.io/</link>
        <description>블로그 옮겼어요! &gt;&gt; https://doeunnkimm-me.vercel.app/</description>
        <lastBuildDate>Sat, 03 May 2025 13:43:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>doeunnkimm_.log</title>
            <url>https://velog.velcdn.com/images/doeunnkimm_/profile/c792f84d-e729-4545-99f4-b117d003c855/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. doeunnkimm_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/doeunnkimm_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[밑바닥부터 모듈 따라가보기 (with. React)]]></title>
            <link>https://velog.io/@doeunnkimm_/%EB%B0%91%EB%B0%94%EB%8B%A5%EB%B6%80%ED%84%B0-%EB%AA%A8%EB%93%88-%EB%94%B0%EB%9D%BC%EA%B0%80%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@doeunnkimm_/%EB%B0%91%EB%B0%94%EB%8B%A5%EB%B6%80%ED%84%B0-%EB%AA%A8%EB%93%88-%EB%94%B0%EB%9D%BC%EA%B0%80%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 03 May 2025 13:43:54 GMT</pubDate>
            <description><![CDATA[<p>최근에 <strong><code>모듈 시스템</code></strong>에 대해 공부하게 되었는데요. 공부하다보니 내가 작성하는 이 모듈이 브라우저에서 로드되는 것을 당연하게 생각하고 마음껏 개발만 하고 있었던 것 같았어요.</p>
<p>이번 글에서는 모듈을 로드하고 트랜스파일하고 빌딩하고 브라우저에서 로드하는 과정 하나하나에 집중해 보려고 합니다.</p>
<h2 id="구성하려는-환경">구성하려는 환경</h2>
<ul>
<li><code>tsx</code> 파일을 브라우저에서 실행</li>
</ul>
<h2 id="런타임-환경">런타임 환경</h2>
<blockquote>
<p>프로그램을 실행할 수 있는 필수적인 환경을 제공
ex. Node.js, Deno, Bun</p>
</blockquote>
<p>우선 런타임 환경이 필요합니다. 우리가 소스 코드를 작성하게 되면, 어떤어떤..〰️ 과정을 통해 브라우저에서 실행될텐데, 이 작업들이 런타임 환경에서 돌아가게 됩니다.</p>
<p>우리가 프로젝트에서 모듈을 활용했다면 경로도 처리해야할테고, tsx로 작성했다면 브라우저가 이해할 수 있는 js로 변환해야 하는 이런 처리들이 런타임 환경에서 이루어져야 합니다. 최종적으로 브라우저가 실행할 수 있는 파일로 만들어주는 것이라고 할 수 있겠습니다.</p>
<p><strong><code>🤔 런타임 환경은 왜 특정 언어만을 지원하는걸까?</code></strong></p>
<ul>
<li>각 언어는 문법, 메모리 관리 방식 등 세부적인 동작 원리가 다름</li>
<li>그래서 최적화하는 방식도 다름</li>
<li>그래서 특정 언어에 필요한 자원 관리 방식에 맞게 설계</li>
</ul>
<p><strong><code>🤔 나는 왜 당연하게 Node.js를 설치했던 것인가?</code></strong></p>
<ul>
<li>일단 Node.js는 JavaScript 런타임 환경</li>
<li>JavaScript는 웹 브라우저 내에서 바로 실행될 수 있어서, 웹 애플리케이션을 만드는 데에 효율적</li>
<li>브라우저에서 실행되는 JavaScript를, 브라우저에 닿기 전에도 처리할 수 있게 하여 여러 작업을 가능하게 해주는 것이 런타임 환경</li>
<li>Deno, Bun과 같은 다른 런타임 환경도 있지만, 아직은 성숙도와 생태계로 인해 많은 프로젝트에서 Node.js를 채택 중</li>
</ul>
<p><strong><code>🥟 Bun! 좋다</code></strong></p>
<ul>
<li>개인적으로 Bun을 좋아한다.</li>
<li>자세한 구성에 대해서는 아직 못 알아봤지만, 단순 사용해봤을 때 경험이 무척 좋았다. 정말 빨랐다.</li>
<li>시간이 된다면 Bun에 대해 다루는 포스팅을 남겨볼까 한다.</li>
<li>다들 써보시라!</li>
</ul>
<h2 id="패키지-매니저">패키지 매니저</h2>
<blockquote>
<ul>
<li>패키지: 파일이나 코드의 집합</li>
<li>패키지 매니저: 프로젝트에 필요한 패키지를 설치, 관리, 업데이트, 제거하는 도구</li>
</ul>
</blockquote>
<p>우리는 이미 작성되어 있는 코드를 활용해서 프로젝트 개발을 빠르게 하곤 하는데요, <code>React</code> 역시 외부 라이브러리로, 패키지 매니저를 통해 설치하고 관리해야 합니다.</p>
<p>패키지 매니저가 무슨 일을 하는지는 <a href="https://toss.tech/article/lightning-talks-package-manager">토스 기술 블로그</a>에 잘 설명되어 있어 생략합니다!</p>
<h2 id="packagejson">package.json</h2>
<blockquote>
<p>프로젝트에 대한 정보를 관리하는 파이롤, 패키지 매니저가 이 파일을 참조하여 모듈을 설치하고 관리</p>
</blockquote>
<p>런타임 환경에서 사용할 변환 도구 같은 것들을 패키지 매니저를 통해 설치해서 사용할 겁니다. 이때 패키지 매니저는 <code>package.json</code>을 통해 이 의존성을 버전과 함께 관리하게 됩니다. 물론 <code>node_modules</code>나 <code>lock</code>파일이 필요하지만 의존성 이야기니까 조금 생략해볼게요.</p>
<p>프로젝트에 <code>package.json</code>이 없어도 패키지 매니저는 알아서 <code>package.json</code>을 생성해서 관리하는 것을 확인할 수 있었어요.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/c3d2f9f7-40de-4472-839b-15330b4aef86/image.gif" alt=""></p>
<p><strong><code>🤔 패키지 매니저가 달라도 항상 package.json을 참조하던데, 누가 정한 표준 같은건가?</code></strong></p>
<ul>
<li><code>npm(Node Pacakge Manager)</code>에서 표준화한 파일</li>
<li>npm이 처음 <code>package.json</code> 파일 형식을 정의했고, 이후 다른 패키지 매니저들도 이 표준을 따르며 사용 중</li>
<li>덕분에 라이브러리에서 사용 중인 패키지 매니저와 내 환경에서의 패키지 매니저가 달라도 문제 없이 의존성을 설치하고 관리 가능</li>
</ul>
<h2 id="react-설치--구성">React 설치 &amp; 구성</h2>
<blockquote>
<ul>
<li><a href="https://react.dev/learn/add-react-to-an-existing-project">https://react.dev/learn/add-react-to-an-existing-project</a></li>
<li><a href="https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts">https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts</a></li>
</ul>
</blockquote>
<h3 id="indexhtml">index.html</h3>
<p>브라우저가 처음 로딩할 실제 HTML 문서를 하나 추가합니다. <code>React</code>는 JavaScript를 실행해서 이 HTML 문서 안에서 컴포넌트를 렌더링 하게 됩니다.</p>
<p><code>id=&quot;root&quot;</code> 하위로 리액트 컴포넌트를 렌더링하기 위해 DOM을 하나 추가했습니다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;body&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p><strong><code>🤔 그냥 body에 하면 안되나?</code></strong></p>
<ul>
<li>vite도 그렇고 <code>body</code>가 아니라 <code>div#root</code>에 리액트 컴포넌트를 렌더링 하는 편</li>
<li><code>body</code>에는 새로운 portal이나 다른 전역 요소들이 들어갈 수 있음</li>
<li>그 안에 React가 관리하는 영역은 따로 두는 것</li>
</ul>
<h3 id="maintsx">main.tsx</h3>
<p>index.html에서 생성해두었던 <code>div#root</code>에 리액트 컴포넌트 루트를 만들고 간단하게 하나 렌더할 수 있도록 했어요.</p>
<pre><code class="language-tsx">import React from &#39;react&#39;;
import { createRoot } from &#39;react-dom/client&#39;;

createRoot(document.getElementById(&#39;root&#39;)!).render(&lt;h1&gt;hello react&lt;/h1&gt;);</code></pre>
<p>우리는 이 코드가 실행된 결과를 브라우저에서 보기를 원합니다. 브라우저가 js를 실행하는 방법을 복기해봅시다.</p>
<ol>
<li>브라우저는 HTML 파싱을 하기 시작합니다.</li>
<li>파싱 중 script 태그를 만나면 js를 로드하고 실행합니다.</li>
</ol>
<p>즉, html 파일에서 우리가 방금 만든 <code>main.tsx</code> 파일을 만날 수 있게 해줘야 한다는 말인데요,</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;body&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
    &lt;script src=&quot;/src/main.tsx&quot;&gt;&lt;/scripts&gt; &lt;!-- 👈 추가 --&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>위 코드가 렌더링된다고 생각해보면 이런 흐름일 거 같아요.</p>
<pre><code>index.html
   ↓
&lt;script src=&quot;/src/main.tsx&quot;&gt;
   ↓
JS 실행 (main.tsx → .render)
   ↓
&lt;div id=&quot;root&quot;&gt; 안에 UI 렌더링</code></pre><p>브라우저는 index.html을 읽을 수 있고, script를 실행할 수는 있지만, tsx 자체도 그렇고</p>
<pre><code class="language-tsx">import { createRoot } from &#39;react-dom/client&#39;;</code></pre>
<p>이 코드가 <code>main.tsx</code> 안에 있다면 브라우저는 <code>&quot;음… react-dom이란 패키지가 필요하네? 그럼 node_modules/react-dom/client.js를 찾아야지!&quot;</code>를 하지 못합니다. 이건 바로 번들러가 하는 일로, 해당 파일을 찾고 필요한 소스코드를 번들링하며 이 번들 파일을 브라우저가 직접 실행 가능한 것이죠!</p>
<p>그럼 이제 우리는 트랜스파일링, 번들링을 해야겠네요.</p>
<h2 id="트랜스파일러--번들러">트랜스파일러 &amp; 번들러</h2>
<blockquote>
<p><a href="https://github.com/s1owjke/js-bundler-benchmark">https://github.com/s1owjke/js-bundler-benchmark</a></p>
</blockquote>
<p><code>esbuild</code>로 해볼게요. vite를 좀 따라해보고 싶어서 설정을 좀 해서 써보겠습니다. 사실 <code>index.html</code>에서 <code>script</code>의 src에는 번들링된 내용을 기준으로 경로를 찾을 수 있게 했어야 했는데요,</p>
<pre><code class="language-html">&lt;script src=&quot;dist/main.js&quot;&gt;&lt;/script&gt;</code></pre>
<p>vite에서는 <code>src=&quot;src/main.tsx&quot;</code>처럼 개발 환경 기준에서 좀 더 직관적으로 쓰고 있더라구요. 저도 이 방법을 차용하고 조금 처리를 더해보겠습니다.</p>
<pre><code class="language-ts">// scripts/build.ts
import { build } from &#39;esbuild&#39;;
import fs from &#39;fs/promises&#39;;
import path from &#39;path&#39;;

const htmlPlugin = () =&gt; ({
  name: &#39;html-plugin&#39;,
  setup(build) {
    build.onEnd(async () =&gt; {
      const templatePath = path.resolve(&#39;index.html&#39;);
      let html = await fs.readFile(templatePath, &#39;utf8&#39;);
      html = html.replace(&#39;src=&quot;src/main.tsx&quot;&#39;, &#39;src=&quot;main.js&quot;&#39;);
      await fs.writeFile(path.resolve(&#39;dist/index.html&#39;), html);
    });
  },
});

build({
  entryPoints: [&#39;src/main.tsx&#39;],
  bundle: true, // 여러 파일을 하나로 묶기 &lt;- import된 다른 파일들을 모두 묶음
  outfile: &#39;dist/main.js&#39;,
  plugins: [htmlPlugin()],
  loader: {
    &#39;.tsx&#39;: &#39;tsx&#39;,
  },
});</code></pre>
<p><code>esbuild</code>는 기본적으로 TypeScript와 JSX를 자동으로 트랜스파일링한다고 합니다. 다만, 타입 검사는 하지 않기 때문에 실패 시 빌드를 깨트리려면 별도의 타입 검사 도구를 추가로 사용해야 합니다.</p>
<p>번들링에 대해서는 <code>entryPoints</code>로 지정한 파일을 시작으로, 그 파일에서 import된 파일들을 모두 묶어 최적화된 하나의 출력 파일을 생성합니다.</p>
<p>추가로 <code>htmlPlugin</code>을 넣었어요. index.html에서 개발 환경 시 좀 더 직관적인 확인이 가능한 경로인 <code>src=&quot;src/main.tsx&quot;</code>를 유지하고 빌드 시점에 이걸 replace한 후에 빌드하도록 했어요.</p>
<p>package.json에는 아래와 같은 필드를 추가해서 빌드할 수 있었습니다. <code>build.ts</code> 파일을 <code>ESM</code>으로 작성했기 때문에 <code>ESM</code> 방식으로 해석할 수 있도록 <code>type</code> 필드를 추가했습니다. 또, TypeScript로 작성된 <code>build.ts</code> 파일을 실행하기 위해 <code>tsx</code> 패키지를 설치하고 이를 통해 실행했습니다. </p>
<pre><code>node scripts/build.ts    # ❌ 기본 Node.js는 .ts 파일 실행 불가
tsx scripts/build.ts     # ✅ 바로 실행 가능</code></pre><pre><code class="language-json">{
  &quot;type&quot;: &quot;module&quot;,
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;tsx scripts/build.ts&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;tsx&quot;: &quot;^4.19.4&quot;
  }
}</code></pre>
<h2 id="브라우저">브라우저</h2>
<p>여기까지 해서 <code>dist</code> 파일이 만들어졌네요. 이제 이 파일이 어떻게 브라우저에서 로드되어 우리가 보고 있는건지 살펴보려고 합니다.</p>
<p>우리가 프로젝트를 배포한다고 하면, 빌드된 결과물인 dist 폴더가 실제로 서버에 올라가게 됩니다.
사용자가 웹사이트에 접속하면, 서버는 가장 먼저 <code>dist/index.html</code> 파일을 응답하게 되죠!</p>
<p>이 HTML 문서는 브라우저에 의해 파싱되면서, <code>&lt;script&gt;</code> 태그를 만나게 되면 해당 JavaScript 파일을 불러와 실행하게 됩니다.
즉, 브라우저는 <code>index.html</code>을 시작으로 우리가 번들링한 JavaScript를 실행해서 실제 화면을 구성하고, 필요한 기능들을 동작시킵니다.</p>
<h2 id="모듈-시스템-좀-더-살펴보기">모듈 시스템 좀 더 살펴보기</h2>
<blockquote>
<p>플러그인 파일이나 잘게 쪼개져있는 코드 조각들을 재사용하기 위해서 각각의 파일을 등록하고, 등록된 파일을 불러와 사용할 수 있게 해주는 프로그램</p>
</blockquote>
<h3 id="브라우저에서의-모듈-시스템-동작">브라우저에서의 모듈 시스템 동작</h3>
<blockquote>
<p>브라우저가 여러 모듈을 어떤 방식으로 불러오고, 실행하는지에 대한 기능</p>
</blockquote>
<p><strong><code>🤨 ESM이 나오기 전에는</code></strong></p>
<p>과거에는 <code>&lt;script&gt;</code>로 많은 파일들을 그냥 다 불러왔다고 하는데요,</p>
<pre><code class="language-html">&lt;script src=&quot;a.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;b.js&quot;&gt;&lt;/script&gt;</code></pre>
<p>이렇게 되면 모든 파일이 <code>window</code> 변수에 등록이 되고, 순서가 잘못되면 런타임 에러가 발생할 수도 있었습니다. 이러한 문제를 해결하기 위해 런타임 환경에서는 <code>AMD</code>, <code>RequireJS</code> 같은 모듈 로더가 등장했다고 해요. 모듈 간의 의존성을 명시할 수 있도록 해서 순서를 관리할 수 있도록 하고 비동기로 로드할 수 있게도 했어요.</p>
<p>즉, 런타임 환경에서 비동기로 로드하기도 하고 모듈 로드 순서를 관리하는 방식을 활용합니다.</p>
<pre><code class="language-js">require([&#39;foo&#39;, &#39;bar&#39;], function(foo, bar) {
  foo();
  bar();
});</code></pre>
<p>그런데 결국 위 방식도 개발자들이 모듈 의존성을 수동으로 관리해야했고, 불필요한 번들링이나 중복 로딩이 발생할 수 있었다고 해요.</p>
<p><strong><code>✈️ ESM을 표준으로</code></strong></p>
<p>ESM(ECMAScript Modules)는 ECMAScript 표준으로 JavaScript의 공식 모듈 시스템으로 설계되었어요. 비공식 모듈 시스템(ex. CJS, AMD 등) 방식들은 런타임 환경에서와 브라우저에서 모듈을 다르게 처리하는 문제가 있어 이를 동일한 방식으로 처리할 수 있는 표준화된 방법이 필요했다고 합니다.</p>
<p>ESM이 도입되면서 브라우저와 Node.js를 포함한 모든 자바스크립트 환경에서 사용할 수 있는 공식적인 방법이 되었답니다.</p>
<p><strong><code>🤔 패키지에서 ESM으로 모듈을 정의했는데, 구 버전 브라우저라면?</code></strong></p>
<blockquote>
<p><a href="https://techblog.woowahan.com/17710/">배민 기술 블로그 - Vite로 구버전 브라우저 지원하기</a></p>
</blockquote>
<p>구 버전 브라우저에서는 ESM을 지원하지 않을 수 있겠습니다. ES6에서 처음 도입되어 ESM을 사용할 수 없는 경우가 생길 수 있는데요,</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/1fbcfc0d-0af2-4b59-9ee9-fafa2156e5e0/image.png" alt=""></p>
<p>트랜스파일러를 이용하여 팀에서 지원하고자 하는 범위에서 실행될 수 있도록 트랜스파일링하여 구형 브라우저에서도 사용할 수 있도록 해야 합니다.</p>
<pre><code class="language-html">&lt;!-- ESM을 지원하는 최신 브라우저에서만 로드됨 --&gt;
&lt;script type=&quot;module&quot; src=&quot;dist/main.esm.js&quot;&gt;&lt;/script&gt;

&lt;!-- ESM을 지원하지 않는 구형 브라우저에서만 로드됨 --&gt;
&lt;script nomodule src=&quot;dist/main.es5.js&quot;&gt;&lt;/script&gt;</code></pre>
<p><strong><em>🪴 ESM은 기존에 어떤 문제를 해결했을까?</em></strong></p>
<ol>
<li>표준화된 모듈 시스템을 제공하여 다양한 시스템 간의 호환성 문제를 해결</li>
<li>비동기적이고 효율적인 모듈 로딩을 통해 초기 로딩 속도와 성능 최적화</li>
<li>정적 분석과 최적화가 가능하여, 더 작은 번들을 생성하고 불필요한 코드를 제거</li>
<li>모듈 의존성 관리의 명확성 제공으로 충돌이나 잘못된 순서 문제를 해결</li>
<li><code>import</code>, <code>export</code> 키워드로 개발자 경험 개선을 통해 모듈 시스템을 더 쉽게 이해하고 사용할 수 있게 함</li>
</ol>
<h3 id="esm-only">ESM-only</h3>
<blockquote>
<p><a href="https://antfu.me/posts/move-on-to-esm-only">Move on to ESM-only</a></p>
</blockquote>
<p>라이브러리에서는 <a href="https://toss.tech/article/commonjs-esm-exports-field">이러한 이유로 CJS, ESM을 같이 지원</a>하곤 하는데요, 점점 더 많은 라이브러들이 ESM 전용으로 전환을 고려하거나 이미 그렇게 하고 있다고 합니다.</p>
<p>CJS와 ESM은 근본적으로 다른 모듈 시스템으로, 상호 운용할 경우 까다로운 문제로 이어지는 경우가 여전히 많다고 하네요. 또, 라이브러리에서 두 포멧을 지원한다는 것을 패키지의 크기를 2배가 되는 것이죠.</p>
<p><strong><code>🫢 Node.js에서 require로 esm 모듈을 로드할 수 있는 기능을 제공한다고 해요</code></strong></p>
<blockquote>
<p><a href="https://socket.dev/blog/require-esm-backported-to-node-js-20">https://socket.dev/blog/require-esm-backported-to-node-js-20</a></p>
</blockquote>
<p>Node.js에서 require로 ESM을 로드할 수 있도록 지원한다는 것은 혼합 사용을 권장한다는 의미보다는 듀얼 포맷의 지원을 줄이고, ESM으로 통일해 나가려는 호환성 조치로 보여요.</p>
<p>장기적으로는 새로운 모듈은 ESM으로만 작성되고, 점차 CJS를 줄이려는 방향으로 보이기도 했습니다.</p>
<p><strong><code>🧐 생태계의 움직임을 따라야 우리도 움직여야 이유</code></strong></p>
<blockquote>
<p>기술의 지속 가능성</p>
</blockquote>
<p>생태계가 이동하는 방향은 기술적 한계를 극복다고 효율을 높이기 위한 집단의 선택인 것인데요, 예를 들어 ESM으로의 전환은 정적 분석 최적화 트리 쉐이킹 등의 필요에서 비롯된 것처럼요. </p>
<p>생태계에서 사용되지 않거나 구식이 되어버린 기술을 고수하면, 언젠가 지원이 끊기고, 보안 업데이트도 안 되며, 관련 자료도 점점 사라지게 됩니다.</p>
<p>반면 생태계가 움직이는 방향을 따르면 그에 맞춰 새로운 도구와 커뮤니티도 그 방향으로 발전된다는 말이며, 이와 방향이 같아야 그에 맞춰 시스템을 계속 지속할 가능성이 커진다는 것이겠죠?</p>
<p><strong><code>😰 프로젝트에서 node 버전을 올리기 전에 고려해야하는 것?</code></strong></p>
<p>프로젝트에서 사용 중인 패키지들이 새로운 node 버전과 호환되는지 확인해야 합니다. 저도 회사에서 레거시 프로젝트의 스토리북을 열어 봐야할 일이 있었는데, node v18로 전환해야만 스토리북이 실행되더라구요 🤔</p>
<p>이처럼 node 버전을 올렸을 때 사용 중인 라이브러리나 배포 환경에 문제가 없는지 테스트하고 문제가 생겼다면 의존하고 있는 것들의 버전을 함께 업데이트해야 하는 큰 작업이 될 수 있을 것 같네요.</p>
<hr>
<p>너무 당연하게 런타임 환경, 패키지 매니저, 스캐폴딩을 사용하기만 했던 것 같았는데요, 이번 글을 통해 하나씩 필요한 걸 찾아가면서 그 도구들의 필요를 느끼고 당연하게 생각했던 것들에 대한 의문을 풀었던 것 같아요.</p>
<p>물론 빌드 설정은 당장 필요한 필드만 추가하여 더 정교한 최적화가 필요하다면 더 많은 설정들이 필요하겠지만, 모듈이 어떻게 인식될 것인가에 집중해보면 원하는 설정을 골라서 쓸 수 있을 것 같아요. 🏃</p>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://snyk.io/blog/javascript-runtime-compare-node-deno-bun/">Node.js vs. Deno vs. Bun: Performance &amp; JavaScript Runtime Comparison</a></li>
<li><a href="https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/">ES modules: A cartoon deep-dive</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[플러그인으로 DX 개선하기 with Webpack]]></title>
            <link>https://velog.io/@doeunnkimm_/%ED%8A%B8%EB%9E%9C%EC%8A%A4%ED%8C%8C%EC%9D%BC%EB%A7%81-%EA%B3%BC%EC%A0%95%EC%9C%BC%EB%A1%9C-DX-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@doeunnkimm_/%ED%8A%B8%EB%9E%9C%EC%8A%A4%ED%8C%8C%EC%9D%BC%EB%A7%81-%EA%B3%BC%EC%A0%95%EC%9C%BC%EB%A1%9C-DX-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 30 Nov 2024 07:51:55 GMT</pubDate>
            <description><![CDATA[<p>최근에 플러그인을 통해서 DX를 개선한 경험이 있어 이를 공유해보려고 합니다. 방법이 번뜩 생각이 났고, 비교적 간단한 작업이 후딱 반영했는데요. 이번 글에서 경험을 공유하고, 또 저도 더 자세히 알아보려고 합니다. 🚀</p>
<hr>
<h2 id="어떤-상황">어떤 상황?</h2>
<p><code>.md</code> 파일들을 이관해야 하는 작업이 필요했어요. 문서화를 위해 사용하고 있는 도구는 다행히도 <code>md</code> → <code>mdx</code>로 변환하는 기능이 내장되어 있긴 하더라구요.</p>
<p>그런데, MDX는 Markdown과 JSX(JavaScript XML)를 결합한 포맷으로, Syntax 오류가 발생할 수 있습니다. 예를 들면 JSX 태그가 제대로 닫히지 않았다는 이유로 말이죠.</p>
<p>그래서 아래와 같은 상황이 다릅니다.</p>
<pre><code class="language-md">// md: ✅ no problem
이 문장은 줄바꿈을 &lt;br&gt; 태그로 표현합니다.
</code></pre>
<pre><code class="language-mdx">// mdx: 🚨 error
이 문장은 줄바꿈을 &lt;br&gt; 태그로 표현합니다.</code></pre>
<p>이미 작성되어 있는 많은 파일들에서 <code>&lt;br&gt;</code> 혹은 <code>&lt;Br&gt;</code>이 이곳저곳에서 사용되고 있었어요. 그래서 아래와 같은 에러를 마주했습니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/77d1a88f-a087-4928-b16b-1c8a2262fc9b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/3c070d18-938e-49d9-88fc-e94503b8dee5/image.png" alt=""></p>
<p>정리해보자면 상황은 이렇습니다.</p>
<ol>
<li>md 파일을 이관해옵니다.</li>
<li>사용하고 있는 도구가 알아서 md → mdx로 바꿔줍니다.</li>
<li>그런데 md에서 사용하고 있던 문법들이 mdx에 와서 터지고 있습니다.</li>
</ol>
<hr>
<h2 id="일차원적인-해결-방법">일차원적인 해결 방법</h2>
<p>위 문제를 해결하기 위해서는 아주 일차원적인 방법이 생각나기도 했어요.</p>
<ul>
<li>손수 수정하기 (ㅋㅋ)</li>
<li>파일을 돌면서 일괄적으로 바꿔주는 script 추가하기</li>
<li>앞으로는 이런 일을 방지하기 위해 mdx 문법을 지키기</li>
</ul>
<p>위 방법들은 비용이 조금씩 반복적으로 발생할 것 같았어요. 왜나면, 앞으로 문서를 같이 작성하게 될 사람들은 MD에 이미 익숙하기 때문에, 앞으로도 동일하게 문서를 작성할 경우 2번도 반복해야 하며, 3번도 생산성을 저하시키는 요인이 될 것 같았습니다.</p>
<hr>
<h2 id="저는-이렇게-해결했어요">저는 이렇게 해결했어요</h2>
<p>제가 원하는 건 이렇습니다.</p>
<ul>
<li>MD에 익숙한 사람들도 그대로 사용할 수 있는 환경. 즉, 신경 안 쓸 수 있는 환경</li>
</ul>
<p>이러한 환경 차이를 중간에서 처리해주는 방식을 사용하면 어떨까 생각이 들었어요. 기존 MD 파일을 별도의 수정 없이 MDX 환경에서 사용할 수 있도록 만들어줄 수 있지 않을까 했습니다.</p>
<h3 id="플러그인-추가">플러그인 추가</h3>
<p>우선 발견한 문제는 <code>&lt;br&gt;</code>이 문제였어서, 이를 transform하는 과정을 추가하면 될 거라고 생각했어요.</p>
<p>그래서 제가 선택한 방법은, 플러그인을 추가하는 것이에요.</p>
<p>사용하고 있는 문서화 도구는 Next.js 기반이기 때문에 <code>next.config.js</code>에 <strong>webpack</strong> config를 쉽게 추가할 수 있었습니다. (Next.js는 기본적으로 Webpack을 통해 번들링)</p>
<pre><code class="language-js">// lib/replaceBr.js
const replaceBr = async() =&gt; {
  const { default: visit } = await import(&#39;unist-util-visit&#39;);

  return (tree) =&gt; {
    visit(tree, &#39;html&#39;, (node) =&gt; {
      node.value = node.value.replace(/&lt;br\s*\/?&gt;/gi, &#39;&lt;br /&gt;&#39;);
    });
  };
}

module.exports = replaceBr;</code></pre>
<pre><code class="language-js">// next.config.js
const replaceBr = require(&#39;./lib/replaceBr&#39;);

const nextConfig = {
  webpack: (config) =&gt; {
    config.module.rules.push({
      test: /\.md$/,
      use: [
        {
          loader: &#39;remark-loader&#39;,
          options: {
            remarkOptions: {
              plugins: [replaceBr],
            },
          },
        },
      ],
    });
    return config;
  },
}
module.exports = nextConfig</code></pre>
<p>MD 파일을 Remark를 사용해 파싱을 하고, 제가 추가한 플러그인이 적용될 수 있도록 설정했어요.</p>
<p>결과적으로 아래와 같이 이전과는 다르게 빌드에 문제없이 성공할 수 있었어요.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/979adcfa-09c7-4516-8db4-9dbc35fc56aa/image.gif" alt=""></p>
<p>최근 회사에서 플러그인을 추가해서 어떤 문제를 해결했다고 들었던 것을 출발로, 번뜩 생각이 났던 것이였어요. 아이디어와 gpt만으로 코드를 작성하여 빠르게 반영할 수 있었지만, 더 깊은 내용을 이해하기 위해 추가적으로 알아보려고 합니다.</p>
<hr>
<h2 id="webpack">Webpack</h2>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/e0737324-f6a0-4965-96b7-33c68ffd34e6/image.png" alt=""></p>
<p>Webapck은 번들러입니다. 번들러는 모듈과 리소스를 번들로 묶어 효율적으로 관리하고 최적화하는 역할을 해요.</p>
<h3 id="번들링-과정">번들링 과정</h3>
<p>번들링(bundling)이라는 건, 여러 개의 모듈, 리소스들을 하나 또는 여러 개의 출력 파일로 묶는 과정입니다. 주로 JavaScript, CSS, 이미지와 같은 각종 리소스들을 묶어서 효율적으로 배포할 수 있도록 최적화하는 작업입니다.</p>
<p><strong><code>🤔 효율적 배포라는 건 뭘까?</code></strong></p>
<p>배포되어야 하는 파일들의 크기가 클수록 이를 다운로드하고 실행하는 데 더 많은 시간이 소요됩니다. 번들러가 수행하는 효율적인 배포를 위한 작업들에는 즉, 번들링 하는 과정에서 수행하는 작업에는 아래와 같은 것들이 있어요.</p>
<ul>
<li><strong>번들링</strong>: 여러 개의 작은 파일을 하나로 합쳐 네트워크 요청수 절약</li>
<li><strong>파일 크기 최적화</strong>: 불필요한 공백, 주석, 줄바꿈 등을 제거하여 파일 크기 최소화 → 압축하여 다운로드 속도 개선 및 네트워크 대역폭 절약</li>
<li><strong>코드 스플리팅</strong>: 여러 개의 작은 번들로 분할되어, 필요한 코드만 로드할 수 있도록 → 초기 로딩 시간 단축</li>
<li><strong>이미지 최적화</strong>: 이미지 파일을 압축하거나, 포맷을 변경하여 파일 크기 절약 가능. 이미지 최적화 플러그인(ex. <code>image-webpack-loader</code>, <code>url-loader</code>)을 사용하여 압축 및 파일 크기 절약 가능</li>
<li><strong>Tree Shaking</strong>: 사용되지 않는 코드를 제거 → 모듈 시스템을 분석하여 실제로 사용되지 않는 코드를 제거하여 번들 크기 절약</li>
</ul>
<p>위와 같은 작업을 거쳐서 번들 파일을 생성하는 것인데, 하나씩 자세히 알아보려고 합니다.</p>
<h4 id="번들링-적당한-번들링을-위한-전략">번들링: 적당한 번들링을 위한 전략</h4>
<p><strong><code>🤔 적당히 번들링한다는 건 뭘까?</code></strong></p>
<p>너무 많이 쪼개거나 너무 많이 묶는 것은 모두 성능에 부정적인 영향을 줄 것 같아요. 추상적이지만.. 적당히 번들링하는 전략이 필요할 것 같았습니다.</p>
<p>보통 적절한 번들링을 위한 전략에는 다음과 같은 것들이 있어요.</p>
<p><code>⚔️ 코드 스플리팅</code></p>
<blockquote>
<p><a href="https://webpack.js.org/guides/code-splitting/">https://webpack.js.org/guides/code-splitting/</a></p>
</blockquote>
<p>엔트리 포인트에서부터 모듈을 탐색하여 의존성을 파악하고, 필요한 모듈을 추적해서 번들로 묶습니다. 예를 들어 <code>A.js</code>에서 <code>B.js</code>와 <code>C.js</code>를 import해서 사용한다면, 번들에 포함되도록 추가하고 각각의 모듈들을 하나의 번들 파일 또는 여러 개의 파일로 묶어서 최종 결과물을 만들어요.</p>
<br />

<p><code>📦 모듈 공유하기</code></p>
<p><a href="https://webpack.js.org/configuration/entry-context/#dependencies"><code>dependOn</code></a>이라는 옵션을 통해 청크 간 모듈을 공유할 수도 있다고 합니다. 중복을 줄이기 위해 모듈을 공유하도록 하는 것인데요.</p>
<pre><code class="language-js">const path = require(&#39;path&#39;);

module.exports = {
  mode: &#39;development&#39;,
  entry: {
    index: {
      import: &#39;./src/index.js&#39;,  // &#39;index&#39; 엔트리 포인트
      dependOn: &#39;shared&#39;,        // &#39;shared&#39; 모듈에 의존
    },
    another: {
      import: &#39;./src/another-module.js&#39;,  // &#39;another&#39; 엔트리 포인트
      dependOn: &#39;shared&#39;,        // &#39;shared&#39; 모듈에 의존
    },
    shared: &#39;lodash&#39;,  // &#39;shared&#39;는 &#39;lodash&#39; 라이브러리로 지정
  },
  output: {
    filename: &#39;[name].bundle.js&#39;,  // 번들 파일 이름은 엔트리 포인트 이름을 따름
    path: path.resolve(__dirname, &#39;dist&#39;),  // 결과물은 &#39;dist&#39; 폴더에 저장
  },
};</code></pre>
<p>위와 같이 <code>shared</code>로 <code>lodash</code>를 공유하도록 설정할 수 있어요. index와 another 모두 lodash를 의존하고 있기 때문에 Webpack은 이를 별도의 번들로 추출하고 중복을 피합니다. lodash는 <code>shared.bundle.js</code>라는 별도의 파일로 추출되고 이를 참조하는 형태로 구성되게 됩니다.</p>
<p>라이브러리를 명시한다고 해서 라이브러리 <strong>전체 모듈이 번들되는 것은 아니고, 트리 쉐이킹에 의해 사용되지 않는 부분은 제거되고 공통 코드만 포함</strong>된다고 합니다. 그럼 <code>lodash</code> 같이 CJS 모듈 시스템을 지원하는 라이브러리 즉 트리 쉐이킹이 어려운 라이브러리는 전체가 번들에 포함되게 되는 것이겠네요.</p>
<br />

<p><code>🌪️ dymamic import</code></p>
<p>필요한 시점에만 모듈을 로드할 수 있도록 번들을 분리하는 기법을 말합니다. 엔트리 파일에서 사용은 되지만, 초기 렌더링 시에는 실질적으로 사용되지 않는다면, dynamic import 번들로 분리하여 초기 렌더링 속도를 개선할 수 있습니다. <strong>즉, 당장 필요하지 않으니까 나중에 로드할게</strong> 같은 것이죠. </p>
<p>주의할 점도 있을 것 같아요. 아무래도 동적 임포트는 모듈을 분리하는 것이므로 네트워크 요청이 늘어납니다. 너무 많은 사용은 오히려 네트워크 응답 시간을 느려지게 하여 사용자가 기다려야 하는 상황이 일어날 수 있음을 주의해야겠습니다.</p>
<h4 id="최적화-최적화는-어떻게">최적화: 최적화는 어떻게?</h4>
<blockquote>
<p><a href="https://webpack.js.org/configuration/optimization/">https://webpack.js.org/configuration/optimization/</a></p>
</blockquote>
<p>Webpack에서 소개하고 있는 최적화 전략이 정말 많더라구요.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/0d86c041-9225-4aba-b74a-958dc11a9fc8/image.png" alt=""></p>
<p>이 중에서 제가 흥미로웠던 최적화 기법들 몇 가지를 소개해보려고 합니다.</p>
<p><a href="https://webpack.js.org/configuration/optimization/#optimizationchunkids"><code>🗽 chunkIds</code></a></p>
<ul>
<li>생성되는 청크의 이름을 결정하는 설정</li>
<li>기본적으로는 파일 일므에 해당하는 고유 식별자(숫자, 해시 값, ...)를 가진다</li>
<li><code>named</code>로 설정할 경우, 명시적인 이름을 할당 → 디버깅 시 용이</li>
<li><code>deterministic</code>으로 설정할 경우, 빌드마다 동일한 ID 할당 가능 → 캐시 관리가 중요하거나 디버깅 환경에서 예측 가능하도록 설정하고 싶을 때 사용</li>
</ul>
<br />

<p><a href="https://webpack.js.org/configuration/optimization/#optimizationminimize"><code>👝 minimize, minimizer</code></a></p>
<ul>
<li>코드 압축을 담당하는 설정</li>
<li>자동으로 코드 최소화를 할지 여부를 결정 (기본값 true) → TerserPlugin(기본 플러그인)을 사용해 JavaScript 파일을 최소화</li>
<li>minimizer에는 최소화할 때 사용할 플러그인 설정 가능</li>
<li>디버깅 용도로 <code>false</code>로 설정하여 분석 활용</li>
</ul>
<br />

<p><a href="https://webpack.js.org/configuration/optimization/#optimizationsideeffects"><code>❄️ sideEffects</code></a></p>
<ul>
<li>Webpack은 트리 쉐이킹을 통해 사용되지 않는 코드를 제거하는데</li>
<li>일부 모듈은 side effect를 가질 수 있기 때문에, 모듈을 제거하지 않도록 설정 가능</li>
<li>true로 설정할 경우, side effect를 가질 수 있는 코드를 트리쉐이킹 하지 않음으로써 보호</li>
<li>package.json에서도 <code>sideEffects</code> 필드를 통해 설정 가능</li>
</ul>
<p>Webpack이 판단하는 side effect가 없는 코드는 즉, 순순히 트리 쉐이킹을 해주는 코드는 순수 함수만 포함된 모듈이나 상태를 변경하지 않는 모듈이라고 해요. (빡빡함) 그래서 판단하에 직접적으로 설정을 해주면 번들 크기 면에서 이점을 얻을 수 있어 보입니다.</p>
<h3 id="🎄-tree-shaking-오해와-진실">🎄 Tree Shaking: 오해와 진실</h3>
<p>트리 쉐이킹은 정적 분석을 통해 사용되지 않는 부분을 찾아서 제거한다는 간단한 원리에 대한 코멘트를 남기고..</p>
<p>이번에는 <strong>트리 쉐이킹에 대한 오해와 진실</strong>에 대해 정리해보려고해요. 최근 회사에서 트리 쉐이킹 관련해서 작업이 있었는데 헷갈리더라구요. <code>🤔 ㄷ..되는건가?</code></p>
<br />

<p>1️⃣ <code>export ... = Object.assign(...)</code> → ❌</p>
<p>서브 컴포넌트를 제공하기 위해 <code>Object.assingn</code>를 통해 통합해서 제공하기도 하는데요. </p>
<pre><code class="language-tsx">export const Foo = Object.assign(FooImpl, {
  A: FooAComponent,
  B: FooBComponent,
  C: FooCComponent,
})</code></pre>
<p><code>Object.assign</code> 함수는 런타임에 객체를 생성합니다. 따라서 정적 분석 시점에서는 프로퍼티에 담은 모듈을 다 포함하게 됩니다. </p>
<p>실제로 해봤어요.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/bd6cd22f-6721-402b-8445-8d11789b33a0/image.gif" alt=""></p>
<p>빌드된 결과는 아래와 같이 <code>Object.assign</code>이 그대로 담기면서 모든 프로퍼티를 포함하고 있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/91e526d5-4cda-4041-a412-346029a1ef0c/image.png" alt=""></p>
<br />

<p>2️⃣ <code>export * as Foo from &#39;Foo&#39;</code> → ⭕️ (최신)</p>
<p>아래와 같은 케이스는 어떨까요?</p>
<pre><code class="language-tsx">// Foo.tsx
import { FooA } from &#39;FooA&#39;
import { FooB } from &#39;FooB&#39;
import { FooC } from &#39;FooC&#39;

export { FooA as A }
export { FooB as B }
export { FooC as C }

// index.ts
export * as Foo from &#39;Foo&#39;</code></pre>
<p>위와 같은 구조는 개별적으로 export하고, <code>index.ts</code> 에서 <code>Foo.tsx</code>에서 export된 모든 내용을 <code>Foo</code>라는 네임스페이스로 다시 export하고 있는 구조에요. 사용처에서는 아래와 같이 쓸 수 있어요.</p>
<pre><code class="language-tsx">import { Foo } from &#39;lib-foo&#39;

&lt;Foo.A /&gt;
&lt;Foo.B /&gt;
&lt;Foo.C /&gt;</code></pre>
<p><strong>여러 모듈을 하나의 네임스페이스로</strong> 묶은 방식으로, <strong>각 모듈은 독립적인 모듈로 처리</strong>됩니다. 따라서 정적 분석이 가능하여 트리 쉐이킹이 가능합니다.</p>
<p>찾아보니, webpack4 이전에는 불가능했다고 해요. 정확하게 <code>이제는 됩니다!</code>라는 포스팅을 못찾아서..직접 해보았습니다. 결과는 아래와 같아요.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/c4fe8e2c-e534-45e1-983b-d6eac0c43e61/image.gif" alt=""></p>
<p>아래는 결과입니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/4eede740-2eee-412e-b57e-a51b34e2558a/image.png" alt=""></p>
<p><strong>트리 쉐이킹이 된 것</strong>을 확인할 수 있었습니다 👍🏽</p>
<br />

<p>3️⃣ <code>import * as React from &#39;react&#39;</code> → ⭕️</p>
<p>이 또한 네임스페이스로 불러오는 방식으로, 각 모듈을 독립적인 모듈로 처리가 가능하기 때문에 트리 쉐이킹에 문제가 없습니다. (<a href="https://theodorusclarence.com/shorts/react/namespace-vs-named">관련 포스팅</a>)</p>
<h3 id="plugin의-동작-원리">plugin의 동작 원리</h3>
<p>Webpack에서 플러그인은 빌드 프로세스를 확장하거나 커스터마이징하는 데 사용되는 도구에요. 플러그인은 특정 작업을 수행하는데, 주로 빌드 최적화, 번들링, 코드 스플리팅, 파일 시스템 조작 등을 처리할 수 있습니다.</p>
<p>그래서 플러그인이 실행되는 시점을 설정할 수도 있고, 이를 통해 특정 시점에 원하는 작업을 추가할 수도 있습니다. </p>
<p>Webpack이 처리할 수 있도록 로더를 통해 JS 모듈로 변환해서 처리합니다. Webpack은 JS 파일만 처리할 수 있기 때문에 css-loader나 style-loader 와 같은 로더를 통해 변환을 하는 것이죠.</p>
<hr>
<h2 id="끝으로">끝으로</h2>
<p>우연한 기회로 플러그인을 통해 DX를 개선한 경험이 생겨서 공유하게 되었는데요! 하다보니 Webpack의 전반적인 내용까지 공부하게 되었습니다 😁 </p>
<p>이 경험을 통해, 그동안 코드에서 에러가 발생하면 그냥 맞춰서 돌아가게 만드는 것에서 벗어나, 이제는 필요할 때 빌드 프로세스를 활용해 변환을 통해 문제를 해결할 수 있다는 자신감이 조금.. 생긴 것 같았다는 말을 남기고 글을 마무리해보려고 합니다 :hooray: ! 👋</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[작고 소중한 디자인 시스템을 NPM 배포해보자]]></title>
            <link>https://velog.io/@doeunnkimm_/%EC%9E%91%EA%B3%A0-%EC%86%8C%EC%A4%91%ED%95%9C-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%84-NPM-%EB%B0%B0%ED%8F%AC%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@doeunnkimm_/%EC%9E%91%EA%B3%A0-%EC%86%8C%EC%A4%91%ED%95%9C-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%84-NPM-%EB%B0%B0%ED%8F%AC%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Thu, 19 Sep 2024 16:51:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://github.com/depromeet/15th-team3-FE/tree/main/packages/core/sds"><code>@sambad/sds</code></a></p>
</blockquote>
<p>아직 부족한 게 정말 너무 매우 엄청나게 많지만, 작게나마 디자인 시스템 구축을 했었어요. </p>
<p>최근에 개인적으로 <strong>의존성 관련 공부</strong>를 하다보니 <code>package.json</code>와 빌드 시 신경 써줄 수 있는 부분이 생각보다 세세하고 재밌더라구요.</p>
<p>그래서 디자인 시스템 패키지을 가지고 직접 처음 해보려고 합니다. 이 과정에서 NPM 배포를 위해 고려하고 설정한 내용들을 함께 남겨보려고 합니다.</p>
<p>제가 개발하고 있는 환경은 다음과 같아요.</p>
<ul>
<li><code>&quot;turbo&quot;: &quot;^2.0.4&quot;</code></li>
<li><code>&quot;react&quot;: &quot;^18&quot;</code></li>
<li><code>&quot;typescript&quot;: &quot;^5&quot;</code></li>
<li><code>&quot;@emotion/react&quot;: &quot;^11&quot;</code></li>
</ul>
<p>터보레포를 사용하고 있고, 내부에 존재하는 <code>@sambad/sds</code> 라는 패키지를 배포하려고 해요.</p>
<h2 id="bundler를-골라보자-vite-⚡️">Bundler를 골라보자, vite ⚡️</h2>
<p><strong><code>🤔 뭘로 하지?</code></strong></p>
<p>우선 <code>@sambad/sds</code> 패키지는 단순히 UI 관련 모듈들을 모아둘 목적으로 생성된 단순 패키지로 번들러는 없었어요. 그래서 번들러를 선택해야 했습니다.</p>
<p>제가 후보로 고민했던 번들러들은 다음과 같아요.</p>
<ul>
<li><a href="https://turbo.build/pack/docs">turbopack</a></li>
<li><a href="https://vitejs.dev/guide/">vite</a></li>
</ul>
<p>저는 프로덕션 빌드 시에만 번들러가 필요해서, 프로덕션 빌드타임만을 비교해보려고 합니다.</p>
<p><strong><code>🤖 turbopack</code></strong></p>
<blockquote>
<p><a href="https://turbo.build/pack/docs/why-turbopack">Why Turbopack?</a></p>
</blockquote>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Incremental_computing">incremental computation</a> → 변경 사항을 효율적으로 처리하여 재구축 시간을 줄이고 개발 생산성을 향상</li>
<li>모든 함수의 결과를 캐싱 → 동일한 작업을 두 번 수행할 필요 X</li>
<li>현재는 <code>Next.js</code>에서만 사용 가능<ul>
<li>개발 서버에만 내장</li>
<li>Next.js도 프로덕션 빌드 시에는 <a href="https://webpack.js.org/"><code>Webpack</code></a> 사용</li>
</ul>
</li>
</ul>
<br/>

<p><strong><code>⚡️ vite</code></strong></p>
<blockquote>
<p><a href="https://vitejs.dev/">https://vitejs.dev/</a></p>
</blockquote>
<ul>
<li>Esbuild(개발 모드) + Rollup(프로덕션 빌드)<ul>
<li>Esbuild가 현재 어떤 번들링 툴 보다 가장 빠른 성능을 자랑하지만</li>
<li>아쉬운 생태계와 브라우저용 번들링에서 아직은 다른 툴보다 안정성이 떨어진다는 평가가 있어 </li>
<li>프로덕션 빌드 시에는 안정성과 생태계가 비교적 더 큰 <a href="https://rollupjs.org/"><code>Rollup</code></a>을 선택</li>
</ul>
</li>
<li>Typescript 는 <a href="https://github.com/evanw/esbuild"><code>Esbuild</code></a> 를 사용<ul>
<li><code>tsc</code> 대비 20~30배 빠른 퍼포먼스</li>
</ul>
</li>
<li>es2015 이상만을 지원<ul>
<li>그 이하도 지원하고 싶다면 <a href="https://github.com/vitejs/vite/tree/main/packages/plugin-legacy"><code>@vitejs/plugin-legacy</code></a> 사용해서 가능</li>
</ul>
</li>
<li>라이브러리 모드 지원<ul>
<li>build 의 lib 옵션과, rollupOptions 으로 빌드시에 번들링에서 제외할 라이브러리 등 여러 설정을 쉽게 가능</li>
<li>TS로 작성한다면 <a href="https://github.com/qmhc/vite-plugin-dts"><code>vite-plugin-dts</code></a> 플러그인을 추가하여 모듈마다 <code>.d.ts</code> 파일 자동 생성 가능</li>
</ul>
</li>
</ul>
<br/>

<p><strong><code>🤚 vite로 결정했어요</code></strong></p>
<p>사실 <code>turbopack</code>이 아직은 범용적으로 사용할 수는 없어, 자연스레 <code>vite</code>가 되긴 했습니다.</p>
<p>제가 직접 벤치마크를 측정해보진 않았지만, <a href="https://medium.com/@ramouz.muzacky/unpacking-the-best-comparing-webpack-vite-and-turbopack-for-modern-developers-3042da7c644c">해당 포스팅</a>을 참고했을 때 모든 부분에서 <code>vite</code> &gt; <code>turbopack</code> 이였습니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/ad83735c-df9e-41ff-b356-c306b5bac8bd/image.png" width="500"/></p>

<p><code>Webpack</code>을 비교 대상에서 제외했던 이유는, 필수적으로 해아하는 보일러플레이트 량이 꽤 된다는 것이 가장 큰 이유였습니다. 또, 위 벤치마크 표에서도 알 수 있듯 <code>Webpack</code>의 성능은 <code>vite</code>와 비교했을 때 약 10배 정도의 차이가 있었습니다.</p>
<h3 id="vite-시작하기">vite 시작하기</h3>
<p><code>vite</code>를 install하고 <code>package.json</code>에 <code>build</code> script만 추가한 후, 기본적인 config만 추가하면 기본적인 build는 실행됩니다.</p>
<pre><code class="language-ts">// vite.config.ts
import { defineConfig } from &#39;vite&#39;;
import { resolve } from &#39;path&#39;;

export default defineConfig({
  build: {
    lib: {
      entry: resolve(__dirname, &#39;src/index.ts&#39;),
      name: &#39;index&#39;,
      fileName: &#39;index&#39;,
    },
    outDir: &#39;dist&#39;,
  },
});
</code></pre>
<h2 id="peerdependencies">peerDependencies</h2>
<p>최근에 어떤 자리에서 peerDependencies에 대해 아주 잠깐 언급되었어서, 알아봐야겠다~ 했었는데 번들 사이즈를 위해 중요한 역할을 하는 <code>package.json</code> 의 필드여서 이번 글에도 포함하게 되었어요. </p>
<p>간단하게 개념을 살펴보고 제가 어떻게 적용을 했는지 남겨보려고 합니다.</p>
<p><strong><code>🤔 peerDependencies?</code></strong></p>
<ul>
<li>특정 패키지가 다른 패키지의 특정 버전이 설치되어 있어야 함을 명시하는 데 사용</li>
<li>예를 들어, React 관련 라이브러리에서 <code>react</code>와 <code>react-dom</code>을 <code>peerDependencies</code>로 지정하면, 이 라이브러리를 사용하는 프로젝트에서 반드시 해당 버전의 React가 설치되어 있어야 함을 의미</li>
</ul>
<br/>

<p><strong><code>🥸 무엇을 위해 필요한걸까?</code></strong></p>
<p><code>호환성 보장</code></p>
<p>특정 라이브러리와 함께 사용될 다른 라이브러리의 버전을 명시하여, 충돌이나 호환성 문제를 방지</p>
<blockquote>
<p>💡 보통은 여러 개를 허용해주거나, 특정 버전 이상으로 명시해주어 폭넓게 허용</p>
</blockquote>
<p>아래는 대표적인 디자인 시스템 패키지들의 <code>package.json</code> 내 peerDependencies 필드예요.</p>
<ul>
<li><a href="https://github.com/radix-ui/primitives/blob/main/packages/react/avatar/package.json#L39">@radix-ui/primitive</a></li>
<li><a href="https://github.com/chakra-ui/chakra-ui/blob/main/packages/react/package.json#L52">@charka-ui/react</a></li>
</ul>
<p>그래서 peerDependencies는 사용처 버전에 따라 갈 수 있도록 통합 패키지에서는 devDependencies로 사용하거나 개별 패키지를 제공하는 경우(모노레포를 사용) 번들에 포함되지 않도록 하여 사용합니다.</p>
<br/>

<p><strong><code>🤨 peerDepencies에 설정해주고, dependencies에서 제거해주었다</code></strong></p>
<p>저는 모노레포를 사용하고 있어서, 아래와 같이 명시해주었어요.</p>
<pre><code class="language-json">// as-is
&quot;dependencies&quot;: {
  &quot;@emotion/react&quot;: &quot;&gt;=11&quot;,
  &quot;react&quot;: &quot;&gt;=18&quot;,
  &quot;react-dom&quot;: &quot;&gt;=18&quot;
},

// to-be
&quot;peerDependencies&quot;: {
  &quot;@emotion/react&quot;: &quot;&gt;=11&quot;,
  &quot;react&quot;: &quot;&gt;=18&quot;,
  &quot;react-dom&quot;: &quot;&gt;=18&quot;
},</code></pre>
<p>이제 해당 패키지에서 해당 패키지들을 번들에 포함하지 않고, 사용처에서 설치된 버전에 맞게 사용되는 것을 상상했기 때문에 <strong>dependencies 필드에서는 제거</strong>해주었어요.</p>
<h3 id="번들에서도-제외하기">번들에서도 제외하기</h3>
<p>위와 같이 package.json을 구성하고 build를 하게되면 제외될 거라고 생각했습니다. <code>🤔 dependencies에 명시 안 했으니까 포함 안 되지 않을까?</code> 했는데 <strong>포함되었습니다</strong>.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/a2824d3d-b79f-42ea-8e70-0ff798a0bad9/image.png" alt=""></p>
<p>이미 코드 곳곳에는 peer로 넣어둔 라이브러리를 사용하고 있긴 하므로 번들에 포함되었던 것입니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/a1899cc6-7c70-4fd0-b8d2-e0c93a14ecb6/image.png" width="500"/></p>

<p>그렇지만, 저는 이걸 바란 게 아니였어요. </p>
<p>이 라이브러리에서는 이 번들을 포함하지 않고, 사용처에서 이미 사용하고 있는 버전의 패키지와는 다르게 중복되어 들어가지 않는 것과 사용처에서의 패키지가 사용되기를 원했습니다(제가 peer로 설정한 버전과 호환이 되어야겠지만요). peer로 추가한 이유도 마찬가지였습니다.</p>
<br/>

<p><strong><code>🥳 vite로 쉽게 제외하기</code></strong></p>
<p><code>vite.config.ts</code>파일로 아주 쉽게 번들에서 제외할 수 있었어요.</p>
<pre><code class="language-ts">import { defineConfig } from &#39;vite&#39;;
import { resolve } from &#39;path&#39;;

export default defineConfig({
  build: {
    rollupOptions: {
      external: [&#39;react&#39;, &#39;react-dom&#39;, &#39;@emotion/react&#39;], // 번들 포함 X
    },
    ...
  },
});</code></pre>
<p>번들에서 제외하고 번들 사이즈를 약 <code>81.2%</code>나 줄일 수 있었어요.</p>
<table>
<thead>
<tr>
<th align="center">as-is</th>
<th align="center">to-be</th>
</tr>
</thead>
<tbody><tr>
<td align="center">186.52kB</td>
<td align="center">34.77kB</td>
</tr>
</tbody></table>
<h2 id="esm-cjs">ESM, CJS</h2>
<p>이런 경험이 처음이다보니, 많은 레퍼런스들을 보게 되었었어요. 보다보면, <code>package.json</code>에 이런 필드들이 있었어요.</p>
<pre><code class="language-json">&quot;type&quot;: &quot;module&quot;,
&quot;main&quot;: &quot;dist/cjs/index.cjs&quot;,
&quot;module&quot;: &quot;dist/esm/index.js&quot;,</code></pre>
<p><code>ESM</code>과 <code>CJS</code>에 대해서도 지식이 얕아서 관련되어 알아보고, 어떻게 적용했는지도 기록해보려고 합니다.</p>
<br/>

<p><strong><code>🤔 둘은 뭐길래?</code></strong></p>
<p><code>ESM</code>과 <code>CJS</code>는 자바스크립트 <strong>모듈 시스템</strong>입니다.</p>
<p>모듈 시스템에서의 <code>모듈</code>은 프로그램을 구성하는 시스템을 기능 단위로 독립적인 부분으로 분리한 것을 의미합니다. </p>
<p>모듈 시스템은 모듈을 정의하고, 가져오고, 관리하는 규칙과 방법을 제공합니다. 자바스크립트에서는 <code>ESM(ECMAScript Modules)</code>와 <code>CJS(CommonJS)</code> 두 가지 주요 모듈 시스템이 있는 것이죠!</p>
<br/>

<p><strong><code>🤨 ESM(ECMAScript Modules)</code></strong></p>
<ul>
<li><code>import</code>와 <code>export</code> 키워드를 사용</li>
<li>비동기 로딩: 모듈을 비동기적으로 로드 가능</li>
<li>정적 분석: 코드가 실행되기 전에 의존성 분석 가능 → Tree-shaking 쉽게 가능</li>
<li>정적인 구조로 모듈끼리 의존하도록 강제 → 불러온 값 수정 X</li>
</ul>
<br/>

<p><strong><code>😑 CJS(CommonJS)</code></strong></p>
<ul>
<li><code>require()</code>와 module.exports` 사용</li>
<li>동기 로딩: 모듈을 동기적으로 로드</li>
<li>require/module.exports를 동적으로 하는 것에 제약 X = 불러온 모듈 수정 가능<ul>
<li>그래서 런타임에 동적으로 로드되고 수정될 수 있기 때문에, 코드가 실행되기 전에는 어떤 모듈이 어떤 의존성을 가질지 예측하기 어려움</li>
<li>특정 조건에 따라 모듈을 다르게 로드하거나 수정하는 경우, 빌드 도구는 이러한 동적 관계를 분석 X</li>
</ul>
</li>
</ul>
<br/>

<p><strong><code>🥸 라이브러리 관점에서 둘 다 지원해야 하는건가?</code></strong></p>
<ul>
<li>Node.js 12부터 <code>ESM</code>라는 새로운 모듈 시스템이 추가가 된 것</li>
<li>그래서, 이미 많은 프로젝트에서 <code>CJS</code> 사용하고 있을 것</li>
<li>둘 다 지원하면, 사용처의 필요에 따라 하나를 선택 가능 → <strong>유연성 증가</strong></li>
</ul>
<br/>

<p><strong><code>🙂 package.json에서 type, main, module 필드</code></strong></p>
<ul>
<li><code>type</code>: &quot;module&quot; | &quot;commonjs&quot;<ul>
<li>모듈의 유형을 지정. 기본적으로 어떤 모듈 시스템으로 처리될지를 결정</li>
<li>정의되어 있지 않다면 &quot;commonjs&quot;로 처리</li>
</ul>
</li>
<li><code>main</code><ul>
<li>패키지를 불러올 때 기본적으로 사용할 진입점 파일을 지정</li>
</ul>
</li>
<li><code>module</code><ul>
<li><code>main</code> 필드와 유사한 목적으로 사용되는 필드</li>
<li><code>ESM</code> 환경에서 패키지를 사용할 때 진입되는 경로</li>
</ul>
</li>
</ul>
<br/>

<p><strong><code>🤔 아무 생각없이 ESM으로 개발한 것 같은데..뭐지?</code></strong></p>
<p>최신 스캐폴딩 도구를 사용하면, <code>package.json</code>에서 <code>type</code> 필드를 <code>module</code>로 설정해주기 때문인데요.</p>
<p><code>type</code> 필드 값에 따라 <code>.js</code>가 어떻게 처리될지가 결정됩니다.</p>
<ul>
<li>&quot;module&quot;이면, ESM으로 처리</li>
<li>&quot;commonjs&quot;이면, CJS로 처리</li>
</ul>
<p>아니면, 직접적으로 확장자명을 입력하여 처리되도록 하는 방법도 있습니다.</p>
<ul>
<li><code>.mjs</code>: ESM 파일로 인식</li>
<li><code>.cjs</code>: CJS 파일로 인식</li>
</ul>
<p>그래서 JS 파일이 <code>CJS</code>인지 <code>ESM</code>인지 확인하려면 파일 확장자, <code>package.json</code>의 <code>type</code> 필드, 사용된 모듈 구문을 확인하면 됩니다.</p>
<br/>

<h3 id="type-필드를-설정했더니-타입-에러-">type 필드를 설정했더니 타입 에러 ?</h3>
<p>이제 위애서 알아본대로 프로젝트가 ESM으로 동작할 수 있도록 <code>&quot;type&quot;: &quot;module&quot;</code>을 명시해주었습니다.</p>
<pre><code class="language-json">&quot;type&quot;: &quot;module&quot;</code></pre>
<p>그랬더니 아래와 같이 갑자기 와장창 타입 에러가 발생했습니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/293a914e-3a76-4660-92df-00515728bc6e/image.png" width="500"/></p>

<p>이렇게 설정한 순간부터는 <code>import/export</code> 문이 ESM 규칙을 따라야합니다. 이 규칙 중에는 <strong>파일 확장자를 명시</strong>해야 하는 규칙이 존재합니다.</p>
<p>명시하라고 타입 에러가 잔뜩 난 것이죠.. 🥹</p>
<p>그렇지만 타입 에러이기 때문에 tsconfig를 설정하면 컴파일러가 알아서 잘 인식해줄 것 같기도 합니다. 결론적으로는 아래와 같이 설정하면 해결됩니다.</p>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;moduleResolution&quot;: &quot;Bundler&quot;
  }
}</code></pre>
<p><strong><code>🤔 moduleResolution</code></strong></p>
<ul>
<li>TypeScript가 모듈을 찾는 방식을 지정</li>
<li>모듈 해석 방식에 따라 import문을 해석하고 필요한 파일을 찾는 방법에 영향을 미침</li>
</ul>
<p>다음과 설정할 수 있는 값들입니다.</p>
<ul>
<li><code>node</code>, <code>node16</code>, <code>nodenext</code>: ESM, CJS 모두 지원. 모듈을 찾는 방식이 조금씩 다르긴 하다</li>
<li><code>classic</code>: 초기 모듈 해석 방식. node_modules를 통해 모듈 탐색 X</li>
<li><code>bundler</code>: 번들러에 맞춰 해석 가능하도록. 파일 확장자 생략 가능</li>
</ul>
<p>그래서 저는 <code>bundler</code>로 설정했습니다.</p>
<br/>

<p><strong><code>😟 &quot;moduleResolution&quot;: &quot;bundler&quot;를 그냥은 못쓴단다</code></strong></p>
<p>모노레포에서 base로 설정하고 있는 tsconfig에 moduleResolution만 오버라이드하여 설정해주려고 했더니 아래와 타입 에러는 만났습니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/2d699a82-83a2-4f00-bebb-7dbed153106f/image.png" alt=""></p>
<p>대충 <code>module</code>이라는 값을 맞추라는 에러 같아요 일단..?</p>
<br/>

<p><strong><code>🥹 tsconfig에서 module</code></strong></p>
<p>TS 컴파일러가 어떤 모듈 시스템으로 해석할지를 결정하는 필드입니다.</p>
<p>사실 이 타입 에러 지옥은 package.json에서 <code>&quot;type&quot;: &quot;module&quot;</code>로 설정한 후부터였는데요.</p>
<p>프로젝트 package.json에서 <code>&quot;type&quot;: &quot;module&quot;</code>로 설정했다면, 프로젝트는 ESM 방식으로 동작하게 된다는 것이고 그렇다면 TypeScript도 ESM 방식으로 코드를 해석해야 합니다.</p>
<p>그래서 ESM으로 해석할 수 있는 <code>ESNext</code> | <code>ES2015</code> 이상으로 설정해야 합니다.</p>
<p>선택지가 아주 많군요!</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/89d4768c-067c-4426-b135-c173e68f4dfb/image.png" width="400" /></p>

<p>보통 최신 스캐폴딩 도구를 사용하면 <code>ESNext</code> 혹은 <code>NodeNext</code>로 최신 문법을 사용할 수 있도록 설정되어지는 듯 합니다.</p>
<p>정말 최신의 문법을 사용한다면, 폴리필이 존재하는지 확인해야겠지만 저는 엄청나게 갓 나온 문법같은 건 사용하지 않았기 때문에..</p>
<p>그리고 특정 ES 버전으로 맞춰서 개발해야 하는 스펙도 없었기 때문에 <code>ESNext</code>로 설정해주었습니다. 또, 번들링 될 때 트랜스파일링 될 것도 생각이 들어 큰 문제는 없을 거라고 생각했습니다.</p>
<p>드디어 아무 문제 없이 빌드가 되는 것을 확인할 수 있었습니다 😂</p>
<br/>

<p><strong><code>🤔 근데 이때까지 왜 아무 문제 없이 동작했던거지?</code></strong></p>
<p>이때까지 <code>package.json</code>에 <code>type</code> 필드를 설정하지 않아 CJS로 동작해서 <code>tsconfig</code>의 <code>module: &quot;NodeNext&quot;</code>가 짝짝꿍 잘 동작했던 것이라고 해봅시다.</p>
<p>그렇다면 저는 이때까지 어떻게 <code>import/export</code>문을 사용할 수 있었던걸까요?</p>
<p>Node.js 12 이상부터는 package.json에서 명시하지 않아도 ESM으로 처리가 가능하다고 합니다. TS 컴파일러도 파일에 import/export 문이 포함되어 있다면 이를 인식하고 변환이 가능하다고 합니다.</p>
<h3 id="esm-cjs-모두-지원하기">ESM, CJS 모두 지원하기</h3>
<p><strong><code>😟 아무튼 난 ESM으로 만들었는데, CJS도 지원하려면?</code></strong></p>
<p>운좋게도 <code>vite</code>에서 <code>ESM</code>와 <code>CJS</code>를 둘 다 지원할 수 있도록 기능을 제공합니다.</p>
<pre><code class="language-json">// vite.config.ts
import { defineConfig } from &#39;vite&#39;;
import { resolve } from &#39;path&#39;;

export default defineConfig({
  build: {
    ...
    lib: {
      ...
      formats: [&#39;cjs&#39;, &#39;es&#39;],
    },
    ...
  },
});
</code></pre>
<p>그러면 아래와 같이 <code>dist</code> 파일 하위에 <code>CJS</code> 파일과 <code>ESM</code> 파일이 생성됩니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/c761721c-47b0-4c0b-9e46-9b6373579723/image.png" width="400" /></p>

<p>그럼 아래와 같이 <code>package.json</code>에 인식할 수 있도록 경로를 입력해주면 됩니다.</p>
<pre><code class="language-json">// package.json
&quot;main&quot;: &quot;./dist/index.js&quot;,
&quot;module&quot;: &quot;./dist/index.mjs&quot;,</code></pre>
<h2 id="dts">.d.ts</h2>
<p>레퍼런스들을 보면 <code>package.json</code>에 <code>types</code> 필드에 <code>*.d.ts</code> 파일을 넣어주더라구요. 사실 <code>.d.ts</code> 파일에 대한 지식도 얕고 관련되어 어떻게 설정하는지 알아보려고 합니다.</p>
<br/>

<p><strong><code>😲 .d.ts ?</code></strong></p>
<p><code>TypeScript</code>에서 사용되는 <strong>타입 정의 파일</strong>입니다.</p>
<p>이 파일을 통해서 JS 코드의 타입 정보를 제공하고 TS가 해당 코드를 이해하고 타입 검사를 수행할 수 있도록 도와줍니다.</p>
<br/>

<p><strong><code>🧐 그래서 왜 필요한걸까?</code></strong></p>
<p>TypeScript는 정적 타입 검사를 통해 코드 작성 시 오류를 사전에 발견할 수 있도록 도와주는 JavaScript의 상위 집합일 뿐이지, 브라우저와 런타임에서는 JavaScript만을 이해하고 실행할 수 있습니다.</p>
<p>따라서, 번들링될 때 TS파일들은 전부다 타입 정보가 제거된 JS파일로 변환됩니다.</p>
<p>그런데, 또 우리는 라이브러리를 가지고 개발할 때 다시 또 정적 검사가 필요하기 때문에 함수의 인자나 컴포넌트의 props나 타입 정보가 필요합니다.</p>
<p>이럴 때 타입 정보를 제공하기 위해 <code>.d.ts</code> 파일이 필요한 것이죠!</p>
<br/>

<h3 id="플러그인으로-쉽게-생성하기">플러그인으로 쉽게 생성하기</h3>
<p><strong><code>😉 플러그인으로 가능!</code></strong></p>
<p><code>vite-plugin-dts</code> 라는 플러그인을 통해 간편하게 생성이 가능합니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/30a3df24-1e79-4e0a-9b7f-4bb526c1e07d/image.png" width="300"/></p>

<br/>

<p><strong><code>🤔 근데 이 .d.ts 파일이 사용처에서는 어떻게 매핑되어 정적 검사를 제공해줄 수 있는걸까?</code></strong></p>
<p>TypeScript 컴파일러는 코드 작성 시 <code>.d.ts</code> 파일을 참조하여 타입을 확인합니다.</p>
<p>생성된 부분을 자세히 보면 <code>.d.ts.map</code>이란 파일이 같이 생성되었어요. 이 파일은 <code>소스 맵</code>인데, 변환된 JS에서 원본 코드를 쉽게 찾을 수 있도록 도와줍니다.</p>
<p>이 소스 맵 파일이 없다고 해서 매핑될 수 없다는 것은 아닙니다. 즉, 없어도 매핑은 가능!</p>
<br/>

<p><strong><code>🤔 .d.ts.map 소스 맵 파일 없어도 된다면.. 있으면 뭐가 좋은걸까?</code></strong></p>
<blockquote>
<p><a href="https://stackoverflow.com/questions/17493738/what-is-a-typescript-map-file">https://stackoverflow.com/questions/17493738/what-is-a-typescript-map-file</a></p>
</blockquote>
<p>소스 맵 파일이 있으면, 코드 에디터나 브라우저에서 이 파일을 이용할 수 있어 JS 파일 대신 TS 파일에 직접 들어가 디버깅을 가능하게 해준다고 합니다.</p>
<p>어쨌든 이것도 파일이며 생성에 대한 리소스가 필요하기 때문에 빌드 시간이나 크기가 늘어날 것 같기도 합니다.</p>
<p>유명한 라이브러리들을 직접 빌드 돌려보고 소스 맵을 생성하는지 살펴보았는데요.</p>
<ul>
<li><a href="https://github.com/react-hook-form/react-hook-form?tab=readme-ov-file">react-hook-form</a> ⭕️</li>
<li><a href="https://github.com/chakra-ui/chakra-ui/tree/main/packages/react">@chakra-ui/react</a> ❌</li>
<li><a href="https://github.com/radix-ui/primitives/tree/main/packages/react/checkbox">@radix-ui/react-*</a> ⭕️ (.js.map으로 제공)</li>
</ul>
<blockquote>
<p><a href="https://www.reddit.com/r/typescript/comments/uq6on1/declaration_files_needed_when_using_map_files/">https://www.reddit.com/r/typescript/comments/uq6on1/declaration_files_needed_when_using_map_files/</a></p>
</blockquote>
<p>위 페이지의 내용들은 조금 연식이 되긴 했지만.. 공감이 가서 가져왔는데요. 보통 디버깅이나 코드가 더 궁금하다면 공식 문서나 오픈 소스를 뜯어봄으로써 디버깅 한다고 생각합니다.</p>
<p>보통 라이브러리의 코드가 궁금해서 에디터를 통해서 들어가도 유의미한 정보를 얻기는 힘든 것 같아요.</p>
<p>그래서 내 라이브러리를 쓰는 사용자들의 디버깅을 위해 소스맵을 추가해야겠다!!! 라는 건 좀 어려울 것 같아요.</p>
<p>그래서 <strong>저는 소스 맵을 제외</strong>하려고 합니다.</p>
<h3 id="소스맵-제외하기">소스맵 제외하기</h3>
<p>이것도 tsconfig 파일을 통해 쉽게 제외할 수 있습니다.</p>
<pre><code class="language-json">// tsconfig.json
{
  ...
  &quot;compilerOptions&quot;: {
    ...
    &quot;declarationMap&quot;: false
  },
  ...
}
</code></pre>
<p>소스맵을 제외함으로 번들 사이즈의 축소를 기대했습니다.</p>
<p>아직 작고 귀여운 패키지이기에.. 소수점 2자리까지 나오는 번들 사이즈에는 차이는 없었습니다. 🤣</p>
<h2 id="dist">dist</h2>
<p>그런데 빌드를 할 때마다 생성되는 <code>dist</code> 폴더는 어떻게 사용되는걸까요?</p>
<p>dist는 <strong><code>배포 파일</code></strong>로 볼 수 있는데요. 최종적으로 배포할 파일들이 포함됩니다.</p>
<p>번들러들이 소스 코드를 번들링하고 최적화하여 dist 폴더를 생성하는 것이죠!</p>
<h2 id="🚀-배포">🚀 배포!</h2>
<p><strong><code>🫢 이제 배포할 준비가 된건가..!!!!!!!!!</code></strong></p>
<p>아래에서 버전 관리에 대해 다뤄볼 것이긴 하지만, <a href="https://semver.org/lang/ko/"><code>Sementic Versioning</code></a>에 따라 최초 개발 배포인 지금 <code>0.1.0</code>으로 업데이트 했습니다.</p>
<pre><code class="language-json">// package.json
&quot;version&quot;: &quot;0.1.0&quot;,</code></pre>
<p><strong><code>🙂 패키지에 대한 정보 입력해주기</code></strong></p>
<pre><code class="language-json">// package.json
&quot;license&quot;: &quot;MIT&quot;,
&quot;homepage&quot;: &quot;https://github.com/depromeet/15th-team3-FE&quot;,
&quot;repository&quot;: {
  &quot;type&quot;: &quot;git&quot;,
  &quot;url&quot;: &quot;https://github.com/depromeet/15th-team3-FE&quot;,
  &quot;directory&quot;: &quot;packages/core/sds&quot;
},</code></pre>
<h3 id="그-전에">그 전에..</h3>
<p>난리가 났습니다. </p>
<p>번들에서 <code>react</code>를 제외하면서 고정해뒀던 버전들이 꼬인 걸로 추측됩니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/25ac35db-caa8-42dd-83c0-1f579f124a5c/image.png" alt=""></p>
<p>고정되어 있던 버전들을 update 해줌으로써 사용처에서의 문제는 해결했지만</p>
<pre><code class="language-bash">$ pnpm update</code></pre>
<p>갑자기, 배포하려는 패키지에서 타입 에러 지옥에 빠지게 되었습니다 😅</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/a6f1c5bb-790c-463e-a3fe-2637b8f6b9a2/image.png" width="600" /></p>

<p>원인은 이곳저곳 <code>react</code> 관련 패키지들이 버전이 꼬이면서 생기는 문제였습니다. </p>
<p>문제를 마주하게 되어 프로젝트의 <code>package.json</code>들을 열어보니 엉망이였습니다. 어디에는 <code>react</code>가 devDependencies에 들어가 있으며 (물론 소스코드에서 쓰여서 번들에 포함되긴 했나봅니다)</p>
<p>이곳 저곳 다른 버전을 쓰며 버전이 꼬여 에러가 발생한 걸로 보였습니다.</p>
<p>특정 패키지에서만 의존성을 가져야 하는 패키지와 공통으로 관리해야 할 패키지를 구분하여 <code>package.json</code>을 정리하니 빌드에 성공할 수 있었습니다.</p>
<h3 id="진짜-npm-publish">진짜 npm publish</h3>
<p><strong><code>1. npm 회원가입하기</code></strong></p>
<blockquote>
<p><a href="https://www.npmjs.com/signup">https://www.npmjs.com/signup</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/12c27507-80c8-41fd-a9ae-5519e06eb6fe/image.png" alt=""></p>
<p><strong><code>2. cli에서 로그인</code></strong></p>
<pre><code class="language-bash">$ npm login</code></pre>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/3ce7383e-50c0-4ea1-8aae-02fe660c5b8d/image.png" alt=""></p>
<p>아래 명령어를 실행해서 본인 username이 뜨면 로그인 성공입니다.</p>
<pre><code class="language-bash">$ npm whoami</code></pre>
<p><strong><code>3. 배포</code></strong></p>
<pre><code class="language-bash">npm publish --access=public</code></pre>
<p>모노레포 안에 있는 패키지를 배포하는 것이기 때문에 좀 더 설정이 필요합니다. 배포 명령어를 치면 아래와 같이 <code>scope</code>이 없다며 에러가 납니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/e11d0b76-a717-4434-ae84-3df0f15831bb/image.png" width="450" /></p>

<p>NPM에서 <code>Add Organization</code>을 해줘야 합니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/67661260-64d1-4d3b-9613-f7c710821f78/image.png" width="600"/></p>

<p>저 같은 경우에는 패키지명이<code>@sambad/sds</code>이므로 앞에 <code>sambad</code>를 입력해주었습니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/47142045-001d-4a16-b25a-971ef47c8d6f/image.png" width="400"/></p>

<p>완료 후 위 publish 명령어를 다시 입력해주면 배포 성공입니다 🎉</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/c5d17f53-419a-4353-b76d-4072484c06cc/image.png" alt=""></p>
<p>이제 배포된 패키지의 NPM 경로가 생기게 됩니다.</p>
<pre><code>https://www.npmjs.com/package/패키지명</code></pre><p>🚀🎉</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/496ded09-3202-48fd-96dc-1f5953dca0b7/image.png" width="600" /></p>


<h2 id="버전-관리">버전 관리</h2>
<p>업데이트 예정입니다 🎉</p>
<h2 id="끝으로">끝으로</h2>
<p>번들러를 골라서 손수 빌드를 위한 것들을 챙겨준 것은 처음 경험한 작업이였습니다. 생각보다 배울 게 많았으며 문제를 겪으며 찾아보고 알게된 것들이 정말 재밌었습니다 🫢</p>
<p>아직 해보고 싶은 게 많아요. 버전 관리도 해보고 싶고, 버전 업데이트 시 npm 자동 배포도 구축해보고 싶고, 디자인 시스템도 성장시키고 싶고 해보고 싶은게 많습니다 🤓</p>
<h2 id="reference">Reference</h2>
<ul>
<li><a href="https://muhammad-fiaz.medium.com/turbopack-the-rust-powered-successor-to-webpack-91f85f3ed73c">Turbopack: The Rust-powered successor to Webpack</a></li>
<li><a href="https://medium.com/@ramouz.muzacky/unpacking-the-best-comparing-webpack-vite-and-turbopack-for-modern-developers-3042da7c644c">Unpacking the Best: Comparing Webpack, Vite, and Turbopack for Modern Developers</a></li>
<li><a href="https://velog.io/write?id=b7bad659-9c8d-4f38-a0cf-12e9256ac53dhttps://velog.io/write?id=b7bad659-9c8d-4f38-a0cf-12e9256ac53d">[vite] 프론트엔드 개발자를 위한 Vite 101</a></li>
<li><a href="https://velog.io/@sejinkim/Turbopack%EC%9D%B4-Vite%EB%B3%B4%EB%8B%A4-10%EB%B0%B0-%EB%B9%A0%EB%A5%B4%EB%8B%A4">Turbopack이 Vite보다 10배 빠르다?</a></li>
<li><a href="https://heropy.blog/2019/01/31/node-js-npm-module-publish/">내 NPM 패키지(모듈) 배포하기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CSS가 우리에게 닿기까지]]></title>
            <link>https://velog.io/@doeunnkimm_/CSS%EA%B0%80-%EC%9A%B0%EB%A6%AC%EC%97%90%EA%B2%8C-%EB%8B%BF%EA%B8%B0%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@doeunnkimm_/CSS%EA%B0%80-%EC%9A%B0%EB%A6%AC%EC%97%90%EA%B2%8C-%EB%8B%BF%EA%B8%B0%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Mon, 17 Jun 2024 17:08:29 GMT</pubDate>
            <description><![CDATA[<p>이번 글에서는 다양한 CSS를 작성하는 방식을 살펴보며, 번들부터 렌더링까지 어떠한 과정이 일어나는지를 자세히 알아보려고 합니다.</p>
<p>최근 프로젝트를 새로 시작하게 되었어요. 스타일링 라이브러리를 선택하고 또 그것을 어떻게 사용할 것인지를 정하다 보니 제대로 공부해봐야겠다는 마음이 들어 작성해보려고 합니다 🙏🏽</p>
<h1 id="브라우저에게-스타일이란">브라우저에게 스타일이란</h1>
<p>CSS를 파싱해서 CSSOM(CSS Object Model) 트리로 파싱을 한 후, DOM과 결합하여 페인트하는 것은 잘 알려진 브라우저 렌더링 과정의 일부인데요.</p>
<p><strong>브라우저는 어떻게 스타일 속성을 알고 적용할 수 있는걸까요?</strong></p>
<p>CSS는 <a href="https://www.w3.org/">W3C</a>에서 정의한 표준입니다. 브라우저의 렌더링 엔진이 CSS 표준을 기반으로 동작합니다. CSS 명세를 구현하여 스타일 속성을 해석하고 적용하는 기능을 가지고 있어 이러한 명세를 기반으로 동작하여 스타일링을 처리합니다.</p>
<h1 id="css-module">css module</h1>
<blockquote>
<p><code>*.(module).css</code> 파일을 생성하고, CSS를 <strong>정적으로 분석</strong>하여 별도의 CSS파일로 추출하는 방식</p>
</blockquote>
<p>코드로 미리보면 아래와 같습니다.</p>
<pre><code class="language-css">/* styles.module.css */
.button {
  background-color: blue;
  color: white;
}</code></pre>
<pre><code class="language-jsx">// Button.js
import React from &#39;react&#39;;
import styles from &#39;./styles.module.css&#39;;

const Button = () =&gt; {
  return &lt;button className={styles.button}&gt;BUTTON&lt;/button&gt;
};

export default Button;</code></pre>
<p>css module를 사용하면, 컴포넌트 기반 스타일링이 가능해서 클래스명 충돌을 방지하고 스타일을 모듈화할 수 있어요. 번들링 과정에서 모듈 간 동일한 className을 사용하더라도 충돌을 방지하게 위해 <strong>고유한 클래스 이름으로 변환</strong>됩니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/66f4c68e-fe6e-4eff-a5f8-fa59c89d0d92/image.png" width="400" /></p>

<p>브라우저에서 렌더링 되기 위해서는 아래 JSX를 실제 DOM 노드로 변환합니다. (위에서 말한 클래스 이름 변환 포함) 이제 이걸로 브라우저에 렌더링하게 되는 것이죠!</p>
<pre><code class="language-jsx">const Button = () =&gt; {
  return &lt;button className={styles.button}&gt;BUTTON&lt;/button&gt;
}</code></pre>
<p>렌더링을 했으니 스타일을 적용해야 하는데, 스타일은 어디서 가져와 적용하는걸까요?</p>
<p>번들 과정에서 번들러는 CSS를 처리하는 로더를 통해 <code>&lt;head&gt;</code> 태그 안에 <code>&lt;style&gt;</code> 태그를 삽입하게 됩니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/39d3820a-21f9-4069-984d-1136be417588/image.png" /></p>

<p>이제 유니크한 className을 통해 스타일 속성값들을 찾고 DOM에 적용합니다.</p>
<p>정리해보자면 아래와 같습니다.</p>
<ol>
<li>브라우저에서 사용자 URL을 입력하고 접속하면 웹 서버에서 HTML 파일을 브라우저에 내려준다.</li>
<li>브라우저가 HTML 파일을 파싱한다. 파싱하면서 DOM을 생성한다.</li>
<li><code>&lt;link&gt;</code> 태그를 만나면 외부 리소스를 다운로드한다.</li>
<li>css 외부 리소스를 다운로드하면 css 파일로 CSSOM을 생성한다.</li>
<li><code>&lt;script&gt;</code> 태그를 만나면 JS 파일을 다운로드한다.</li>
<li>DOM + CSSOM을 합쳐 Render Tree를 만든다.</li>
<li>Element의 위치와 간격을 계산하는 Layout 과정이 일어난다.</li>
<li>레이어 별로 실제 그리기 작업을 수행하는 Paint 과정이 일어난다.</li>
</ol>
<p>위와 같은 방식은 CSS를 <strong>정적</strong>으로 분석하여 별도의 CSS 파일로 추출하는 방식입니다. 그러므로 동적으로 스타일을 적용하기 위해서는 inline으로 조건문을 통해 적용하는 방식이 되어야 했습니다 😨</p>
<h1 id="css-in-js">css-in-js</h1>
<h2 id="css-module과의-이야기">css module과의 이야기</h2>
<p>css-in-js는 자바스크립트 내에서 CSS를 작성하는 방식을 의미합니다.</p>
<p>css module의 마지막에 언급했던, <strong>동적인 스타일링의 어려움</strong>을 극복하고자 <strong><code>런타임 개념</code></strong>을 도입하여 해결했어요.</p>
<p><code>prop</code>가 변할 때마다 동적으로 생성하여 JS 코드를 동적인 스타일링이 가능해요. 즉, 빌드 타임에서 모든 스타일을 생성하는 것이 아닌, 런타임을 활용한 것입니다.</p>
<p>런타임에 스타일을 생성하는 방식은 대부분 문제가 없지만, 스타일 계산 비용이 커지기 때문에 스타일이 복잡한 컴포넌트에서는 차이가 발생한다고 합니다.</p>
<blockquote>
<p>👇 <a href="https://fe-developers.kakaoent.com/2022/220210-css-in-kakaowebtoon/">카카오웹툰에서 비교한 css module vs css-in-js 성능</a><br/><img src="https://velog.velcdn.com/images/doeunnkimm_/post/e73ee3bd-6087-40bf-911d-14f292b54459/image.png" alt=""></p>
</blockquote>
<p>Scriping은 2배 가까운 성능 차이를 보였는데, CSS-in-JS는 당연하게도 JS를 CSS로 변환하는 과정이 필요하기 때문이였어요. 빌드 타임에 모든 CSS가 만들어지는 것이 아니라 동적으로 추가되는 과정이 필요하기 때문에 아무래도 느릴 수 밖에 없는 것이죠.</p>
<blockquote>
<p>결론적으로, css-in-js는 <strong>런타임 동적 스타일 생성</strong>이 필요하기 때문에 성능적인 차이가 발생할 수 있다.</p>
</blockquote>
<h2 id="번들과-렌더">번들과 렌더</h2>
<blockquote>
<p>런타임에 동적으로 스타일을 생성해야 하는 경우에 대해서?</p>
</blockquote>
<p>최근 <code>emotion</code>을 자주 사용하고 있어서, <code>emotion</code>을 기준으로 css-in-js가 번들링 되고 렌더링 되는 과정을 살펴보려고 해요.</p>
<p>간단하게 다음과 같이 런타임에 동적으로 스타일링 해야 하는 간단한 코드가 있다고 해봅시다.</p>
<pre><code class="language-tsx">// styles.ts
import { css } from &#39;@emotion/react&#39;;

export const buttonCss = (color: string) =&gt;
  css({
    backgroundColor: color,
  });

// Button.tsx
import { buttonCss } from &#39;./styles&#39;;

export const Button = (props: ButtonProps) =&gt; {
  const { color } = props;

  return &lt;button css={buttonCss(color)}&gt;BUTTON&lt;/button&gt;;
};</code></pre>
<p><code>buttonCss</code> 함수는 호출될 때마다 동적으로 className을 생성하고, <code>emotion</code>은 이를 <code>&lt;style&gt;</code> 태그에 삽입합니다. </p>
<p>Button 컴포넌트는 buttonCss <strong>함수를 호출하여 동적으로 className을 생성하여 필요한 스타일을 적용</strong>합니다. </p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/8769e543-aa59-4fe6-9d30-04c85e87052c/image.gif" alt=""></p>
<h2 id="동적-스타일의-불가피함">동적 스타일의 불가피함</h2>
<blockquote>
<p>같이 있는 정적인 스타일 속성들까지 모두 다시 로드 필요</p>
</blockquote>
<p>css 함수가 호출될 때마다 고유한 className을 생성하여 스타일을 적용한다고 했습니다. </p>
<p>보통은 css 함수 내에서 <strong>일부는 정적으로, 일부는 동적으로 사용</strong>하는 경우가 많아 다음과 같이 극단적인 상황이 떠올랐는데요.</p>
<pre><code class="language-jsx">const dynamicStyle = (color) =&gt; css({
    color: ${color};
      // ... 100줄의 정적인 스타일 속성들
      backgroundColor: white;
})</code></pre>
<p>위 코드에서 <code>color</code> 값이 바뀔 때마다 전체 스타일 블록이 다시 생성되므로, 모든 스타일 속성이 다시 로드되게 됩니다.</p>
<p>만약 위와 같이 극단적인 상황에서 개선을 해봅다면 아래와 같을 것 같아요.</p>
<pre><code class="language-jsx">export const MyComponent = ({ color }) =&gt; {
  return (
    &lt;div css={[staticStyle, dynamicColorStyle(color)]}&gt;
      Hello, Emotion!
    &lt;/div&gt;
  );
}</code></pre>
<p>동적인 부분만 다시 생성되고, 정적인 부분은 캐싱된 스타일을 재사용할 수 있도록 동적으로 변경되는 color 속성을 분리해서 결합하는 형태로 주입할 수 있겠습니다.</p>
<blockquote>
<p>복잡한 스타일 계산에서 발생하는 Runtime overhead</p>
</blockquote>
<p>자주 변경되는 스타일을 브라우저가 지속적으로 CSS 속성을 재계산하고 DOM 요소에 적용해야 합니다.</p>
<p>동적 스타일링은 <strong>실행 중에 JS에 의해 계산되고 적용</strong>이 되는데, 이는 상태 변경, 사용자 입력, API 호출 등의 이벤트에 따라 스타일이 변경될 수 있음을 의미해요. <strong>상태 변화가 있을 때마다 스타일을 다시 계산하고 적용해야 함</strong>을 의미하기도 합니다. </p>
<p>이러한 문제를 해결하기 위해 <strong><code>zero-runtime</code></strong>을 주장하는 라이브러리들이 등장하게 됩니다.</p>
<p><strong><code>🤔 빌드 타임에 동적인 스타일링 한계로 런타임을 도입했는데, 런타임 없이?</code></strong></p>
<h1 id="zero-runtime-css-in-js">zero-runtime css-in-js</h1>
<blockquote>
<p>zero-runtime = 런타임에 동작 X, = 동적으로 스타일 생성 X</p>
</blockquote>
<p>빌드 타임에 미리 css를 생성해두는 방식으로, 런타임 이전에 미리 필요한 css를 빌드 시점에 생성하여 제공하는 형식입니다. </p>
<p>따라서 동적 스타일링은 <strong>사전에 정의한 스타일의 조합을 기반으로만</strong> 가능합니다. 결론부터 말해보자면 일반적으로 <a href="https://developer.mozilla.org/ko/docs/Web/CSS/var"><code>css variable</code></a>를 이용해 해결합니다.</p>
<p>👆 위 말의 의미가 와닿진 않아 대표적인 라이브러리 하나를 잡고 빌드 결과를 살펴보려고 합니다.</p>
<h2 id="linaria">linaria</h2>
<blockquote>
<p><a href="https://linaria.dev/">https://linaria.dev/</a></p>
</blockquote>
<p><code>zero-runtime</code> 스타일링 라이브러리 라이브러리 중 <code>linaria</code>를 기준으로 살펴보려고 합니다.</p>
<pre><code class="language-tsx">import { styled } from &#39;@linaria/react&#39;;

const backgroundByType = {
  default: &#39;grey&#39;,
  primary: &#39;blue&#39;,
  danger: &#39;red&#39;,
};

export const Button = styled.button&lt;ButtonProps&gt;`
  background-color: ${({ type }) =&gt; backgroundByType[type]};
`;</code></pre>
<p>위와 같은 코드가 빌드되면 아래와 같아져요.</p>
<pre><code class="language-js">.Button {
  background-color: var(--background-color);
}

export const Button = ({ type, ...props }) =&gt; (
  &lt;button
    className={styles.Button}
    style={{ &#39;--background-color&#39;: backgroundByType[type] }}
    {...props}
  /&gt;
);</code></pre>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/562aa31f-d59d-480d-8c90-843e3df5441c/image.gif" alt=""></p>
<p>동적 스타일을 추출하여 정적 CSS로 변환하게 되는데요. 스타일 시트를 새로 만들지 않고 <strong>css variable을 빌드 타임에 미리 생성</strong>해두고, 이를 상황에 맞게 적용하는 방식을 사용합니다.</p>
<p>스타일을 변경할 때 자세히 보게되면, css variable명은 그대로이지만 그 값만 바꾸면서 스타일을 적용하고 있습니다.</p>
<p><strong><code>🤔 css variable을 어떻게 뭘 생성?</code></strong></p>
<p>동적 스타일을 추출하여 아래와 같이 정적 CSS로 변환하게 된다고 했는데요.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/eb2b2108-b4d3-4a2d-8883-378e2e889c36/image.png" width="400"/></p>

<p>css variable을 <code>--root</code>에서 선언해 사용하는 것이 아닌 <strong><code>스타일을 적용한 컴포넌트 스콥 안에서</code></strong> css variable을 선언해 사용합니다. 덕분에 css variable을 사용하지만, 컴포넌트 별로 스타일 적용할 수 있는 것이죠. </p>
<p><strong><code>🤔 빌드 타임에 미리 생성한다는 것?</code></strong></p>
<p>빌드될 때 이미 변수들이 정의되어 있는 상태를 의미할 수 있습니다.</p>
<p>예를 들어 React 코드에서 <code>prop</code>에 따른 css variable을 <strong>객체로 정의하고 이를 매핑</strong>해 스타일을 적용하게 되면, 런타임에 스타일 시트를 새로 생성하지 않고도 css varibale만 수정하여 달라지는 조건에 대해 스타일을 다르게 줄 수 있는 것입니다.</p>
<p>이 방법을 사용하면 런타임에 새로운 스타일 시트를 생성할 필요 없이, 미리 정의된 css variable만 수정하여 다양한 스타일을 적용할 수 있습니다.</p>
<p><strong><code>🤔 css variable을 수정하는 것도 zero-runtime?</code></strong></p>
<p>보통은 런타임에 상태에 따라 css variable을 수정해야 할 것으로, 이게 정녕 zero-runtime이 맞나 의심이 들었어요.</p>
<p><code>zero-runtime</code>이라고 소개하는 라이브러리들은 런타임에 JS를 실행하지 않지만, css varaible을 런타임에 수정해야 하는 것은 JS를 사용합니다.</p>
<p><strong><code>🤔 zero-runtime이 그래서 더 좋긴 하다는건가?</code></strong></p>
<p>zero-runtime 라이브러리는 컴파일 타임에 css를 추출하므로, 런타임에 JS가 스타일을 계산하고 적용하는 과정을 생략할 수 있습니다. </p>
<p>또, 동적으로 css variable을 수정한다고 해도 <strong>이미 존재하는 css variable의 값을 변경하는 것</strong>이므로 스타일 시트를 재로드하는 것이 아닌 <strong>해당 변수의 값만을 업데이트하고 해당 값을 참조하는 스타일만을 재계산</strong>합니다.</p>
<p>반면 런타임에 JS를 사용하여 동적으로 생성하고 관리하는 css-in-js는 아래와 같이 <strong>동적인 변경이 필요할 때마다 className을 재생성</strong>합니다. 이말은 곧, <strong>새로운 스타일 규칙을 생성하고 이를 DOM에 추가하는 과정이 반복</strong>됨을 의미합니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/154bf25b-0b3b-4d01-bab4-dc802492f2d9/image.gif" alt=""></p>
<h2 id="emotion에-zero-runtime-도입해보기">emotion에 zero-runtime 도입해보기</h2>
<p><code>linaria</code>를 실제로 install해서 사용해보니, 평소에 <code>emotion</code>에서 자주 쓰던 기능들을 못 쓴다는 것과 <code>linaria</code>를 사용하기 위해서는 컴파일 시점에 css를 추출하기 위한 추가적인 설정이 필요하는 등 불편함이 존재하더라구요.</p>
<p>그래서 평소에 잘 쓰는 <code>emotion</code>에 <code>zero-runtime</code> 개념을 도입하여 css-in-js의 런타임 오버헤드를 개선해보고자 합니다.</p>
<p><code>prop</code>에 따라 css varaible 값을 결정하는 객체를 매핑하여 스타일을 적용했더니 아래와 같이 className 재생성 없이 <code>linaria</code>와 비슷하게 동작할 수 있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/2de10682-dcff-43ad-afeb-fd1a25913174/image.gif" alt=""></p>
<h1 id="마무리">마무리</h1>
<p>이번 글에서는 다양한 스타일링 방식에 대해 살펴보았습니다. </p>
<p>동적인 스타일링은 거의 필수적으로 필요한 부분이므로 완벽한 <code>zero-runtime</code>은 불가능하겠지만, css variable을 통해 css-in-js에서의 문제점이였던 className을 매번 생성하여 DOM에 반영하는 불편함(?)을 해소할 수 있음을 알 수 있었어요.</p>
<p>실제로 이 글을 작성하면서 <code>linaria</code>를 사용해보았는데, 몇 가지 불편함이 있었어요. 추가적인 config가 필요했다는 점과 문제 해결 관련해서 정보를 얻기 힘들다는 점이였는데요 🤔 <code>emotion</code>이나 <code>styled-components</code>에 비해서 말이죠 😂</p>
<p>이미 css-in-js로 프로젝트를 진행하고 있거나, 익숙한 API로 스타일링을 원하시는 분들은 css variable을 통해 부분적으로 <code>반 zero-runtime</code> 개념을 도입해보는 것도 성능 개선에 도움이 될 것 같습니다. 👍🏽</p>
<h1 id="reference">Reference</h1>
<ul>
<li><a href="https://so-so.dev/web/css-in-js-whats-the-defference/">CSS-in-JS, 무엇이 다른가요?</a></li>
<li><a href="https://velog.io/@rookieand/Vanilla-Extract-%EC%99%80-Zero-Runtime-%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90">Vanilla Extract 와 Zero Runtime 에 대하여 알아보자.</a></li>
<li><a href="https://pozafly.github.io/css/explore-how-to-apply-modern-css/">모던 CSS 적용 방법 둘러보기(CSS-in-JS with zero-runtime)</a></li>
<li><a href="https://velog.io/@sun1301/CSS-in-JSvsAtomicCSS">[CSS] CSS-in-JS vs Atomic CSS</a></li>
<li><a href="https://moon-ga.github.io/tailwind_css/2-dynamic-classnames/">[Tailwind CSS] Tailwind CSS의 작동 방식과 동적 스타일링 작성 방법 - (2)</a></li>
<li><a href="https://fe-developers.kakaoent.com/2022/221013-tailwind-and-design-system/">FE개발그룹에서는 Tailwind CSS를 왜 도입했고, 어떻게 사용했을까?</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js에서 동적으로 메타데이터와 오픈그래프 적용하기]]></title>
            <link>https://velog.io/@doeunnkimm_/%EB%8F%99%EC%A0%81%EC%9C%BC%EB%A1%9C-mete%EC%99%80-og%ED%83%9C%EA%B7%B8-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0-opengraph-image%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@doeunnkimm_/%EB%8F%99%EC%A0%81%EC%9C%BC%EB%A1%9C-mete%EC%99%80-og%ED%83%9C%EA%B7%B8-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0-opengraph-image%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Fri, 16 Feb 2024 09:46:58 GMT</pubDate>
            <description><![CDATA[<p>최근 프로젝트에서는 본인의 고유 id를 통해 url에 접근할 수 있고, 그 안에 내용들은 id마다 서로 다른 정보를 가지고 있어요. 그래서 동적으로 메타데이터와 오픈그래프를 적용하기로 했고, 최종적으로 적용 결과는 아래와 같았습니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/117db9cf-23a4-4ac3-8f15-32eeca7a2c63/image.png" /></p>

<p>구글 검색 결과에서 최상위에 위치하고 있어요.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/1d665e2f-adda-483b-bdcf-d8a6aa1261e9/image.png" alt=""></p>
<p>title에도 id가 들어가 있고, opengraph-image에도 제 id가 들어가도록 해주었어요. 이외에도 <code>&lt;head&gt;</code> 태그를 뜯어보면 동적으로 설정되도록 적용했습니다.</p>
<h1 id="🤔-먼저-알아보자">🤔 먼저 알아보자</h1>
<h2 id="metadata">Metadata</h2>
<p><code>&lt;meta&gt;</code> 태그는 웹 사이트에 대한 정보를 제공하는 기능을 하고 있습니다. 그러한 정보를 <strong><code>메타데이터</code></strong> 라고 부릅니다. 메타데이터는 웹 페이지에 나타나지 않고, 검색 엔진이나 웹 크롤러를 통해 수집됩니다. </p>
<p>Metadata는 웹사이트에서 <code>&lt;head&gt;</code> 태그를 열어보면 쉽게 볼 수가 있는데요!</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/cd80e923-b123-44dc-9cd9-39de73b2fc62/image.png" width="400"/></p>

<p>메타태그의 종류에는 대표적으로 <code>keywords</code>, <code>description</code>, <code>title</code> 등이 있습니다. 이외에도 브라우저 호환성을 지정하거나, 제작 도구, 저작권, 최종 수정일, 뷰포트 정의 등 다양한 정보들을 제공할 수 있습니다.</p>
<p>한 마디로 정의하면, <strong><code>웹 사이트에 대한 정보</code></strong></p>
<h3 id="왜-필요한-걸까">왜 필요한 걸까?</h3>
<blockquote>
<p>검색 엔진 최적화 (SEO)를 위해서</p>
</blockquote>
<p>적절히 구현된 메타데이터는 검색 엔진인 웹 페이지를 정확하게 색인화하고 순위를 지정하여, 궁긍적으로 검색 결과에서 상위에 노출되도록 할 수 있게 해줍니다.</p>
<p>즉, <strong>웹 사이트의 정보가 사용자에게 잘 전달되기 위해</strong>서는 검색엔진에서 적절히 인식되고 노출되어야 하고, 따라서 웹사이트는 메타데이터를 사용해야 합니다. 이는 곧 <strong>사용자가 우리 서비스를 쉽게 찾아볼 수 있도록 하는 것을 의미</strong>하므로 인지도를 높이고, 사용자 <strong>유입을 증가</strong>시키는 데 중요한 역할을 하는 것이죠!</p>
<h3 id="좋은-메타데이터란-뭘까">좋은 메타데이터란 뭘까</h3>
<p><strong>🤔 메타데이터를 그냥 막 설정한다고 해서 검색 결과가 좋아지는 건 아닐 겁니다. 그렇다면 좋은 메타데이터란 무엇일까요?</strong></p>
<blockquote>
<p>웹 페이지의 내용을 정확하게 반영하고, 사용자가 검색할 만한 키워드를 포함하는 것</p>
</blockquote>
<p><code>(당연,,🥹)</code></p>
<p>다양한 레퍼런스들을 보면 공통적으로 <code>사용자가 클릭할 만한</code>, <code>너무 길지 않은</code>을 많이 언급하는 거 같아요. 결론적으로 정답이 있다기 보다, <strong><code>정확</code></strong>하고 <strong><code>사용자에게 이목을 끌만한</code></strong>키워드가 포함되면 될 것 같다고 생각 들었어요.</p>
<p>저희 웹 사이트의 경우 구글 검색 시 최상단에 노출되게 되었어요. </p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/117db9cf-23a4-4ac3-8f15-32eeca7a2c63/image.png" width="400"/></p>

<p>물론 저희 팀에서도 <code>사용자가 검색할 만한 키워드 포함</code>을 신경 쓰면서 메타데이터를 추가했지만, 그것보다도 <strong><code>유니크한 서비스명</code></strong> 부분에서 이점을 얻어갔다 생각합니다. </p>
<p><del>사실 경쟁할 대상이 없기 때문에 최상위에 떴다고 볼 수도 있겠지만,,</del> 아무튼</p>
<p>새로 시작하는 웹 서비스의 경우 <strong>너무 흔한 키워드</strong>거나 <strong>이미 너무 유명한 컨텐츠가 있는</strong> 경우 불리하게 시작할 수밖에 없는 것 같아요.</p>
<p>저희는 덕분에 등록되자마자 서비스 홍보할 때 <code>&quot;반디부디 검색해서 들어오면 됨 ㅇㅇ&quot;</code> 라고 할 수 있었습니다 👍</p>
<h3 id="포털-사이트에-내-사이트-노출하기">포털 사이트에 내 사이트 노출하기</h3>
<p>포털 사이트에서 웹 사이트를 노출시키려면, 대부분의 경우 해당 포털 사이트의 웹마스터 도구나 검색 엔진 등록 서비스를 이용해 서비스를 등록해야 해요. </p>
<p>사이트를 직접 등록하면 포털 사이트가 <strong>사이트를 더 빠르게 인식</strong>하고, 사이트의 색인화 상태나 이슈 등을 효과적으로 관리할 수 있습니다.</p>
<p>→ <a href="https://imweb.me/faq?mode=view&amp;category=29&amp;category2=35&amp;idx=5945">네이버, 구글, 다음, 야후 등록 방법 및 SEO 내용이 담긴 가이드</a></p>
<p>사이트를 등록하지 않은 경우, 검색 엔진의 크롤러가 자동으로 새로운 사이트를 발견하고 색인화할 수도 있겠지만.. 그들이 와주기 전에.. 필요한 우리가 먼저 등록해서 더 빠르게 인식하도록 하는 게 좋겠습니다,. 😮‍💨</p>
<h2 id="open-graph">Open Graph</h2>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/b5f19ec6-fd59-48e3-ab23-217da35fa808/image.png" width="400"/></p>

<p>위와 같이 <strong>미리보기 화면을 구성</strong>할 수 있게 해주는 메타데이터를 오픈 그래프라고 해요.</p>
<p>웹 사이트 URL을 보내는 순간, 크롤러가 잽싸게 해당 URL을 먼저 방문해서 웹 사이트에 있는 오픈 그래프 데이터를 수집해 미리보기 형식으로 구성해 주는 방식입니다.</p>
<h3 id="왜-필요한-걸까-1">왜 필요한 걸까?</h3>
<p>정보를 공유할 때 URL로만 구성된 텍스트보다 잘 정돈된 이미지, 제목, 내용으로 구성된 미리보기가 <strong>클릭률이 높기 때문</strong>이죠!</p>
<p>왼쪽과 오른쪽 중 어딜 더 누르고 싶으신가요 🤔</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/ce9b9d29-9c8c-48d6-8d04-6332a8ed561a/image.png" width="500"/></p>

<h3 id="트위터는-왜-따로-또">트위터는 왜 따로 또..</h3>
<p>오픈 그래프는 <strong>Facebook에 의해 만들어진 프로토콜</strong>인데요! 다른 소셜 미디어 플랫폼들도 이를 지원합니다.</p>
<p>그런데, <strong>트위터는 자체적인 메타데이터 태그</strong>를 가지고 있어요. 이를 <code>Twitter Cards</code>라고 부르고, 트위터에서 웹페이지를 공유할 때 어떻게 표시될지를 정의합니다.</p>
<p>그래서 오픈 그래프를 정의할 때 <code>twitter</code>에 대해서는 따로 정의해 주어야 트위터에서 최적으로 표시되는 데 도움이 됩니다.</p>
<h1 id="🌪️-동적으로-적용">🌪️ 동적으로 적용?</h1>
<p>위에서 <code>Metadata</code>는 웹 사이트에 대한 정보, <code>Open Graph</code>는 미리보기 제공이라고 알아보았는데요! 이를 동적으로 적용하는 것에 대해 알아보려고 해요.</p>
<p>즉, 페이지 별로 콘텐츠에 따라 메타데이터를 동적으로 변경하는 것을 말합니다. 이는 각 페이지가 각각의 메타데이터를 가지고, 이를 통해 검색 엔진이 각 페이지의 콘텐츠를 정확하게 이해할 수 있도록 돕습니다.</p>
<p>또, 오픈그래프에 대해 동적으로 적용한다면, 페이지 별로 다른 미리보기를 보여줄 수 있어요.</p>
<p>대표적으로 블로그를 예시로 들 수 있어요.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/940f9ebd-6aca-4b99-8c3e-3b0d076a60b0/image.png" width="200" /></p>

<p>velog에서도 페이지 별로 서로 다른 미리보기를 가지고 있는 것처럼 말이죠!</p>
<p><strong><code>🤩 동적으로 적용하기 좋은 상황</code></strong></p>
<ul>
<li><p><strong>개별 페이지의 내용이 중요한 경우</strong></p>
<pre><code>블로그 포스트, 뉴스 기사, 제품 페이지 등 개별 페이지의 내용이 중요하고, 각 페이지가 고유한 정보를 가지고 있는 경우</code></pre></li>
<li><p><strong>사용자 정의 콘텐츠</strong></p>
<pre><code>사용자가 생성하는 콘텐츠가 있는 경우</code></pre></li>
<li><p><strong>웹 사이트의 내용이 자주 변경되는 경우</strong></p>
<pre><code>웹 사이트의 내용이 시간에 따라 자주 변경되는 경우, 동적으로 반영하면 최신 상태를 정확하게 반영 가능</code></pre></li>
<li><p><strong>검색 엔진 최적화(SEO)</strong></p>
<pre><code>동적 메타데이터는 검색 엔진이 각 웹 페이지를 정확하게 이해하는 데 도움</code></pre></li>
</ul>
<p>위와 같은 상황에서 동적 메타데이터를 적용하여 웹 사이트의 효과를 높일 수 있습니다.</p>
<h1 id="🫡-이제-진짜-해보자">🫡 이제 진짜 해보자</h1>
<p>제가 프로젝트에서 동적으로 <code>Metadata</code>와 <code>OpenGraph</code>를 어떻게 적용했는지를 정리해보려고 합니다.</p>
<ul>
<li>with Next.js <code>(v14)</code></li>
</ul>
<p><strong><code>🖤 Next.js 감사하다..</code></strong></p>
<ul>
<li><a href="https://nextjs.org/docs/app/building-your-application/optimizing/metadata">Metadata</a></li>
<li><a href="https://nextjs.org/docs/app/building-your-application/optimizing/metadata#dynamic-image-generation">Dynamic Image Generation</a></li>
<li><a href="https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image">opengraph-image and twitter-image</a></li>
</ul>
<p>친절한 Next.js의 공식 문서 덕분에 코드 자체를 작성하는 것에는 큰 어려움은 없었어요. 위 레퍼런스를 기준으로 저희 팀에서 적용한 방식을 이야기 해볼게요!</p>
<h2 id="1-메타데이터-정보는-상수로">1. 메타데이터 정보는 상수로</h2>
<p>이 부분이 나중에 갈수록 <code>아 잘했었다</code> 라는 생각이 들었었는데요. 메타데이터 정보를 입력하다보면 <strong>같은 내용을 여러 군데에서 작성할 일이 많아서</strong> 상수화 해두었더니 간편하게 사용하고 수정할 때도 간편해서 좋았어요. 추천 !</p>
<pre><code class="language-ts">// @/constants/metadata.ts
export const META = {
  title: &#39;반디부디: 내가 직접 그리는 나의 인생지도&#39;,
  siteName: &#39;BANDIBOODI | 반디부디&#39;,
  description:
    &#39;이루고 싶은 목표를 지도에 남겨보세요. 목표와 관련된 다양한 스티커를 붙여 나만의 지도를 만들고 공유할 수 있어요.&#39;,
  keyword: [
    &#39;반디부디&#39;,
    &#39;bandiboodi&#39;,
    &#39;인생지도&#39;,
    &#39;신년계획&#39;,
    &#39;계획&#39;,
    &#39;목표설정&#39;,
    &#39;목표달성&#39;,
    &#39;자기계발&#39;,
    &#39;회고&#39;,
    &#39;응원&#39;,
  ],
  url: &#39;https://www.bandiboodi.com&#39;,
  googleVerification: &#39;xxx&#39;,
  naverVerification: &#39;xxx&#39;,
  ogImage: &#39;/opengraph-image.png&#39;,
} as const;
</code></pre>
<p><code>googleVerification</code>과 <code>naverVerification</code>에는 사이트를 <strong>등록하고 받은 검증 코드</strong>를 입력해주면 됩니다.</p>
<p>위 코드에서 보이는 <code>ogImage</code> 경로는 참고로 <code>public</code> 폴더 기준 경로이며, 이 이미지는 <strong>정적 opengraph-image로 사용할 이미지</strong> 입니다.</p>
<h2 id="2-metadata를-만드는-함수">2. metadata를 만드는 함수</h2>
<p>원래는 저희 팀에서도 정적 메타데이터만을 사용할 때는 <code>layout.tsx</code> 파일에 바로 넣어서 사용했었는데요. 이제는 동적으로 인자를 받아서 metadata를 생성할 수 있도록 함수를 정의해보려고 합니다.</p>
<pre><code class="language-tsx">export const getMetadata = (metadataProps?: generateMetadataProps) =&gt; {
  const { title, description, asPath, ogImage } = metadataProps || {};

  const TITLE = title ? `${title} | 반디부디` : META.title;
  const DESCRIPTION = description || META.description;
  const PAGE_URL = asPath ? asPath : &#39;&#39;;
  const OG_IMAGE = ogImage || META.ogImage;

  const metadata: Metadata = {
    metadataBase: new URL(META.url),
    alternates: {
      canonical: PAGE_URL,
    },
    title: TITLE,
    description: DESCRIPTION,
    keywords: [...META.keyword],
    openGraph: {
      title: TITLE,
      description: DESCRIPTION,
      siteName: TITLE,
      locale: &#39;ko_KR&#39;,
      type: &#39;website&#39;,
      url: PAGE_URL,
      images: {
        url: OG_IMAGE,
      },
    },
    verification: {
      google: META.googleVerification,
      other: {
        &#39;naver-site-verification&#39;: META.naverVerification,
      },
    },
    twitter: {
      title: TITLE,
      description: DESCRIPTION,
      images: {
        url: OG_IMAGE,
      },
    },
  };

  return metadata;
};</code></pre>
<p>우선 인자 자체를 optional로 하여 <strong>아무 것도 넘기지 않고 호출했을 때는 default로 해둔 값들을 metadata로 가지도록</strong> 했어요. 또, 값도 필요한 것들만 넘기도록 하여 넘기지 않은 인자에 대해서는 default값을 가지도록 했습니다.</p>
<p>인자로 받은 값이 있다면, 그걸 통해 <code>metadata</code>를 생성합니다. 사용할 때는 아래와 같이 사용할 수 있어요.</p>
<p><strong><code>🤔 metadataBase</code></strong></p>
<p>상대 경로를 사용할 수 있게 해주는 필드입니다. 즉 해당 필드를 설정해두면, 다른 필드들에 <code>/example</code>로 입력하면 실제 <code>&lt;head&gt;</code>에는 <code>(metadataBase로 설정한 경로)/example</code> 이렇게 들어가게 되는 것이죠!</p>
<p>Next.js에서 <code>Metadata</code> 타입의 <code>metadataBase</code>에는 URL 객체 타입이 들어가야 하므로 <code>new URL()</code> 메서드를 사용해줘야 합니다.</p>
<p>→ <a href="https://nextjs.org/docs/app/api-reference/functions/generate-metadata#metadatabase">친절한 공식문서</a></p>
<p><strong><code>🤔 alternates.canonical</code></strong></p>
<p>웹페이지가 다른 URL에서 동일하게 접근 가능할 때, 어떤 버전이 원본(주요) 콘텐츠임을 검색 엔진에 알려주는 요소입니다.</p>
<p>예를 들어, 온라인 쇼핑몰에서 같은 상품을 여러 카테고리에서 보여주는 경우가 있을 수 있는데요. </p>
<ul>
<li><a href="http://www.example.com/clothes/shirt/123">www.example.com/clothes/shirt/123</a> <code>(1)</code></li>
<li><a href="http://www.example.com/sale/shirt/123">www.example.com/sale/shirt/123</a> <code>(2)</code></li>
</ul>
<p>이 두 URL이 같은 상품 페이지를 가리키고 있지만, <strong>검색 엔진은 이 두 페이지를 서로 다른 콘텐츠로 인식할 수</strong> 있습니다. 이렇게 되면, 중복 콘텐츠 문제가 발생하고, 각 페이지가 독립적으로 SEO 점수를 쌓게 됩니다.</p>
<p>이런 경우, <strong><code>Canonical Tag</code></strong>를 사용하여 어떤 URL이 원본 URL인지를 명확히 지정할 수 있습니다. 예를 들어, <code>www.example.com/clothes/shirt/123 (2)</code>를 원본 URL로 지정한다면, 검색 엔진은 <code>www.example.com/sale/shirt/123 (1)</code> 페이지의 콘텐츠가 원래 <code>www.example.com/clothes/shirt/123 (2)</code>에서 생성된 것임을 인식하고, 이를 원본 URL로 취급하게 됩니다. 이로써 중복 콘텐츠 문제를 해결하고, SEO 점수를 하나의 페이지에 집중시킬 수 있는 것이죠!</p>
<p>저희 팀 서비스는 당장은 그런 페이지는 존재하지 않았어요. 그래도 이 태그를 통해 검색 엔진에게 <strong><code>원본 컨텐츠임을 명확하게 알려줄 수 있는</code></strong> 역할로 우선 사용하되, 추후에 URL 변동과 같은 경우를 대비하기 위해 우선 추가하기로 했어요.</p>
<h2 id="3-metadata-적용">3. Metadata 적용</h2>
<p>Next.js에서는 fetch된 Data 혹은 slug값을 기반으로 메타데이터를 동적으로 생성할 수 있도록 <code>generateMetadata</code>라는 함수를 제공합니다.</p>
<p>저희 팀에서도 slug값에 포함되어 있는 <code>id</code>를 사용하기 위해 <code>generateMetadata</code> 함수를 사용했습니다.</p>
<pre><code class="language-tsx">export const generateMetadata = async ({ params: { username } }: HomeRouteParams): Promise&lt;Metadata&gt; =&gt; {
  return getMetadata({ title: `반짝반짝 빛날 ${username}님의 인생지도`, asPath: `/home/${username}` });
};</code></pre>
<p><code>generateMetadata</code> 함수의 리턴값으로는 미리 선언했던 인자를 통해 메타데이터 객체를 생성해주는 함수를 호출하여 리턴해주었습니다.</p>
<h3 id="고민했던-부분">고민했던 부분</h3>
<p><code>asPath</code> 프로퍼티의 경우, 당장은 현재 url을 그대로 넘기면 되는 부분이라, 메타데이터를 주입하는 기능의 컴포넌트를 선언하여 <code>usePathname</code> 과 같은 hook을 사용해서 지금의 url을 읽어 내부에서 바로 처리할 수도 있었습니다.</p>
<p>그러할 경우, Metadata 타입을 이용한 객체를 사용하는 대신 <code>&lt;Head&gt;</code> 컴포넌트를 사용하면서(둘 다 Next.js에서 제공하는 기능이긴 합니다) 아래와 같이 적용해 주어야 한다는 점이 조금 번거롭게 다가왔던 것 같습니다.</p>
<pre><code class="language-tsx">&lt;Head&gt;
  &lt;title&gt;{TITLE}&lt;/title&gt;
  &lt;link rel=&quot;canonical&quot; href={URL} /&gt;
  &lt;meta name=&quot;description&quot; content={DESCRIPTION} /&gt;

  &lt;meta property=&quot;og:title&quot; content={TITLE} /&gt;
  &lt;meta property=&quot;og:description&quot; content={DESCRIPTION} /&gt;
  &lt;meta property=&quot;og:image&quot; content={IMAGE} /&gt;
  &lt;meta property=&quot;og:url&quot; content={URL} /&gt;

  {/* for twitter */}
  &lt;meta name=&quot;twitter:title&quot; content={TITLE} /&gt;
  &lt;meta name=&quot;twitter:description&quot; content={DESCRIPTION} /&gt;
  &lt;meta name=&quot;twitter:image&quot; content={IMAGE} /&gt;
&lt;/Head&gt;</code></pre>
<p><code>Metadata</code> 타입을 붙여서 사용할 경우, <strong>올바르지 않은 프로퍼티에 대해서는 컴파일 에러</strong>로 확인할 수 있다는 점에서도 편리하게 느껴졌었거든요!</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/72fb710f-5172-487f-9f36-a564a9691fcc/image.png" width="300"/></p>

<p>그래서 고민 끝에, 필요한 경우 경로를 <code>asPath</code>를 통해 넘기는 방식으로 결정했습니다.</p>
<h2 id="4-동적-opengraph-image-적용">4. 동적 opengraph-image 적용</h2>
<p>저희 팀이 원하는 opengraph-image 설정은 다음과 같습니다. </p>
<ul>
<li>동적으로 설정하지 않았을 경우에는 정적 이미지로 설정해둔 경로가 잡히도록</li>
<li>동적으로 설정한 경우 그 이미지가 잡히도록</li>
</ul>
<p>즉, 정적 &amp; 동적 둘 다 사용하고 싶다는 말이죠!</p>
<h3 id="정적-이미지-적용">정적 이미지 적용</h3>
<p>우선 초반에 ogImage 경로로 정적 이미지 경로를 잡아두었었어요.</p>
<pre><code class="language-tsx">// @/constants/metadata.ts
export const META = {
  ...
  ogImage: &#39;/opengraph-image.png&#39;,
} as const;</code></pre>
<p><code>metadataBase</code>에 default 경로를 설정했기 때문에 <code>&lt;head&gt;</code>에는 <strong><code>www.bandiboodi.com/opengraph-image.png</code></strong> 라는 경로로 들어가게 돼요.</p>
<p>즉, 제가 설정한 대로라면, <strong>public 폴더 바로 아래에 존재</strong>해야 이미지를 찾을 수 있다는 말입니다.</p>
<p><strong><code>🚨 이미지 경로를 꼭 잘 매우 엄청 잘 확인합시다</code></strong></p>
<p>이렇게 잘 해두면 어느 페이지를 공유하더라도 정적 이미지가 미리보기에 뜨게 됩니다.</p>
<h3 id="동적-이미지-적용">동적 이미지 적용</h3>
<p>Next.js에서는 <code>opengraph-image.tsx</code>라는 파일 시스템 컨벤션을 제공해요. <a href="https://nextjs.org/docs/app/api-reference/file-conventions/metadata/opengraph-image#generate-images-using-code-js-ts-tsx">공식 문서에 너무 잘 나와있어서</a> 적용법에 대해서는 건너뛰고 제가 어려움을 겪었던 부분을 이야기해보려고 해요.</p>
<h3 id="🥵-개발보다-어려웠다고-한-이유-1">🥵 개발보다 어려웠다고 한 이유 1</h3>
<p>오픈 그래프는 미리보기를 통해 볼 수 있는 내용이였죠? 그래서 제가 작성한 코드가 잘 반영되었는지 확인하려면 아래와 같은 과정들을 반복해야 했어요.</p>
<ul>
<li>커밋 → 푸쉬 → PR → PR에 대한 building → merge → main 브랜치에서 building → 카톡으로 보내서 확인</li>
</ul>
<p><del>그래서 merge하고 빌딩될 때마다 하느님 부처님 예수님을 호출했었는데요</del></p>
<p>특히 <strong>opengraph-image가 잘 적용되었는지 확인하고 다시 트라이 하는 과정</strong>에서 시간이 많이 소요되었었어요.</p>
<p><strong><code>🥲 로컬에서도 opengraph-image가 잘 적용되었는지 확인하는 방법</code></strong></p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/c9803c94-26b6-4852-914c-cd607e7ff68d/image.png" alt=""></p>
<p>로컬에서 <code>&lt;head&gt;</code>를 열어보면 <code>og:image</code>를 찾을 수 있어요. 잘 적용되었는지 확인하기 위해서는 <strong>이미지 경로를 가져와서 브라우저를 통해 들어가보면</strong> 잘 적용되었는지 빠르게 확인할 수 있어요.</p>
<p>잘 적용된 경우 바로 확인이 가능하고, 경로가 이상하거나 파일 자체에 문제가 있다면 원하는 화면이 보이지 않아요.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/0628e9dc-ba2e-43c7-9a88-28f0f5110f83/image.png" width="400"/></p>

<h3 id="🥵-개발보다-어려웠다고-한-이유-2">🥵 개발보다 어려웠다고 한 이유 2</h3>
<h4 id="ogimage-권장-사항">og:image 권장 사항</h4>
<p><code>og:image</code>에는 권장되는 이미지 크기와 용량에 대해서도 권장되는 사항들이 있어요.</p>
<ul>
<li>1200 x 630 크기 권장</li>
<li>8MB 용량 이하 권장</li>
</ul>
<p>그래서 크기를 맞추고, 용량은 최대한 작게 줄일 수 있을 정도로 줄이고 사용했습니다.</p>
<p>→ <a href="https://www.iloveimg.com/ko/compress-image">이미지 용량 줄이는 웹 사이트</a></p>
<h4 id="동적으로-할-때-로컬-이미지는">동적으로 할 때 로컬 이미지는..</h4>
<p>위에서 보셨다싶이 동적으로 생성한 <code>og:image</code>는 <strong>이미지를 깔고 그 위에 텍스트</strong>를 입력한 형태에요.</p>
<p>이미지 url에 로컬 이미지 경로를 넣어주었을 때 위에서 소개했던 방법으로는 확인할 수가 없었어요. 결국 저는 <strong>외부를 통해 이미지 경로를 가져와</strong> 이미지를 출력할 수 있었습니다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/d34058c7-c2ca-4e51-8a21-009f954a0bb2/image.png" alt=""></p>
<p>이번 글에서는 메타데이터와 오픈그래프의 개념부터 제가 적용한 과정들을 소개해보았습니다. 누구 하나 이게 정답이라고 알려준 글 없이 다들 적용한 방법이 가지각색이라 적용하는 과정에서 어려움이 좀 있었던 거 같아요. 이번 기회를 통해 또 한번 새로운 도전을 시도하고 실패하고 실패하고 실패하고 끝내 성공할 수 있었습니다,, 🫡</p>
<h1 id="references">References</h1>
<ul>
<li><a href="https://yozm.wishket.com/magazine/detail/816/">검색 잘 되는 사이트의 필수조건, 메타(meta) 태그란?</a></li>
<li><a href="https://inpa.tistory.com/entry/HTML-%F0%9F%93%9A-meta-%ED%83%9C%EA%B7%B8-%EC%A0%95%EB%A6%AC">메타(meta) 태그 종류 &amp; 사용법</a></li>
<li><a href="https://appmaster.io/ko/blog/metadeiteo-jungyoseong-web-gaebal">웹 개발에서 메타데이터의 중요성</a></li>
<li><a href="https://stronglytyped.uk/articles/open-graph-images-nextjs-app-router">Open Graph images using the Next.js App Router</a></li>
<li><a href="https://blog.treblle.com/dynamic-og-image-generation-nextjs-method/">How we developed our method to generate dynamic OG Images using NextJS</a></li>
<li><a href="https://miriya.net/blog/MKkWLVdLpZo0tARvG8za">Next.js APP에서 og:image 자동으로 생성하기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[복세편개 타입 잘 쓰고 싶은 사람.. 나 🙋‍♀️]]></title>
            <link>https://velog.io/@doeunnkimm_/%EB%B3%B5%EC%84%B8%ED%8E%B8%EA%B0%9C-%ED%83%80%EC%9E%85-%EC%9E%98-%EC%93%B0%EA%B3%A0-%EC%8B%B6%EC%9D%80-%EC%82%AC%EB%9E%8C..-%EB%82%98</link>
            <guid>https://velog.io/@doeunnkimm_/%EB%B3%B5%EC%84%B8%ED%8E%B8%EA%B0%9C-%ED%83%80%EC%9E%85-%EC%9E%98-%EC%93%B0%EA%B3%A0-%EC%8B%B6%EC%9D%80-%EC%82%AC%EB%9E%8C..-%EB%82%98</guid>
            <pubDate>Wed, 07 Feb 2024 05:43:14 GMT</pubDate>
            <description><![CDATA[<h1 id="내가-선정한-복세편개-타입">내가 선정한.. 복세편개 타입</h1>
<blockquote>
<p>복잡한 세상.. 편하게 개발하게 해주는 타입 🤔</p>
</blockquote>
<ul>
<li><strong>typeof</strong></li>
<li><strong>keyof</strong></li>
<li><strong>Parameters</strong></li>
<li><strong>ReturnType</strong></li>
</ul>
<p>저는 주로 TypeScript를 사용하고 있는데요! 쓰다보면 <strong><code>유기적으로 잘 엮어 쓰고 있을 때</code></strong> 더욱 견고하게 쓰고 있다는 느낌을 받는 거 같아요.</p>
<p><code>유기적으로 잘 엮어 쓰고 있다</code>의 간단한 예시를 들어볼게요! (단순한 설명을 위해 유틸 타입은 사용하지 않았어요!)</p>
<pre><code class="language-tsx">// good
interface ModalProps {
  title: string
  content: string
}
interface ModalBodyProps {
  content: ModalProps[&#39;content&#39;]
}

// bad
interface ModalProps {
  title: string
  content: string
}
interface ModalBodyProps {
  content: string
}</code></pre>
<p>위와 같이 간단하더라도, 비록 타입이 <code>string</code>이더라도, 연관이 있다면 <strong>엮어주어서</strong> 유지보수할 때 편해야 한다고 생각합니다.</p>
<p>즉, <strong><code>기존에 있는 타입들을 요리조리 잘 써먹을 수</code></strong> 있어야 한다는 말과도 같아요. 그래서 제가 이번 글에서 소개할 타입들은 기존에 있는 것들을 이용할 수 있게 해주는 타입들이에요.</p>
<h1 id="typeof">typeof</h1>
<p><code>typeof</code>를 사용하면 선언된 객체를 바탕으로 프로퍼티의 타입을 몽땅 가져와 사용할 수 있어요. 아래와 같은 객체를 선언했다고 해봅시다.</p>
<pre><code class="language-ts">const man = {
  name: &#39;bytefer&#39;,
  email: &#39;bytefer@gmail.com&#39;,
  address: {
    street: &#39;Kulas Light&#39;,
    suite: &#39;Apt. 556&#39;,
    city: &#39;Gwenborough&#39;,
    zipcode: &#39;92998-3874&#39;,
    geo: {
      lat: &#39;-37.3159&#39;,
      lng: &#39;81.1496&#39;
    }
  }
}</code></pre>
<p>그럼 이 데이터 형태와 동일한 객체를 안전하게 또 선언하고 싶으면 위 프로퍼티를 하나하나 보면서 타입을 만들어줄 수도 있겠지만, <code>typeof</code>를 사용하면 자동으로 타입을 정의할 수 있어요.</p>
<pre><code class="language-tsx">// bad
type Person = {
  name: string;
  email: string;
  address: {
    street: string;
    suite: string;
    zipcode: string;
    geo: {
      lat: string;
      lng: string
    }
  }
}

// good
type Person = typeof man</code></pre>
<p>단순한 객체일 때는 큰 문제가 되지 않겠지만, 객체가 크고 복잡한 경우에는 <code>keyof</code>를 통해 훨씬 쉽게 타입을 정의할 수 있습니다.</p>
<h1 id="keyof">keyof</h1>
<p><code>keyof</code>를 사용하면 선언된 <strong>객체 형태의 타입</strong>의 프로퍼티 key를 <strong>유니온 타입</strong>으로 구성할 수 있는데요! 즉, <strong>선택지</strong>로서 프로퍼티 키를 사용하고 인자로 이 프로퍼티키를 받아 사용해야 할 경우 유용합니다.</p>
<p>아래와 같은 선택지로 사용할 객체가 있다고 해봅시다.</p>
<pre><code class="language-ts">type Person = {
  name: string;
  age: number;
  married: boolean;
}</code></pre>
<p>위와 같이 선언된 객체 형태의 타입에서 프로퍼티 키를 값으로 사용하고 싶다면 <code>keyof</code>가 유용해요.</p>
<pre><code class="language-ts">// bad
type PersonKeys = &#39;name&#39; | &#39;age&#39; | &#39;married&#39;

// good
type PersonKeys = keyof Person</code></pre>
<p><code>keyof</code>는 보통 <code>typeof</code>랑 더 많이 사용되는 거 같은데요! 선언되어 있는 객체가 있고, 그 객체의 프로퍼티 키를 통해 값을 사용해야 하는 상황에 매우 유용합니다!</p>
<pre><code class="language-tsx">const sizeCSS = {
  small: &#39;h-[10px]&#39;,
  medium: &#39;h-[15px]&#39;,
  large: &#39;h-[20px]&#39;,
}

type SizeProps = keyof typeof sizeCSS // &#39;small&#39; | &#39;medium&#39; | &#39;large&#39;

function Foo({size}: {size: SizeProps}) {
  return (
    &lt;Bar size={sizeCSS[size]} /&gt;
  )
}</code></pre>
<p>위와 같이 작성함으로써 <code>sizeCSS</code>에 선언한 <strong>프로퍼티 키 만을 타입으로 하여</strong> 받을 수 있으므로 <strong>반드시 매칭이 될 수 있도록</strong> 할 수 있습니다.</p>
<p>참고로, <code>keyof typeof</code>의 과정은 아래와 같습니다.</p>
<pre><code class="language-tsx">type Typeof = typeof sizeCSS // { small: string; medium: string; large: string }

type KeyofTypeof = keyof Typeof // &#39;small&#39; | &#39;medium&#39; | &#39;large&#39;
</code></pre>
<p>만약에 <code>keyof typeof</code>를 사용하지 않고 <code>sizeCSS</code>를 보며 타입을 선언해주었다면, </p>
<pre><code class="language-ts">const sizeProps = &#39;small&#39; | &#39;medium&#39; | &#39;large&#39;</code></pre>
<p><code>sizeCSS</code>에 프로퍼티가 추가 혹은 수정되었을 때 일일이 변경점을 찾아 수정해줘야 할 것입니다. <del>번거롭다 번거로워..</del></p>
<p>반면, <code>keyof typeof</code>를 통해 타입을 정의해주면, 객체가 변경되어도 <strong>자동으로 인식</strong>이 되기 때문에 <strong>타입은 따로 신경써주지 않아도 됩니다.</strong></p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/8287d12b-6080-463a-919d-ba29385f6ee4/image.gif" alt=""></p>
<h1 id="parameters">Parameters</h1>
<p><code>Parameters</code>는 함수 타입 Type의 <strong>매개변수에 사용된 타입들의 튜플 타입</strong>을 생성합니다.</p>
<pre><code class="language-ts">type Type = Parameters&lt;(a: string, b: number) =&gt; void&gt; // [string, number]</code></pre>
<p><code>Parameters</code>를 사용하면 메서드의 매개변수의 타입을 튜플로 받아 사용할 수 있습니다. 좀 더 와닿을 수 있는 예시로, 기존에 선언한 메서드를 이용한다면 아래와 같습니다.</p>
<pre><code class="language-ts">function testMethod(a: string, b: number) {
  // ...
}
type Type = Parameters&lt;typeof testMethod&gt; // [string, number]</code></pre>
<h2 id="🫢-실제로-유용하다고-느꼈을-때">🫢 실제로 유용하다고 느꼈을 때</h2>
<p>최근에 api 요청에 성공했을 때, 응답 객체의 타입에 대해 조금 커스텀 해줘야 하는 task가 있었어요. 저희 팀에서는 httpClient 라이브러리로 <code>axios</code>를 사용하고 있어서 axios의 <strong>http 메서드를 사용했을 때 return 값을 다뤄야</strong> 했습니다.</p>
<p>즉, <code>axios.get</code>에 인자를 넘기고 사용하는 건 다 동일한데, return값만 손을 봐야했던 거죠?</p>
<p><code>axios.get</code>에 마우스를 갖다대면 아래와 같이 타입을 알려줬어요.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/ac116bae-492f-4984-959a-6dbc8f16a874/image.png" alt=""></p>
<p>그래서 처음에는 아래와 같은 형태의 타입으로 정의되었었어요(return되는 데이터에서 result라는 프로퍼티에 한번더 감싸져 있는 형태라 <code>BaseResponse</code>라는 유틸 타입을 선언하고 이를 감싸주었어요).</p>
<pre><code class="language-tsx">const api = {
  get: &lt;T, R&gt;(url: string, params?: R): Promise&lt;AxiosResponse&lt;BaseResponse&lt;T&gt;&gt;&gt; =&gt; createAxiosInstance.get(url, { params }),
  ...
}</code></pre>
<p>단순히 <strong><code>기존 메서드의 매개변수를 사용하고 싶다</code></strong>의 의미였는데 명시되어 있는 타입을 따라가다보니 위와 같이 되었던 거죠. 그래서 위 타입을 <code>Parameters</code>를 이용해 개선하면 아래와 같은 형태가 될 수 있습니다.</p>
<pre><code class="language-tsx">const api = {
    get: &lt;T&gt;(...args: Parameters&lt;typeof axiosInstance.get&gt;) =&gt; {
    return axiosInstance.get&lt;BaseResponse&lt;T&gt;&gt;(...args);
  },
  ...
}</code></pre>
<p>확실히 가독성도 좋아지고 한결 깔끔해질 수 있었습니다. 🥲</p>
<p>이런 식으로, 라이브러리의 메서드에서 <strong>인자는 그대로 사용하면서도 커스텀이 필요할 때</strong> <code>Parameters</code>를 사용하면 매우 유용할 것 같다는 생각이 들었습니다.</p>
<h1 id="returntype">ReturnType</h1>
<p><code>ReturnType</code>은 함수 Type의 <strong>반환 타입</strong>으로 구성된 타입을 생성합니다.</p>
<pre><code class="language-ts">type Type = ReturnType&lt;() =&gt; string&gt;  // string</code></pre>
<p>좀 더 일반적이게 기존 메서드를 이용해보면 아래와 같습니다.</p>
<pre><code class="language-ts">function testMethod(name: string, age: string) {
  return `안녕하세요. 저는 ${age}살 ${name}입니다.`
}
type Type = ReturnType&lt;typeof testMethod&gt; // string</code></pre>
<h2 id="🫢-실제로-유용하다고-느꼈을-때-1">🫢 실제로 유용하다고 느꼈을 때</h2>
<p>최근에 디바운스 작업을 해야했었어요. <code>useDebounceCall</code>이라는 커스텀 훅을 만들어 작업을 했습니다. 내부 로직은, <code>useRef</code>로 선언된 <code>timer</code>에 값을 넣어놓고, 이 값을 통해 <code>clearTimeout</code>하기도 하고, 다시 여기에 새로운 <code>setTimeout</code>을 넣기도 해야 했어요.</p>
<p><code>setTimeout</code>에 마우스를 갖다대니 아래와 같이 타입을 알려주었습니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/47faa504-017e-4d6e-99ff-0134f607a9ec/image.png" alt=""></p>
<p>그래서 리턴 타입인 <code>NodeJS.Timeout</code>을 보고 아래와 같이 타입을 명시했어요.</p>
<pre><code class="language-tsx">const timer = useRef&lt;NodeJS.Timeout | null&gt;(null);</code></pre>
<p>사실 위 예시에서 봤던 <code>axios.get</code> 보다는 간단하고 가독성에 해가 되는 정도까지는 아니였지만, 좀 더 <code>setTimeout</code>과 유기적으로 사용하고 싶다면 아래와 같이 <code>ReturnType</code>을 활용할 수 있습니다.</p>
<pre><code class="language-tsx">  const timer = useRef&lt;ReturnType&lt;typeof setTimeout&gt; | null&gt;(null);</code></pre>
<hr>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/1616db0f-728d-4b0d-8a99-0a79f19d6929/image.png" alt=""></p>
<p>이번 글에서는, 제가 최근에 <code>Parameters</code>와 <code>ReturnType</code>을 직접 맛(?)보았던 이야기들과 느낀 점들에 대해 정리해보았어요. 이제는 내가 선언한 것 뿐만 아니라, 라이브러리에서 제공되는 타입들도 요리조리 잘 써먹어봐야겠다는 생각이 들었던 거 같다고 소감(?)을 말해보며... 글을 마무리해보겠습니다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내 입맛대로 toast 만들고 react-query랑 같이 써보기]]></title>
            <link>https://velog.io/@doeunnkimm_/%EB%82%B4-%EC%9E%85%EB%A7%9B%EB%8C%80%EB%A1%9C-toast-%EB%A7%8C%EB%93%A4%EA%B3%A0-react-query%EB%9E%91-%EA%B0%99%EC%9D%B4-%EC%8D%A8%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@doeunnkimm_/%EB%82%B4-%EC%9E%85%EB%A7%9B%EB%8C%80%EB%A1%9C-toast-%EB%A7%8C%EB%93%A4%EA%B3%A0-react-query%EB%9E%91-%EA%B0%99%EC%9D%B4-%EC%8D%A8%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 14 Jan 2024 06:09:50 GMT</pubDate>
            <description><![CDATA[<p>이번 글에서는 제가 Toast 컴포넌트를 만들기도, 사용하기 편하게 로직을 만들기도, 문서화를 하기도, react-query에서 성공 시 띄우기도 한 과정들을 글로 남겨 보려고 합니다.</p>
<p>아래는 최종적으로 만든 Toast 컴포넌트에요!</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/06bad696-e57d-4770-9363-871e2333455c/image.gif" alt=""></p>
<p>좋은 레퍼런스를 찾았어서 처음부터 대단한 고민을 한 것은 아니지만, 그 과정 속에서 제 입맛대로(?) 바꾸고 발견한 것들이 있었어요! 그것들을 함께 이야기 해보려고 합니다.</p>
<blockquote>
<p>일단 선 감사합니다 부터...
→ <a href="https://www.halang.tech/making-toast">참고한 레퍼런스</a></p>
</blockquote>
<h1 id="😉-만들어봅시다">😉 만들어봅시다</h1>
<p>제가 지금 사용하고 있는 기술 스택은 다음과 같습니다.</p>
<ul>
<li>Next.js (<code>v14</code>)</li>
<li>typescript</li>
<li>tailwind css</li>
<li>jotai</li>
<li>react-query (<code>v5</code>)</li>
</ul>
<h2 id="1-전역-상태로-toast들-관리">1. 전역 상태로 toast들 관리</h2>
<p>보통 toast는 여러 개가 나오면서 시간차로 슥슥슥슥 하나씩 없어지는 거 많이 보셨을 거에요.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/183f8acc-d7ce-4609-99d0-f8b32057ba83/image.gif" alt=""></p>
<p>즉, toast를 배열을 통해 여러 개를 관리한다는 말을 의미하는데요! 그래서 toast들을 담을 수 있는 배열을 전역 상태로 만들어 주었어요.</p>
<pre><code class="language-tsx">// Toast.atom.ts
export const toastsAtom = atom&lt;ToastsProps&gt;([]);</code></pre>
<h2 id="2-toasts에-toast를-쉽게-추가하도록">2. toasts에 toast를 쉽게 추가하도록</h2>
<p>jotai는 기존에 선언되어 있는 atom을 가지고 write하거나 read할 수 있는 atom을 만들 수 있어요. 저는 이를 이용해서 <code>toastsAtom</code>에 편하게 toast를 추가할 수 있는 write용 atom을 만들었어요.</p>
<pre><code class="language-tsx">// Toast.atom.ts
export const toastAtom = atom(null, (get, set, type: ToastProps[&#39;type&#39;]) =&gt; (title: string) =&gt; () =&gt; {
  const prev = get(toastsAtom);
  const newToast = { type, title, id: Date.now().toString() };
  set(toastsAtom, [...prev, newToast]);
});</code></pre>
<p>*<em>🚨 주의1. *</em>
저희 팀에서 toast를 사용할 때 필요한 정보는 2개 뿐이였어요.</p>
<ul>
<li>toast의 종류 (<code>success / warning</code>)</li>
<li>toast에 들어 갈 텍스트</li>
</ul>
<p>위 코드 중에서 이 부분을 주목해주세요!</p>
<pre><code class="language-tsx">(get, set, type: ToastProps[&#39;type&#39;]) =&gt; (title: string) =&gt; () =&gt; { ... }</code></pre>
<p>즉, 해당 atom을 write할 때 <code>type</code>을 넘겨주면 <code>(title) =&gt; () =&gt; {}</code>를 반환하도록 되어 있습니다. 즉 위 코드를 사용할 때는 아래와 같이 사용한다는 것을 의미합니다.</p>
<pre><code class="language-tsx">const addToast = useSetAtom(toastAtom)
...
addToast(type)(title)()</code></pre>
<p><a href="https://ko.javascript.info/currying-partials">커링</a>형태로 사용 시 깔끔하게 호출할 수 있도록 구성했습니다.</p>
<p><code>🤔 엥 뭐가 깔끔하다는 거냐....</code> 할 수 있지만 다 계획 된.. 아무튼 글을 계속 읽어봐주세요 😉</p>
<p>여기까지 하면 이제 <strong><code>어디서든 toast를 추가</code></strong>할 수 있습니다.</p>
<p><strong>🚨 주의2.</strong></p>
<pre><code class="language-tsx">const newToast = { type, title, id: Date.now().toString() };</code></pre>
<p>toast가 필요한 정보 중에 id가 필요한 이유는 고유한 값으로 그걸 가지고 해당 toast를 지우기 위해 필요해요. </p>
<p>처음에는 사실 <code>id: prev.length</code>로 했었는데, 렌더링되고 시간차로 없어지면서 length가 줄어들었다 늘었다 하는 과정 속에서 map 돌릴 때 key값이 중복되어 warning이 뜨더라구요. 그래서 중복될 일이 없는 <code>Date.now()</code>를 이용하여 toast에게 id를 부여해 주었습니다.</p>
<h2 id="3-사라질-타이밍을-아는-toast-컴포넌트-만들기">3. 사라질 타이밍을 아는 Toast 컴포넌트 만들기</h2>
<p>그 다음으로 Toast 컴포넌트를 만들었어요. 그런데, 위 gif를 보면 알 수 있듯이 각 toast가 독립적으로 일정 시간이 지나면 슥하고 사라지고 있어요.</p>
<p>이 말은 <strong>전역에서 관리하고 있는 toasts 배열에서 지워졌음</strong>을 의미합니다.</p>
<h3 id="3-1-removeatom-만들기">3-1. removeAtom 만들기</h3>
<p>아까 추가를 위한 atom과 비슷한 맥락으로 삭제하는 atom을 만들었어요.</p>
<pre><code class="language-tsx">// Toast.atom.ts
...
export const removeToastAtom = atom(null, (get, set, id: string) =&gt; {
  const prev = get(toastsAtom);
  set(
    toastsAtom,
    prev.filter((toast) =&gt; toast.id !== id),
  );
});</code></pre>
<p>삭제하기를 원하는 id를 받아서 이 id를 가지고 toasts 배열에서 없애줍니다.</p>
<h3 id="3-2-일정-시간을-가지고-opacity-변화와-remove하기">3-2. 일정 시간을 가지고 opacity 변화와 remove하기</h3>
<p>Toast 컴포넌트는 각자 시간만큼 보여주기를 완수하면 사라집니다. 즉, 이 timer는 Toast 컴포넌트 내에서 관리한다는 말을 의미합니다.</p>
<p>그래서 이 일정 시간이라는 주기를 <code>useEffect</code>를 통해 작성하면 아래와 같습니다.</p>
<pre><code class="language-tsx">// Toast.tsx
const TOAST_DURATION = 3000;
const ANIMATION_DURATION = 350;

export const Toast = ({ id, title, type = &#39;success&#39; }: ToastProps) =&gt; {
  const [opacity, setOpacity] = useState(&#39;opacity-[0.2]&#39;);
  const removeToastItem = useSetAtom(removeToastAtom);

  useEffect(() =&gt; {
    setOpacity(&#39;opacity-[0.8]&#39;);
    const timeoutForRemove = setTimeout(() =&gt; {
      removeToastItem(id);
    }, TOAST_DURATION);

    const timeoutForVisible = setTimeout(() =&gt; {
      setOpacity(&#39;opacity-0&#39;);
    }, TOAST_DURATION - ANIMATION_DURATION);

    return () =&gt; {
      clearTimeout(timeoutForRemove);
      clearTimeout(timeoutForVisible);
    };
  }, [id, removeToastItem]);

  return (
    &lt;div
      className={`w-fit flex gap-5xs justify-center items-center px-3xs py-5xs bg-gray-60 rounded-[12px] mb-5xs transition-all duration-350 ease-in-out ${opacity}`}
    &gt;
      ...
    &lt;/div&gt;
  );
};
</code></pre>
<p>위와 같이 opacity와 remove를 해주게 되면 스타일에 적용되어 있는 <code>transition</code> 덕분에 <code>스르륵</code> 보였다가 <code>스르륵</code> 없어지는 것처럼 보이게 됩니다. <del>스르륵 이거 무슨 느낌인지 다들 아시죠?</del></p>
<h2 id="4-toastprovider-만들기">4. ToastProvider 만들기</h2>
<p>여기까지 우리가 한 작업들은 다음과 같아요.</p>
<ul>
<li>전역으로 관리하는 toast 배열</li>
<li>전역에서 toast 배열에 고유한 id와 함께 toast를 추가하는 메서드</li>
<li>사라질 타이밍을 아는 Toast 컴포넌트</li>
</ul>
<p>만들 건 다 만들었는데 렌더링하는 코드만 없죠! <code>map</code>을 사용해서 toast를 렌더링할 Provider를 만들어봅시다.</p>
<p><strong>🤔 왜 Provider 일까?</strong></p>
<p>Toast는 레이아웃과 상관없이 어디서든 뜰 수 있게 하고 싶었었죠? Toast 메시지를 띄우려는 컴포넌트들이 직접 메시지를 관리하지 않고, ToastProvider를 통해 메시지를 렌더링할 수 있도록 합니다.</p>
<pre><code class="language-tsx">// ToastProvider.tsx
import { Portal } from &#39;@radix-ui/react-portal&#39;;
import { useAtomValue } from &#39;jotai&#39;;

import { Toast } from &#39;./Toast&#39;;
import { toastsAtom } from &#39;./Toast.atom&#39;;

export const ToastProvider = () =&gt; {
  const toasts = useAtomValue(toastsAtom);

  return (
    &lt;Portal&gt;
      &lt;div className={`fixed bottom-[82px] left-1/2 transform translate-x-[-50%]`}&gt;
        {toasts.map((toast) =&gt; (
          &lt;Toast key={toast.id} {...toast} /&gt;
        ))}
      &lt;/div&gt;
    &lt;/Portal&gt;
  );
};</code></pre>
<p><code>toastProvider</code>에서는 전역 상태로 관리 중인 toast 배열을 가져와서 map 돌려주는 역할을 하고 있어요. 이때 오버레이 형태로 렌더링되어야 하기 때문에 현재 팀에서 <code>headless UI</code>가 필요할 때 사용하기로 했던 <code>radix-ui</code>의 <code>Portal</code> 컴포넌트를 사용했어요.</p>
<p>덕분에 <code>z-index</code>를 신경쓰지 않을 수 있었습니다.</p>
<h2 id="5-usetoast로-간편하게-추가할-수-있도록">5. useToast로 간편하게 추가할 수 있도록</h2>
<p>아까 toast 배열에 toast를 추가할 수 있는 atom을 만들었던 거 기억하시나요?</p>
<pre><code class="language-tsx">addToast(type)(title)()</code></pre>
<p>이런 코드와 함께 소개했던 것요..!</p>
<p>이제 이걸 좀 더 이쁘게(?) 사용할 수 있도록 하는 커스텀 훅을 만들어 보려고 합니다.</p>
<pre><code class="language-tsx">// useToast.ts
import { useSetAtom } from &#39;jotai&#39;;

import { toastAtom } from &#39;@/components/atoms/toast/Toast.atom&#39;;

export const useToast = (option?: ToastOptionProps) =&gt; {
  const addToast = useSetAtom(toastAtom);

  return {
    success: addToast(&#39;success&#39;),
    warning: addToast(&#39;warning&#39;),
  };
};</code></pre>
<p>위 코드를 살펴보면 훅 함수로 <strong><code>2가지의 메서드를 리턴</code></strong>하고 있어요. 한 가지를 가지고 이야기해보면, <code>addToast(&#39;success&#39;)</code>라고 하면 success라는 프로퍼티에는 <code>(title: string) =&gt; () =&gt; {}</code>라는 메서드를 가지게 되는 것과 같아요. 다시 말해서, title을 매개변수를 받아서 <code>VoidFunction</code>을 리턴하는 함수를 갖게 되는 것이죠!</p>
<h2 id="6-선택-option-받기">6. (선택) option 받기</h2>
<p>현재는 toast의 color나 position이 고정되어 있어요. 별도의 옵션 없이 이들의 값을 하드하게 넣어줘도 되지만, 저희 팀에서는 option을 받을 수 있는 전역 상태를 통해 좀 더 나중에 유연할 수 있도록 해주었어요.</p>
<p>우선 UI에서는 스타일 면이나 위치가 항상 동일해서 전달 받은 점은 없었지만, 우선 위치에 대한 옵션을 만들어 두었어요.</p>
<pre><code class="language-tsx">// Toast.atom.ts
export const toastOptionAtom = atom&lt;ToastOptionProps&gt;({
  position: &#39;bottom-[84px]&#39;,
});</code></pre>
<p>default로 위치를 명시해주었어요. </p>
<pre><code class="language-tsx">// Toast.atom.ts
...
export const toastOptionChangeAtom = atom(null, (get, set, changeOption: ToastOptionProps) =&gt; {
  const prev = get(toastOptionAtom);
  const updatedOption = { ...prev, ...changeOption };
  set(toastOptionAtom, updatedOption);
});</code></pre>
<p>이 write용 atom을 통해서는 default 옵션은 default대로 적용을 하고, 더 필요한 옵션을 넘겨주면 <strong><code>default + 필요한 옵션</code></strong>이 set될 수 있도록 만들어주었어요.</p>
<p>이를 적용해보면 아래와 같아요.</p>
<pre><code class="language-tsx">// ToastProvider.tsx
export const ToastProvider = () =&gt; {
  ...
  const { position } = useAtomValue(toastOptionAtom);

  return (
    &lt;Portal&gt;
      &lt;div className={`fixed ${position} ...`}&gt;
        ...
      &lt;/div&gt;
    &lt;/Portal&gt;
  );
};</code></pre>
<p>필요한 부분에 맞게 불러와서 사용하면 됩니다.</p>
<p>옵션을 넘겨주고 싶을 때는 커스텀훅을 통해 넘길 수 있도록 <code>useToast</code>도 수정해주었습니다.</p>
<pre><code class="language-tsx">// useToast.ts
export const useToast = (option?: ToastOptionProps) =&gt; {
  ...
  const setOption = useSetAtom(toastOptionChangeAtom);

  useEffect(() =&gt; {
    if (option) setOption(option);
  }, [option, setOption]);

  ...
};</code></pre>
<h1 id="🥳-사용해봅시다">🥳 사용해봅시다</h1>
<p>만든 과정을 통해 우리가 사용할 때 건드릴 건 다음과 같아요.</p>
<ul>
<li><code>ToastProvider</code></li>
<li><code>useToast</code></li>
</ul>
<p>atom들도 빠지고 Toast 컴포넌트가 빠져있어요! <code>useToast</code>를 통해 추가를 하면 <code>ToastProvider</code>에서 toast의 렌더링을 제어하게 되는 거죠</p>
<h2 id="1-최상단에-toastprovider-렌더링해주기">1. 최상단에 ToastProvider 렌더링해주기</h2>
<p>ToastProvider만 한번 렌더링해주면 이 친구가 toast들의 렌더링을 제어해줄 거에요!</p>
<p>저희 팀 같은 경우에는 provider들을 모아두는 파일이 존재해서 여기에 포함했어요. 일반적인 경우라면 <code>layout.tsx</code>에 해주시면 돼요!</p>
<pre><code class="language-tsx">// providers.tsx
&#39;use client&#39;;

const Providers = ({ children }: PropsWithChildren) =&gt; {

  return (
    ...
      &lt;ToastProvider /&gt;
      {children}
    ...
  );
};

export default Providers;
</code></pre>
<h2 id="2-usetoast로-바로-사용하기">2. useToast로 바로 사용하기</h2>
<pre><code class="language-tsx">const toast = useToast()
...
return (
  &lt;button onClick={toast.success(&#39;토스트가 열렸어요.&#39;)}&gt;이거 누르면 토스트&lt;/button&gt;
)</code></pre>
<p>지금 <code>toast.success(&#39;토스트가 열렸어요.&#39;)</code>는 <code>VoidFunction</code> 타입이에요. 다시 돌아볼게요.</p>
<pre><code class="language-tsx">(type: TypeProps) =&gt; (title: string) =&gt; () =&gt; {}</code></pre>
<p>우리는 위와 같은 함수를 통해 여기까지 온 것인데요! 만약</p>
<pre><code class="language-tsx">(type: TypeProps) =&gt; (title: string) =&gt; {}</code></pre>
<p>이렇게 해도 전혀 문제가 없어요! 다만, 위와 같이 될 경우 사용할 때 코드는 아래와 같이 되게 되어요.</p>
<pre><code class="language-tsx">&lt;button onClick={() =&gt; toast.success(&#39;토스트가 열렸어요.&#39;)}&gt;이거 누르면 토스트&lt;/button&gt;</code></pre>
<p><code>onClick</code>은 항상 <code>VoidFunction</code>을 받기 때문에 우리는 항상 저런 식으로 <code>() =&gt; ...</code>이라는 코드를 통해 <code>VoidFunction</code>을 만들어서 넘겨주었던 것인데요.</p>
<p>저는 이러한 특별한 의미없는 코드를 줄이고자, 설계 시 최종적으로 <code>VoidFunction</code>을 리턴할 수 있도록 커링 형태를 가져간 것이였어요.</p>
<p><strong>🚨 굳이라는 생각이 드신다면 안 해도 전혀 상관 없습니다! 🚨</strong></p>
<h1 id="🤩-요청이-성공하면-toast-띄우기">🤩 요청이 성공하면 toast 띄우기</h1>
<p>저희 팀은 <code>react-query</code>를 사용 중이에요. <code>react-query</code>의 장점 중 하나는 비동기 처리에 대한 성공과 실패에 대한 처리를 명시적으로 처리할 수 있다는 점이라고 생각해요!</p>
<p>이를 이용해서 <code>useMutation</code>의 onSuccess를 통해 <strong><code>&quot;성공하면 toast 띄워!&quot;</code></strong>를 작성해주었어요.</p>
<pre><code class="language-tsx">export const useDeleteTask = () =&gt; {
  ...
  const toast = useToast();

  return useMutation({
    ...
    onSuccess: toast.success(&#39;세부 목표를 삭제했어요.&#39;),
    ...
  });
};</code></pre>
<p>그러면 요청이 성공했을 경우 toast가 띄워지게 됩니다. 아래와 같이요!</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/1751af5c-0155-4d4d-a293-e53bf18631d8/image.gif" alt=""></p>
<p>여기까지 하면 끝입니다!</p>
<h2 id="내-코드에-내가-발목-잡혀-수정까지">내 코드에 내가 발목 잡혀 수정까지</h2>
<p>추가로.. <strong>🤔 이 toast를 띄우는 과정에서 제가 한 바보 같은 짓이 있었어요.</strong></p>
<pre><code class="language-tsx">export const useDeleteTask = () =&gt; {
  ...
  const toast = useToast();

  return useMutation({
    ...
    onSuccess: () =&gt; {
        toast.success(&#39;세부 목표를 삭제했어요.&#39;)
    },
    ...
  });
};</code></pre>
<p>위와 같이 작성하면 토스트가 죽어도(?) 안 뜨더라구요. 처음에 이렇게 해놓고 왜 안 될까 계속 고민하다가 제가 작성한 로직들을 돌아보면서 깨닫게 되었어요.</p>
<p>지금 <code>toast.success(&#39;세부 목표를 삭제했어요.&#39;)</code>는 다음과 같은 값이에요.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/923460b0-6015-493b-bd51-a1aa01d8a95b/image.png" width="300"/></p>

<p><code>&quot;🫤 이게 왜&quot;</code> 하실 수 있지만 잘 보시면 함수를 호출하고 있지 않아요. 그냥 함수 자체를 불러온 거죠 저는.....</p>
<p>그래서 만약에 <code>onSuccess: () =&gt; { ... }</code> 형태를 유지하면서 toast를 띄우고 싶다면 아래와 같이 해야 하는 것입니다.</p>
<pre><code class="language-tsx">export const useDeleteTask = () =&gt; {
  ...
  const toast = useToast();

  return useMutation({
    ...
    onSuccess: () =&gt; {
        toast.success(&#39;세부 목표를 삭제했어요.&#39;)()
    },
    ...
  });
};</code></pre>
<p>이건 제가 설계했던 커링에 의해 발목을 잡힌 것 같았어요. <code>(type) =&gt; (title) =&gt; {}</code>로 했더라면 위와 같은 일이 없었을 겁니다.</p>
<p>이로인해 커링으로 인한 장단점이 생겨서, 더 보편적인 상황을 생각해보았어요. 보통 <strong>toast만 띄우는 상황</strong>이 많다면 최종적으로 <code>VoidFunction</code>을 리턴하는 커링을 유지했을 거에요.</p>
<p>고민 끝에 toast만 띄우는 상황 보다는 어떤 상태 처리나 부가적인 액션을 처리를 한 후에 toast를 띄워줄 일이 많다고 판단하여 최종적으로 이전에 작성했던 커링을 조금 수정했어요.</p>
<pre><code class="language-tsx">export const toastAtom = atom(
  (get) =&gt; get(toastsAtom),
  (get, set, type: ToastProps[&#39;type&#39;]) =&gt; (title: string) =&gt; {
    const prev = get(toastsAtom);
    const newToast = { type, title, id: Date.now().toString() };
    set(toastsAtom, [...prev, newToast]);
  },
);</code></pre>
<p>이렇게 해주면 사용할 때는 아래와 같을 거에요.</p>
<pre><code class="language-tsx">const toast = useToast()

const handleToastButton = () =&gt; {
    toast.success(&#39;세부 목표를 수정했어요.&#39;)
}

return (
    &lt;button onClick={handleToastButton}&gt;누르면 토스트가 열려요&lt;/button&gt;
)</code></pre>
<p>그러면 useMutation에서도 다음과 같은 형태가 될 겁니다.</p>
<pre><code class="language-tsx">export const useDeleteTask = () =&gt; {
  ...
  const toast = useToast();

  return useMutation({
    ...
    onSuccess: () =&gt; {
        toast.success(&#39;세부 목표를 삭제했어요.&#39;)
    },
    ...
  });
};</code></pre>
<p>좀 더 일반적인 상황에서 자연스럽게 처리될 수 있었습니다. 다른 개발자가 사용하더라도 지금 작성한 코드의 상황이 더 자연스러울 거라고 생각했습니다.</p>
<hr>
<p align="center"><img src="https://cdn.mediaus.co.kr/news/photo/201212/30684_64827_5625.jpg" /></p>

<p>이번 글에서는 제가 제 코드에 발목이 잡혀 반나절 정도를 고생한 과정들을 적어보았어요. 저는 자주 쓰일 컴포넌트를 만들 때 미래(<code>=사용할 때</code>)를 굉장히 고려하는 습관이 있어요. 이번에는 그 미래를 너무 많이 고려하다보니 일반적인 경우에 대해 지나쳐 버린 것 같았습니다. </p>
<p>또, 코드를 거슬러 올라가면서 문제점을 찾고 이를 개선하는 과정의 의미를 배운 것 같아요. 이러한 과정들을 돌아보기 위함으로 글을 작성했음을 알려드리고 글을 마무리해보려고 합니다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Storybook 편~하게 열어보기 - 배포와 CI/CD 연결 ]]></title>
            <link>https://velog.io/@doeunnkimm_/Storybook-%ED%8E%B8%ED%95%98%EA%B2%8C-%EC%97%B4%EC%96%B4%EB%B3%B4%EA%B8%B0-%EB%B0%B0%ED%8F%AC%EC%99%80-CICD-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@doeunnkimm_/Storybook-%ED%8E%B8%ED%95%98%EA%B2%8C-%EC%97%B4%EC%96%B4%EB%B3%B4%EA%B8%B0-%EB%B0%B0%ED%8F%AC%EC%99%80-CICD-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Sun, 03 Dec 2023 06:16:32 GMT</pubDate>
            <description><![CDATA[<h1 id="storybook을-좋아할수록-솔직히-번거롭긴-했다">Storybook을 좋아할수록 솔직히 번거롭긴 했다</h1>
<p>저는 평소에 Storybook을 자주 사용하기도 하는 편이에요. 컴포넌트를 개발하고 다른 개발자에게 사용법이나 props의 종류 등에 대해서도 문서화하기 좋잖아요!</p>
<p>그런데 개발을 하는 중에 컴포넌트의 형태가 궁금하거나, PR에 스토리 파일이 있거나 할 때는 <strong>확인</strong>을 해야합니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/e8c9c25f-01dd-4b25-8263-ac1f7b211561/image.png" alt=""></p>
<p>이를 위해서는 <strong><code>터미널 하나 새로 열고~ 명령어 치고~ 실행 되는 거 기다리고~ ,,,</code></strong> 해야 합니다. </p>
<p>자주 사용하면 할수록 이 과정을 반복해서 사용해야 했습니다.</p>
<h1 id="배포해서-url로-접근할-수-있어요">배포해서 URL로 접근할 수 있어요</h1>
<p>Storybook을 배포해서 사용할 수 있다는 것을 알게 되었고, 저는 정말 <strong><code>너~무</code></strong> 편할 것 같다는 생각에 팀원분들께 제안을 드렸고 제가 세팅을 맡게 되었습니다.</p>
<p>이번 글에서는 이 과정을 자세하게 정리해보려고 합니다 👏</p>
<h1 id="🚀-chromatic으로-배포하기">🚀 Chromatic으로 배포하기</h1>
<blockquote>
<p>Chromatic은 Storybook을 위한 클라우드 서비스</p>
</blockquote>
<p><a href="https://www.chromatic.com/"><code>Chromatic</code></a>을 사용하면 Storybook을 통해 만든 컴포넌트 라이브러리를 쉽게 관리하고, 배포할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/5e85c53c-6d77-4617-8ba0-3e662049b30c/image.png" alt=""></p>
<p>이를 이용해서 Storybook을 배포해보려고 합니다.</p>
<h2 id="1-프로젝트에-chromatic-설치">1. 프로젝트에 chromatic 설치</h2>
<pre><code class="language-bash">$ yarn add -D chromatic</code></pre>
<h2 id="2-chromatic-연동">2. Chromatic 연동</h2>
<p><a href="https://www.chromatic.com/">Chromatic</a>에서 Github를 연동하여 쉽게 프로젝트를 선택할 수 있습니다.</p>
<h2 id="3-배포">3. 배포</h2>
<pre><code class="language-bash">$ npx chromatic --project-token=&lt;your-project-token&gt;</code></pre>
<p>연동을 완료하면 아래와 같은 화면을 볼 수 있는데요! 제가 아래 이미지에서 가린 부분은 <code>token</code> 부분으로 직접 하실 때는 모든 부분을 복사하셔서 터미널에서 실행 해주면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/d120abdf-4e90-4521-9d45-76a5ff91e6be/image.png" alt=""></p>
<p>여기까지 완료를 하시면 배포하기까지는 끝입니다! 
터미널에 뜨는 URL을 통해 이동하면 Storybook이 잘 배포되었음을 확인할 수 있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/94078517-e1d7-4ba2-81f0-1333342ee93f/image.png" alt=""></p>
<h1 id="⛑️-github-actions로-cicd">⛑️ Github Actions로 CI/CD</h1>
<p><strong>이건 또 왜......뭔데 🤷</strong></p>
<blockquote>
<p>저도 많이 찾아보면서 다들 <code>Github actions</code>까지 하길래 <strong><code>아 나 그런 거 모른다구요..</code></strong> 였어서 일단 이게 왜 필요한지부터 느껴야 했습니다.</p>
</blockquote>
<p><strong>새로 stories 파일 추가될 때마다는 어쩌실런지? 😈</strong></p>
<blockquote>
<p>저희는 stoires 파일을 볼 수 있는 스토리북을 배포해서 봤었죠? 그러면 당연히 새로운 스토리 파일이 추가되면 <strong><code>다시 빌드가 되어야 하고 다시 배포가 되어야</code></strong> 할 겁니다.
<br/> → 이를 <code>Github actions</code>를 통해 <strong>자동화</strong>하는 겁니다 ! </p>
</blockquote>
<p><strong>이런 점에서 좋아요</strong></p>
<blockquote>
<p><strong><code>⭐️ 추가된 스토리 파일을 자동으로 통합해주고 배포</code></strong>
<br/>이를 통해 <code>Pull Request</code> 시 올라온 <strong>스토리를 바로 확인</strong>할 수 있도록 하는 것 가능합니다.</p>
</blockquote>
<p><strong>CI/CD 해봅시다</strong></p>
<blockquote>
<p>참고로 저는 <code>Github Actions</code>를 처음 만져보았습니다 😅</p>
</blockquote>
<h2 id="1-chromatic-token-생성">1. Chromatic token 생성</h2>
<p>프로젝트에서 <code>settings &gt; Secrets and variables &gt; actions</code>에서 <code>New repository secret</code>를 선택합니다.</p>
<p>아래 이미지를 참고하여 내용을 입력하고 add 해줍니다. 참고로 name은 꼭 <code>CHROMATIC_PROJECT_TOKEN</code>일 필요는 없습니다! 기억하고만 있을 수 있다면 뭐든 상관없습니다!</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/a950bbee-eddb-487d-96ad-b301f5e84bc5/image.png" alt=""></p>
<p>마지막 이미지처럼 잘 생성된 것이 확인되었다면 완료입니다.</p>
<h2 id="2-workflow-생성">2. workflow 생성</h2>
<p>Github Actions에서 <code>workflow</code>라는 개념은 <strong>여러 Job으로 구성되고, Event에 의해 트리거될 수 있는 자동화된 프로세스</strong>를 의미합니다.</p>
<p>여기에서 <code>on</code>이라는 키를 통해 workflow가 언제 실행될지를 설정해줄 수 있습니다.</p>
<p>저는 아래와 같은 상황에서 프로세스가 자동화 될 수 있도록 해주었어요.</p>
<ul>
<li>main branch에 <code>Pull Request</code> 되었을 때</li>
<li>main branch에 <code>push</code>가 되었을 때</li>
</ul>
<p>그리고 <code>Pull Request</code> 시 <strong>배포된 주소를 comment로 바로 확인</strong>할 수 있도록 action을 추가했어요.</p>
<p>결론적으로 아래와 같은 코드로 YAML 파일을 작성해주었습니다.</p>
<p>📃 .github/workflow/storybook.yml</p>
<pre><code class="language-yml"># Workflow name
name: &#39;Chromatic Deployment&#39;

on:
  pull_request:
    branches: [main]
    paths:
      - &#39;**.stories.tsx&#39;
  push:
    branches: [main]
    paths:
      - &#39;**.stories.tsx&#39;

jobs:
  chromatic-deployment:
    runs-on: ubuntu-latest
    steps:
      - name: actions/checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Set node linker to nodeModules
        run: yarn config set nodeLinker &quot;node-modules&quot;

      - name: Install dependencies
        run: yarn install --immutable --check-cache

      - name: Publish to Chromatic
        id: publish_chromatic
        uses: chromaui/action@v1
        with:
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN  }}
          buildScriptName: build-storybook
          onlyChanged: true

      - name: comment PR
        uses: thollander/actions-comment-pull-request@v1
        if: ${{ github.event_name == &#39;pull_request&#39; }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          message: &#39;🚀 **storybook**: ${{ steps.publish_chromatic.outputs.storybookUrl }}&#39;
</code></pre>
<p><strong>🚨🚨🚨 잘 맞춰주어야 하는 부분</strong></p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/dd612a68-6621-4d37-b75e-c9ee58170be4/image.png" alt=""></p>
<p>이 부분인데요! <code>Publish to Chromatic</code>의 id가 <code>comment PR</code>의 아래 부분과 일치해야 합니다. 그래야만 output을 제대로 찾아서 comment에 잘 출력될 수 있습니다.</p>
<p>일치하지 않다면 배포된 주소를 찾질 못해 아무 것도 출력이 안 되더라구요.</p>
<p>또, <code>buildScriptName</code>도 잘 맞춰주어야 합니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/485b60ea-a271-4fa0-a395-e6eebeb505a5/image.png" alt="">
아래는 package.json의 script 명령어 부분입니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/85ec9f8c-c0b5-4b41-9cbe-f6adeca31af5/image.png" width="400" height="400"/></p>

<p>일치하지 않다면 빌드에 실패합니다. <del>(당연한 소리 왈왈..</del></p>
<h3 id="pnp-모드-사용-중이라면">PnP 모드 사용 중이라면</h3>
<p>PnP 모드는 zero-install로 node_modules 파일이 없습니다. 그래서 위에 있는 workflow 코드에서 아래 코드에 해당하는 부분을 제거해줘야 합니다. 불필요하기 때문이에요!</p>
<pre><code class="language-yml">- name: Set node linker to nodeModules
    run: yarn config set nodeLinker &quot;node-modules&quot;</code></pre>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/9865a13e-3832-442e-ae9d-1a2a8f046a81/image.png" alt=""></p>
<p>제거했더니 <strong><code>20초 정도</code></strong> 속도가 빨라진 것을 확인할 수 있었습니다.</p>
<h1 id="성공한-당신">성공한 당신..?</h1>
<p>여기까지 하게 되면 Storybook 배포 및 CI/CD 설정이 끝이 납니다. <strong>한번 스토리 파일을 추가하고 Pull Request를 보내게 되면</strong> 아래와 같이 잘 되는 것을 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/bda2835c-cd55-4ee2-ba72-d40ebcbb4cac/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/1636df38-43c8-4b58-8586-c222a7cdc3de/image.png" alt=""></p>
<p>덕분에 다른 개발자가 새로 스토리 파일을 추가하여 올린 PR도 배포된 스토리북을 통해 빠르게 확인이 가능하다는 점에서 정-말 편하다고 느꼈습니다 ,, 🥳</p>
<p align="center"><img src="https://mblogthumb-phinf.pstatic.net/MjAyMTAxMDFfMTMx/MDAxNjA5NDg1MzU5NTg0.qUHUiq_1KSV0ZNKhCrRWMxbkBrIa_REGcXvom1yIb-cg.GNIBhyoskKiiwSXeC2KiE3j2wz7tYzoATXrjnBBfD1Ag.JPEG.kiwigkstjd/SE-07a44465-efb8-4e79-a883-a2d5efd6afa9.jpg?type=w800" /></p>



<h2 id="😓-ui-tests-생각보다-번거롭다">😓 UI Tests 생각보다 번거롭다</h2>
<p>세팅을 끝내고 PR이 올라오면 UI Tests 부분이 계속 pending 상태가 되는데요.</p>
<p>이 부분은 해당 프로젝트 member가 chromatic에 접속해서 accept를 해주게 되면 바로 access되는 부분입니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/ca0906ec-fb61-4d72-ab41-2dafa65b5ea9/image.png" alt=""></p>
<p>이렇게 chromatic에서 accepted가 되면 아래와 같이 성공하게 됩니다.
<img src="https://velog.velcdn.com/images/doeunnkimm_/post/bd166fc5-385f-43ea-86e2-3065e3d1b05c/image.png" alt=""></p>
<p>그런데 이 과정을 어떻게 보면 더 엄(?)하게 팀원끼리 컴포넌트를 컨펌할 수 있는 것이라고 생각이 들지만, 저희 팀에서는 이 과정이 조금 번거롭다는 의견이 많았습니다.</p>
<p>또, PR을 통해 merge가 되기 전에 컨펌을 하는 것까지는 나쁘지 않았는데, main에 merge가 되면 한번더 빌드가 되기 때문에 이 과정에서 다시 또 컨펌이 필요했어요.</p>
<p>그래서 저희 팀에서는 이 <code>accept</code>하는 과정을 아예 제외하기로 했습니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/9ce95295-e9a1-401c-8d93-1557aff59c81/image.png" width="400" height="200"/></p>

<p><code>chromatic &gt; manage &gt; Tests</code> 부분에서 위 부분을 disable 해주었습니다.</p>
<hr>
<p align="center"><img src="https://mblogthumb-phinf.pstatic.net/MjAyMTEyMTJfMTg5/MDAxNjM5MjcyOTk0ODcw.sI9LvEL-Tn8VGJB0VY7bISFhGR0tbgi1_Xkg9XHm3lIg.iFN4nKlyL51I1Pdtf1CdBE-j8p2S4uOZZpFJYCl-i3wg.JPEG.funnycry/IMG_4900.JPG?type=w800" /></p>

<p>이번 글에서는 지난 저의 불편함을 해소해 가는 과정들을 정리해보았습니다. 항상 환경설정하는 과정은 조심스럽기도 하지만 막상 잘 끝내면 참 뿌듯한 것 같아요. 앞으로 유용하게 우리 프로젝트를 도와줄 자동화들에게 무한 감사인사...</p>
<p>저는 요즘 <strong><code>자동화</code></strong> 라는 것에 관심이 많아요. 최근에 CI/CD에 대해 공부했었어요! 개발하면서 사소하게 느꼈던 <strong><code>불편함 / 번거로움 / 귀찮음</code></strong> 을 자동화를 통해 해소할 수 있다는 게 너무 재밌더라구요 ? 아직 자동화에 대해 손가락만 담궈본 거겠지만 앞으로도 재밌는 자동화를 많이 겪어보고 싶다는 생각이 듭니다 😎</p>
<h1 id="references">References</h1>
<ul>
<li><a href="https://min-kyung.tistory.com/160#--%---github%-Fworkflows%-Fstorybook-yml%--%EC%--%-D%EC%--%B-">Chromatic으로 Storybook 지속적 배포하기 (Github Actions)
</a></li>
<li><a href="https://velog.io/@93minki/Storybook-Chromatic-%EC%9C%BC%EB%A1%9C-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0">Storybook Chromatic 으로 배포하기</a></li>
<li><a href="https://zzsza.github.io/development/2020/06/06/github-action/">Github Action 사용법 정리</a></li>
<li><a href="https://www.daleseo.com/github-actions-first-workflow/">GitHub Actions 첫 워크플로우 생성해보기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Barrel 파일 왜 쓰시나요 ?]]></title>
            <link>https://velog.io/@doeunnkimm_/Barrel-%ED%8C%8C%EC%9D%BC-%EC%99%9C-%EC%93%B0%EC%84%B8%EC%9A%94</link>
            <guid>https://velog.io/@doeunnkimm_/Barrel-%ED%8C%8C%EC%9D%BC-%EC%99%9C-%EC%93%B0%EC%84%B8%EC%9A%94</guid>
            <pubDate>Fri, 27 Oct 2023 01:47:21 GMT</pubDate>
            <description><![CDATA[<h1 id="😇-barrel-파일을-좋아하게-된-사연">😇 Barrel 파일을 좋아하게 된 사연(?)</h1>
<p>저도 베럴파일이라는 것을 알게된 건 몇 달도 되지 않았어요! 그냥 무작정 깃허브를 떠돌아다니며 제 기준 잘 되어 있는 프로젝트를 구경하는 걸 좋아해서 보다보니 아래와 같은 파일 구조가 있었습니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/76141af9-3a50-4823-89b4-ec384dd60483/image.png" width="300" height="300"/></p>



<p>잘 보시면 폴더마다 <code>index.ts</code> 파일이 존재하고, 그 폴더 바로 위에 또 <code>index.ts</code>파일이 존재해요.</p>
<h2 id="import문이-깔끔해져요">import문이 깔끔해져요</h2>
<p>제가 이러한 폴더 구조를 가져가는 것을 좋아하게 된 계기가 깔끔한 import문이 되기 때문이였어요.</p>
<p>예를 들어 위에서 봤던 폴더 구조에서 <code>index.ts</code> 파일들만 빠져도 다른 파일에서 위 컴포넌트들을 import할 때 아래와 같아야 합니다.</p>
<pre><code class="language-tsx">import { InnerSection } from &#39;@/_components/InnerSection/InnerSection&#39;
import { LevelCard } from &#39;@/_components/LevelCard/LevelCard&#39;
import { NextStepButton } from &#39;@/_components/NextStepButton/NextStepButton&#39;</code></pre>
<p>이게 익숙하신 분들은 <code>&quot;이게 뭐가..?&quot;</code>라고 할 수도 있습니다(저도 위와 같이 사용했었으니까요 하하). 
그런데 베럴파일을 활용하게 되면 아래와 같이 import문이 깔끔해질 수 있습니다.</p>
<pre><code class="language-tsx">import { InnerSection, LevelCard, NextStepButton } from &#39;@/_components&#39;</code></pre>
<p>저는 이러한 점 때문에 베럴파일 사용하는 것을 좋아하게 되었습니다 👍</p>
<h1 id="🔎-barrel-파일에-더-자세히-알아보자">🔎 Barrel 파일에 더 자세히 알아보자</h1>
<p>막연히 위와 같이 이유 때문에 즐겨 사용했었는데 이번 포스팅을 통해 자세히 알아보려고 합니다 🤔</p>
<h2 id="barrel-파일이-무엇인가요">Barrel 파일이 무엇인가요?</h2>
<p>JavaScript(또는 TypeScript)에서 베럴 파일은 여러 모듈의 내보내기를 단일 import 문을 사용하여 가져올 수 있는 편리한 단일 모듈로 통합하는 방법입니다.</p>
<p>제가 위에서 설명했던 <code>_components</code> 폴더에서는 아래와 같이 베럴 파일(index.ts)을 만들 수 있습니다.</p>
<pre><code class="language-tsx">// _components/index.ts
export * from &#39;./module1&#39;
export * from &#39;./module2&#39;
export * from &#39;./module3&#39;</code></pre>
<p>그러면 이제 <strong>단일 import문</strong>으로 필요한 모든 값을 가져올 수 있습니다.</p>
<pre><code class="language-tsx">import { value1, value2, value3 } from &#39;@/_components&#39;</code></pre>
<h2 id="뭐가-좋은가요">뭐가 좋은가요?</h2>
<p><strong>1. 깔끔해지는 import문</strong></p>
<p>우선 위에서 계속 살펴본 것처럼, <strong>가져오기를 깔끔하게 유지하는 데 편리</strong>합니다.</p>
<pre><code class="language-tsx">// before
import { value1 } from &#39;@/_components/module1/module1&#39;
import { value2 } from &#39;@/_components/module2/module2&#39;
import { value3 } from &#39;@/_components/module3/module3&#39;

// after
import { value1, value2, value3 } from &#39;@/_components&#39;</code></pre>
<p><strong>2. 번들 실수 예방(캡슐화 개선)</strong></p>
<p>Barrel 파일을 사용하면 다른 파일에 노출할 항목만 내보내므로 캡슐화가 개선될 수 있습니다. 이를 통해 import할 때 실수를 방지할 수 있고 이는 불필요하게 번들링 되지 않도록 도와줍니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/0760ccd5-bc7f-4e70-a8a2-fe1e23fa5295/image.png" width="300" height="300"/></p>

<p>위 파일 구조를 보았을 때 사실 해당 파일에 존재하는 <code>types.ts</code> 파일을 <code>InnerSection.tsx</code> 파일에서 관심사 분리를 위해 따로 빼낸 파일이지 외부로 나갈 필요는 없는 파일입니다. 즉, <strong>외부에서는 번들될 필요가 없는 파일</strong>, 즉 <code>import</code>가 될 필요가 없는 파일임을 의미합니다. </p>
<p><strong>이러한 파일을 import하는 실수를 방지하기 위해</strong> 해당 폴더 베럴 파일(index.ts) 파일에서는 아래와 같이 <strong>내보내기할 파일만을 명시해주는 것</strong>입니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/9abc3445-34bd-4ac6-bbe9-3914ccbc1e4f/image.png" width="350" height="300"/></p>

<p>그럼 사용할 때에는 내가 베럴 파일을 통해 export했던 모듈들만 보이게 됩니다.</p>
<h2 id="뭐가-나쁜가요">뭐가 나쁜가요?</h2>
<p><strong>1. 출처를 알기 어려움</strong></p>
<p>폴더마다 베럴 파일(<code>index.ts</code>) 파일이 존재하기 때문에 해당 모듈의 출처를 이해하기 어려울 수 있습니다.</p>
<p>비교적 크지 않은 프로젝트에서도 해당 컴포넌트를 찾아 들어가려면 아래와 같이 복잡하게 들어가야 하는 문제점이 있을 수 있습니다.</p>
<p algin="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/89cee5b5-c733-406a-8fcb-a4526fef9af0/image.png" width="400" height="400"/></p>

<p>위 이미지는 제가 <code>from &#39;...&#39;</code>을 계속 타고 들어가면서 찾고 싶은 컴포넌트가 나올 때까지의 절차입니다.</p>
<p>위와 같은 이유로 프로젝트 규모가 크다면 유지보수에 좋지 못할 수 있습니다.</p>
<h1 id="🥵-barrel-파일을-남용한-경우">🥵 Barrel 파일을 남용한 경우</h1>
<p>저 같은 경우에도, 이 Barrel 파일을 사용하는 것을 컨벤션으로 정해놓고 사용을 하다보니 이런 경우가 <strong>남용인지 모르고 사용한 적</strong>이 있습니다.</p>
<p>아래는 제가 최근에 작업한 프로젝트(<code>Next13</code>)의 파일 중 일부입니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/2b0da5fb-b2c5-4a47-862c-eb54f73e4a8f/image.png" width="350" height="350"/></p>

<h2 id="이게-왜-남용">이게 왜 남용?</h2>
<p>기억하시나요? Barrel 파일은 import문을 깔끔하게 해준다는 이유와 캡슐화를 개선해주는 장점 때문에 사용하지만 <code>index.ts</code>파일이 폴더마다 존재하게 되어 복잡성을 높인다라는 사실이요!</p>
<p>제가 위에서 작성한 폴더들 안에는 컴포넌트가 하나 존재하면서 Barrel 파일을 가지고 있어요. 즉, <strong>캡슐화 개선의 의미는 없는 것</strong>입니다. 😱</p>
<p>그렇다면, 여기서 위와 같은 구조를 가지고 갔을 때 장점은 아래와 같은 이유 하나가 됩니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/80ecb840-8755-406d-aab6-421b2c52cef2/image.png" width="250" height="250"/></p>

<p>Step1, Step2, Step3, Stpe4 폴더를 위에 있는 <code>index.ts</code>에서 위와 같이 좀 더 가져오기 구문이 깔끔해진다는 것입니다. </p>
<p>깔끔함을 택한 대신에 폴더마다 <code>index.ts</code>파일을 생성해서 프로젝트 복잡성이 늘어났다는 사실도 존재합니다.</p>
<h2 id="개선해보자">개선해보자</h2>
<p>제가 남용했다는 사실을 알고, 생각해보니 굳이 위와 같은 구조가 아니여도 됐었습니다. 개선을 해봅시다!</p>
<p><strong>1. 하나의 파일만 존재하는 폴더에서의 베럴 파일은 지우자</strong></p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/93b4c642-9fc6-44fc-921d-620de81dcea1/image.png" width="300" height="300"/></p>

<p>그럼 위 폴더들 위에 있는 베럴 파일에서는 아래와 같이 작성해주면 되겠습니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/7f9e32cc-d305-4692-80aa-dce1263995da/image.png" width="300" height="300"/></p>

<p>위와 같이 작성해줘도 외부 모듈에서 해당 모듈을 사용하려고 했을 때 이전과 동일하게 사용이 가능합니다.</p>
<p><strong>2. 파일 하나인 폴더라면 그냥 폴더 없애기</strong></p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/db65f5b3-894b-4c5c-b68e-e00027e7eeeb/image.png" width="200" height="200"/></p>

<p>폴더 자체를 없애버리면 폴더마다의 베럴 파일이 없을 수 있습니다. 그럼 위 이미지에서 보이는 <code>index.ts</code>에서의 아래와 같이 써줄 수 있겠습니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/589da02a-7c44-42d4-b84c-c32dbdbb7243/image.png" width="250" height="200" /></p>

<p>생각보다 간단하죠? 😦</p>
<hr>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/054d33dd-1316-407c-bf10-904c763dacff/image.png"/></p>


<p>이번 글을 통해 저를 다시 또 돌아본 것 같습니다 🥺 항상 새로운 무언가를 습득하는 것을 좋아하는데, 저도 모르게 <strong><code>&quot;오 많이들 쓰네 좋은 건가봐~ 나도 해봐야지&quot;</code></strong> 라는 생각을 했던 것 같아요. 그래서 누군가 저한테 &quot;이거 왜 쓰셨어요?&quot;라고 물어봤을 때 자신있게 또 논리적으로 대답하지 못하겠더라구요.</p>
<p>그래서 그것보다는 <strong><code>&quot;음 많이들 쓰네 뭐가 좋아서 쓰고 나쁜 점은 없나?&quot;</code></strong> 하는 태도가 항상 필요하겠다고 생각들었던 것 같습니다 !</p>
<h1 id="reference">Reference</h1>
<ul>
<li><a href="https://velog.io/@lky5697/reduce-webapp-bundle-size">(번역) 웹 앱의 번들 크기를 줄이기 위해 할 수 있는 모든 것</a></li>
<li><a href="https://flaming.codes/ko/posts/barrel-files-in-javascript">JavaScript의 배럴 파일 - JavaScript에서 Barrel 파일을 사용할 때의 장단점</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js에서 MSW 세팅하기]]></title>
            <link>https://velog.io/@doeunnkimm_/Next.js%EB%9E%91-MSW-%ED%99%94%ED%95%B4%EC%8B%9C%ED%82%A4%EA%B8%B0</link>
            <guid>https://velog.io/@doeunnkimm_/Next.js%EB%9E%91-MSW-%ED%99%94%ED%95%B4%EC%8B%9C%ED%82%A4%EA%B8%B0</guid>
            <pubDate>Thu, 12 Oct 2023 05:03:54 GMT</pubDate>
            <description><![CDATA[<h1 id="🍎-저는-지금-이런-환경인데요">🍎 저는 지금 이런 환경인데요!</h1>
<p>Next.js App router, 컴파일 옵션은 es5(기본으로 세팅되어 있는) 환경 입니다.</p>
<h1 id="🫨-일단-setup-하기">🫨 일단 Setup 하기</h1>
<h3 id="1-msw-install">1. msw install</h3>
<pre><code class="language-bash">$ yarn add -D msw</code></pre>
<h3 id="2-mocks-폴더에-mock-api-만들어주기">2. mocks 폴더에 mock api 만들어주기</h3>
<pre><code class="language-tsx">// @/mocks/apis/study.apis.ts

import { rest } from &#39;msw&#39;

const getCreatedStudyList = rest.get(&#39;/api/study&#39;, (_, res, ctx) =&gt; {
  return res(ctx.status(200), ctx.json({ message: &#39;success!!&#39; }))
})</code></pre>
<h3 id="3-handlers-만들어주기">3. <strong>handlers 만들어주기</strong></h3>
<pre><code class="language-tsx">// @/mocks/handlers.ts

import * as apis from &#39;./apis&#39;

export const handlers = [...Object.values(apis)]</code></pre>
<h3 id="4-worker에-handlers-등록하기">4. worker에 handlers 등록하기</h3>
<pre><code class="language-tsx">// @/mocks/browser.ts

import { setupWorker } from &#39;msw&#39;
import { handlers } from &#39;./handlers&#39;

export const worker = setupWorker(...handlers)</code></pre>
<h3 id="5-server에-handlers-등록하기">5. server에 handlers 등록하기</h3>
<p>api 테스트 코드 작성 예정이기에 미리 세팅했습니다.</p>
<pre><code class="language-tsx">// @/mocks/server.ts

import { setupServer } from &#39;msw/node&#39;
import { handlers } from &#39;./handlers&#39;

export const server = setupServer(...handlers)</code></pre>
<h3 id="6-초기화-함수-만들기">6. 초기화 함수 만들기</h3>
<p>브라우저 환경에서 동작하는지, 서버 환경에서 동작하는지에 따라 다른 인스턴스를 실행합니다.</p>
<pre><code class="language-tsx">// @/mocks/index.ts

const initMocks = async () =&gt; {
  const isServer = typeof window === &#39;undefined&#39;

  if (isServer) {
    const { server } = await require(&#39;./server&#39;)
    server.listen({ onUnhandledRequest: &#39;bypass&#39; }) // 처리되지 않은 요청이라도 통과시키도록
  } else {
    const { worker } = await require(&#39;./browser&#39;)
    worker.start({ onUnhandledRequest: &#39;bypass&#39; }) // 처리되지 않은 요청이라도 통과시키도록
  }
}
export default initMocks</code></pre>
<p>❓ 처리되지 않은 요청이라도 통과?</p>
<ul>
<li>모든 api를 mock으로 하지 않았더라도 (일부는 실제 api일 수 있기 때문) server/worker가 통과시키도록 합니다.</li>
</ul>
<h3 id="7-providers클라이언트-사이드-파일에서-초기화-함수-실행">7. providers(클라이언트 사이드) 파일에서 초기화 함수 실행</h3>
<p>환경에 따라 설정을 다르게 해줄 수 있도록 env를 이용했습니다.</p>
<p>env파일에서 바로 <code>enable/disable</code> 해줌에 따라 msw를 구동할 수도 하지 않을 수도 있습니다.</p>
<pre><code class="language-tsx">// @/app/providers.tsx

&#39;use client&#39;

import initMocks from &#39;@/mocks&#39;
import { PropsWithChildren } from &#39;react&#39;

if (process.env.NEXT_PUBLIC_API_MOCKING === &#39;enable&#39;) {
  initMocks()
}

const Providers = ({ children }: PropsWithChildren) =&gt; {
  return &lt;div&gt;{children}&lt;/div&gt;
}

export default Providers</code></pre>
<pre><code>// env

NEXT_PUBLICK_API_MOCKING=&#39;enable&#39;</code></pre><h3 id="8-msw-init-해주기">8. msw init 해주기</h3>
<pre><code class="language-bash">$ npx msw init public/</code></pre>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/bd31d574-e0ca-4e66-b1fc-264481cce2cf/image.png" width="60%" height="60%"/></p>

<p>잘 동작하는 것을 확인할 수 있었습니다 👍</p>
<h1 id="📺-nextjs는-서버-사이드에서-msw는-클라이언트-사이드에서-동작한다-원래">📺 Next.js는 서버 사이드에서, MSW는 클라이언트 사이드에서 동작한다 원래..</h1>
<p>Next.js는 서버 사이드에서 렌딩하기 때문에 <strong>브라우저의  Service Worker API를 사용</strong>하여 네트워크 요청을 가로채고 모의 응답을 제공하는 <strong>msw는 서버 사이드에서 제대로 초기화되지 못합니다.</strong></p>
<blockquote>
<p>✨ 따라서 Next.js에서는 클라이언트 사이드 컴포넌트에서 초기화되어야 합니다.</p>
</blockquote>
<p>그런 이유 때문에 <strong><code>use client</code></strong> 되어 있는 providers 파일에서 init을 했던 겁니다 :)</p>
<hr>
<p>이번 디버깅 과정을 통해서 <strong>브라우저에서 초기화되어야만 하는</strong> 특성이 존재함을 알게 되었고, 이를 Next.js에서 반영해주기 위해서는 어떻게 해야하는지를 알게 되었습니다 🙌</p>
<p>그리고 기존에 msw를 세팅할 때 development 환경에서는 msw가 구동되도록 했었습니다. 그래도 개발 환경 중 실제 api의 반영을 확인해야 하는 순간들이 존재합니다. 그럴 때 주석을 했다 풀었다하며 변경 사항이 발생하던 불편함이 있었는데요. 이를 env에서 설정하는 방법 덕분에 해소할 수 있었습니다 😉 </p>
<p><del>app.tsx 파일에서 주석 추가했다가 제거했다가 커밋 내역에는 포함되지 않도록 하려고 신경쓰고 했던 기억이 새록새록하네요 🤔</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[선언적인 Modal을 위한 여정]]></title>
            <link>https://velog.io/@doeunnkimm_/Modal-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%AA%A8%EB%93%88%ED%99%94%EB%9D%BC%EA%B3%A0-%EC%93%B0%EA%B3%A0-%EC%84%A0%EC%96%B8%EC%A0%81%EC%9D%B8-%EC%BD%94%EB%93%9C%EB%9D%BC%EA%B3%A0-%EC%9D%BD%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@doeunnkimm_/Modal-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%AA%A8%EB%93%88%ED%99%94%EB%9D%BC%EA%B3%A0-%EC%93%B0%EA%B3%A0-%EC%84%A0%EC%96%B8%EC%A0%81%EC%9D%B8-%EC%BD%94%EB%93%9C%EB%9D%BC%EA%B3%A0-%EC%9D%BD%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 17 Sep 2023 05:46:19 GMT</pubDate>
            <description><![CDATA[<h1 id="일단-저는-이렇게-모듈화-했어요">일단! 저는 이렇게 모듈화 했어요</h1>
<p>결론부터, 제가 모듈화한 <code>Modal</code> 컴포넌트를 Stoybook으로 확인해 보면 다음과 같습니다.
<img src="https://velog.velcdn.com/images/doeunnkimm_/post/e44c9b28-13fd-4c63-a6d4-25589c2ed0d2/image.gif" alt=""></p>
<p>이번 글에서는 제가 <code>Modal</code> 컴포넌트를 과거에 어떻게 모듈화했으며 그 모듈화는 어떤 점이 잘못 되었었고, 이번에 그 점을 어떻게 개선했는지를 이야기해보려고 합니다 :)</p>
<p>고민을 많이 했던 만큼 자세하게 적을 예정입니다 😅..!</p>
<h1 id="😂-지난-나의-modal">😂 지난 나의 Modal</h1>
<p>제가 <code>Modal</code>이라는 컴포넌트를 모듈화한 것이 처음은 아니였습니다. 아래는 실제 과거에 제가 작성했던 <code>Modal</code> 컴포넌트 코드입니다.</p>
<pre><code class="language-jsx">function Modal({ size, children, ...rest }) {
    const setIsOpenModal = useSetRecoilState(isOpenModalAtom)

    const onClickCloseModal = () =&gt; {
        setIsOpenModal(false)
    }

    return (
      ...
      &lt;span onClick={onClickCloseModal}&gt;❎&lt;/span&gt;
      &lt;div&gt;{children}&lt;/div&gt;
      ...
    )
}</code></pre>
<p>모달은 언제 어디서든 열릴 수 있다고 생각했고, 그래서 모달의 <strong>열고 닫혀 있는 상태를 전역 상태로 관리</strong>했습니다.</p>
<p>일단 좋아요! 덕분에 자식 컴포넌트에 내려 내려 내려 주는 일 없이도 어디서든 모달을 열고 닫는 것이 가능해졌습니다. 아래와 같이 말이죠(<strong>아래도 제가 실제 작성했던 패턴입니다</strong>).</p>
<pre><code class="language-jsx">function Foo() {
    const [isOpenModal, setIsOpenModal] = useRecoilState(isOpenModalAtom)

    return (
        &lt;&gt;
            &lt;button onClick={() =&gt; setIsOpenModal(true)}&gt;모달 열려랏&lt;/button&gt;
            {isOpenModal &amp;&amp; (
                &lt;Modal&gt;
                    &lt;div&gt;안녕하세요&lt;/div&gt;
                &lt;/Modal&gt;
            )}
        &lt;/&gt;
    )
}</code></pre>
<p>일단 모달이 열려 있는지 닫혀 있는지에 따라 모달을 보여줄지 말지를 결정해야 하니 모달의 상태 코드를 불러왔습니다. 그리고 이제 View 로직에서는 &quot;<code>isOpenModal</code>이 true라면~ 모달을 보여줘&quot;라는 식의 패턴으로 작성했었습니다.</p>
<h2 id="😭-사실-dx에-좋지-못한-코드였어요">😭 사실 DX에 좋지 못한 코드였어요</h2>
<p>위와 같은 패턴으로 모달을 모듈화하게 되면, <strong>DX에 좋지 못합니다.</strong> 이유는 다음과 같습니다.</p>
<ul>
<li>사용하는 컴포넌트에서의 핵심 로직에 모달 상태와 관련된 코드가 섞여있어요.</li>
<li>모달을 사용할 때마다 전역 상태 관련 코드를 작성해야 해요.</li>
<li>모달을 사용할 때마다 <code>{isOpenModal &amp;&amp; ...}</code>을 작성해야 해요.</li>
</ul>
<p>여기서 문제는 <strong>사용할 때마다 반복적이고도 섞여서</strong> 선언 및 작성해줘야 하는 코드가 있다는 겁니다.</p>
<p>그래서 이번에 <code>Modal</code> 컴포넌트 모듈화를 맡게 되었을 때 가장 고민을 많이 한 부분은 <strong>어떻게 하면 쉽고 간단하고도 쿨하게 사용할 수 있을까?</strong> 였습니다.</p>
<h1 id="👏-이번-나의-modal">👏 이번 나의 Modal</h1>
<p>이번에 제가 모듈화를 한 방법에 대해 소개해 보려고 합니다.</p>
<h2 id="1-headless-ui-컴포넌트로-modal-컴포넌트-만들기">1. Headless UI 컴포넌트로 Modal 컴포넌트 만들기</h2>
<p>사실 UI 라이브러리(Tailwind, bootstrap 같은)를 사용중이여서, Modal 컴포넌트가 포함되어 있었지만 사용하지 않았습니다. 해당 컴포넌트들은 스타일적으로 정형화 되어있어 <strong>커스터마이징 하기가 어렵습니다.</strong> 라이브러리에서 제공하는 스타일이 마음에 든다면 사용해도 상관없지만요!</p>
<p>저 같은 경우에는 프로젝트에서 사용되는 모달은 분명 UI 라이브러리에서 제공되는 Modal 컴포넌트와는 스타일적으로 달랐기에 이를 사용하지 않고 만들어 쓰기로 결정했습니다.</p>
<p>우선 Modal을 사용하기 위해 Modal 컴포넌트부터 만들어야겠죠? 저는 이때 Headless UI 라이브러리의 컴포넌트를 이용했습니다.</p>
<blockquote>
<p>🤔 <strong>Headless UI 라이브러리를 이용하면!</strong>
     컴포넌트들이 스타일링을 포함하지 않고, 로직과 기능만을 제공한다는 것을 의미
     → 즉, 동작 방식에 대해 신경 쓸 필요 없이, 오직 어떻게 보일지에만 집중할 수 있다 ✨</p>
</blockquote>
<p>이는 UI 라이브러리의 컴포넌트의 기능을 사용하고 싶기도 했지만서도, 스타일을 커스터마이징 하기 어려워 사용하지 않았던 부족함을 채워줄 수 있었습니다.</p>
<h3 id="ark-ui-라이브러리"><a href="https://ark-ui.com/">ark-ui 라이브러리</a></h3>
<p><a href="https://ark-ui.com/docs/components/dialog">Dialog</a>를 이용했습니다. 살펴보면 여러 기능 컴포넌트와 Props를 소개해주고 있습니다. 공식 문서가 은근 불친절해서 github 코드를 살펴보고 이것저것 시도해본 결과 제가 알짜로 사용한 컴포넌트들은 아래와 같습니다.</p>
<ul>
<li><code>Portal</code> : z-index 선언없이 페이지 바로 아래 children에 element를 놓을 수 있어요.</li>
<li><code>DialogContainer</code> : Container (쉽게 말해 Background부터 포함)</li>
<li><code>DialogContent</code> : Content 부분 (쉽게 말해 실제 모달 내용 부분)</li>
</ul>
<p>좀 전에 말했다싶이 이 컴포넌트들에는 스타일이 쏙 빠져있기 때문에 스타일 컴포넌트는 원하는 대로 선언하여 중간중간에 넣어주면 됩니다.</p>
<p>제가 만든 Modal 컴포넌트는 아래와 같습니다.</p>
<pre><code class="language-tsx">&quot;use client&quot;;

import { Dialog, DialogContainer, DialogContent, Portal } from &quot;@ark-ui/react&quot;;

import * as Styled from &quot;./Modal.styles&quot;;
import type { ModalProps } from &quot;./Modal.types&quot;;
import { ModalBody, ModalFooter } from &quot;./components&quot;;

export const Modal = ({ isOpen, props, onClose }: ModalProps) =&gt; {
  return (
    &lt;Dialog open={isOpen} onClose={onClose}&gt;
      &lt;Portal&gt;
        &lt;DialogContainer&gt;
          &lt;Styled.Background&gt;
            &lt;DialogContent&gt;
              &lt;Styled.ModalWrapper&gt;
                &lt;ModalBody props={props} /&gt;
                &lt;ModalFooter props={props} onClose={onClose} /&gt;
              &lt;/Styled.ModalWrapper&gt;
            &lt;/DialogContent&gt;
          &lt;/Styled.Background&gt;
        &lt;/DialogContainer&gt;
      &lt;/Portal&gt;
    &lt;/Dialog&gt;
  );
};</code></pre>
<h2 id="2-선언적인-컴포넌트를-위한-props-결정">2. 선언적인 컴포넌트를 위한 Props 결정</h2>
<p>선언적인 컴포넌트가 되기 위해서는 아래와 같은 부분에 집중해야 합니다.</p>
<blockquote>
<p>📌 무엇을 하는지만 외부에서 보여지고, 어떻게 하는지는 컴포넌트 내부에서 처리한다.</p>
</blockquote>
<p>이를 다시 생각해보면, <strong>props를 통해서 해당 컴포넌트가 무엇을 하는지가 파악이 되어야 한다</strong>는 점이라고도 할 수 있습니다.</p>
<p>저의 Modal 컴포넌트의 경우 무엇을 하는지 파악하기 위해 다음과 같은 내용들이 중요했습니다.</p>
<ul>
<li>내용</li>
<li>Modal의 타입 (본 프로젝트의 경우 3가지 타입의 모달이 존재했습니다)</li>
<li>확인을 눌렀을 때의 이벤트</li>
</ul>
<p>무엇을 하는지 파악하기 위해서는 위 3가지가 중요했으며, 이 외 모달이 열렸는지와 닫기 위한 이벤트 함수와는 구분을 지어 묶어주었습니다.</p>
<pre><code class="language-ts">export interface ModalProps {
  isOpen: boolean;
  props: {
    type: &quot;positive&quot; | &quot;negative&quot; | &quot;warning&quot;;
    title: string;
    onConfirm: VoidFunction;
  };
  onClose: VoidFunction;
}

export type EssentialModalProps = ModalProps[&quot;props&quot;];</code></pre>
<h2 id="3-props는-어디서는-넘겨줄-수-있도록">3. Props는 어디서는 넘겨줄 수 있도록</h2>
<p>지난 저의 Modal처럼 전역 상태를 이용하여 어디서든 Modal을 이용하는 것도 필요합니다. 하지만 지난 컴포넌트에서는 모달이 필요한 매 페이지마다 <code>{ isOpenModal &amp;&amp; ... }</code>을 작성해야 했습니다.</p>
<p>이번에는 Context와 Provider를 사용하여 <code>Provider</code> 바로 아래 <code>children</code>과 <code>Modal</code>을 함께 두어 매번 작성하지 않도록 하려고 했습니다.</p>
<pre><code class="language-tsx">const initialModalProps: ModalProps = { ... };

export const ModalContext = createContext&lt;ModalContextProps&gt;({
  onOpenModal: () =&gt; {},
});

export const ModalProvider = ({ children }: PropsWithChildren) =&gt; {
  const [modalProps, setModalProps] = useState(initialModalProps);

  const onOpenModal = (props: EssentialModalProps) =&gt; {
    setModalProps((prev) =&gt; ({ ...prev, isOpen: true, props }));
  };

  const onClose = () =&gt; {
    setModalProps((prev) =&gt; ({ ...prev, isOpen: false }));
  };

  return (
    &lt;ModalContext.Provider value={{ onOpenModal }}&gt;
      {children}
      &lt;Modal {...modalProps} onClose={onClose} /&gt;
    &lt;/ModalContext.Provider&gt;
  );
};</code></pre>
<p>즉, 무엇을 나타내는지를 위해 중요했던 props는 사용할 때 받도록 하고 이 외였던 열려있는지 상태 그리고 닫기 위한 이벤트 함수는 미리 <code>Provider</code> 내부에서 <strong>같이 처리되거나 미리 전달하는 형태</strong>로 로직을 작성했습니다.</p>
<p>또, children이랑 Modal 컴포넌트를 같이 둠으로써 매번 선언하지 않고도 isOpen이 true 상태가 되기만 한다면 모달은 화면에 보일 것입니다.</p>
<h2 id="4-provider를-쉽게-사용하기">4. Provider를 쉽게 사용하기</h2>
<p>위에서 isOpen이 true가 되면 모달이 화면에 보인다고 했습니다. 즉, 모달을 어디선가 사용을 했다라는 말을 의미하죠?</p>
<p>이제 편한 사용을 위한 코드를 작성해 봅시다.</p>
<p>저는 좀 전에 <code>Context</code>와 <code>Provider</code>를 선언했는데요. 보통 이를 편하게 사용하기 위해 hook 함수를 만들어 사용합니다. 저는 <code>useModal</code>이라고 이름 지어주었어요.</p>
<p>이제 <code>useModal</code>을 어떻게 구성할지를 생각해야 했습니다. 저는 이 구성을 할 때 <a href="https://toss.tech/article/frontend-declarative-code">TOSS의 useOverlay</a>를 통해 아이디어를 얻었었는데요.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/c852c570-dc09-495b-af0b-8c128f70a7dd/image.png" /></p>

<p>useOverlay와 완벽하게 똑같게는 아니였지만, 훅 함수를 통해 <code>open</code>이라는 메서드를 포함한 객체를 return 해주어, 사용할 때 open을 통해 필요한 내용들을 실어서 사용하도록 해야겠다고 생각했습니다.</p>
<p>Provider를 선언할 때 <code>onOpenModal</code>이라는 필수 요소를 인자로 받고 isOpen은 true로 같이 처리해주는 메서드를 선언해서 Provider의 value로 넣어주었었습니다.</p>
<pre><code class="language-tsx">const onOpenModal = (props: EssentialModalProps) =&gt; {
    setModalProps((prev) =&gt; ({ ...prev, isOpen: true, props }));
};</code></pre>
<p>따라서 위 메서드를 훅 함수를 통해 return 해주면 됩니다.</p>
<pre><code class="language-tsx">import { useContext } from &quot;react&quot;;
import { ModalContext } from &quot;../context/ModalContext&quot;;

export const useModal = () =&gt; {
  const { onOpenModal } = useContext(ModalContext);

  if (onOpenModal === (() =&gt; {})) {
    throw new Error(&quot;useModal should be used within ModalContext.Provider&quot;);
  }

  return { open: onOpenModal };
};</code></pre>
<p>추가로, <code>Context</code>는 최상위에 <code>Provider</code>를 감싸주지 않으면 사용하지 못합니다. 혹시 최상위에 <code>Provider</code> 감싸주는 것을 까먹어 모달을 사용하지 못하는 상황을 빠르게 파악할 수 있도록 Error를 throw하는 부분도 넣어주었습니다.</p>
<h2 id="5-사용해보기">5. 사용해보기</h2>
<p>이제 만들어 둔 <code>useModal</code>을 이용하면 2줄이면 모달을 명확하고도 간단하게 사용할 수 있습니다.</p>
<pre><code class="language-tsx">const Foo = () =&gt; {
    const modal = useModal()

      const onClickOpenModal = () =&gt; {
        modal.open({ type: &quot;positive&quot;, title: &quot;테스트 모달입니다.&quot;, onConfirm: () =&gt; alert(&quot;잘 실행되었네요!&quot;) });
      };

     return (
        &lt;button onClick={onClickOpenModal}&gt;모달 열려랏&lt;/button&gt;
    )
}</code></pre>
<h2 id="6선택-storybook으로-문서화하기">6(선택). Storybook으로 문서화하기</h2>
<p>제가 아무리 (물론 제가 생각하기에는 이지만) 간편하게 사용할 수 있도록 모듈화를 했다 하더라도 다른 개발자들은 당연히 어떻게 사용하는지는 관련된 여러 코드를 열어보면서 파악을 하고 테스트로 사용을 해보면서 사용법을 알아가야 할 수도 있습니다.</p>
<p>이럴 때 스토리북은 최고의 문서화 방법이겠죠!
props만 간단하게 넘길 때는 <code>args</code>를 통해도 빠르게 Story를 만들 수 있지만, 저는 이번에 사용 방법에 초점을 맞추어 Story를 작성해 두었습니다.</p>
<p>물론 더 필요한 Story도 같이 작성해 주었구요 😉</p>
<pre><code class="language-tsx">const ModalSample = () =&gt; {
  const modal = useModal();

  const onClickOpenModal = () =&gt; {
    modal.open({ type: &quot;positive&quot;, title: &quot;테스트 모달입니다.&quot;, onConfirm: () =&gt; alert(&quot;잘 실행되었네요!&quot;) });
  };

  return &lt;button onClick={onClickOpenModal}&gt;누르면 모달이 떠요&lt;/button&gt;;
};

export const ButtonToModal: Story = {
  render: () =&gt; {
    return (
      &lt;ModalProvider&gt;
        &lt;ModalSample /&gt;
      &lt;/ModalProvider&gt;
    );
  },
};</code></pre>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/44074b13-4183-43c7-93f1-8b801a11781e/image.gif" alt=""></p>
<hr>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/8d43007c-1f05-49a6-ba12-5c15f13b032d/image.png" alt=""></p>
<p>이번 글을 통해 지난 저의 Modal 컴포넌트에 대해 돌아보고 문제점을 짚어보기도 했습니다. 이를 통해 개선점을 찾아보고 새로 Modal 컴포넌트를 모듈화 해보았습니다.</p>
<p>이번 글 역시도 이 코드가 정답이야!!는 절대 아니며 제가 고민한 내용들을 정리해보기 위해 작성했음을 말씀드리고 글을 마무리하려고 합니다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Suspense와 Concurrent Mode로 DX와 UX 둘 다 챙기기 ]]></title>
            <link>https://velog.io/@doeunnkimm_/%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC%EC%99%80-Suspense-%EA%B7%B8%EB%A6%AC%EA%B3%A0-Concurrent-Mode</link>
            <guid>https://velog.io/@doeunnkimm_/%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC%EC%99%80-Suspense-%EA%B7%B8%EB%A6%AC%EA%B3%A0-Concurrent-Mode</guid>
            <pubDate>Sun, 03 Sep 2023 06:35:33 GMT</pubDate>
            <description><![CDATA[<h1 id="비동기-프로그래밍">비동기 프로그래밍</h1>
<pre><code>📌 비동기
   앞에서 행하여진 사상(事象)이나 연산이 완료되었다는 신호를 받고 비로소 특정한 사상이나 연산이 시작되는 방식
   - [네이버 국어사전]</code></pre><p>즉, 비동기 프로그래밍이란 특정 코드의 처리가 완료되기 전, 처리하는 도중에도 아래로 계속 내려가면 수행을 하는 것을 말합니다.</p>
<h2 id="비동기-프로그래밍이-필요한-이유">비동기 프로그래밍이 필요한 이유</h2>
<p>예를 들어, 서버에게 요청을 보내고 응답만을 기다리고 유저의 다른 인터렉션은 반응하지 않는다면 그냥 멈춰있게 됩니다. 여기서 비동기 프로그래밍이 있다면, 서버에 요청을 보내고, 기다리는 게 아니라 다른 작업을 하면서 사용자에게 좋은 경험을 보여주다가, 서버 응답이 돌아오면 이어서 할 일을 할 수 있습니다.</p>
<blockquote>
<p>⭐️ 비동기 프로그래밍은 좋은 사용자 경험을 위해 필수!</p>
</blockquote>
<h1 id="react-컴포넌트에서의-비동기-처리">React 컴포넌트에서의 비동기 처리</h1>
<h2 id="😰-컴포넌트에서-로딩과-에러-처리-수행">😰 컴포넌트에서 로딩과 에러 처리 수행</h2>
<pre><code class="language-jsx">function App() {
  return (
    &lt;&gt;
      &lt;Foo /&gt;
    &lt;/&gt;
  )
}

function Foo() {
  const foo = useFooQuery()

  if (foo.error) return &lt;div&gt;로딩에 실패했습니다.&lt;/div&gt;
  if (!foo.data) return &lt;div&gt;로딩중..&lt;/div&gt;
  return &lt;div&gt;{foo.data.name}님 안녕하세요!&lt;/div&gt;
}</code></pre>
<p>위 코드는 비동기인 foo를 가져오는데, foo가 에러이면 실패 메시지를 보여주고, foo가 없으면 로딩 중이라고 보여주고, foo가 있으면 안녕하세요라는 메시지를 보여주고, 위와 같이 코드를 많이 작성합니다. <del>(저도 그랬는데요.)</del></p>
<p><strong>😣 이렇게 컴포넌트에서 로딩과 에러 처리를 수행하면 좋지 못해요</strong></p>
<ol>
<li><strong>핵심 로직에 집중하기 어려워요</strong>
 한 컴포넌트에서 성공하는 경우와 실패하는 경우가 섞여서 처리되고 있어, 핵심 로직에 집중하기 어려워집니다.</li>
</ol>
<ol start="2">
<li><p><strong>여러 개의 비동기 작업이 실행된다면, 점점 더 복잡해져요</strong></p>
<pre><code class="language-js"> function Foo() {
   const foo = useFooQuery()
   const bar = useBarQuery(foo)

   if (foo.error || bar.error) return &lt;div&gt;로딩에 실패했습니다.&lt;/div&gt;
   if (!foo.data || !bar.data) return &lt;div&gt;로딩 중입니다...&lt;/div&gt;
   return /* foo와 bar로 적합한 처리하기 */
 }</code></pre>
<p>   위 코드는 foo와 bar라는 하는 값을 비동기로 가져오는 상황입니다. <strong>bar를 가져오기 위해서는 foo가 있어야 하는 상황</strong>인데요. foo를 가져오고, bar는 foo가 로드될 때까지 기다리고, if문을 복잡해지고... 복잡합니다.</p>
</li>
</ol>
<h2 id="react의-비동기-처리가-어려운-이유">React의 비동기 처리가 어려운 이유</h2>
<p>위 코드에서 볼 수 있었듯이, 성공하는 경우에만 집중해 컴포넌트를 구성하기 어려웠습니다. 특히 2개 이상의 비동기 로직이 개입해 복잡해질 때 비즈니스 로직을 파악하기 점점 어려워졌습니다.</p>
<h2 id="🥳-suspense--errorboundary로-위임하여-처리-수행">🥳 Suspense &amp; ErrorBoundary로 위임하여 처리 수행</h2>
<p>좀 전에 있었던 비동기 처리의 어려움과 컴포넌트 내부에서 처리했을 때의 문제점들을 Suspense와 ErrorBoudnary를 통해 해결할 수 있게 되었습니다.</p>
<pre><code class="language-jsx">function App () {
  return (
    &lt;ErrorBoundary fallback={&lt;MyErrorPage /&gt;}&gt;
      &lt;Suspense fallback={&lt;Loader /&gt;}&gt;
        &lt;FooBar /&gt;
      &lt;/Suspense&gt;
    &lt;/ErrorBoundary&gt;
  )
}

function FooBar() {
  return (
    /* 성공했을 때의 비즈니스 로직 */
  )
}</code></pre>
<p>위와 같이 Suspense와 ErrorBoundary를 이용해 에러 상태와 로딩 상태 처리를 외부로 위임하여 <strong>비동기를 처리하면서 간단하고 읽기 편한 React 컴포넌트</strong> 만들기가 가능해지게 되었습니다.</p>
<p><strong>🤩 Suspense와 ErrorBoudnary로 위임함으로써 얻게 되는 이점은요</strong></p>
<ol>
<li><strong>로딩 상태와 에러 상태 로직이 분리되어, 성공한 경우에만 집중할 수 있게 되었어요.</strong>
 FooBar 컴포넌트는 성공한 경우의 로직만을 작성할 수 있게 되어 핵심 기능이 드러나게 됩니다.</li>
</ol>
<h1 id="😦-suspense의-문제점">😦 Suspense의 문제점</h1>
<p>Suspense로 로딩 상태를 분리함으로써 컴포넌트는 핵심 로직에 집중할 수 있게 되었으나, 만약 응답 속도가 매우 빠르게 이루어지는 비동기 요청에 대해서는 <code>Loader</code>로 인해 오히려 깜빡임으로 보여질 수 있습니다. 너무 빨라서 로더도 잠깐 뜨게 되는 것이죠.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/7635b9e2-1d34-44d2-9864-386627c9bdb5/image.gif" alt=""></p>
<p>위 이미지에서 보이는 것처럼 비동기 호출이 이루어질 때 어느정도 로딩이 발생한다면 Loader를 보여주는 UI는 필요합니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/79104a80-ea6b-40e2-a54b-a67e7b7866a6/image.gif" alt=""></p>
<p>하지만 두 번째 이미지처럼 비동기 처리가 매우 빠르게 처리된다면 Loader를 띄우는 과정 때문에 오히려 깜빡임처럼 보이게 됩니다. 이는 사용자 경험 측면에서 화면 깜빡거림으로 인식되어 좋지 못한 UI가 되어 버립니다.</p>
<p>위와 같은 상황을 <strong>충분히 렌더링이 빠름에도 의미 없는 로딩을 보여주는 경우</strong>라고 말할 수 있겠습니다.</p>
<h2 id="✨-usetransition으로-의미없는-로딩깜빡임-해결">✨ useTransition으로 의미없는 로딩(깜빡임) 해결</h2>
<p>위와 같은 현상을 해결할 수 있는 방법으로 React 18에서는 <code>useTransition</code>이라는 훅을 제공합니다.</p>
<pre><code class="language-jsx">const [isPending, startTransition] = useTransition()</code></pre>
<ul>
<li><code>isPending</code> : 작업이 지연되고 있음을 알리는 boolean</li>
<li><code>startTransition</code> : 낮은 우선순위로 실행할 함수를 인자로 받는다.</li>
</ul>
<pre><code class="language-jsx">function App() {
  const [isPending, startTransition] = useTransition()
  const [page, setPage] = useState(1)

  const onNextPage = () =&gt; {
    startTransition(() =&gt; {
      setPage(prev =&gt; prev + 1)
    })
  }

  return (
    &lt;Suspense fallback={&lt;Loader /&gt;}&gt;
      &lt;Foo page={page} onNextPage={onNextPage} /&gt;
    &lt;/Suspense&gt;
  )
}

function Foo({ page, onNextPage }) {
  return /* 성공했을 경우만 고려하여 작성 가능 */
}</code></pre>
<p>useTranstion으로부터 나온 startTransition이라는 함수에 상태 업데이트 로직을 부여하면 해당 상태 업데이트로 인해 새롭게 발생하는 비동기 처리가 끝날 때까지 화면 렌더링 변화를 지연시킵니다. 정확히는 원래의 UI를 보여주다가 업데이트된 UI를 보여주는 형태입니다. 이를 적용해보면 아래처럼 깜빡임 없이 개선해 볼 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/e5e58c90-1618-4a4b-b8ef-aeab84c4e248/image.gif" alt=""></p>
<p>제가 써봐도 사용자 경험이 이전보다 훨씬 좋아진 걸 느낄 수 있었습니다 👍🏻</p>
<p>그런데, 위와 같은 동작은 어떤 원리일까요? 그걸 알아보기 위해서는 <code>Concurrent Mode</code>를 알아봐야 합니다.</p>
<h1 id="concurrent-mode">Concurrent Mode</h1>
<p>JavaScript는 싱글 스레드 언어입니다. 이는 하나의 작업을 수행할 때 다른 작업을 동시에 수행할 수 없음을 의미합니다. 하지만 React에서 <code>concurrent mode</code>를 사용하면 여러 작업을 동시에 처리할 수 있습니다.</p>
<blockquote>
<p>📌 concurrent mode를 사용하면 <strong>여러 작업을 동시에 처리</strong> 가능</p>
</blockquote>
<h2 id="react가-여러-작업을-동시에-처리하는-방식">React가 여러 작업을 동시에 처리하는 방식</h2>
<p>React는 <strong>여러 작업을 작은 단위로 나눈 뒤</strong>, 그들 간의 우선순위를 정하고 그에 따라 작업을 번갈아 수행합니다. 서로 다른 작업들이 실제로 동시에 수행되는 것은 아니지만, <strong>작업 간의 전환이 매우 빠르게 이루어지면서 동시에 수행되는 것처럼</strong> 보이게 되는 것입니다. 이를 <strong>동시성</strong>이라고 합니다.</p>
<p>이렇게 React는 동시성 개념을 도입해 싱글 스레드 환경에서 여러 작업을 동시에 할 수 있게 되었습니다.</p>
<h2 id="concurrent-mode의-동작-원리">Concurrent mode의 동작 원리</h2>
<p>특정 state가 변경되었을 때 <strong>현 UI를 유지</strong>하고 해당 변경에 따른 <strong>UI 업데이트를 동시에 준비</strong>합니다. 준비 중인 UI의 렌더링 단계가 특정 조건에 부합하게 되면 실제 DOM에 반영하는 것이죠.</p>
<p>그래서 fallback으로 지정했던 컴포넌트를 띄우는 것이 아니라 현 UI를 유지하면서 변경을 준비합니다.</p>
<h2 id="렌더링-단계">렌더링 단계</h2>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/8b402f50-dd62-4c85-90db-5f41a40024f0/image.png" alt=""></p>
<p>state 변경의 관점에서 보는 렌더링 관계는 위와 같이 3단계가 있습니다.</p>
<p><strong>1. Transition 단계</strong></p>
<p>Transition는 state 변경 직후에 일어날 수 있는 UI 렌더링 단계입니다.</p>
<ul>
<li>Pending : useTransition 훅을 사용하면 state 변경 직후에 UI를 업데이트하지 않고 현 UI를 잠시 유지할 수 있는데 이를 Pending 상태라고 합니다.</li>
<li>Receded : useTransition 훅을 사용하지 않은 기본 상태. state 변경 직후 UI(Loader를 생각)가 변경됩니다. </li>
</ul>
<p>🤔 <strong>그럼 Pending으로 걸어놨는데, 로딩 시간이 길어지면 UI에 안 좋은 거 아닌가?</strong></p>
<p>→ <strong>Pending 상태에서도 Receded 상태로 넘어갈 수 있습니다!</strong> Pending 상태의 시간이 useTransition 옵션으로 지정된 <code>timeoutMs</code>를 넘으면 강제로 Receded 상태로 넘어갑니다 🙌</p>
<pre><code class="language-jsx">const [isPending, startTransition] = useTransition({
    timeoutMs: 3000
  });</code></pre>
<p><strong>2. Loading 단계</strong></p>
<p>현재 컴포넌트의 자식 요소에서 발생되는 비동기 처리하는 과정을 처리 중인 단계입니다. 위에서 봤던 <code>&lt;h1&gt;로딩중입니다~&lt;/h1&gt;</code>가 화면에 나왔었죠? 그걸 말합니다.</p>
<p><strong>3. Done 단계</strong></p>
<p>비동기 처리가 완료됨에 따라 완성된 UI를 보여줍니다.</p>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://www.youtube.com/watch?v=FvRtoViujGg">토스 | SLASH 21 - 프론트엔드 웹 서비스에서 우아하게 비동기 처리하기</a></li>
<li><a href="https://velog.io/@blackeichi/%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%B4%EB%9E%80">비동기 프로그래밍이란?</a></li>
<li><a href="https://velog.io/@seungchan__y/React-18-Concurrent-%EB%A7%9B%EB%B3%B4%EA%B8%B0#-concurrent-feature-%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0">React 18 Concurrent로 UX 개선하기</a></li>
<li><a href="https://tecoble.techcourse.co.kr/post/2021-07-24-concurrent-mode/">사용자 경험 개선 2편 - react concurrent mode</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Type Guard 함수와 '즉시 실행 함수'를 같이 쓰면 일어나는 일]]></title>
            <link>https://velog.io/@doeunnkimm_/%ED%83%80%EC%9E%85-%EA%B0%80%EB%93%9C-%ED%95%A8%EC%88%98%EC%99%80-%EC%A6%89%EC%8B%9C-%EC%8B%A4%ED%96%89-%ED%95%A8%EC%88%98-%EA%B0%99%EC%9D%B4-%EC%8D%A8%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@doeunnkimm_/%ED%83%80%EC%9E%85-%EA%B0%80%EB%93%9C-%ED%95%A8%EC%88%98%EC%99%80-%EC%A6%89%EC%8B%9C-%EC%8B%A4%ED%96%89-%ED%95%A8%EC%88%98-%EA%B0%99%EC%9D%B4-%EC%8D%A8%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Thu, 31 Aug 2023 05:07:01 GMT</pubDate>
            <description><![CDATA[<h1 id="우선-가드해야-할-타입들을-살펴봅시다">우선 가드해야 할 타입들을 살펴봅시다</h1>
<p>일단 가드해야 할 타입들은 다음과 같습니다.</p>
<pre><code class="language-ts">export enum TodoEnum {
  DAILY = &#39;DAILY&#39;,
  WEEKLY = &#39;WEEKLY&#39;,
  MONTHLY = &#39;MONTHLY&#39;,
}

export type TodoDataBase =
  | {
      type: TodoEnum.DAILY
      content: string
      title: string
    }
  | {
      type: TodoEnum.WEEKLY
      total: Date
    }
  | {
      type: TodoEnum.MONTHLY
      goal: string
    }</code></pre>
<p>위 타입들을 파악해 보자면, 우선 <code>TodoEnum</code>이라는 이넘 타입이 존재합니다. 즉, API 콜을 통해 받을 수 있는  Todo의 종류는 총 3가지로 이넘 타입을 이용해 선언해 두었네요.</p>
<p>그리고 이때 받게 되는 Todo의 종류마다 조금씩 다른 객체를 들고 옵니다. 그래서 위와 같이 <code>TodoDataBase</code>라는 타입은 유니온 타입으로 이거일 수도 이거일 수도 이거일 수도라고 설명하고 있네요.</p>
<h1 id="이런-상황을-떠올려봅시다">이런 상황을 떠올려봅시다</h1>
<p>만약 한 컴포넌트에서 위와 같이 세 가지 Todo 종류를 처리해야 한다고 생각해 봅시다. 그렇다면 todo의 <strong>타입에 따라 조금씩 다른 View 로직</strong>을 작성해야겠죠?</p>
<p>우선 <code>TodoEnum</code>을 이용해서 <code>TodoDataBase</code>에서 유니온 중 하나를 가져오는 유틸 타입을 하나 선언해 봅시다.</p>
<h1 id="util-타입-만들어서-쓰기">Util 타입 만들어서 쓰기</h1>
<pre><code>📌 Util은 &#39;유용한, 도움이 되는, 쓸모 있는&#39;을 뜻하며...</code></pre><p>지금 만들 유틸 타입은 <code>TodoEnum</code>을 이용해서 <code>TodoDataBase</code>에서 유니온 중 하나를 가져오는 타입입니다.</p>
<p>대강 구상을 해본다면, <code>TodoEnum</code>을 제네릭으로 받아서 <code>TodoDataBase</code>에서 <strong>Extract</strong>하면 되겠다 생각이 날 수 있습니다.</p>
<pre><code class="language-ts">export type TodoType&lt;T extends TodoEnum = TodoEnum&gt; = Extract&lt;
  TodoDataBase,
  { type: T }
&gt;</code></pre>
<p>조금 설명을 보태보자면, 제네릭에 <code>TodoEnum</code> 중 하나를 받습니다. 예를 들면 <code>TodoEnum.DAILY</code> 정도가 되겠네요. 그럼 이때 <code>TodoDataBase</code>에서 Extract합니다. 어떤 애를? 제네릭으로 받은 <code>type</code>을 가진 애를!</p>
<p>추가로 기본적으로 TodoEnum 자체를 제네릭으로 넣어두어, 아무 것도 입력하지 않은 경우 <code>TodoDataBase</code> 전체를 가져올 수 있도록 합니다.</p>
<p>연습으로 아래와 같이 작성했다면 타입 A는 어떤 형태일까요?</p>
<pre><code class="language-ts">type A = TodoType&lt;TodoEnum.DAILY&gt;</code></pre>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/cdf29af0-eeb7-4afe-920c-cb485992eea1/image.png" width="50%" height="50%"/></p>

<p><code>TodoEnum.DAILY</code> 를 제네릭으로 넘겼더니 원하는대로 이에 해당하는 타입을 가져온 것을 확인할 수 있었습니다.</p>
<h1 id="타입-가드를-해봅시다">타입 가드를 해봅시다</h1>
<p><code>Type Alias</code> 혹은 <code>Interface</code>로 선언된 타입을 가드하기 위해서는 <strong>사용자 정의 타입 가드 함수</strong>를 사용할 수 있습니다.</p>
<p>그럼 아래와 같이 Todo 타입을 가드 함수를 만들 수 있습니다.</p>
<pre><code class="language-ts">const todotypeIsDaily = (todo:TodoDataBase): todo is TodoType&lt;TodoEnum.DAILY&gt; =&gt; todo.type === TodoEnum.DAILY;</code></pre>
<p>이제 이 <code>booelan</code> 값을 리턴하는 함수를 통해 <code>todo</code>가 무슨 타입인지를 구별하고, 이에 맞는 View 로직을 return 하면 될 것 같습니다.</p>
<h1 id="😥-좋지-못한-예시">😥 좋지 못한 예시</h1>
<p>사실 타입 가드를 하는 방법에는 여러 방법이 존재합니다. 현재 <code>TodoDataBase</code>를 살펴보면, 동일하게 <code>type</code>이라는 프로퍼티가 존재해 이를 통해 가드를 하는 것도 사실 가능합니다. <code>if todo.type === TodoEnum.DAILY</code> 이런 식으로 말이죠.</p>
<pre><code class="language-tsx">import type { TodoDataBase } from &#39;@/types/todo&#39;
import { TodoEnum } from &#39;../../types/todo&#39;

interface Props {
  todo: TodoDataBase
}

const OneTodo= = ({ todo }: Props) =&gt; {
  if (todo.type === TodoEnum.DAILY) {
    return (
      &lt;div&gt;
        &lt;h3&gt;{todo.title}&lt;/h3&gt;
        &lt;div&gt;{todo.content}&lt;/div&gt;
        &lt;hr /&gt;
      &lt;/div&gt;
    )
  }

  if (todo.type === TodoEnum.WEEKLY) {
    return (
      &lt;div&gt;
        &lt;h3&gt;{JSON.stringify(todo.total)}&lt;/h3&gt;
      &lt;/div&gt;
    )
  }

  if (todo.type === TodoEnum.MONTHLY) {
    return &lt;div&gt;{todo.goal}&lt;/div&gt;
  }
}
export default OneTodo</code></pre>
<p>그런데, 이렇게 <strong>if문을 통해 View 로직을 return 하는 패턴은 유지보수에 좋지 못합니다.</strong> 중복되는 코드를 작성하게 되거나, 혹은 이벤트 함수가 추가될 경우 위 아래 코드를 넘나들며 확인해야 할 것입니다.</p>
<p>만약 컴포넌트를 분리한다고 하더라도, 해당 컴포넌트에서 중요한 로직이라고 할 수 있는 내용들 혹은 이벤트 함수들이 모두 감춰지게 되어 분리된 컴포넌트들을 넘나 들며 확인해야 할 수 있습니다.</p>
<p>물론 상황에 따라 다를 수 있지만요!</p>
<h1 id="🤩-즉시-실행-함수-활용">🤩 즉시 실행 함수 활용</h1>
<p>우선 <strong>즉시 실행 함수</strong>란, 선언과 동시에 실행하여 return 값 할당까지 즉시 가능합니다.</p>
<p>저는 아래와 같이 타입 가드 함수와 즉시 실행 함수를 이용했습니다.</p>
<pre><code class="language-tsx">const TODO = (() =&gt; {
  if (todoTypeIsDaily(todo)) {
    const { type, title, content } = todo
    return { type, title, content }
  }

  if (todoTypeIsMonthly(todo)) {
    const { type, goal } = todo
    return { type, goal }
  }

  if (todoTypeIsWeekly(todo)) {
    const { type, total } = todo
    return { type, total }
  }
})()</code></pre>
<p>의문이 들 수 있는 점들에 대해 차근차근 정리해 봅시다.</p>
<h2 id="사용자-정의-타입-가드-함수-vs-객체-속성-비교">사용자 정의 타입 가드 함수 vs 객체 속성 비교</h2>
<p>우선 맨 처음에 사용했던 <code>if todo.type === TodoEnum.DAILY</code> 방식과는 다르게 사용자 정의 타입 가드를 선언해 사용했습니다.</p>
<p>1️⃣ <strong>사용자 정의 타입 가드 함수를 사용할 경우</strong> 좀 더 안전하게 타입 검사가 가능해져 복잡하거나 다양한 조건 및 형태의 데이터를 다루어야 하는 경우 유용할 수 있습니다.</p>
<p>2️⃣ <strong>반면 단순히 객체 속성을 비교해 가드하는 경우</strong> 간단하며 직관적인 비교 로직으로 필요한 기능을 충분히 구현할 수 있는 경우 사용하기 좋습니다.</p>
<p>우리의 경우라면 단순히 객체 속성을 비교해 가드해도 충분할 수 있지만, 만약 type이 동일하게 <code>TodoEnum.DAILY</code>이고 나머지가 달라진다면요? 추가 조건문을 걸어 구분해 주어야 할 것이며, 이는 길어질 수록 가독성이 떨어지게 될 것입니다. 따라서 구체적인 데이터 형태를 다루기 위해서라면 사용자 정의 타입 가드 방법이 좀 더 안전해 보입니다.</p>
<h2 id="view-로직에-적용하기">View 로직에 적용하기</h2>
<p>위에서 만들었던 즉시 실행 함수가 실행되면 바로 todo의 타입을 체크하고 <code>TODO</code>에는 바로 객체가 반환됩니다. 이렇게 되면, View 로직에서는 동시에 가능한 모든 여러 프로퍼티에 접근할 수 있게 됩니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/cd9812aa-5f10-4a16-aa06-ba85ad4be090/image.png" width="40%" height="40%"/></p>

<p>이제 if 문에 따라 View 로직을 따로 따로 분리해 놓지 않아도 되었지만, 명확하게 todo의 타입에 따른 View 로직을 구분해 볼 수 없다는 점이 아쉽긴 합니다.</p>
<p>따라서 위와 같이 즉시 실행 함수를 사용하는 방법은 중복된 로직이 많고 타입의 종류에 따라서는 일부 UI의 추가가 필요한 상황에 좋을 것 같습니다.</p>
<p>결과적으로는, 아래처럼 View 로직을 작성했습니다.</p>
<pre><code class="language-tsx">return (
  &lt;&gt;
    &lt;h2&gt;{TODO?.type}&lt;/h2&gt;
    &lt;hr /&gt;

    &lt;section&gt;
      {/* DAILY */}
      &lt;h3&gt;{TODO?.title}&lt;/h3&gt;
      &lt;p&gt;{TODO?.content}&lt;/p&gt;

      {/* WEEKLY */}
      &lt;h3&gt;{JSON.stringify(TODO?.total)}&lt;/h3&gt;

      {/* MONTHLY */}
      &lt;h3&gt;{TODO?.goal}&lt;/h3&gt;
    &lt;/section&gt;
    &lt;/&gt;
)</code></pre>
<h2 id="고차함수로-사용자-정의-타입-가드-함수도-util로-만들어-쓰기">&#39;고차함수&#39;로 사용자 정의 타입 가드 함수도 Util로 만들어 쓰기</h2>
<p>위에서 사용자 정의 타입 가드 함수 하나를 이렇게 작성했었습니다.</p>
<pre><code class="language-tsx">const todotypeIsDaily = (todo:TodoDataBase): todo is TodoType&lt;TodoEnum.DAILY&gt; =&gt; todo.type === TodoEnum.DAILY;</code></pre>
<p>그런데 우리는 TodoEnum에 3가지가 있었죠? 그럼 아래와 같이 비슷하게 3번 써줘야 할 것입니다.</p>
<pre><code class="language-tsx">const todotypeIsDaily = (todo:TodoDataBase): todo is TodoType&lt;TodoEnum.DAILY&gt; =&gt; todo.type === TodoEnum.DAILY;
const todotypeIsWeekly = (todo:TodoDataBase): todo is TodoType&lt;TodoEnum.WEEKLY&gt; =&gt; todo.type === TodoEnum.WEEKLY;
const todotypeIsMonthly = (todo:TodoDataBase): todo is TodoType&lt;TodoEnum.MONTHLY&gt; =&gt; todo.type === TodoEnum.MONTHLY;</code></pre>
<p>저는 이렇게 계속 비슷하지만 반복되는 로직을 보고 유틸로 만들어겠다고 생각이 들었는데요.</p>
<p>우선 위 타입 가드 함수들은 <strong>함수</strong>입니다. 그러므로 지금 만들 Util은 함수를 리턴하는 함수가 될 것입니다. 이때 함수를 리턴하는 함수를 <strong>고차 함수</strong>라고 부릅니다.</p>
<pre><code>📌 고차 함수(Higher order function)
   고차 함수는 함수를 인자로 전달받거나 함수를 결과로 반환하는 함수를 말한다.</code></pre><p>아래와 같이 타입 가드 함수를 리턴하는 함수를 선언해 주었습니다.</p>
<pre><code class="language-tsx">export const todoTypeIs = &lt;T extends TodoEnum&gt;(type: T) =&gt; {
  return (todo: TodoDataBase): todo is TodoType&lt;T&gt; =&gt; todo.type === type
}</code></pre>
<p>복잡하네요. 좀 살펴봅시다. 일단 제네릭을 사용합니다. 인자로 TodoEnum을 extends 하고 있는 예를 들어 <code>TodoEnum.DAILY</code> 이런 식으로 넘겨 받게됩니다. 그러면 함수를 리턴하게 되어있죠? 이때 리턴되는 함수를 떼서 봅시다.</p>
<pre><code class="language-ts">(todo: TodoDataBase): todo is TodoType&lt;T&gt; =&gt; todo.type === type</code></pre>
<p>우리가 사용자 정의 타입 가드 함수를 선언했을 때 코드와 비슷합니다. 고차 함수의 인자로 받은 제네릭을 안에 중복되는 로직 중에 유일하게 달랐던 그 부분에 넣어주었을 뿐입니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/a061504d-25eb-41ff-bfe1-75d9eb772534/image.png" alt=""></p>
<p>사용하게 될 때는 아래와 같습니다.</p>
<pre><code class="language-tsx">const todoTypeIsDaily = todoTypeIs(TodoEnum.DAILY) //todoTypeIsDaily는 함수
...
if (todoTypeIsDaily(todo)) { ... }</code></pre>
<h1 id="결론">결론</h1>
<p>이번 글에서는 Util 타입 혹은 Util 타입 가드 함수를 선언해 사용하는 방법 그리고 즉시 실행 함수를 활용하는 방법에 대해 알아보았습니다. 즉시 실행 함수를 사용함으로써 로직을 분리하지 않고 작성할 수 있었지만, 명확히 구분해 보지 못하게 되었다는 단점이 생기기도 했습니다. 따라서 상황에 따라 선택할 수 있어야겠습니다 :)</p>
<p>제가 이번 글을 남긴 이유는 이 방법이 좋은 방법이다! 라기보다는 이런 방법도 있구나 하고 정리해 보았던 것이였습니다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React에서 TypeScript가 쓰고 싶다면 알아야 할 타입들]]></title>
            <link>https://velog.io/@doeunnkimm_/React%EC%97%90%EC%84%9C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A5%BC-%EC%9C%84%ED%95%B4-%EC%A7%80%EC%9B%90%ED%95%98%EB%8A%94-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@doeunnkimm_/React%EC%97%90%EC%84%9C-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A5%BC-%EC%9C%84%ED%95%B4-%EC%A7%80%EC%9B%90%ED%95%98%EB%8A%94-%ED%83%80%EC%9E%85</guid>
            <pubDate>Sat, 05 Aug 2023 15:53:24 GMT</pubDate>
            <description><![CDATA[<h1 id="react에서-타입스크립트-지원을-위해-자체적으로-정의한-타입">react에서 타입스크립트 지원을 위해 자체적으로 정의한 타입</h1>
<pre><code>✨ 생성된 프로젝트에서 react에서 타입스크립트를 지원하기 위한 타입들에 대하여 설명하고 실제로 적용해보기</code></pre><h2 id="1-reactfc">1. React.FC</h2>
<pre><code>- React 18버전 이전까지 FC 사용을 지양했던 이유와 이제 다시 사용할 수 있는 이유는 무엇일까?
- 만약 FC를 사용할 수 없는 환경이라면 이유는 무엇이고 어떻게 대처가 가능한가</code></pre><h3 id="fc-">FC ?</h3>
<p><code>Function Component</code> 타입의 줄임말로, React + Typescript 조합으로 개발할 때 사용하는 타입입니다. 함수형 컴포넌트 사용 시 타입 선언에 쓸 수 있도록 React에서 제공하는 타입입니다.</p>
<h3 id="fc가-태어난-배경">FC가 태어난 배경</h3>
<p><code>React.FC</code>는 함수 컴포넌트의 Props 타입을 간결하게 표현하기 위해 만들어졌습니다. 이를 통해 타입스크립트를 사용하는 프로젝트에서 함수 컴포넌트의 Props를 명시적으로 지정할 수 있었습니다.</p>
<p>뿐만 아니라 Props에 기본적으로 <code>children</code>이 포함되어 있어, 컴포넌트에서 자식 요소를 손쉽게 다룰 수 있기를 의도했다고 합니다.</p>
<h3 id="reactfc의-사용">React.FC의 사용</h3>
<p>개인적으로 TS+React 프로젝트에서 컴포넌트를 작성할 때 아래와 같은 형태를 많이 사용했습니다.</p>
<pre><code class="language-tsx">import { FC } from &#39;react&#39;

interface Props {
    name: string
}

const Foo: FC&lt;Props&gt; = ({ name }) =&gt; {
    return (
        ...
    )
}</code></pre>
<p>참고한 블로그에서도 그렇고 저도 그렇고, 별 다른 이유는 없이 <code>FC</code>에 제네릭을 통해 Props를 전달할 수 있어 오히려 간결하고 편하다는 느낌으로 계속 사용해왔던 것 같습니다. <code>Props</code>를 넘겨받는 방법이 위와 같은 방법 하나가 아님을 알고 있긴 해 개인 취향 차이로만 생각했었습니다.</p>
<h3 id="props를-넘겨-받는-또-다른-방법들">Props를 넘겨 받는 또 다른 방법들</h3>
<p><strong>FC를 사용하지 않는 방법</strong></p>
<pre><code class="language-tsx">interface Props {
    name: string
}

const Foo = ({ name }: Props) =&gt; {
    return (
        ...
    )
}</code></pre>
<h3 id="fc를-쓰지-말야야-하는-이유">FC를 쓰지 말야야 하는 이유</h3>
<h4 id="children을-암시적으로-가지고-있습니다">children을 암시적으로 가지고 있습니다</h4>
<p><code>FC</code>를 이용하면 컴포넌트 props는 type이 <code>ReactNode</code>인 <code>children</code>을 암시적으로 가지게 됩니다.</p>
<pre><code class="language-tsx">const App: React.FC = () =&gt; {
  return &lt;div&gt;hi&lt;/div&gt;
}

const Example = () =&gt; {
  return (
    &lt;App&gt;
      &lt;div&gt;Unwanted children&lt;/div&gt;
    &lt;/App&gt;
  )
}</code></pre>
<p>위 코드를 보게 되면 <code>&lt;App /&gt;</code> 컴포넌트에서 <code>children</code>을 다루고 있지 않음에도 <code>Example</code>에서 <code>children</code>을 넘겨주고 있으며, 어떤 런타임 에러도 발생하지 않습니다.</p>
<pre><code>🤔 이게 왜 문제가 되나요?</code></pre><pre><code>👩🏻‍💻 Props의 명확성
`React.FC`가 `children`을 암시적으로 포함하면서 `Props`의 타입이 명확하지 않아 의도하지 않게 동작하는 등의 문제가 있었습니다.</code></pre><h3 id="👏-18-버전에서는-없어졌습니다">👏 18 버전에서는 없어졌습니다</h3>
<p>React 18 업데이트로, <code>FC</code>의 암시적인 <code>children</code>이 <strong>삭제</strong>되었습니다. 해당 변경 사항은 이 <a href="https://github.com/DefinitelyTyped/DefinitelyTyped/pull/56210">PR</a>에서 확인할 수 있습니다.</p>
<h3 id="fc를-사용할-수-없는-환경">FC를 사용할 수 없는 환경?</h3>
<pre><code>export default function App() {} 와 같은 일반 함수 선언문에서 ❌</code></pre><p>위와 같은 형태에서는 <code>FC</code>를 사용하지 않고 명확하게 지정해주는 방법으로도 가능합니다.</p>
<pre><code class="language-tsx">interface Props {
    name: string
}

export default function Foo({ name }: Props) {
    return (
        ...
    )
}</code></pre>
<h3 id="🔥-그래서-fc랑-명확하게-지정하는-방법-둘-중에-뭘-써야-하는가">🔥 그래서 FC랑 명확하게 지정하는 방법, 둘 중에 뭘 써야 하는가?</h3>
<pre><code>가장 문제였던 FC의 암묵적인 children이 18 버전에서 사라졌기 때문에
어떤 것을 사용해야 할 지의 판단은 개발자의 몫이 될 것 같습니다.

다만, FC에는 제네릭 타입이 포함되어 있기 때문에
타입이 길고 장황해질 수 있다는 의견도 있었습니다.

FC에 이미 제네릭이 있기 떄문에 Props에도 제네릭이 필요할 경우
FC&lt;Props&lt;string&gt;&gt;과 같이 작성해줘야 해 복잡해질 수 있다고 보았습니다.</code></pre><p>그래서 결론적으로 저의 생각은 아래와 같습니다.</p>
<pre><code>기본적으로 프로젝트의 컨벤션에 맞게 작성하면 될 것 같습니다.
저 혼자 프로젝트를 진행한다면 FC를 사용할 것 같습니다.

제가 FC를 사용하는 이유는 FC 이후 부분(매개변수 부분)이, 기본 React에서의 코드 스타일과 비슷하기 때문입니다.

✔️ FC를 사용하는 경우
const Foo: FC&lt;Props&gt; = ({ name, age }) =&gt; {}

✔️ 명시적으로 나타내는 경우
const Foo = ({ name, age }: Props) =&gt; {}</code></pre><h2 id="2-reactnode">2. ReactNode</h2>
<p><code>ReactNode</code>는 <code>children</code> 속성의 타입으로 가장 많이 사용하는 타입이기도 합니다.</p>
<pre><code class="language-tsx">import { ReactNode } from &#39;react&#39;

interface Props {
  children: ReactNode
}

const Component: FC&lt;Props&gt; = ({ children }) =&gt; {
  return &lt;div&gt;{children}&lt;/div&gt;
}</code></pre>
<p><a href="https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L179">@types/react에서 살펴본 ReactNode</a>은 다음과 같았습니다.</p>
<pre><code class="language-ts">type ReactNode =
  | ReactElement
  | string
  | number
  | Iterable&lt;ReactNode&gt;
  | ReactPortal
  | boolean
  | null
  | undefined</code></pre>
<p><code>ReactNode</code> 타입은 <code>jsx</code> 내에서 사용할 수 있는 모든 요소의 타입을 의미합니다. 즉 <code>string</code>, <code>null</code>, <code>undefined</code> 등을 포함하는 <strong>가장 넓은 범위</strong>를 갖는 타입이죠!</p>
<pre><code class="language-tsx">const node: React.ReactNode = &lt;div /&gt;
const node2: React.ReactNode = &#39;hello world&#39;
const node3: React.ReactNode = 123
const node4: React.ReactNode = undefined
const node5: React.ReactNode = null</code></pre>
<pre><code>⭐️ ReactNode는 가장 넓은 범위를 갖는 타입이며,
   원시타입 및 jsx 내에서 사용할 수 있는 모든 요소의 타입을 허용한다</code></pre><h2 id="3-reactelement">3. ReactElement</h2>
<p><code>ReactElement</code>는 <code>ReactNode</code>에 포함되어 있기도 합니다. 우선 <code>d.ts</code>에서 살펴봅시다.</p>
<pre><code class="language-ts">interface ReactElement&lt;
  P = any,
  T extends string | JSXElementConstructor&lt;any&gt; =
    | string
    | JSXElementConstructor&lt;any&gt;
&gt; {
  type: T
  props: P
  key: Key | null
}</code></pre>
<p><code>ReactElement</code> 는 <code>createElement</code> 함수를 통해 생성된 객체의 타입입니다. 즉, 위에서 알아보았던 <code>ReactNode</code>과 달리 <strong>원시타입을 포함하지 않고 완성된 <code>jsx</code> 요소만을 허용</strong>합니다.</p>
<pre><code>⭐️ ReactElement는 원시타입을 포함하지 않고 완성된 `jsx` 요소만을 허용한다.</code></pre><p>따라서 <strong><code>jsx</code> 요소를 리턴하는 <code>children</code>에 대해서는</strong> <code>ReactElement</code>을 타입으로 지정해 주어도 전혀 문제가 없습니다.</p>
<pre><code class="language-tsx">import { ReactElement } from &#39;react&#39;

interface Props {
  children: ReactElement
}

const Component: FC&lt;Props&gt; = ({ children }) =&gt; {
  return &lt;div&gt;{children}&lt;/div&gt;
}</code></pre>
<h2 id="4-propswithchildren">4. PropsWithChildren</h2>
<p><code>PropsWithChildren</code> 타입을 사용하게 되면 반복적으로 children 타입을 설정해줘야하는 번거로움이 사라질 수 있습니다.</p>
<pre><code class="language-tsx">import { PropsWithChildren } from &#39;react&#39;

interface Props {
  name: string
}

export const Foo: FC&lt;PropsWithChildren&lt;Props&gt;&gt; = ({ name, children }) =&gt; {
  return (
    &lt;&gt;
      &lt;div&gt;{name}&lt;/div&gt;
      &lt;div&gt;{children}&lt;/div&gt;
    &lt;/&gt;
  )
}</code></pre>
<p>이전에는 Props에 <code>children: ReactNode</code> 혹은 <code>children: ReactElement</code> 하고 적어주었었습니다.</p>
<p>반면, 위 코드에서는 Props에서 <code>children</code>을 명시하지 않고 바로 children을 사용해 주었습니다.</p>
<p><strong><code>d.ts</code>에서 살펴봅시다.</strong></p>
<pre><code class="language-ts">type PropsWithChildren&lt;P = unknown&gt; = P &amp; { children?: ReactNode | undefined }</code></pre>
<p><code>PropsWithChildren</code>의 <code>children</code> 타입이 <strong>옵셔널</strong>인 것을 확인할 수 있습니다.</p>
<pre><code>🤔 FC에서 children이 암시적으로 존재해서 문제가 있었다면서요.</code></pre><pre><code>👩🏻‍💻 맞아요.
   그래서 PropsWithChildren도 children을 넘겨주지 않아도
   에러가 발생하지 않기 때문에 의도하지 않은 동작을 할 수 있어요.

   따라서, children을 반드시 받아야 하는 경우에는
   PropsWithChildren을 사용하지 않는 게 좋아요.</code></pre><pre><code>🤔 그럼 그냥 ReactNode나 ReactElement를 사용하면 되나요?</code></pre><pre><code>👩🏻‍💻 네, 그래도 돼요.
   일반적인 경우에는 ReactNode를 사용하는 것 같아요.
   Props에 { children: ReactNode }로 명시해주면
   사용하는 쪽에서 &quot;children이 없으면 필수라고 에러&quot;로 알려줘요.</code></pre><h2 id="5-refobject">5. RefObject</h2>
<p>React에서 특정 DOM을 선택해야할 땐 이 기능을 대체할 수 있는 useRef 훅을 제공합니다.</p>
<p><code>useRef</code>를 사용하다보면 인자로 어떨 때 null을 넣어야할지? 비어둘지? 고민을 하게 되었었는데요. 이와 관련이 되어 있습니다!</p>
<pre><code>⭐️ useRef에는 3가지 오버로딩이 존재

1. 인자: [초기값]         =&gt; 리턴: MutableRefObject&lt;T&gt;;

2. 인자: [초기값 | null]  =&gt; 리턴: RefObject&lt;T&gt;;

3. 인자: []             =&gt; 리턴: MutableRefObject&lt;T | undefined&gt;;</code></pre><p>위를 보게 되면 총 2개의 타입이 존재합니다. <code>MutableRefObject</code>과 <code>RefObject</code>입니다.</p>
<pre><code>✔️ useRef는 .current 프로퍼티에 변경 가능한 값을 담고 있는 “상자” 📦
  인수를 .current에 저장하게 된다.

  아래 두 개의 리턴타입은 .current 프로퍼티를 직접 수정 가능 여부에 따라 구분

✔️ MutableRefObject&lt;T&gt;
  직접 수정 가능 ⭕️

✔️ MutableRefObject&lt;T | undefined&gt;
  직접 수정 불가능 ❌, 다만 undefined이 아님이 체크되면 가능 ⭕️

✔️ RefObject&lt;T&gt;
  직접 수정 불가능 ❌</code></pre><p>즉, 특정 초기값 혹은 비어두게 되면 current를 직접 수정 가능하며, null을 부여할 경우 current를 직접 수정 불가능하게 됩니다. 쉽게 말해 null로 부여할 경우 아래와 같은 경우에 에러가 발생하는 것이죠.</p>
<pre><code class="language-tsx">const ref = React.useRef&lt;number&gt;(null)

ref.current += 1
//~~~~~~~~~~~~~~ 읽기 전용 속성이므로 &#39;current&#39;에 할당할 수 없습니다.</code></pre>
<pre><code>🤔 그래서 결론적으로 어떤 상황에 어떤 걸 사용해야 하나요?</code></pre><pre><code>👩🏻‍💻 DOM 요소를 참조하고 싶은 경우에는 null을 입력해주면 돼요.
   DOM 요소에 ref를 연결하고 싶다면 readonly인 RefObject만을 할당할 수 있어요.

   만약 DOM 요소에 MutableRefObject한 ref를 할당해주려면 아래와 같이 에러가 발생해요.</code></pre><p align="center"><img width="584" alt="image" src="https://github.com/Doeunnkimm/Frontend/assets/112946860/63e4dcba-4841-4035-927b-5a931b5eac02"></p>

<h2 id="6-setstateaction">6. SetStateAction</h2>
<p>Props로 상태를 업데이트하는 함수, 예를 들어 <code>setData</code>를 넘겨야 할 때도 있습니다. 이때 타입을 어떻게 주어야 할까요?</p>
<p>저는 평소에 아래와 같이 해주었었습니다.</p>
<pre><code class="language-tsx">interface Props {
  setData: (data: string) =&gt; void
}</code></pre>
<p>사실 위와 같이 타입을 선언해 주어도 문제를 없지만 <strong>일반 함수와 구별이 되지 않으며 state함수임을 이름을 통해서만 유추</strong>해야 합니다.</p>
<pre><code>⭐️ React에서는 state함수를 명시적으로 나타낼 수 있는 적절한 타입을 제공하고 있다

→ SetStateAction !!</code></pre><p>이떄 사용할 수 있는 타입이 바로 <code>SetStateAction</code>입니다. <code>SetStateAction</code>은 React의 <code>useState</code> 또는 <code>useReducer</code> 훅에서 상태 값을 업데이트하기 위해 사용되는 타입입니다. 이 타입은 새로운 상태 값을 계산하는 함수를 나타내며, 이 함수는 현재 상태 값을 인자로 받아 새로운 상태 값을 반환합니다.</p>
<p>사용 방법은 아래와 같습니다.</p>
<pre><code class="language-tsx">interface Props {
  setData: Dispatch&lt;SetStateAction&lt;string&gt;&gt;
}</code></pre>
<p><code>d.ts</code>에서 <code>SetStateAction</code>는 다음과 같습니다.</p>
<pre><code class="language-ts">type SetStateAction&lt;S&gt; = S | ((prevState: S) =&gt; S)</code></pre>
<p>제네릭에 타입을 넘겨주면 state함수의 형태에 알맞게 타입을 지정해줍니다. 그런데 여기서 드는 생각이 있습니다.</p>
<pre><code>🤔 그럼 SetStateAction만 써도 될 거 같은데, 겉에 왜 Dispatch가 필요한가요?</code></pre><pre><code>👩🏻‍💻 그럼 한번 없애보고 뭐가 문제가 되는지 알아볼까요?

   interface Props {
     setData: SetStateAction&lt;string&gt;
   }

   이렇게 한번 해봅시다. 그럼 아래와 같이 에러가 발생해요.</code></pre><p align="center"><img width="499" alt="image" src="https://github.com/Doeunnkimm/Mobi/assets/112946860/408a4d9f-d024-4c11-abe4-3d505cf8a441">
</p>

<pre><code>🤔 호출 시그니처부터 모르겠는데요..</code></pre><pre><code>👩🏻‍💻 호출 시그니처는 타입스크립트에서 함수의 타입을 지정할 때 사용하는 문법이에요.
   함수에 함수를 인수로 전달하거나,
   함수를 반환하는 경우 이 문법을 통해 인수나 반환 함수의 타입을 지정할 수 있어요.

   SetStateAction을 다시 살펴보면 (prev: S) =&gt; S 라고 되어있어요.
   사실 이는 우리가 setState(여기에) 넣는 것이고
   정확하게는 아무것도 리턴하지 않아요. 아래 제가 테스트해 본 결과를 같이 봅시다.</code></pre><pre><code class="language-tsx">const handleInputValue = () =&gt; {
  const test = setData(&#39;hello&#39;)
  console.log(`test: ${test}`) // test: undefined
}</code></pre>
<p align="center"><img width="467" alt="image" src="https://github.com/Doeunnkimm/Mobi/assets/112946860/a3a7d5a1-271c-49de-884d-19a9dbd7267c"></p>

<pre><code>👩🏻‍💻 위처럼 undefined 즉, 아무 것도 리턴하지 않고 있는 것도 확인해보았고,
   setData에 마우스를 가져가서 확인해도 리턴타입이 void예요.

   결론적으로, SetStateAction는 본인을 인자로 하여 void를 리턴하도록 해줄 수 있는
   호출 시그니처라는 자기를 감싸주는 그런 것이 필요했던 거죠.</code></pre><h2 id="7-dispatch">7. Dispatch</h2>
<p>위에서 Dispatch의 필요성에 대해 조금 알아보았습니다.</p>
<pre><code>⭐️ Dispatch는 React에서 상태를 업데이트하는 함수의 호출 시그니처</code></pre><p><code>d.ts</code>에서 직접 확인해봅시다.</p>
<pre><code class="language-ts">type Dispatch&lt;A&gt; = (value: A) =&gt; void</code></pre>
<p>위에서 알아보았던 대로, 리턴타입을 void로 해주는 호출 시그니처의 모습이 맞았습니다.</p>
<h2 id="8-type-alias와-interface의-차이점">8. type alias와 interface의 차이점</h2>
<pre><code>- 각각 type alias와 interface로 props 타입을 정의하고 주석을 통해 차이점을 작성
- 비교를 통해 무엇을 사용하는게 좋을지 자기 의견을 자유롭게 써볼 것</code></pre><p>타입스크립트에서 <code>named Type</code>을 정의하는 방법은 두 가지가 있습니다.</p>
<pre><code class="language-ts">// type alias
type TState = {
  name: string
  age: number
}

// interface
interface IState {
  name: string
  age: number
}</code></pre>
<pre><code>🤔 언제 type alias / interface를 사용해야 하나요?</code></pre><pre><code>👩🏻‍💻 대부분의 경우에는 type alias를 사용해도 되고 interface를 사용해도 돼요.
   그러나, 둘 사이에 존재하는 차이를 분명히 알고
   같은 상황에서는 동일한 방법으로 사용해 일관성을 유지해야 해요.</code></pre><p>차이점에 대해 알아봅시다.</p>
<hr>
<p><strong>1️⃣ 차이점1: 타입을 확장하는 방식</strong></p>
<pre><code>✔️ type alias : &amp;
✔️ interface  : extends</code></pre><p>여기서 말하는 <strong>타입 확장</strong>은 기존에 정의된 타입을 기반으로 새로운 타입을 만드는 것을 의미합니다.</p>
<pre><code class="language-ts">// type alias
type Person = {
  name: string
  age: number
}

type Developer = Person &amp; { skill: string }</code></pre>
<pre><code class="language-ts">// interface
interface Person {
  name: string
  age: number
}

interface Developer extends Person {
  skill: string
}</code></pre>
<hr>
<p><strong>2️⃣ 차이점2: 객체만 허용하는가</strong></p>
<pre><code>✔️ type alias : 리터럴 타입부터 객체까지 다룬다
✔️ interface  : 객체만 다룬다</code></pre><p><code>interface</code>의 경우 객체의 타입만을 다룹니다. 반면 <code>type alias</code>는 객체뿐만 아니라 리터럴 타입까지 해당 리터럴을 유니온 타입으로까지도 표현이 가능합니다.</p>
<pre><code class="language-ts">// type alias
type Color = &#39;Red&#39; | &#39;Green&#39; | &#39;Blue&#39;

// interface
interface ?? // 객체가 아닌 것은 다룰 수 X</code></pre>
<hr>
<p><strong>3️⃣ 차이점3: mapped type 사용이 가능한가</strong></p>
<pre><code>✔️ type alias : 가능 ⭕️
✔️ interface  : 불가능 ❌</code></pre><p><code>mapped type</code>은 기존의 타입을 변환하여 새로운 타입을 만들기 위한 도구로, 기존의 타입을 순회합니다.</p>
<pre><code class="language-ts">// type alias
type PersonField = &#39;name&#39; | &#39;address&#39; | &#39;phone&#39;

type Person = {
  [key in PersonField]: string
}</code></pre>
<p>비슷하게 <code>interface</code>를 통해 선언해보면 에러가 발생합니다.</p>
<p align="center"><img width="453" alt="image" src="https://github.com/Doeunnkimm/Frontend/assets/112946860/a5c65cf8-9d48-438b-b426-b0ca58be619f">
</p>

<hr>
<p><strong>4️⃣ 차이점4: 선언 병합이 가능한가</strong></p>
<pre><code>✔️ type alias : 불가능 ❌
✔️ interface  : 가능 ⭕️</code></pre><p>타입스크립트에서 선언 병합(declaration merging)이란 <strong>같은 이름을 가진 여러 선언들을 하나로 합치는 기능</strong>을 말합니다.</p>
<pre><code class="language-ts">// interface
interface Person {
  name: string
  age: string
}

interface Person {
  address: string
}</code></pre>
<p>위와 같이 작성하면 <code>Person</code>이라는 타입은 결국 아래와 같은 형태로 병합됩니다.</p>
<p align="center"><img width="341" alt="image" src="https://github.com/Doeunnkimm/Frontend/assets/112946860/46e39fd2-2e9b-485c-a4f4-9548f325645d"></p>

<p>병합되어 결국에는 <code>name</code>, <code>age</code>, <code>address</code>가 프로퍼티가 된 것을 확인할 수 있었습니다.</p>
<p>이를 비슷하게 <code>type alias</code>에서 하게 되면 아래와 같이 <strong>식별자가 중복되었다</strong>면서 에러가 발생합니다.</p>
<p align="center"><img width="364" alt="image" src="https://github.com/Doeunnkimm/Frontend/assets/112946860/1bc42b18-daaa-47fa-b826-d22e0f28f269"></p>

<hr>
<pre><code>🤔 차이점은 알겠는데, 그래서 언제 어떤 걸 써야 하나요?</code></pre><pre><code>👩🏻‍💻 이 또한 프로젝트의 컨벤션에 맞게 사용해야겠지만, 저 혼자 프로젝트를 진행한다면

   특별한 경우를 제외하곤 interface를 사용할 것 같습니다.
   항상 확장 가능성을 염두해 둔다면, interface 사용을 좀 더 고려할 것 같습니다.

   여기서 특별한 경우라면 튜플, 리터럴, 유니온 등과 같은 type alias에서만 사용 가능한 경우입니다.</code></pre><h1 id="참고문서">참고문서</h1>
<ul>
<li><a href="https://emewjin.github.io/why-not-fc/">리액트에서 FC를 사용하지 말아야 하는 이유</a></li>
<li><a href="https://itchallenger.tistory.com/641">타입스크립트 : React.FC는 그만! children 타이핑 올바르게 하기</a></li>
<li><a href="https://merrily-code.tistory.com/209">ReactNode, ReactChild, ReactElement 타입 비교</a></li>
<li><a href="https://www.totaltypescript.com/jsx-element-vs-react-reactnode">React.ReactNode vs JSX.Element vs React.ReactElement</a></li>
<li><a href="https://velog.io/@kkojae91/PropsWithChildren%EB%8A%94-%EC%95%88%EC%A0%84%ED%95%9C-%ED%83%80%EC%9E%85%EC%9D%BC%EA%B9%8C">PropsWithChildren는 안전한 타입일까?!</a></li>
<li><a href="https://yceffort.kr/2021/03/typescript-interface-vs-type">타입스크립트 type과 interface의 공통점과 차이점</a></li>
<li><a href="https://ch3coo2ca.github.io/2022-06-20/useref-types-in-typescript">useRef의 3가지 정의와 타입 알아보기 (feat. MutableRefObject, RefObject)</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SVG(Scalable Vector Graphics) 알아보기]]></title>
            <link>https://velog.io/@doeunnkimm_/SVGScalable-Vector-Graphics-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@doeunnkimm_/SVGScalable-Vector-Graphics-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 05 Aug 2023 03:58:14 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/0904aa17-6a98-4fb1-a03f-7e7d901da344/image.png" alt=""></p>
<p>개발을 하다보면 위와 같이 다양한 이미지 형식을 마주하게 되는데요! 그 중에서도 SVG는 특이한 점들이 있습니다. SVG는 왜 이미지 프로그램으로는 열리지 않고 크롬과 같은 웹 브라우저로 열어야 보이는지? 그리고 <code>&lt;svg&gt;</code> 태그로만 구성되지 않고 안쪽에 또 다른 태그들이 왜 있는지?</p>
<p>그래서 이번 글에서는 이렇게 특이한(?) SVG 형식에 대해 알아보려고 합니다 :)</p>
<h1 id="래스터-이미지가-아니라-벡터">래스터 이미지가 아니라 벡터</h1>
<p>일반적인 이미지(JPG, GIF, PNG 등)은 래스터(Raster) 형식으로 되어 있습니다. 각 픽셀에 색을 포함한 형태의 데이터가 저장되는 형태입니다. 즉, <strong>작은 점을 무수히 여러 번 찍어 만들어낸 이미지</strong>를 가리킵니다.</p>
<p>선과 면을 채우는 대신 점을 사용하는 이유는 적은 물감으로 최대한 완성도 높은 이미지를 표현하기 위함이라고 합니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/dc141663-7931-4e44-aeff-e7665c4a0e12/image.png" alt="raster_image" width="55%"/></p>

<p>따라서 래스터 형식의 이미지를 확대할 경우, 픽셀이 눈에 보일만큼 이미지 품질이 저하되어 보이게 됩니다. 또한, 축소를 하거나 회전 등의 이미지 변형 작업을 할 경우, 안티앨리어싱 과정이 반복되면서 품질 저하가 일어나기 때문에 원본을 완벽하게 유지할 수 없게 됩니다.</p>
<blockquote>
<p>📌 안티앨리어싱 과정
앨리어싱(Aliasing)은 디지털 처리 과정에서 발생하게 되는 노이즈를 말하는 용어
다시 말해, 안티앨리어싱은 그래픽 노이즈를 방지하는 기술
즉, 위 사진에 보았던 우둘투둘한 경계의 그래픽을 약간 뭉개는 방법으로 처리하여 매끄럽게 만드는 기술</p>
</blockquote>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/f9456b11-04ec-4d07-b65d-9fcd98ced4d1/image.png" /></p>

<p>확대했을 때 이미지가 깨지는 것을 확인할 수 있습니다.</p>
<p>이와 다르게 <strong>벡터 형식</strong>은 점, 선, 면의 형태로 데이터가 저장됩니다. 점(Anchor)을 이용하면 직선과 곡선, 그리고 면까지 모두 만들어낼 수 있습니다. Figma와 같은 도구에서 펜 툴이 바로 이 원리를 사용하는 것입니다. 이러한 데이터를 가지고 위치나 형태를 수학적으로 계산하여 렌더하는 형식입니다. SVG는 벡터 형식을 가집니다.</p>
<p algin="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/c55b0b04-c9e8-4e43-a3a0-3f1213b7b833/image.gif" width="80%"/>
</p>

<p>그래서 벡터 형식인 SVG는 확대/축소하거나 변경해도 수학적으로 점, 선, 면이 계산되기 때문에 흔히 말하는 &quot;이미지 깨짐 현상&quot;이 없으며, 수정도 훨씬 용이합니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/31e8cdb3-328e-4ef7-982e-542051f74533/image.png" /></p>

<p>벡터 형식의 경우 확대를 해도 화질 저하가 없었습니다.</p>
<h2 id="벡터-형식에서의-계산-">벡터 형식에서의 계산 ?</h2>
<ul>
<li>벡터 형식: 데이터를 가지고 연산과 렌더 과정 필요</li>
<li>래스터 형식: 이미지 렌더가 되어 있는 상태여서 그대로 보여주면 된다</li>
</ul>
<p>그래서 기기의 성능을 많이 요구하지 않은 덕분에 래스터 형식의 이미지가 그동안 많이 사용되어 왔던 것입니다. 하지만 지금은 컴퓨터 성능이 많이 좋아져 렌더 과정에 큰 무리가 없으며, 텍스트 형태의 코드로 되어 있는 SVG가 래스터 형식의 이미지보다 훨씬 작은 파일 크기를 가진다는 장점이 있습니다.</p>
<p>하지만 카메라로 찍은 사진과 같이 복잡한 형태의 이미지일 경우, 벡터 형식이라면 연산 과정을 훨씬 더 복잡하며 시간도 오래 걸릴 수 있기 때문에, 이때는 래스터 형식이 더 좋습니다.</p>
<h2 id="브라우저에서만-svg-파일이-열렸던-이유">브라우저에서만 SVG 파일이 열렸던 이유</h2>
<p>SVG 파일은 이미지 프로그램이 아닌 브라우저에서 열리는 이유는 SVG의 내부 형식과 특성 때문인데요!</p>
<p>SVG는 <strong>XML 문서로 작성</strong>되어 있습니다. XML은 데이터 계층 구조로 나타내기 위한 마크업 언어(ex. HTML, XML, SVG, ...)입니다. </p>
<p>브라우저에서는 SVG 형식의 그래픽을 해석하고 화면에 표시하는 데 사용되는 계산을 수행할 수 있습니다.</p>
<p>반면 이미지 프로그램은 주로 래스터 이미지를 처리하고 편집하기 위한 용도로 되어 있어 브라우저를 통해 SVG를 확인하는 것이 일반적입니다.</p>
<h2 id="화질-저하-때문이면-png-출력을-높여볼까">화질 저하 때문이면 PNG 출력을 높여볼까</h2>
<p>틀린 말은 아니지만, PNG가 깨지는 문제를 해결하기 위해 PNG에 더 많은 데이터를 추가하여서 축소 시에도 선명하게 만들 수는 있지만 그러면 웹 성능에 치명적일 수 있습니다.</p>
<p>고해상도의 PNG는 일반적으로 크기가 매우 큽니다. 그렇기 때문에 용량이 큰 PNG를 로드하는 것 자체의 시간이 오래 걸리기 때문에 브라우저의 로딩 속도에 문제가 생길 수 있습니다.</p>
<p>반면 SVG는 코드로 이루어져 있기 때문에 바이트도 안 되는 크기로 이루어져 있어 PNG나 JPG의 이미지보다 용량적인 측면에서 훨씬 적어 웹사이트의 로딩 속도를 훨씬 빠르게 만들어줍니다.</p>
<h1 id="svg-태그">svg 태그</h1>
<p><code>&lt;svg&gt;</code> 태그는 svg 그 자체라기보다 SVG 그래픽을 담기 위한 박스📦라고 생각해야 합니다. svg 태그 내부에 담을 수 있는 것은 원, 사각형, 다각형, 라인, path 등이 있습니다.</p>
<p>내부에 그래픽을 담는 이유는 위에서 말한 것처럼, 수학적 계산을 위한 경로를 사용하여 도형을 그려야하기 때문입니다.</p>
<pre><code class="language-html">&lt;svg width=&quot;가로영역&quot; height=&quot;세로영역&quot;&gt;
  SVG 그래픽...
&lt;/svg&gt;</code></pre>
<h2 id="path-태그">path 태그</h2>
<p>svg 태그 안에 들어가는 태그 중 path 태그도 존재하는데요! 이는 선과 면을 이용한 태그로 여러 속성이 존재합니다.</p>
<table>
<thead>
<tr>
<th align="center">데이터 종류</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="center">M</td>
<td align="left">이동자표</td>
</tr>
<tr>
<td align="center">L</td>
<td align="left">선길이</td>
</tr>
<tr>
<td align="center">H</td>
<td align="left">수평선</td>
</tr>
<tr>
<td align="center">V</td>
<td align="left">수직선</td>
</tr>
<tr>
<td align="center">C</td>
<td align="left">곡선</td>
</tr>
<tr>
<td align="center">S</td>
<td align="left">부드러운 곡선</td>
</tr>
<tr>
<td align="center">Q</td>
<td align="left">2차 베지어 곡선</td>
</tr>
<tr>
<td align="center">T</td>
<td align="left">부드러운 2차 베지어 곡선</td>
</tr>
<tr>
<td align="center">A</td>
<td align="left">타원형 호</td>
</tr>
<tr>
<td align="center">Z</td>
<td align="left">가까운 경로</td>
</tr>
</tbody></table>
<p>따라서 아래와 SVG를 만들면 그 다음 형태와 같습니다.</p>
<pre><code class="language-html">&lt;svg width=&quot;400&quot; height=&quot;210&quot; &gt;
  &lt;path d=&quot;M150 0 L75 200 L225 200 Z&quot; /&gt;
&lt;/svg&gt;</code></pre>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/3c99a2be-25cd-4a34-8ea9-ac9cd12fed7e/image.png" width="50%" /></p>

<h1 id="아이콘과-로고에는-svg가-국룰처럼-쓰이는-이유">아이콘과 로고에는 SVG가 국룰처럼 쓰이는 이유</h1>
<ol>
<li><strong>크기 조절 및 고화질 유지</strong></li>
</ol>
<ul>
<li>아이콘과 로고는 여러 크기로 사용되어야 하는 경우가 많습니다. SVG 형식은 벡터 기반이기 때문에 크기가 조절되어도 이미지 품질이 손상되지 않기 때문에 유연한 크기 조절이 가능한 SVG 형식이 아이콘과 로고 같은 작은 이미지에 적합합니다.</li>
<li>반면, PNG과 JPG는 래스터 기반이기 떄문에 크기를 확대하면 품질이 저하됩니다. 따라서 다양한 크기의 아이콘과 로고를 제공해야 하는 상황에서는 SVG가 선호됩니다.</li>
</ul>
<ol start="2">
<li><strong>스타일링 및 편집 용이성</strong></li>
</ol>
<ul>
<li>SVG는 코드로 이미지가 저장되기 때문에 CSS를 사용하여 스타일을 쉽게 적용할 수 있습니다. 색상, 크기, 배경 등의 스타일 변경이 용이하며, 동적인 효과도 줄 수 있습니다.</li>
<li>PNG나 JPG는 이미지 파일 자체에 스타일 정보가 내장되어 있지 않아 스타일을 변경이 단순하지는 않습니다.</li>
</ul>
<ol start="3">
<li><strong>파일 크기</strong></li>
</ol>
<ul>
<li>SVG 파일은 텍스트 기반으로 일반적으로 파일 크기가 상대적으로 작습니다. 특히 아이콘과 로고 같이 작은 이미지에적합하며, 웹 페이지 로딩 속도에 영향이 매우 적습니다.</li>
<li>반면 PNG나 JPG는 이미 렌더가 된 상태로 해당도와 색상 수준에 따라 파일 크기가 증가합니다.</li>
</ul>
<ol start="4">
<li><strong>복잡한 그래픽 표현</strong></li>
</ol>
<ul>
<li>SVG는 수학적인 경로와 도형을 사용하여 복잡한 그래픽을 표현하기에 적합합니다. 따라서 로고와 아이콘 등에서 다양한 형태와 디테일을 나타내는 데 유용합니다.</li>
</ul>
<p>결론적으로, 아이콘과 로고 같은 작은 크기의 이미지는 다양한 크기와 스타일을 유지하면서 품질을 유지하기 위해 주로 SVG를 사용하며, 일반 이미지의 경우 오히려 SVG를 사용할 경우 계산 과정이 훨씬 복잡해져 래스터 형식인 PNG나 JPG를 사용하는 것이 보편적입니다.</p>
<h1 id="svg-사용-케이스별-성능-비교">svg 사용 케이스별 성능 비교</h1>
<p>생각을 해보니 저도 여러 방법으로 svg를 사용하고 있었던 것 같습니다. 인터넷에서 svg 이미지 링크를 복사한 경우 <code>&lt;img src=&quot;&quot; /&gt;</code> 방법을 사용했던 것 같고, 라이브러리를 통해 아이콘을 사용하는 경우에는 <code>&lt;svg&gt;</code>를 사용했던 것 같습니다.</p>
<p>이렇게 svg를 사용하는 방법에는 여러 가지가 있을 수 있는데, 이들에는 어떤 성능 차이가 존재할까요?</p>
<p>아래는 svg 사용 방법에 따른 성능을 비교한 포스팅입니다. 이를 바탕으로 정리를 해보려고 합니다 :)</p>
<p><a href="https://cloudfour.com/thinks/svg-icon-stress-test/">Which SVG technique performs best for way too many icons?</a></p>
<p>많은 양의 svg 아이콘 성능에 중점을 둡니다.</p>
<p>한번에 표시되는 아이콘이 100개 미만인 경우 svg 렌더링 최적화에 <strong>너무 많은 노력을 기울일 만큼의 크기를 가지지는 않습니다.</strong></p>
<p>아래는 렌더링하는 여러 방법으로 1,000개의 아이콘을 렌더링하는 데 걸린 평균(밀리초) 시간입니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/41fb89e8-689f-4cdc-8f5a-13f2453c59bf/image.png" alt=""></p>
<h2 id="inline-svg">Inline SVG</h2>
<pre><code class="language-html">&lt;svg viewBox=&quot;0 0 24 24&quot; width=&quot;24&quot; height=&quot;24&quot;&gt;
  &lt;!-- paths, shapes, etc. --&gt;
&lt;/svg&gt;</code></pre>
<p>HTML svg 태그 그대로 사용했을 경우입니다.
최적화된 아이콘의 경우, inline SVG가 성능이 가장 우수했습니다. 반면 최적화되지 않은 경우에는 가장 느립니다.</p>
<p>여기서 &#39;최적화된 svg&#39;라는 것은 중복되고 쓸모없는 정보를 제거하고 지오메트리 경로를 단순화하여 크기를 줄이고 깔끔하게 만드는 것을 의미한다고 합니다.</p>
<p>뿐만 아니라 성능 향상을 위한 <a href="https://github.com/svg/svgo">svgo</a>라는 최적화 도구도 소개되고 있습니다.</p>
<h2 id="symbol-sprite">Symbol Sprite</h2>
<pre><code class="language-html">&lt;svg style=&quot;display:none&quot;&gt;
  &lt;symbol id=&quot;example&quot; viewBox=&quot;0 0 24 24&quot; width=&quot;24&quot; height=&quot;24&quot;&gt;
    &lt;!-- paths, shapes, etc. --&gt;
  &lt;/symbol&gt;
&lt;/svg&gt;

&lt;svg&gt;&lt;use href=&quot;#example&quot;/&gt;&lt;/svg&gt;</code></pre>
<p>svg를 표현하는 symbol 태그를 만들고, 개별 아이콘은 해당 symbol을 참조하여 표시됩니다. 이 방법의 성능은 렌더링하는 엘<strong>리먼트의 개수에 따라 영향</strong>을 받습니다.</p>
<h2 id="img-태그-사용">img 태그 사용</h2>
<pre><code class="language-html">&lt;img src=&quot;path/to/icon/color.svg&quot; alt=&quot;&quot;&gt;</code></pre>
<p>일반적으로 많이 사용하는 방법인데, inline svg과 비슷하게 img 태그도 성능면에서 좋은 결과를 보였습니다.</p>
<p>조금 눈여겨 볼 점으로는, img 태그를 사용하여 svg를 사용하면 inline svg와 약간의 차이가 발생합니다. 아래 이미지를 보면, 위가 inline svg로 표현한 이미지이고, 아래가 img 태그를 사용한 경우입니다. 이미지 간격이 미세하게 다른 것을 알 수 있습니다.</p>
<p align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/74350f25-e556-45b8-9601-d83e22cc71c4/image.png" width="80%" height="80%"/></p>

<p>위와 같이 다른 간격을 가지는 이유에는 여러 가지가 있을 수 있지만, svg 파일 내에 뷰포트(viewport) 설정이나 CSS 스타일링 등이 설정되어 있는데 img 태그를 사용할 경우 해당 설정을 무시하고 이미지를 처리하므로 크기와 위치가 달라질 수 있습니다.</p>
<h2 id="data-uri를-사용한-img-태그-사용">data URI를 사용한 img 태그 사용</h2>
<pre><code class="language-html">&lt;img src=&quot;data:image/svg+xml,...&quot; alt=&quot;&quot;&gt;</code></pre>
<p>svg 파일은 마크업 텍스트로 되어 있기 때문에, base64 인코딩 없이 data URI 스트링 형태로 쉽게 변환이 가능합니다. 해당 방법이 최적화/최적화 되지 않은 svg 모든 경우에서 <strong>성능이 가장 우수</strong>했습니다.</p>
<blockquote>
<p>📌 data URI
이미지 등의 외부 바이너리 파일을 웹페이지에 인라인으로 넣기 위해 사용
외부 데이터를 별도의 파일로 두지 않고 HTML 파일로 관리할 수 있다.
request 수를 줄여 빠른 전송효과를 볼 수 있다.</p>
</blockquote>
<h2 id="background-image">Background image</h2>
<pre><code class="language-html">&lt;div style=&quot;background-image: url(path/to/icon/color.svg);&quot;&gt;...&lt;/div&gt;</code></pre>
<p>백그라운드 이미지는 img 태그나 inline svg보다는 성능이 떨어졌습니다.</p>
<h2 id="mask-image">Mask image</h2>
<pre><code class="language-html">&lt;div style=&quot;
  -webkit-mask-image: url(path/to/icon.svg);
  mask-image: url(path/to/icon.svg);&quot;&gt;
  ...
&lt;/div&gt;</code></pre>
<p>CSS mask 속성은 아이템이 부분적으로만 보여지게 하거나 혹은 완전히 가려서 보여지지 않게 할 수 있는 기능입니다. mask-image 속성을 사용하면 요소의 마스크 레이어로 사용되는 이미지를 설정할 수 있습니다. 저는 사용해본 적이 없는 방법인데, 이렇게도 이미지를 표현할 수 있구나하고 알게 되었던 것 같습니다. 성능은 그닥 좋지는 않았습니다.</p>
<p><strong>결론</strong></p>
<p>svg 태그를 사용하는 방법과 uri를 사용하는 방법 그리고 img 태그를 사용하는 방법이 성능면에서 가장 우수했습니다.
반면, 흔히 사용될 수 있는 background-image는 성능면에서 좋은 편에 속하지는 못하다는 것도 알 수 있었습니다.</p>
<h1 id="결론">결론</h1>
<p>작은 크기의 이미지(흔히 아이콘이나 로고)에서는 품질 저하 없이 크기 조절 및 스타일링이 가능한 svg를 사용하는 것이 적합합니다. 반면, 일반 이미지의 경우 svg는 계산을 요하기 때문에 오히려 이미 렌더되어 있는 래스터 형식의 PNG나 JPG가 더 적합합니다.</p>
<p>성능 면에서는 svg 태그를 사용하는 방법, img 태그를 사용하는 방법, uri를 사용하는 방법이 가장 우수했습니다.</p>
<h1 id="참고문서">참고문서</h1>
<ul>
<li><a href="https://puterism.com/overview-svg">SVG(Scalable Vector Graphics) 살펴보기</a></li>
<li><a href="https://woozzang.tistory.com/139">래스터 이미지와 비트맨 그리고 벡터 이미지</a></li>
<li><a href="https://grapherstory.tistory.com/1141">안티 앨리어싱 뜻 의미 간단하게 정리</a></li>
<li><a href="https://brunch.co.kr/@ggk234/11">&#39;SVG&#39;를 사용하는 이유!</a></li>
<li><a href="https://velog.io/@ggong/SVG-%EC%95%8C%EA%B3%A0-%EC%93%B0%EA%B8%B0-%EC%B5%9C%EC%A0%81%ED%99%94">SVG 구조, 렌더링 성능 알고 쓰기</a></li>
<li><a href="https://shubamba.tistory.com/58">이미지 data uri 만들기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Patterns - Design Patterns]]></title>
            <link>https://velog.io/@doeunnkimm_/Patterns-Design-Patterns</link>
            <guid>https://velog.io/@doeunnkimm_/Patterns-Design-Patterns</guid>
            <pubDate>Wed, 02 Aug 2023 15:58:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>📌 <a href="https://www.patterns.dev/">원본</a></p>
</blockquote>
<h1 id="introduce">Introduce</h1>
<p>디자인 패턴은 소프트웨어 디자인에서 일반적으로 반복되는 문제에 대해 일반적인 솔루션을 제공하므로 소프트웨어 개발의 기본 부분이다. 디자인 패턴은 특정 소프트웨어를 위해 제공하는 것이 아닌 반복되는 코드를 최적화된 방식으로 처리하는 데 사용할 수 있는 개념이다.</p>
<p>Facebook의 JavaScript 기반 라이브러리 React의 인기로 인해 현재의 현대 웹 개발 생태계에서 가치를 제공하기 위해 디자인 패턴이 수정되고 최적화되었으며 새로운 패턴이 만들어졌다. 최신 버전의 React는 애플리케이션 디자인에서 매우 중요한 역할을 하고 있으며 많은 기존 디자인 패턴을 대체할 수 있는 Hooks라는 새로운 기능도 도입되었다.</p>
<p>최신 웹 개발에는 다양한 종류의 패턴이 포함된다. 이번 글을 통해 ES2015+를 사용한 일반적인 디자인 패턴의 구현, 이점 및 함정, React 관련 디자인 패턴 및 React Hooks를 사용한 가능한 수정 및 구현, 그리고 최신 웹 앱을 개선하는 데 도움이 될 수 있는 더 많은 패턴과 최적화를 다룬다.</p>
<h1 id="singleton-pattern">Singleton Pattern</h1>
<blockquote>
<p>애플리케이션 전체에서 단일 글로벌 인스턴스 공유</p>
</blockquote>
<p>싱글톤은 한 번 인스턴스화할 수 있고 전역적으로 액세스할 수 있는 클래스이다. 이 단일 인스턴스는 응용 프로그램 <strong>전체에서 공유될 수 있으므로</strong> 싱글폰은 응용 프로그램의 <strong>전역 상태를 관리하는 데 적합</strong>하다.</p>
<p>먼저 ES2015 클래스를 사용하여 싱글톤이 어떻게 보이는지 살펴보자. 이 예제에서는 다음을 포함하는 <code>Counter</code> 클래스를 빌드할 것이다.</p>
<ul>
<li>인스턴스의 값을 반환하는 <code>getInstance</code> 메서드</li>
<li><code>counter</code> 변수의 현재 값을 반환하는 <code>getCount</code> 메서드</li>
<li><code>counter</code> 값을 1씩 증가시키는 <code>increment</code> 메서드</li>
<li><code>counter</code> 값을 1씩 감소시키는 <code>decrement</code> 메서드</li>
</ul>
<pre><code class="language-js">let counter = 0;

class Counter {
  getInstance() {
    return this
  }

  getCount() {
    return counter
  }

  increment() {
    return ++counter
  }

  decrement() {
    return --counter
  }
}</code></pre>
<p>그러나 위 클래스는 싱글톤 기준을 충족하지 않는다.</p>
<pre><code>⭐️ 싱글톤은 한번만 인스턴스화 할 수 있어야 한다.</code></pre><p>현재 <code>Counter</code> 클래스는 여러 인스턴스를 만들 수 있다.</p>
<pre><code class="language-js">const counter1 = new Counter()
const counter2 = new Counter()

console.log(counter1.getInstance() === counter2.getInstance()) // false</code></pre>
<p><code>new</code> 메서드를 2번 호출함으로써 counter1과 counter2는 서로 다른 인스턴스가 생성되었다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/4fb8e49f-776a-4d14-9dd2-6ead1bafcd1e/image.png" alt=""></p>
<p>🤔 <code>Counter</code> 클래스의 인스턴스를 <strong>하나만</strong> 만들 수 있을까?</p>
<pre><code>⭐️ 방법은 인스턴스라는 변수를 만드는 것

→ 인스턴스에 대한 참조를 동일하게 !</code></pre><p><code>Counter</code>의 생성자에서 새 인스턴스가 생성될 때 인스턴스에 대한 참조와 동일하게 인스턴스를 설정할 수 있다. 인스턴스 변수에 이미 값이 있는지 확인하여 새 인스턴스 화를 방지할 수 있다.</p>
<pre><code class="language-js">let instance
let counter = 0

class Counter {
  constructor() {
    if (instance) {
      throw new Error(&#39;You can only create one instance!&#39;)
    }
    instance = this
  }

  ...
}

const counter1 = new Counter()
const counter2 = new Counter()
// Error: You can only create one instance!</code></pre>
<p>더 이상 여러 새로운 인스턴스를 만들 수 없다.</p>
<pre><code>⭐️ 인스턴스를 내보내기 전에 freeze시켜 변경하거나 덮어쓸 수 없도록 해야한다.</code></pre><p><code>Counter</code> 인스턴스를 내보낼 때 인스턴스를 freeze해야 한다. <code>Object.freeze</code> 메서드는 싱글톤을 수정할 수 없도록 한다. freeze된 인스턴스의 속성을 추가하거나 수정할 수 없으므로 싱글톤의 값을 실수로 덮어쓸 위험이 줄어든다.</p>
<pre><code class="language-js">...
class Counter {
  ...
}

const singletonCounter = Object.freeze(new Counter())
export default singletonCounter</code></pre>
<p><strong>장단점</strong></p>
<p><strong>🟢 장점1: 메모리 공간 절약</strong>
인스턴스화를 하나의 인스턴스로 제한하면 잠재적으로 많은 메모리 공간을 절약할 수 있다. 매번 새 인스턴스에 대한 메모리를 설정하는 대신 응용 프로그램 전체에서 참조되는 해당 인스턴스에 대한 메모리만 설정하면 된다.</p>
<p><strong>🔴 단점1: 테스트 하기 까다로움</strong>
싱글톤에 의존하는 테스트 코드는 까다로울 수 있다. 매번 새 인스턴스를 만들 수 없기 때문에 모든 테스트는 이전 테스트의 전역 인스턴스 수정에 의존한다. 이 경우 테스트 순서가 중요하며 작은 수정 하나하나가 전체 테스트 실패로 이어질 수 있다. 테스트 후 테스트에 의해 수정된 사항을 재설정하려면 전체 인스턴스를 재설정해야 한다.</p>
<p><strong>🔴 단점2: 글로벌 행동</strong>
싱글톤 인스턴스는 전체 앱에서 참조될 수 있어야 한다. 전역 변수는 기본적으로 동일한 동작을 보여준다. 전역 변수는 전역 범위에서 사용할 수 있으므로 응용 프로그램 전체에서 해당 변수에 액세스할 수 있다.</p>
<p>전역 변수를 갖는 것은 일반적으로 잘못된 설계 결정으로 간주된다. 전역 범위 오염은 전역 변수의 값을 실수로 덮어쓰는 결과를 초래할 수 있으며 이로 인해 많은 예기치 않은 동작이 발생할 수 있다.</p>
<p>ES2015에서 전역 변수를 생성하는 것은 흔하지 않다. <code>let</code> 그리고 <code>const</code> 키워드로 선언된 변수는 블록 범위로 유지함으로써 개발자가 실수로 글로벌 범위로 오염시키는 것을 방지한다.</p>
<p>그러니 싱글톤의 일반적인 사용 사례는 응용 프로그램 전체에서 일종의 <strong>전역 상태</strong>를 갖는 것이다. 이는 예기치 않은 동작으로 이어질 수 있다.</p>
<p><strong>React의 상태 관리</strong></p>
<p>React에서는 싱글톤을 사용하는 대신 <strong>Redux</strong> 또는 <strong>React Context</strong>와 같은 상태 관리 도구를 통해 전역 상태에 의존하는 경우가 많다. 전역 상태 동작이 싱글톤과 비슷해 보일 수 있지만 이러한 도구는 싱글톤의 가변 상태가 아닌 <strong>읽기 전용 상태를 제공</strong>한다.</p>
<p>이러한 도구를 사용해도 전역 상태의 단점이 마법처럼 사라지지는 않지만, 구성 요소가 상태를 직접 업데이트할 수 없기 때문에 최소한 전역 상태가 우리가 의도한 대로 변경되도록 할 수 있다.</p>
<h1 id="proxy-pattern">Proxy Pattern</h1>
<blockquote>
<p>대상 객체에 대한 상호작용에 대한 중간다리</p>
</blockquote>
<p>프록시 개체를 사용하면 특정 개체와의 상호작용을 더 잘 제어할 수 있다. 프록시 개체는 개체와 <strong>상호작용을 할 때마다(예: 값을 가져오거나 값을 설정할 때) 동작을 결정</strong>할 수 있다.</p>
<pre><code>⭐️ 일반적으로 프록시는 다른 사람을 대신하는 것을 의미

→ 대상 개체와 직접 상호작용하는 대신 프록시 개체와 상호작용</code></pre><p>John Doe를 나타내는 <code>person</code> 개체를 만들어 보자.</p>
<pre><code class="language-js">const person = {
  name: &#39;John Doe&#39;,
  age: 42,
  nationality: &#39;American&#39;
}</code></pre>
<p>이 개체와 직접 상호 작용하는 대신 <strong>프록시 개체와 상호작용하려고</strong> 한다. JavaScript에서는 새 <code>Proxy</code>인스턴스를 만들어 새 프록시시를 쉽게 만들 수 있다.</p>
<pre><code class="language-js">const personProxy = new Proxy(person, {});</code></pre>
<p>Proxy의 두 번째 인수는 핸들러를 나타내는 개체이다. 핸들러 개체에서는 상호 작용의 유형에 따라 특정 동작을 정의할 수 있다. Proxy 핸들러에 추가할 수 있는 여러 가지 방법이 있지만 가장 일반적인 두 가지 방법은 get과 set이다.</p>
<ul>
<li>get: 속성에 액세스할 때 호출</li>
<li>set: 속성을 수정할 때 호출</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/8d66a34d-4976-49ae-b13c-91890dcbe002/image.gif" alt=""></p>
<p><code>person</code> 개체와 직접 상호 작용하는 대신 프록시와 상호 작용한다.</p>
<p><code>personProxy</code>에 <strong>핸들러를 추가</strong>해보자. 속성을 수정하여 프록시에 설정된 메서드를 호출할 때 프록시가 속성의 이전 값과 새 값을 기록하기를 원한다. 속성에 액세스하여 프록시에서 <code>get</code> 메서드를 호출할 때 프록시가 속성의 키와 값을 포함하는 더 읽기 쉬운 문자을 기록하기를 원한다.</p>
<pre><code class="language-js">const personProxy = new Proxy(person, {
  get: (obj, prop) =&gt; {
    console.log(`The value of ${prop} is ${obj[prop]}`)
  },
  set: (obj, prop, value) =&gt; {
    console.log(`Changed ${prop} from ${obj[prop] to ${value}}`)
    obj[prop] = value
  }
})

personProxy.name;
personProxy.age = 43;</code></pre>
<pre><code>⭐️ 프록시는 유효성 검사를 추가하는 데 유용할 수 있다.</code></pre><p><code>person</code>은 나이를 문자열 값을 변경하거나 빈 이름을 지정할 수 없어야 한다. 또는 사용자가 존재하지 않는 개체의 속성에 액세스하려는 경우 사용자에게 알려주어야 한다.</p>
<pre><code class="language-js">const personProxy = new Proxy(person, {
  get: (obj, prop) =&gt; {
    if (!obj[prop]) {
      console.log(`Hmm.. this property doesn&#39;t seem to exist on the target object`)
    } else {
      console.log(`The value of ${prop} is ${obj[prop]}`)
    }
  },
  set: (obj, prop, value) =&gt; {
    if (prop === &#39;age&#39; &amp;&amp; typeof value !== &#39;number&#39;) {
      console.log(`Sorry, you can only pass numeric values for age`)
    } else if (prop === &#39;name&#39; &amp;&amp; value.length &lt; 2) {
      console.log(`You need to provide a valid name`)
    } else {
      console.log(`Changed ${prop} from ${obj[prop] to ${value}}`)
    }
  }
})</code></pre>
<p>** Reflect **</p>
<p>JavaScript는 <code>Reflect</code>라는 기본 객체를 제공하므로써 프록시 작업 시 대상 개체를 쉽게 조작할 수 있다.</p>
<p>이전에는 직접 값을 가져오거나 괄호 표기로 설정하여 프록시 내에서 대상 개체의 속성을 수정하고 액세스하려고 했다. 대신 <code>Reflect</code>를 사용할 수 있다. <code>Reflect</code> 개체의 메서드 이름은 핸들러 개체의 메서드 이름과 동일하다. 예를 들어 <code>Reflect.get()</code> 및 <code>Reflect.set()</code>을 통해 대상 개체의 속성을 액세스하거나 수정할 수 있다.</p>
<pre><code class="language-js">const personProxy = new Proxy(person, {
  get: (obj, prop) =&gt; {
    console.log(`The value of ${prop} is ${Reflect.get(obj, prop)}`)
  },
  set: (obj, prop, value) =&gt; {
    console.log(`Changed ${prop} from ${obj[prop]} to value`)
    Reflect.set(obj, prop, value)
  }
})</code></pre>
<p><strong>장단점</strong></p>
<p><strong>🟢 장점1: 동작 제어 추가</strong>
프록시는 개체의 동작에 대한 제어를 추가하는 강력한 방법이다. </p>
<p><strong>🟢 장점2: 다양한 사용 사례</strong>
프록시에는 다양한 사용 사례가 있을 수 있다. 유효성 검사, 서식 지정, 알림 또는 디버깅에 도움이 될 수 있다.</p>
<p><strong>🔴 단점1: 과도한 사용, 성능</strong>
프록시 개체를 과도하게 사용하거나 각 핸들러 메서드 호출에 대해 과도하게 작업을 수행하면 응용 프로그램의 성능에 부정적인 영향을 미칠 수 있다. <strong>성능에 중요한 코드에는 프록시를 사용하지 않는 것</strong>이 가장 좋다.</p>
<h1 id="provider-pattern">Provider Pattern</h1>
<blockquote>
<p>여러 하위 구성 요소에서 데이터를 사용 가능</p>
</blockquote>
<p>경우에 따라 응용 프로그램의 많은 구성 요소에서 데이터를 사용할 수 있도록 한다. props를 사용하여 구성 요소에 데이터를 전달할 수 있지만, 응용 프로그램의 거의 모든 컴포넌트가 props의 값에 접근해야 하는 경우에는 데이터를 전달하기 어려울 수 있다.</p>
<p>우리는 종종 props drilling이라는 것으로 끝을 보는데, 이것은 우리가 컴포넌트 트리 아래 멀리 props를 전달할 때 그렇다. <strong>props에 의존하는 코드를 리팩토링하는 것은 거의 불가능</strong>하고, <strong>특정 데이터가 어디에서 왔는지 아는 것은 어렵다.</strong></p>
<p>저 아래에 있는 자식 컴포넌트에게 props를 전달하기 위해 중간 컴포넌트를 타고 타고 전달한다고 생각했을 때, 이 데이터를 사용할 필요가 없는 구성 요소의 계층을 모두 생략할 수 있다면 최적일 것이다.</p>
<pre><code>⭐️ props drilling에 의존하지 않고 직접 액세스할 수 있도록 돕는 것이 provider 패턴

→ provider 패턴을 사용하면 여러 구성 요소에 데이터를 사용할 수 있다.</code></pre><p>provider는 context 개체가 제공하는 상위 구성 요소이다. React가 제공하는 <code>createContext</code> 메서드를 사용하여 context 개체를 만들 수 있다. 이 provider 내에서 래핑되는 모든 구성 요소는 props로 넘겨지는 value 값에 접근할 수 있다.</p>
<pre><code class="language-jsx">const DataContext = React.createContext()

function App() {
  const data = {...}

  return (
    &lt;DataContext.Provider value={data}&gt;
      &lt;MyApp /&gt;
    &lt;/DataContext.Provider&gt;
  )
}</code></pre>
<p><strong>장단점</strong></p>
<p><strong>🟢 장점</strong>
provider 패턴/Context API를 사용하면 데이터를 수동으로 각 구성 요소 계층에 전달할 필요 즉, prop drilling 없이도 많은 컴포넌트에 전달할 수 있다.</p>
<p>코드 리팩터링 시 실수로 버그가 발생할 위험을 줄여준다. 이전에는 나중에 props 이름을 변경하려면 이 값이 사용된 전체 애플리케이션에서 이 props의 이름을 변경해야 했다.</p>
<p>우리는 <strong>더이상 props drilling을 다룰 필요가 없다.</strong> 이전에는 특정 props 값이 어디에서 발생했는지 하상 명확지 않았기 때문에 응용 프로그램의 데이터 흐름을 이해하는 것이 어려울 수 있었다. provider 패턴을 사용하면 더 이상 이 데이터를 신경 쓰지 않는 컴포넌트에 props를 불필요하게 전달할 필요가 없다.</p>
<p>provider 패턴을 사용하면 컴포넌트가 이 글로벌 상태에 접근할 수 있으므로 일종의 글로벌 상태를 쉽게 유지할 수 있다.</p>
<p><strong>🔴 단점</strong>
경우에 따라 provider 패턴을 과도하게 사용하면 <strong>성능 문제</strong>가 발생할 수 있다. context를 사용하는 모든 컴포넌트는 각 상태 변경 시 렌더링을 다시 수행한다.</p>
<p>자주 업데이트되는 값을 많은 컴포넌트에 전달하면 성능에 부정적인 영향을 미칠 수 있다.</p>
<p>구성 요소가 업데이트할 수 있는 불필요한 값을 포함하는 provider를 소비하지 않도록 하려면 각 개별 사용 사례에 대해 여러 provider를 만들 수 있다.</p>
<h1 id="prototype-pattern">Prototype Pattern</h1>
<blockquote>
<p>동일한 type의 여러 object 간에 프로퍼티 공유</p>
</blockquote>
<p>프로토타입 패턴은 동일한 type의 여러 object 간에 프로퍼티를 공유할 수 있는 유용한 방법이다. 프로토타입은 JavaScript가 기본인 object이며, 프로토타입 체인을 통해 object가 액세스할 수 있다.</p>
<p>우리의 응용 프로그램에서 우리는 종종 같은 type의 많은 객체를 만들어야 한다. 이를 수행하는 유용한 방법은 ES6 클랫의 여러 인스턴스를 만드는 것이다.</p>
<pre><code class="language-js">class Dog {
  constructor() {
    this.name = name
  }

  bark() {
    return `Woof!`
  }
}

const dog1 = new Dog(&quot;Daisy&quot;)
const dog2 = new Dog(&quot;Max&quot;)
const dog3 = new Dog(&quot;Spot&quot;)</code></pre>
<p>여기서 생성자는 <code>name</code> 속성을 포함하고 클래스 자체는 <code>bark</code> 속성을 포함하는 방법을 주목해보자. ES6 클래스를 사용하는 경우 클래스 자체에 정의된 모든 속성, <strong>이 경우 <code>bark</code>가 자동으로 프로토타입에 추가</strong>된다.</p>
<p>우리는 생성자의 프로토타입 속성에 접근하거나 모든 인스턴스의 <code>__proto__</code> 속성을 통해 프로토타입을 직접 볼 수 있다.</p>
<pre><code class="language-js">console.log(Dog.prototype)
// constructor : f Dog(name) bark: f bark()

console.log(dog1.__proto__)
// constructor: f Dog(name) bark: f bark()</code></pre>
<p>생성자의 어떤 인스턴스에서든 <code>__proto__</code>의 값은 <strong>생성자의 프로토타입을 직접 참조</strong>한다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/b8edc230-2517-4c06-80a1-3805a58042b7/image.png" alt=""></p>
<pre><code>⭐️ 프로토타입 패턴은 동일한 속성에 접근할 수 있어야 하는 object를 작업할 때 매우 강력</code></pre><p>모든 인스턴스는 프로토타입 object에 접근할 수 있으므로 매번 프로퍼티의 복제를 만드는 대신 단순히 프로토타입에 속성을 추가할 수 있다.</p>
<p>아까 만든 <code>Dog</code> 클래스에서 짖을 수 있을 뿐만 아니라 놀 수 있어야 한다고 한다면 프로퍼티를 추가하면 된다.</p>
<pre><code class="language-js">class Dog {
  constructor(name) {
    this.name = name;
  }

  bark() {
    return `Woof!`;
  }
}

const dog1 = new Dog(&quot;Daisy&quot;);
const dog2 = new Dog(&quot;Max&quot;);
const dog3 = new Dog(&quot;Spot&quot;);

Dog.prototype.play = () =&gt; console.log(&quot;Playing now!&quot;);

dog1.play(); // Playing now!</code></pre>
<p><strong>Object.create</strong></p>
<p><code>Object.create</code> 메서드를 사용하면 새 object를 생성할 수 있으며 이를 통해 해당 프로토타입의 값을 명시적으로 전달할 수 있다. 즉, 다른 object로부터 직접 프로퍼티를 상속받을 수 있도록 하는 간단한 방법이다.</p>
<pre><code class="language-js">const dog = {
  bark() {
    return `Woof!`
  },
}

const pet1 = Object.create(dog)

pet1.bark(); // Woof!</code></pre>
<p>프로토타입 패턴을 통해 다른 object로부터 속성을 상속받을 수 있었다. 따라서 프로퍼티의 중복을 방지하고 메모리 사용량을 줄일 수 있다.</p>
<h1 id="containerpresentational-pattern">Container/Presentational Pattern</h1>
<blockquote>
<p>애플리케이션 로직에서 View를 분리하여 관심사 분리 시행</p>
</blockquote>
<p>우리가 6개의 개 이미지를 가져오고 이 이미지들을 화면에 렌더링하는 애플리케이션을 만들고 싶다고 가정해 보자.</p>
<pre><code class="language-js">// DogImages.js

export default function DogImages({ dog }) {
  return dogs.map((dog, i) =&gt; &lt;img src={dog} key={i} alt=&quot;Dog&quot; /&gt;)
}</code></pre>
<pre><code class="language-js">// DogImagesContainer.js

import DogImages from &#39;./DogImages&#39;
import { useState, useEffect } from &#39;react&#39;

export default function DogImagesContainer() {
  const [dogs, useDogs] = useState([])

  useEffect(() =&gt; {
    fetch(&#39;https://dog.ceo/api/breed/labrador/images/random/5&#39;)
      .then(res =&gt; res.json())
      .then(data =&gt; setDogs(data))
  }, [])

  return (
    &lt;DogImages dogs={dogs} /&gt;
  )
}</code></pre>
<p>이상적으로, 우리는 이 프로세스를 두 부분으로 분리함으로써 문제를 분리하기를 원한다.</p>
<ol>
<li><p>Presentational 컴포넌트
데이터가 사용자가에게 표시되는 방식에 관심이 있는 컴포넌트이다. 위 예제에서는 개 이미지 목록을 렌더링하는 컴포넌트(<code>DogImages.js</code>)</p>
</li>
<li><p>Container 컴포넌트
사용자에게 표시되는 데이터에 관심을 가지는 컴포넌트. 위 예제에서는 개 이미지를 가져오는 것(<code>DogImagesContainer.js</code>)</p>
</li>
</ol>
<p><strong>Presentational Component</strong></p>
<p>Presentational 컴포넌트는 props를 통해 데이터를 받는다. 주요 기능은 데이터를 수정하지 않고 <strong>스타일을 포함하여 원하는 방식으로 데이터를 표시하는 것</strong>이다.</p>
<p>개 이미지를 보여주는 예를 통한다면, 개 이미지를 렌더링할 때 API에서 가져온 각 개 이미지를 매핑하여 렌더링하기만 하면 된다. 이를 위해 props를 통해 데이터를 받고 받은 데이터를 렌더링하는 컴포넌트를 만들 수 있다.</p>
<p><strong>Container Components</strong></p>
<p>Container 컴포넌트의 주요 기능은 <strong>데이터를 Presentational 컴포넌트에게 전달하는 것</strong>이며, 이 컴포넌트에는 데이터가 포함된다. Container 컴포넌트 자체는 일반적으로 데이터에 관심을 갖는 Presentaitional 컴포넌트 외에 다른 컴포넌트를 렌더링하지 않는다. Container 컴포넌트 자체는 어떤 것도 렌더링하지 않기 때문에 일반적으로 어떤 스타일링도 포함하지 않는다.</p>
<p><strong>장단점</strong></p>
<p><strong>🟢 장점1: 관심사 분리를 장려한다</strong>
Container/Presentational 패턴은 관심사 분리를 장려한다. Presentational 컴포넌트는 UI를 담당하는 순수한 기능일 수 있는 반면 Container 컴포넌트는 애플리케이션의 상태와 데이터를 담당한다. 이를 통해 관심사 분리를 쉽게 시행할 수 있다.</p>
<p><strong>🟢 장점2: 쉽게 재사용 가능</strong>
Presentational 컴포넌트는 데이터를 변경하지 않고 단순히 데이터를 표시하기 때문에 쉽게 재사용할 수 있다. 다양한 목적으로 애플리케이션 전체에서 Presentational 컴포넌트를 재사용할 수 있다.</p>
<p><strong>🟢 장점3: 테스트하기 쉬운 Presentational 컴포넌트</strong>
Presentational 컴포넌트를 테스트하는 것은 쉬운데, 이는 일반적인 순수한 함수이기 때문이다. 우리는 데이터 저장소를 모의 실험할 필요 없이 전달하는 데이터를 기반으로 구성 요소가 무엇을 렌더링할 것인지 알고 있다.</p>
<h1 id="observer-pattern">Observer Pattern</h1>
<blockquote>
<p>observables을 사용하여 이벤트 발생 시 subscribers에게 알림</p>
</blockquote>
<p>관찰자 패턴을 사용하면 이벤트가 발생할 때마다 <code>observable</code>은 모든 <code>observers</code>에게 알린다.</p>
<p>observable 객체는 일반적으로 3가지 중요한 부분을 포함한다.</p>
<ul>
<li><code>observers</code>: 특정 이벤트가 발생할 때마다 알림을 받은 관찰자 배열</li>
<li><code>subscribe()</code>: 옵저버 목록에 옵저버를 추가하기 위한 메서드</li>
<li><code>unsubscribe()</code>: 옵저버 목록에서 옵저버를 제거하기 위한 메서드</li>
<li><code>notify()</code>: 특정 이벤트가 발생할 때마다 모든 옵저버에게 알리는 방식</li>
</ul>
<p>observable을 만들어보자. 만드는 쉬운 방법은 ES6 클래스를 사용하는 것이다.</p>
<pre><code class="language-js">class Observable {
  constructor() {
    this.observers = []
  }

  subscribe(func) {
    this.observers.push(func)
  }

  unsubscribe(func) {
    this.observers = this.observers.filter(observer =&gt; observer !== func)
  }

  notify(data) {
    this.observers.forEach(observer =&gt; observer(data))
  }
}</code></pre>
<p>이 Observable로 무언가를 만들어보자. 버튼과 스위치의 두 가지 컴포넌트로 구성된 매우 기본적인 app을 가지고 있다.</p>
<pre><code class="language-jsx">export default function App() {
  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;Button&gt;Click me!&lt;/Button&gt;
      &lt;FormControlLabel control={&lt;Switch /&gt;} /&gt;
    &lt;/div&gt;
  )
}</code></pre>
<p>우리는 애플리케이션과의 <strong>상호작용을 추적</strong>하려고 한다. 사용자가 버튼을 클릭하거나 스위치를 전환할 때마다 타임 스탬프와 함께 이 이벤트를 기록하려고 한다. 기록하는 것 외에도 이벤트가 발생할 때마다 표시되는 토스트 알림을 만드로 싶다.</p>
<p>기본적으로 우리가 하려는 것은 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/1c137458-cde4-47ff-a497-4f7da0b9bbdc/image.gif" alt=""></p>
<p>사용자가 <code>handleClick</code> 혹은 <code>handleToggle</code> 함수를 호출할 때마다 함수는 관찰자에서 <code>notify</code> 메서드를 호출한다. <code>notify</code> 메서드는 모든 observers에게 <code>handleClick</code> 혹은 <code>handleToggle</code> 함수가 전달한 데이터를 알려준다.</p>
<p>먼저 logger와 toastify functions를 생성해보자. </p>
<pre><code class="language-jsx">import { ToastContainer, toast } from &#39;react-toastify&#39;

function logger(data) {
  console.log(`${Date.now()} ${data}`)
}

function toastify(data) {
  toast(data)
}

export default function App() {
  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;Button&gt;Click me!&lt;/Button&gt;
      &lt;FormControlLabel control={&lt;Switch /&gt;} /&gt;
      &lt;ToastContainer /&gt;
    &lt;/div&gt;
  )
}</code></pre>
<p>현재 logger 및 toastfiy는 observable을 인식하지 못하고 있다. observable은 아직 통지할 수 없다. observable 기능을 사용하려면 observable의 구독 방법을 사용하여 구독해야 한다.</p>
<pre><code class="language-jsx">observable.subscribe(logger)
observable.subscribe(toastify)</code></pre>
<p>이벤트가 발생할 때마다 logger 및 toastify 함수에 대한 알림이 표시된다. 이제 실제로 observable을 알려주는 함수를 구현하면 된다. 이 함수들은 observable에 대한 알림 방법을 호출하고 observers가 받아야 할 데이터를 전달해야 한다.</p>
<pre><code class="language-jsx">export default function App() {
  function handleClick() {
    observable.notify(&quot;User clicked button!&quot;)
  }

  function handleToggle() {
    observable.nofify(&quot;User toggled switch!&quot;)
  }

  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;Button onClick={handleClick}&gt;Click me!&lt;/Button&gt;
      &lt;FormControlLabel control={&lt;Switch onChange={handleToggle}/&gt;} /&gt;
      &lt;ToastContainer /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<pre><code>⭐️ 비동기 이벤트 기반 데이터를 사용할 때 매우 유용하다.</code></pre><p>특정 데이터가 다운로드를 완료하거나 사용자가 새 메시지를 게시판에 보낼 때마다 특정 구성 요소가 알림을 받기를 원할 수 있으며 다른 모든 구성원이 알림을 받아야 한다.</p>
<p><strong>장단점</strong></p>
<p><strong>🟢 장점1: 단일 책임 원칙을 분리를 강제하는 좋은 방법</strong>
관찰자 패턴을 사용하는 것은 관심사와 단일 책임 원칙의 분리를 강제하는 좋은 방법이다. 관찰자 패턴은 observable 객체에 단단히 결합되지 않고 언제든지 결합/해제 할 수 있다. observable 객체는 사건을 감시하는 역할을 하고, observers는 단순히 수신한 데이터를 처리한다.</p>
<p><strong>🔴 단점1: 성능 문제</strong>
observers가 너무 복잡해지면 모든 구독자에게 알릴 때 성능 문제가 발생할 수 있다.</p>
<h1 id="module-pattern">Module Pattern</h1>
<blockquote>
<p>코드를 더 작고 재사용 가능한 조각으로 분할</p>
</blockquote>
<p>애플리케이션과 코드베이스가 커짐에 따라 코드를 유지 관리 가능하고 분리된 상태로 유지하는 것이 점점 더 중요해진다. 모듈 패턴을 사용하면 코드를 더 작고 재사용 가능한 조각으로 나눌 수 있다.</p>
<p>코드를 더 작은 재사용 가능한 조각으로 분할할 수 있는 것 외에도 모듈을 사용하면 파일 내의 특정 값을 비공개로 유지할 수 있다. 모듈 내의 선언은 기본적으로 <strong>해당 모듈로 범위가 지정(캡슐화)</strong> 된다. 특정 값을 명시적으로 내보내지 않으면 해당 값응ㄴ 모듈 외부에서 사용할 수 없다.</p>
<p>React로 애플리케이션을 구축할 때 많은 양의 컴포넌트를 처리해야 하는 경우가 많다. 이러한 모든 컴포넌트를 하나의 파일에 작성하는 대신 자체 파일에서 컴포넌트를 분리하여 기본적으로 각 컴포넌트에 대한 모듈을 만들 수 있다.</p>
<h1 id="mixin-pattern">Mixin Pattern</h1>
<blockquote>
<p>상속 없이 객체 또는 클래스에 기능 추가</p>
</blockquote>
<p>믹스인은 <strong>상속을 사용하지 않고</strong> 다른 객체나 클래스에 <strong>재사용 가능한 기능을 추가하기 위해</strong> 사용할 수 있다.</p>
<pre><code>⭐️ 믹스인을 단독으로 사용할 수 없다
→ 목적이 상속이 없는 객체나 클래스에 기능을 추가하는 것이기 때문에!</code></pre><p>응용을 해보자. 여러 마리의 개를 만들어야 하는데, 우리가 만드는 기본 개는 어떤 프로퍼티도 없지만 name 프로퍼티만 있다.</p>
<pre><code class="language-js">classs Dog {
  constructor(name) {
    this.name = name;
  }
}</code></pre>
<p>개는 단지 이름을 가지는 것 이상을 할 수 있어야 한다. 짖고, 꼬리를 흔들고, 놀 수 있어야 한다. 이것을 <strong>Dog class에 직접 추가하는 대신, 이것을 제공하는 믹스인</strong>을 만들 수 있다.</p>
<pre><code class="language-js">const dogFunctionality = {
  bark: () =&gt; console.log(&#39;Woof!&#39;),
  wagTail: () =&gt; console.log(&#39;Wagging my tail!&#39;),
  play: () =&gt; console.log(&#39;Playing!&#39;),
}</code></pre>
<p><code>Object.assign</code> 메서드를 사용하여 Dog 프로토타입에 <code>dogFunctionality</code> 믹스인을 추가할 수 있다. 이 방법을 사용하면 대상 객체에 프로퍼티를 추가할 수 있다.</p>
<pre><code class="language-js">Object.assign(Dog.prototype, dogFuncationality)

const pet1 = new Dog(&#39;Daisy&#39;)

pet1.name // Daisy
pet1.bark() // Woof!
pet1.play() // Playing!</code></pre>
<pre><code>⭐️ 믹스인을 사용하면 객체의 프로토타입에 기능을 주입하여 상속 없이 객체에 기능을 쉽게 추가 가능</code></pre><h1 id="mediatormiddleware-pattern">Mediator/Middleware Pattern</h1>
<blockquote>
<p>중앙 중개자 객체를 사용하여 컴포넌트 간의 통신 처리</p>
</blockquote>
<p>중개자 패턴을 사용하면 컴포넌트가 <strong>중재자라는 중심점을 통해</strong> 서로 상호작용할 수 있다. 중재자는 서로 직접 대화하는 대신 요청을 수신하고 전달한다. JavaScript에서 중재자는 종종 객체 리터럴이나 함수에 지나지 않는다.</p>
<p>이 패턴은 항공 교통 관제사와 조종사 간의 관계에 비유할 수 있다. 조종사들이 서로 직접 대화하게 하는 대신 조종사들은 항공 교통 관제사와 대화한다. 항공 교통 관제사는 모든 비행기가 다른 비행기와 충돌하지 않고 안전하게 비행하기 위해 필요한 정보를 받도록 한다.</p>
<p>JavaScript에서 객체 간의 다방향 데이터를 처리해야 하는 경우가 많다. 컴포넌트 수가 많은 경우 컴포넌트 간의 통신이 다소 혼란스러울 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/8b93e8c9-9037-495e-b769-88c8780254af/image.png" alt=""></p>
<p>모든 객체가 다른 객체와 직접 통신하도록 하는 대신 N:N 관계를 생성하는 대신 중재자가 객체의 요청을 처리한다.</p>
<pre><code>⭐️ 중재나는 이 요청을 처리하고 필요한 곳으로 전달한다.</code></pre><p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/8df1a158-0438-4ed1-99bc-9bc09bfaa984/image.png" alt=""></p>
<p>중재자 패턴의 좋은 사용 사례는 채팅방이다. 채팅방 내의 사용자는 서로 직접 대화하지 않고 대신 <strong>채팅방은 사용자 간의 중재자 역할</strong>을 한다.</p>
<pre><code class="language-js">class ChatRoom {
  logMessage(user, message) {
    const time = new Date();
    const sender = user.getName()

    console.log(`${time} [${sender}]: ${message}`)
  }
}

class User {
  constructor(name, chatroom) {
    this.name = name
    this.chatroom = chatroom
  }

  getName() {
    return this.name
  }

  send(message) {
    this.chatroom.logMessage(this, message)
  }
}</code></pre>
<pre><code>⭐️ 중개자/미들웨어 패턴을 사용하면 모든 통신이 하나의 중심점을 통해 흐르도록 하여 
   객체 간의 N:N 관계를 쉽게 단순화할 수 있다.</code></pre><h1 id="⭐️-hoc-pattern">⭐️ HOC Pattern</h1>
<blockquote>
<p>재사용 가능한 로직을 애플리케이션 전체의 컴포넌트에 props로 전달</p>
</blockquote>
<p>응용 프로그램 내에서, 종종 여러 컴포넌트에서 동일한 로직를 사용하기를 원한다. 이 로직는 컴포넌트에 특정 스타일을 적용하거나, 승인을 요구하거나, 전역 상태를 추가하는 것을 포함할 수 있다.</p>
<pre><code>⭐️ 여러 컴포넌트에서 동일한 로직을 재사용할 수 있는 한 가지 방법 
→ 고차 컴포넌트 패턴을 사용하는 것</code></pre><p><code>HOC(Higher Order Component)</code>는 <strong>다른 컴포넌트를 받는 컴포넌트</strong>이다. HOC는 매개변수로 전달하는 컴포넌트에 적용하려는 특정 로직이 포함된다. 해당 로직을 적용한 후 HOC는 추가 로직과 함께 요소를 반환한다.</p>
<p>우리는 항상 응용 프로그램의 여러 컴포넌트에 특정 스타일을 추가하기를 원하는데, 매번 스타일 객체를 파일마다 만드는 대신 전달하는 컴포넌트에 스타일 객체를 추가하는 HOC를 간단히 만들 수 있다.</p>
<pre><code class="language-jsx">function withStyles(Component) {
  return props =&gt; {
    const style = { padding: &#39;0.2rem&#39;, margin: &#39;1rem&#39; }
    return &lt;Component style={style} {...props} /&gt;
  }
}

const Button = () =&gt; &lt;button&gt;Click me!&lt;/button&gt;
const Text = () =&gt; &lt;p&gt;Hello World!&lt;/p&gt;

const StyledButton = withStyles(Button)
const StyledText = withStyles(Text)</code></pre>
<p>위에서 <code>StyledButton</code>과 <code>StyledText</code>는 스타일 HOC와 함께 추가된 스타일을 포함한다.</p>
<p>이전에 했던 DogImages 예제를 살펴보자. 데이터를 가져올 때 사용자에게 &quot;로딩...&quot; 화면을 보여주고 싶다. <code>DogImage</code> 컴포넌트에 데이터를 직접 추가하는 대신 이 로직을 추가하는 고차 컴포넌트를 사용할 수 있다.</p>
<p><code>Loader</code>와 함께 HOC를 생성한다. </p>
<pre><code>⭐️ HOC는 컴포넌트를 수신하고 해당 컴포넌트를 리턴해야 한다.</code></pre><p>이 경우, <code>WithLoader HOC</code>는 데이터를 가져올 때까지 Loading... 이라고 표시되는 요소를 수신해야 한다.</p>
<p>사용할 <code>Loader HOC</code>를 최소 버전으로 생성해 보자.</p>
<pre><code class="language-js">function withLoader(Element) {
  return (props) =&gt; &lt;Element /&gt;
}</code></pre>
<p>여기서 데이터가 여전히 로딩되고 있는지 아닌지를 열려주는 로직을 추가해야 한다.</p>
<p><code>withLoader HOC</code>를 재사용할 수 있도록 하기 위해 해당 컴포넌트의 Dog API URL을 하드코딩하지 않는 대신, <code>withLoader HOC</code>의 인수로 URL을 전달할 수 있으므로 이 Loader는 다른 API에서 데이터를 가져오는 동안 로딩 화면이 필요한 다른 컴포넌트에서도 사용핧 수 있다.</p>
<pre><code class="language-js">functon withLoader(Element, url) {
  return (props) =&gt; {}
}</code></pre>
<p>이제 위 HOC는 데이터를 가져오는 중일 때는 Loading...를 표시할 수 있는 로직을 추가하고자 하는 요소인 컴포넌트를 반환하고, 데이터를 가져온 후에는 가져온 데이터 요소를 전달해야 한다.</p>
<pre><code class="language-jsx">// withLoader.js
import { useEffect, useState } from &#39;react&#39;

export default function withLoader(Element, url) {
  return (props) =&gt; {
    const [data, setData] = useState(null)

    useEffect(() =&gt; {
      const getData = async () =&gt; {
        const res = await fetch(url)
        const data = await res.json()
        setData(data)
      }

      getData()
    }, [])

    if (!data) {
      return &lt;div&gt;Loading...&lt;/div&gt;
    }

    return &lt;Element {...props} data={data} /&gt;
  }
}</code></pre>
<pre><code class="language-jsx">// DogImages.js
import withLoader from &quot;./withLoader&quot;

function DogImages(props) {
  return props.data.message.map((dog, index) =&gt; (
    &lt;img src={dog} alt=&quot;Dog&quot; key={index} /&gt;
  ));
}

export default withLoader(
  DogImages,
  &quot;https://dog.ceo/api/breed/labrador/images/random/6&quot;
)</code></pre>
<p><strong>Composing</strong></p>
<p>여러 개의 HOC를 구성할 수도 있다. 사용자가 DogImages 목록 위를 이동할 때 Hovering! 텍스트 상자를 표시하는 기능을 추가해보자.</p>
<p>우리는 우리가 hover하는 hovering props를 제공하는 HOC를 만들어야 합니다. 그 props를 기반으로 사용자가 DogImages 목록 위를 hovering 하고 있는지 여부에 따라 텍스트 상자를 조건부로 렌더링할 수 있도록 해보자.</p>
<pre><code class="language-jsx">// withHover.js
import { useState } from &quot;react&quot;;

export default function withHover(Element) {
  return props =&gt; {
    const [hovering, setHover] = useState(false);

    return (
      &lt;Element
        {...props}
        hovering={hovering}
        onMouseEnter={() =&gt; setHover(true)}
        onMouseLeave={() =&gt; setHover(false)}
      /&gt;
    );
  };
}</code></pre>
<pre><code class="language-jsx">// DogImages.js
import withLoader from &quot;./withLoader&quot;;
import withHover from &quot;./withHover&quot;;

function DogImages(props) {
  return (
    &lt;div {...props}&gt;
      {props.hovering &amp;&amp; &lt;div id=&quot;hover&quot;&gt;Hovering!&lt;/div&gt;}
      &lt;div id=&quot;list&quot;&gt;
        {props.data.message.map((dog, index) =&gt; (
          &lt;img src={dog} alt=&quot;Dog&quot; key={index} /&gt;
        ))}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

export default withHover(
  withLoader(DogImages, &quot;https://dog.ceo/api/breed/labrador/images/random/6&quot;)
);</code></pre>
<p><strong>Hooks</strong></p>
<pre><code>⭐️ 경우에 따라 HOC 패턴을 React Hooks로 대체할 수 있다.</code></pre><p>Hover HOC를 <code>useHover</code> hook으로 대체해보자. 요소를 HOC에 전달하는 방식이 아닌, mouseOver 및 mouseLeave 이벤트를 얻을 수 있는 hooks에서 참조를 반환할 것이다.</p>
<pre><code class="language-js">// useHover.js
import { useState, useRef, useEffect } from &quot;react&quot;;

export default function useHover() {
  const [hovering, setHover] = useState(false);
  const ref = useRef(null);

  const handleMouseOver = () =&gt; setHover(true);
  const handleMouseOut = () =&gt; setHover(false);

  useEffect(() =&gt; {
    const node = ref.current;
    if (node) {
      node.addEventListener(&quot;mouseover&quot;, handleMouseOver);
      node.addEventListener(&quot;mouseout&quot;, handleMouseOut);

      return () =&gt; {
        node.removeEventListener(&quot;mouseover&quot;, handleMouseOver);
        node.removeEventListener(&quot;mouseout&quot;, handleMouseOut);
      };
    }
  }, [ref.current]);

  return [ref, hovering];
}</code></pre>
<pre><code class="language-jsx">// DogImages.js
import withLoader from &quot;./withLoader&quot;;
import useHover from &quot;./useHover&quot;;

function DogImages(props) {
  const [hoverRef, hovering] = useHover();

  return (
    &lt;div ref={hoverRef} {...props}&gt;
      {hovering &amp;&amp; &lt;div id=&quot;hover&quot;&gt;Hovering!&lt;/div&gt;}
      &lt;div id=&quot;list&quot;&gt;
        {props.data.message.map((dog, index) =&gt; (
          &lt;img src={dog} alt=&quot;Dog&quot; key={index} /&gt;
        ))}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

export default withLoader(
  DogImages,
  &quot;https://dog.ceo/api/breed/labrador/images/random/6&quot;
);</code></pre>
<p>이처럼 컴포넌트에 직접 hook을 추가하면 더 이상 컴포넌트를 HOC로 래핑할 필요가 없다.</p>
<p>HOC를 사용하면 많은 컴포넌트에 동일한 로직을 제공하면서 해당 로직을 모두 한 곳에 유지할 수 있다. hook을 사용하면 컴포넌트 내에서 사용자 지정 동작을 추가할 수 있으므로 여러 컴포넌트가 이 동작에 의존하는 경우 HOC 패턴에 비해 잠재적으로 버그가 발생할 위험이 높아질 수 있다.</p>
<p><strong>HOC vs Hook</strong></p>
<p>** ✅ HOC를 위한 최적의 사용 사례**</p>
<ul>
<li>사용자 지정되지 않은 동일한 동작을 응용 프로그램 전반에 걸쳐 많은 컴포넌트에서 사용해야 할 때</li>
<li>추가된 사용자 지정 로직 없이 컴포넌트가 독립 실행형으로 작동할 수 있을 때</li>
</ul>
<p>** ✅ Hook을 위한 최적의 사용 사례**</p>
<ul>
<li>동작은 해당 동작을 사용하는 각 컴포넌트에 맞게 사용자 지정되어야 할 때</li>
<li>동작은 애플리케이션 전체에 퍼지지 않으며, 하나 또는 몇 개의 컴포넌트만 동작을 사용할 때</li>
<li>이 동작은 컴포넌트에 많은 프로퍼티를 추가할 때</li>
</ul>
<p><strong>장단점</strong></p>
<p><strong>🟢 장점</strong>
HOC 패턴을 사용하면 모든 것을 한 곳에서 재사용하고자 하는 로직을 유지할 수 있다. 아는 코드를 반복적으로 복제하여 매번 새로운 버그를 잠재적으로 도입함으로써 응용 프로그램에 실수로 버그가 확산될 위험을 줄인다. 로직을 한 곳에 유지함으로써 코드를 DRY로 유지하고 쉽게 문제를 분리할 수 있다.</p>
<p><strong>🔴 단점</strong>
HOC가 요소에 전달할 수 있는 props의 이름은 명명 충돌을 일으킬 수 있다.</p>
<pre><code class="language-jsx">function withStyles(Component) {
  return props =&gt; {
    const style = { padding: &#39;0.2rem&#39;, margin: &#39;1rem&#39; }
    return &lt;Component style={style} {...props} /&gt;
  }
}

const Button = () = &lt;button style={{ color: &#39;red&#39; }}&gt;Click me!&lt;/button&gt;
const StyledButton = withStyles(Button)</code></pre>
<p>위 경우 WithStyles HOC는 전달하는 요소에 style이라는 props를 추가한다. 그러나 Button 컴포넌트에는 style이라는 props가 이미 있으므로 이 props는 덮어쓴다. props 이름을 바꾸거나 props를 병합하여 HOC가 실수로 발생한 이름 충돌을 처리할 수 있는지 확인해야 한다.</p>
<h1 id="⭐️-hooks-pattern">⭐️ Hooks Pattern</h1>
<blockquote>
<p>함수를 사용하여 app 전체의 여러 컴포넌트 간의 stateful한 로직을 재사용</p>
</blockquote>
<p>React 16.8은 Hooks라는 새로운 기능을 도입했다. Hook은 ES2015 클래스 구성 요소를 사용할 필요 없이 리액트 상태 및 라이프사이클 메서드를 사용할 수 있도록 한다.</p>
<p>Hooks가 반드시 디자인 패턴인 것은 아니지만, Hooks를 우리의 응용 디자인에서 매우 중요한 역할을 한다. 많은 전통적인 디자인 패턴은 Hooks로 대체될 수 있다.</p>
<p><strong>Restructuring</strong></p>
<p>여러 컴포넌트 간에 코드를 공유하는 일반적인 방법을 상위 컴포넌트를 사용하는 것이다. 컴포넌트가 클수록 까다롭기 때문에 app을 재구성해야 하는 것 외에도 더 깊은 중첩 컴포넌트 간에 코드를 공유하기 위해 많은 래핑 컴포넌트를 갖는 것을 wrapper hell이라고 불리는 것으로 이어질 수 있다. 개발 도구를 열고 다음과 유사한 구조를 보는 것은 드물지 않다.</p>
<pre><code class="language-jsx">&lt;WrapperOne&gt;
  &lt;WrapperTwo&gt;
    &lt;WrapperThree&gt;
      &lt;WrapperFour&gt;
        &lt;WrapperFive&gt;
          &lt;Component&gt;
            &lt;h1&gt;Finally in the component!&lt;/h1&gt;
          &lt;/Component&gt;
        &lt;/WrapperFive&gt;
      &lt;/WrapperFour&gt;
    &lt;/WrapperThree&gt;
  &lt;/WrapperTwo&gt;
&lt;/WrapperOne&gt;</code></pre>
<p>wrapper hell은 응용 프로그램을 통해 데이터가 어떻게 흘러가고 있는지 이해하기 어렵게 만들 수 있으며, 예상치 못한 동작이 발생하는 이유를 파악하기 어렵게 만들 수 있다.</p>
<p><strong>Custom Hooks</strong>
React가 제공하는 기본 제공 Hook(useState, useEffect, useReducer, useRef, ...) 외에도 커스텀 훅을 쉽게 만들 수 있다.</p>
<p>예를 들어, 사용자가 입력을 작성할 때 누를 수 있는 특정 키를 추적하고 싶다고 가정해보자. 우리의 커스텀 훅은 우리가 목표로 하는 키를 인수로 받을 수 있어야 한다. 또한 사용자가 인수로 전달한 키에 keyDown 및 keyUp 이벤트 리스너를 추가하려고 한다. 사용자가 keyDown 이벤트가 트리거되어 키가 눌렸음을 listen하게 되면 hook의 상태가 true로 전환되어야 한다.</p>
<pre><code class="language-jsx">function useKeyPress(targetKey) {
  const [keyPressed, setKeyPressed] = useState(false);

  function handleDown({ key }) {
    if (key === targetKey) {
      setKeyPressed(true);
    }
  }

  function handleUp({ key }) {
    if (key === targetKey) {
      setKeyPressed(false);
    }
  }

  React.useEffect(() =&gt; {
    window.addEventListener(&quot;keydown&quot;, handleDown);
    window.addEventListener(&quot;keyup&quot;, handleUp);

    return () =&gt; {
      window.removeEventListener(&quot;keydown&quot;, handleDown);
      window.removeEventListener(&quot;keyup&quot;, handleUp);
    };
  }, []);

  return keyPressed;
}</code></pre>
<p><strong>장단점</strong></p>
<p><strong>🟢 장점1: 더 적은 코드 라인</strong>
Hook을 사용하면 라이프사이클이 아닌 <strong>관심사와 기능별로 코드를 그룹화</strong>할 수 있다. 이를 통해 코드가 더 깨끗하고 간결할 뿐만 아니라 더 짧다.</p>
<p><strong>🟢 장점2: 복잡한 컴포넌트 단순화</strong>
JavaScript 클래스는 관리하기 어렵고 사용하기 어려우며 축소되지 않을 수도 있다. React Hooks는 이러한 문제를 해결하고 함수형 프로그래밍을 쉽게 만든다. Hooks를 구현하면 클래스 컴포넌트가 필요하지 않다.</p>
<p><strong>🔴 단점1: 린터 플러그인이 없으면</strong>
린터 플러그인이 없으면 규칙을 준수해야 하며 어떤 규칙이 위반되었는지 알기 어렵다.</p>
<p><strong>🔴 단점2: 상당한 연습 시간</strong>
제대로 사용하려면 상당한 시간의 연습이 필요하다.</p>
<h1 id="flyweight-pattern">Flyweight Pattern</h1>
<blockquote>
<p>동일한 객체로 작업할 때 기존 인스턴스 재사용</p>
</blockquote>
<pre><code>⭐️ 플라이급 패턴은 유사한 객체를 많이 만들 때 메모리를 절약하는 유용한 방법</code></pre><p>애플리케이션에서 우리는 사용자들이 책을 추가할 수 있기를 바란다. 모든 책은 제목, 저자, 그리고 isbn 번호를 가지고 있다. 하지만, 도서관은 보통 책의 한 권만 가지고 있지 않다. 보통 같은 책을 여러 권 가지고 있다.</p>
<p>정확히 동일한 책의 복사본이 여러 개인 경우 매번 새로운 책 인스턴스를 만드는 것은 매우 비효율적일 것이다. 대신, 우리는 하나의 책을 나타내는 책 construcotr의 인스턴스를 여러 개 만드는 것을 원한다.</p>
<pre><code class="language-js">class Book {
  constructor(title, author, isbn) {
    this.title = title
    this.author = author
    this.isbn = isbn
  }
}</code></pre>
<p>목록에 새 책을 추가하는 기능을 만들자. 책을 ISBN 번호가 동일하여 완전히 동일한 책 유형이라면 완전히 새로운 책 인스턴스를 만들고 싶지 않다. 대신 이 책이 이미 존재하는지 먼저 확인해야 한다.</p>
<pre><code class="language-js">const books = new Map()

const createBook = (title, author, isbn) =&gt; {
  const existingBook = books.has(isbn)

  if (existingBook) {
    return books.get(isbn)
  }
}</code></pre>
<p>책의 ISBN 번호가 아직 포함되지 않은 경우 새 책을 만들고 ISBN 번호를 isbn 번호 집합에 추가한다.</p>
<pre><code class="language-js">const createBook = (title, author, isbn) =&gt; {
  const existingBook = books.has(isbn)

  if (existingBook) {
    return books.get(isbn)
  }

  const book = new Book(title, author, isbn)
  books.set(isbn, book)

  return book
}</code></pre>
<p><code>createBook</code> 함수는 한 종류의 책에 대한 새로운 인스턴스를 만드는 것을 도와준다. 그러나 도서관은 보통 같은 책의 여러 복사본을 포함한다. 같은 책의 여러 복사본을 추가할 수 있는 <code>addBook</code>을 만들자. 그것은 새로 만든 책 인스턴스를 반환하거나 이미 존재하는 인스턴스를 반환하는 <code>createBook</code> 함수를 호출해야 한다.</p>
<pre><code class="language-js">const bookList = [];

const addBook = (title, author, isbn, availability, sales) =&gt; {
  const book = {
    ...createBook(title, author, isbn),
    sales,
    availability,
    isbn,
  };

  bookList.push(book);
  return book;
};</code></pre>
<p>복사본을 추가할 때마다 새 인스턴스를 만드는 대신, 우리는 해당 특정 복사본에 대해 이미 존재하는 책 인스턴스를 효과적으로 사용할 수 있다.</p>
<pre><code class="language-js">addBook(&quot;Harry Potter&quot;, &quot;JK Rowling&quot;, &quot;AB123&quot;, false, 100);
addBook(&quot;Harry Potter&quot;, &quot;JK Rowling&quot;, &quot;AB123&quot;, true, 50);
addBook(&quot;To Kill a Mockingbird&quot;, &quot;Harper Lee&quot;, &quot;CD345&quot;, true, 10);
addBook(&quot;To Kill a Mockingbird&quot;, &quot;Harper Lee&quot;, &quot;CD345&quot;, false, 20);
addBook(&quot;The Great Gatsby&quot;, &quot;F. Scott Fitzgerald&quot;, &quot;EF567&quot;, false, 20);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 이미지 최적화(Image Optimization) + sharp]]></title>
            <link>https://velog.io/@doeunnkimm_/Next.js-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B5%9C%EC%A0%81%ED%99%94Image-Optimization-6h58dwr4</link>
            <guid>https://velog.io/@doeunnkimm_/Next.js-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B5%9C%EC%A0%81%ED%99%94Image-Optimization-6h58dwr4</guid>
            <pubDate>Wed, 12 Jul 2023 01:56:24 GMT</pubDate>
            <description><![CDATA[<h1 id="이미지-최적화란">이미지 최적화란</h1>
<p>이미지 최적화는 웹 사이트의 성능을 개선하기 위해 이미지의 크기와 품질을 최적화하는 프로세스입니다. <strong>이미지의 품질 저하 없이 개선</strong>하여 사용자에게 <strong>이미지를 빠르고 효율적으로 제공</strong>할 수 있도록 합니다.</p>
<p>이를 통해 웹 사이트 로딩 시간을 크게 단축하여 렌더링 속도를 개선할 수 있고 이는 더 나은 사용자 경험과 연결됩니다. </p>
<p>뿐만 아니라, 이미지 최적화는 검색 엔진 크롤러에게 웹 사이트가 더 빠르게 표시될 수 있으므로 <strong>SEO 순위 향상</strong>에도 도움이 될 수 있습니다.</p>
<blockquote>
<p>궁긍적으로, 이미지 최적화는 웹 사이트 로딩 시간을 줄이고 전반적인 사용자 경험을 개선하는 데 도움이 될 수 있다.</p>
</blockquote>
<h2 id="이미지-최적화를-해야하는-이유">이미지 최적화를 해야하는 이유</h2>
<p><a href="https://almanac.httparchive.org/en/2022/">Web Almanac</a>에 따르면 <a href="https://almanac.httparchive.org/en/2022/page-weight#content-type-and-file-formats">이미지는 일반적인 웹사이트 페이지 무게</a>의 상단 부분을 차지합니다. 또한 이미지는 웹사이트의 <a href="https://almanac.httparchive.org/en/2022/performance#lcp-image-optimization">LCP</a>와 <a href="https://almanac.httparchive.org/en/2022/performance#cumulative-layout-shift-cls">CLS</a> 성능과 직결되어 있습니다.</p>
<blockquote>
<p>결론적으로, 웹 성능을 위해 이미지 최적화를 해야한다.</p>
</blockquote>
<h2 id="이미지가-웹-성능에-미치는-영향">이미지가 웹 성능에 미치는 영향</h2>
<p>이는 Web vitals를 통해 살펴보면 좀 더 이해가 쉬운데요!</p>
<h3 id="web-vitals">Web vitals</h3>
<p>Web vitals은 웹 페이지 로딩 속도, 모바일 친화성, 세이프 브라우징, 암호화(HTTPS 적용 여부), 방해요소 여부 등과 같은 웹 콘텐츠 사용자의 경험에 미치는 다양한 측정 가능한 값들을 말합니다. </p>
<blockquote>
<p>구글은 이를 통해 웹 페이지의 품질을 평가한다.</p>
</blockquote>
<p>즉, 해당 지표를 통해 이미지 최적화가 웹 성능에 어떠한 영향을 미칠 수 있는지를 알 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/fd3a2a34-649e-49ee-867a-32d4e90222b8/image.png" alt=""></p>
<p>이 중에서도 <strong>로딩 속도</strong>와 <strong>레이아웃</strong> 관련된 지표 2가지에 대해서만 알아보려고 합니다. (이미지 관련된 이야기만 해봅시다)</p>
<h4 id="1-lcplargest-contentful-paint-최대-콘텐츠풀-페인트">1. LCP(Largest Contentful Paint, 최대 콘텐츠풀 페인트)</h4>
<p>LCP는 사용자가 화면에 렌더링된 콘텐츠를 보는데 걸리는 시간입니다. </p>
<blockquote>
<p>LCP는 웹 페이지의 로딩 속도에 대한 지표</p>
</blockquote>
<p>LCP는 다음과 같이 사용자 경험과 관련된 로드 시간만 계산합니다.</p>
<ul>
<li>이미지</li>
<li>비미오 썸네일</li>
<li>CSS를 사용한 배경 이미지</li>
<li>단락, 제목 및 목록과 같은 텍스트 요소</li>
</ul>
<h4 id="2-clscumulative-layout-shife-누적-레이아웃-시프트">2. CLS(Cumulative Layout Shife, 누적 레이아웃 시프트)</h4>
<p>CLS는 방문자에게 콘텐츠가 얼마나 불안정한지 측정하는 사용자 경험 측면 항목입니다. 뉴스 기사를 보려고 들어간 웹 사이트에서 기사 링크를 클릭한 순간 레이아웃이 이동해서 광고가 나타나 기사가 아닌 광고를 클릭한 경험이 한번씩 있을텐데...</p>
<blockquote>
<p>페이지에 들어갔을 때 갑작스럽게 발생하는 레이아웃 이동의 정도를 합산한 이동 거리 개념을 도입 <br> → 얼마나 안정적인 레이아웃이냐를 따진다.</p>
</blockquote>
<p>다음은 CLS에 악영향을 미치는 영향입니다.</p>
<ul>
<li><strong>치수가 없는 이미지</strong>
이미지가 로드되는 동안 브라우저가 문서의 공간을 올바르게 할당하여 영역을 미리 확보해 두는 것이 중요하다.</li>
</ul>
<h2 id="이미지-최적화-전략">이미지 최적화 전략</h2>
<p>위에서 웹 성능에 미치는 영향이 다양한 것처럼, 이를 토대로 이미지 최적화 전략에 대해 정리해 보면 다음과 같습니다.</p>
<p><strong>1. webp 사용하기 → avif 사용하기</strong>
<code>webp</code>란 구글에서 개발한 이미지 포맷으로 <code>png</code>나 <code>jpeg</code> 형식의 이미지보다 이미지 파일 용량이 훨씬 작습니다.
<code>webp</code>가 나온 이후에 <code>avif</code>라는 보다 압축률이 좋은 포맷 방법도 개발되었는데요.
<img src="https://velog.velcdn.com/images/doeunnkimm_/post/13e9650e-3974-4a44-a633-adf16eedd227/image.png" alt="">
위 사진을 보게 되면 큰 품질 저하 없이 훨씬 적은 용량으로 이미지를 사용할 수 있어졌습니다. <code>webp</code>도 많은 용량을 절약할 수 있지만 <code>avif</code>는 보다 적은 용량으로 이미지를 사용할 수 있는 것을 확인할 수 있었습니다.</p>
<p><strong>2. 이미지 lazy loading</strong>
한 페이지에 많은 양의 이미지가 있다고 할 때, 사용자가 모든 콘텐츠를 보지 않는다면? 해당 화면에 있는 모든 이미지를 로드하는 것은 분명 비효율적일 것입니다.
따라서 <strong>화면에 나타나기 전에는 placeholder 이미지</strong>를 넣어두었다가, 화면에 나타났을 때(viewport에 걸렸을 때) 리소스를 요청하도록 하는 방법이 있습니다.
이렇게 하면 <strong>필요한 순간에 네트워크에 요청</strong>하도록 만들어 성능을 최적화할 수 있습니다.</p>
<p><strong>3. 이미지 리사이징</strong>
모든 디바이스 화면에서 같은 화질, 같은 사이즈로 이미지를 보여줄 필요가 없습니다. 디바이스의 크기에 알맞게 적절한 이미지를 보여주면 성능을 최적화할 수 있습니다.</p>
<p><strong>4. 이미지 캐싱</strong>
이미지를 캐싱해두면 네트워크 요청 시 속도를 매우 향상시킬 수 있습니다. 하지만 캐싱하기 위해서는 메모리가 필요한 법이니 고민있는 선택이 필요합니다.</p>
<h1 id="nextjs에서의-이미지-최적화">Next.js에서의 이미지 최적화</h1>
<h2 id="1-이미지-포맷-활용">1. 이미지 포맷 활용</h2>
<p>어떤 포맷을 사용할지 결정하기 전에 이미지 포맷에는 어떤 것들이 있는지, 각각에는 어떤 특징들이 있는지 알아봐야겠습니다 :)</p>
<h3 id="이미지의-여러-포맷들">이미지의 여러 포맷들</h3>
<p>이미지 포맷 형식에는 jpg, png, webpg, avif 등이 있습니다. </p>
<blockquote>
<p>이미지의 다양항 형식을 이해할 때 중심점이 되는 것은 <strong>압축형식</strong>이다.</p>
</blockquote>
<h4 id="손실-압축-vs-무손실-압축">손실 압축 vs 무손실 압축</h4>
<ul>
<li><p><strong>손실 압축</strong></p>
<ul>
<li>이미지의 <strong>품질을 희생하고 더 적은 용량</strong>을 선택한 방식</li>
<li>이미지의 중요한 정보만 최대한 보존하고, 불필요한 정보는 조금씩 빼는 방식으로 압축</li>
<li>저장을 하면 자동으로 이미지가 손실되며, <strong>저장이 누적될수록 손실도 누적</strong></li>
</ul>
<ul>
<li><strong>무손실 압축</strong><ul>
<li>이미지의 품질을 떨어뜨리지 않은 채로 압축하는 방식</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="1-jpg">1. jpg</h4>
<p>jpg는 Joint Photograph Experts Group의 줄임말입니다. jpg는 대표적으로 <strong>손실압축</strong> 방식을 채택한 이미지 형식입니다. 따라서 jpg 이미지는 저장하기만 해도 이미지에는 손실이 발생합니다.</p>
<h4 id="2-png">2. png</h4>
<p>png는 Portable Network Graphics의 줄임말입니다. 말 그래도 인터넷에서 표현될 이미지를 염두에 두고 만들어졌습니다. 그래서 색상값은 RGB를 사용하며 투명도를 표현할 수 있습니다. <strong>무손실 압축</strong>을 사용하기 때문에 고품질을 유지한다는 특징이 있습니다.</p>
<h4 id="3-webp">3. webp</h4>
<p>webp는 2010년 구글에서 만들었습니다. webp는 고품질의 이미지를 표현하면서도 png, jpg 등 기존의 포맷보다 파일의 크기가 작습니다. <strong>무손실 압축 방식</strong>입니다.</p>
<h4 id="4-avif">4. avif</h4>
<p><code>avif</code>는 2019년 AOMedia에서 만들었습니다. avif는 여러 형식(<code>jpg</code>, <code>webp</code>, ..) 보다 훨신 더 나은 무손실 압축과 고품질을 자랑합니다. 단순히 <code>jpg</code>와 비교했을 때는 동일한 품질 대비, 최대 10배나 적은 용량을 가집니다.
webp와 비교했을 때는 20% 더 높은 압축률을 보여줍니다.</p>
<h3 id="🤩-적용해보자">🤩 적용해보자</h3>
<p><code>avif</code>를 사용하는 경우 <code>webp</code>보다 20% 높은 압축률을 자랑한다고 했으니, <code>avif</code> 포맷 형식을 사용해봅시다.</p>
<blockquote>
<p>📌 참고 <br> <code>next/image</code>의 <code>&lt;Image&gt;</code>를 사용하면 Next가 알아서 <code>webp</code> 형식으로 이미지를 최적화 해준다.</p>
</blockquote>
<p>하지만, 이미지를 <code>avif</code> 형식을 사용하고 싶다면, <code>next.config.js</code>에서 설정이 필요합니다.</p>
<p>📄 next.config.js</p>
<pre><code class="language-javascript">const nextConfig = {
  images: [&#39;image/avif&#39;, &#39;image/webp&#39;],
}</code></pre>
<p>위와 같이 설정해주고 이미지를 불러오면 모든 이미지가 <code>avif</code> 형식의 이미지로 불러와지고, 해당 설정이 없으면 모든 이미지를 알아서 <code>webp</code> 형식의 이미지로 불러옵니다.</p>
<p>직접 동일한 이미지(크기도 동일하게)에 대해 포맷 형식만 다르게 하여 비교해 보았습니다.</p>
<table>
<thead>
<tr>
<th>포맷 형식</th>
<th>Network</th>
<th>사이즈</th>
</tr>
</thead>
<tbody><tr>
<td>avif</td>
<td><img src="https://velog.velcdn.com/images/doeunnkimm_/post/fd8685b5-58ff-499e-b2c1-2dd5c89dd7c7/image.png" alt=""></td>
<td>202B</td>
</tr>
<tr>
<td>webp</td>
<td><img src="https://velog.velcdn.com/images/doeunnkimm_/post/fa865b22-61f2-45ad-af0b-b9ac83e06142/image.png" alt=""></td>
<td>16.5kB</td>
</tr>
</tbody></table>
<p>직접 Network 탭에서 확인했을 때 avif가 webp에 비해 80% 작은 사이즈를 가지는 것을 확인할 수 있었습니다.</p>
<p>이미지 화질 면에서도 차이를 보여드리자면 (참고로 원래는 png 형식의 이미지입니다)</p>
<table>
<thead>
<tr>
<th>avif</th>
<th>webp</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/doeunnkimm_/post/c59a7027-bdf7-42d5-a3a3-45894076f603/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/doeunnkimm_/post/4fb9c500-9287-4a7a-88fe-ad3fae88d947/image.png" alt=""></td>
</tr>
</tbody></table>
<p>두 이미지 모두 <code>png</code> 이미지에서 보다 큰 화질 저하는 없어 보였습니다. 뿐만 아니라 두 이미지는 80배의 용량 차이가 있지만, 두 이미지을 두고 비교해 보아도 큰 차이가 눈으로는 보이지 않았습니다.</p>
<h4 id="브라우저가-지원하지-않는다면">브라우저가 지원하지 않는다면?</h4>
<p>AVIF는 요즘 대부분의 브라우저에서 지원하지만 일부 지원하지 않는 경우도 있으니 사용할 대 주의해야 합니다.</p>
<p>아래는 caniuse.com에서 AVIF를 검색했을 때의 결과입니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/46dfe8e9-358a-4b91-8e5a-265a83170a7c/image.png" alt=""></p>
<p><strong>그럼 AVIF를 사용하지 말아야 할까?</strong></p>
<p>아닙니다! 일반적으로 이런 경우 <code>&lt;picture&gt;</code> 태그를 사용할 수 있습니다.</p>
<pre><code class="language-jsx">&lt;picture&gt;
    &lt;source srcset=&quot;img/photo.avif&quot; type=&quot;image/avif&quot;&gt;
    &lt;source srcset=&quot;img/photo.webp&quot; type=&quot;image/webp&quot;&gt;
    &lt;img src=&quot;img/photo.jpg&quot; alt=&quot;Description&quot; width=&quot;360&quot; height=&quot;240&quot;&gt;
&lt;/picture&gt;</code></pre>
<p><a href="https://www.w3schools.com/tags/tag_picture.asp">picture 태그</a>를 사용하면 <code>&lt;picture&gt;</code> 태그 안에 있는 이미지를 순서대로 지원 가능한지를 검사하고 브라우저가 인식하지 못하는 이미지라면 건너뛰게 됩니다. </p>
<h2 id="2-이미지-lazy-loading">2. 이미지 lazy loading</h2>
<h3 id="lazy-loading이란">lazy loading이란</h3>
<p>이미지 lazy loading이란 웹 페이지에서 이미지를 지연해서 로드하는 전략입니다. 사용자가 스크롤하거나 필요할 때까지 이미지를 로드하지 않고, 해당 이미지가 뷰포트에 가까워질 때 동적으로 로드합니다. </p>
<p>기존 React에서는 lazy loading을 적용하기 위해서 intersection observer API를 이용하거나 이벤트 함수를 활용하는 방법이 있습니다.</p>
<h3 id="🤩-적용해보자---nextjs에서는-기본적으로-지원">🤩 적용해보자 - Next.js에서는 기본적으로 지원</h3>
<p><code>next/image</code>의 <code>&lt;Image&gt;</code> 컴포넌트를 통해 이미지를 로드할 경우, 기본적으로 lazy loading을 지원해 줍니다. 뿐만 아니라 <code>placeholder</code>라는 옵션을 통해 네트워크에서 불러오는 동안 임시로 보여줄 이미지도 자동으로 blur 처리를 해줍니다.</p>
<pre><code class="language-jsx">&lt;Image
  src=&#39;/images/test.png&#39;
  alt=&#39;test&#39;
  width={300}
  height={300}
  placeholder=&#39;blur&#39;
  blurDataURL=&#39;image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==&#39;
/&gt;</code></pre>
<p>위의 경우 static 이미지를 사용한 경우이고, external 이미지를 이용하려는 경우 추가적인 설정이 필요합니다.</p>
<h4 id="external-이미지-사용하기">external 이미지 사용하기</h4>
<p>next.js에서 외부 이미지를 사용하려면 추가적인 설정이 필요합니다.</p>
<p>📄 next.config.js</p>
<pre><code class="language-js">const nextConfig = {
  images: {
    domains: [&#39;picsum.photos&#39;],
  },
}</code></pre>
<p>위와 같이 가져올 이미지의 도메인을 설정에 추가해주면 됩니다.</p>
<h2 id="3-이미지-리사이징---반응형-이미지">3. 이미지 리사이징 - 반응형 이미지</h2>
<p>가로 길이가 480px인 모바일 화면에서 4K 이미지를 보여주어야 할 일은 없을 것입니다. 480px의 화면에선 그 크기에 알맞는 정도의 이미지만 가져오면 되빈다.
필요 이상 크기의 이미지를 가져오는 것은 네트워크 대역폭을 낭비합니다. 때문에 화면에 알맞는 크기의 이미지를 보여주는 것은 성능을 최적화하는 데에 도움이 됩니다.</p>
<blockquote>
<p>우리가 해야할 일은 브라우저 화면에 알맞는 이미지를 가져오도록 하는 것 <br> → 이를 <strong>resolution switching</strong>이라고 한다.</p>
</blockquote>
<h3 id="이미지-리사이징-하는-방법---srcset--sizes">이미지 리사이징 하는 방법 - srcset &amp; sizes</h3>
<p>일반적으로 다음과 같이 <code>&lt;img&gt;</code> 태그 안에 src와 alt를 넣어 구성하죠?</p>
<pre><code class="language-jsx">&lt;img src=&quot;test.png&quot; alt=&quot;테스트 이미지&quot;&gt;</code></pre>
<p>여기서 반응형 이미지를 제공하고 싶다면, 브라우저가 인지할 수 있도록 srcset 속성과 sizes 속성을 사용할 수 있습니다.</p>
<pre><code class="language-jsx">&lt;img srcset=&quot;test-320w.png 320w,
             test-480w.png 480w,
             test-800w.png 800w&quot;
     sizes=&quot;(max-width: 320px) 280px,
            (max-width: 480px) 440px,
            800px&quot;
     src=&quot;test.png&quot; alt=&quot;테스트 이미지&quot;&gt;</code></pre>
<p>각각에 대해 알아봅시다.</p>
<h4 id="srcset">srcset</h4>
<p>브라우저에게 어떤 크기의 이미지를 보여주면 되는지 알려주는 역할을 합니다. 각각의 이미지의 크기도 함께 정의하면서 보여줍니다.</p>
<pre><code class="language-js">srcset=이미지파일명 픽셀너비w</code></pre>
<h4 id="sizes">sizes</h4>
<p>미디어 조건문을 나타냅니다. 그래서 특정 화면에서 어떤 크기가 최적인지를 나타냅니다.</p>
<pre><code class="language-js">sizes=미디어조건문 이미지가채울슬롯의너비</code></pre>
<h4 id="srcset--sizes를-명시했을-때-브라우저는">srcset &amp; sizes를 명시했을 때, 브라우저는</h4>
<p>위와 같은 속성들을 명시해주었을 때, 브라우저에서 일어나는 일의 순서는 다음과 같습니다.</p>
<ol>
<li>기기의 너비를 확인한다.</li>
<li>sizes에서 가장 먼저 참이 되는 조건문을 확인한다.</li>
<li>그 조건문에서 제공하는 슬롯의 크기를 확인한다.</li>
<li>그 슬롯의 크기에 가장 근접한 이미지를 srcset에서 찾는다.</li>
</ol>
<h3 id="🤩-적용해보자---nextjs에서는-기본적으로-지원-1">🤩 적용해보자 - Next.js에서는 기본적으로 지원</h3>
<p>이번에도 역시 <code>next/image</code>의 <code>&lt;Image&gt;</code>에서 기본적으로 제공합니다.
<strong>Next.js는 srcset을 자동으로 설정</strong>하여 <strong>이미지 후보들을 생성</strong>하고 viewport의 너비에 따라 로드될 이미지 후보들 중에서 선택하여 로드합니다.</p>
<h4 id="image의-layout-속성"><code>&lt;Image&gt;</code>의 layout 속성</h4>
<p>이를 사용하기 위해서는 <code>&lt;Image&gt;</code>에 <code>layout</code> 이라는 속성이 필요합니다.
이 속성은 해당 이미지가 <strong>viewport에 따라 어떻게 반응하는지</strong>에 대한 속성입니다.</p>
<p>4가지 옵션이 존재합니다.</p>
<ul>
<li><p>intrinsic
default값이며, 이미지의 width와 height에 따라 얼마나 많은 자리를 차지하는지 계산</p>
</li>
<li><p>fixed
이미지의 정확한 width와 height를 사용하여 표시</p>
</li>
<li><p>fill
이미지를 상위 엘리먼트의 width와 height에 맞추기 위해 자동으로 width와 height를 조절.
반드시 상위 엘리먼트는 <code>position: relative</code>을 적용해야 한다.
⭐️ 이미지 사이즈를 모를 때 사용하면 좋은 옵션</p>
</li>
<li><p>responsive
부모 컨테이너의 width에 맞게 이미지를 확대. 반드시 부모 컨테이너에 <code>display: block</code>을 추가해야 한다.</p>
</li>
</ul>
<blockquote>
<p>반응형을 위해서는 <code>fill</code> 혹은 <code>responsive</code>로 사용</p>
</blockquote>
<p>🚨 주의할 점은 layout 속성을 사용하면 width와 height 속성을 사용할 수 없습니다.</p>
<h4 id="image의-sizes-속성"><code>&lt;Image&gt;</code>의 sizes 속성</h4>
<p>현재 뷰포트의 너비에 따라 로드될 이미지를 설정할 수 있습니다. <code>sizes</code>는 layout 속성 값이 responsive와 fill인 경우메나 사용되는 속성입니다.</p>
<pre><code class="language-jsx">&lt;div style={{ position: &#39;relative&#39;, width: &#39;300px&#39;, height: &#39;300px&#39; }}&gt;
      &lt;Image
        src=&#39;/images/test.png&#39;
        alt=&#39;test&#39;
        // width={300}
        // height={300}
        layout=&#39;fill&#39;
        sizes=&#39;(max-width: 768px) 50vw,
         (max-width: 1024px) 100vw&#39;
        placeholder=&#39;blur&#39;
        blurDataURL=&#39;image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==&#39;
      /&gt;
    &lt;/div&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/b8500827-557e-4163-a897-ea2ddaccecfb/image.png" alt=""></p>
<p>자동으로 srcset이 지정되어 있는 모습을 볼 수 있었습니다.</p>
<h2 id="4-이미지-용량-압축하기">4. 이미지 용량 압축하기</h2>
<p>이미지의 용량은 성능에 많은 영향을 미칩니다. 때문에 가능하면 용량이 적은 이미지를 사용하는 것이 좋지만, 이미지를 업로드하는 경우 압축이 필요할 수 있습니다.</p>
<h3 id="🤩-적용해보자-1">🤩 적용해보자</h3>
<p>이미지를 압축해주는 라이브러리를 우선 몇 가지 살펴봅시다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/13686b9f-7eb3-44bd-8980-4acecf0ecf73/image.png" alt=""></p>
<p><code>browser-image-compression</code> 와 <code>compressorjs</code> 중에 고르면 좋을 것 같은데, 거의 모든 조건이 비슷한 것 같아 <strong>번들 사이즈가 좀 더 작은</strong> <code>compressorjs</code>를 선택하려고 했으나 class 기반으로 되어 있어, 비교적 사용법이 간단한 <code>browser-image-compression</code> 로 선택하게 되었습니다.</p>
<pre><code class="language-bash">$ yarn add browser-image-compression</code></pre>
<p>📄 utils/compressImage.ts</p>
<pre><code class="language-jsx">import imageCompression from &#39;browser-image-compression&#39;;

export const compressImage = async (file: File) =&gt; {
  const options = {
    maxSizeMB: 0.2,
    maxWidthOrHeight: 1920,
    useWebWorker: true,
  };
  return await imageCompression(file, options);
};</code></pre>
<p>위와 같이 설정해서 압축한 후 업로드하면 되겠습니다.</p>
<h2 id="5-이미지-캐싱하기">5. 이미지 캐싱하기</h2>
<p>캐싱이란 파일 사본을 캐시 혹은 임시 저장소에 저장해서 보다 빠르게 접근할 수 있도록 하는 하나의 프로세스입니다.
쉽게 말하면, 가까운 곳에 데이터를 임시로 보관하는 것이라고 말할 수 있는데요!</p>
<p>가까운 곳에 저장해두면 먼 곳까지 데이터를 가지러 안 가도 되로고, 혹은 오래 걸리는 연산을 다시 하지 않도록 해서 성능을 최적화하는 기법이라고 할 수 있습니다.</p>
<p>다른 리소스에 비해 꽤 큰 용량을 차지하는 이미지의 경우, 캐싱을 해두면 성능 향상에 도움이 될 수 있습니다.</p>
<h3 id="캐싱-방법1-헤더를-통한-캐싱">캐싱 방법1: 헤더를 통한 캐싱</h3>
<p>HTTP/1.1 버전부터 cache-control 및 expire, validation 등의 기능이 지원되면서 캐싱을 효율적으로 할 수 있게 되었습니다. </p>
<p>캐시을 할 때에는 기본적으로 2가지 전략이 있습니다.</p>
<ul>
<li>mutable한 리소스</li>
<li>immutable한 리소스</li>
</ul>
<p>각각 다른 전략을 취해야 합니다.</p>
<h4 id="mutable한-리소스">mutable한 리소스</h4>
<p>mutable한 리소스란 <code>index.html</code>파일처럼 그 내부가 변경될 가능성이 있는 파일입니다. 그래서 리소스를 캐싱해두었다가, <strong>리소스의 내용물이 변경되었을 경우에 캐싱을 무효</strong>하게 만들고, <strong>다시 데이터를 요청</strong>하도록 하는 방법입니다. 이때 헤더에 명시할 내용은 다음과 같습니다.</p>
<p><code>Cache-Control</code>에 <code>no-cache</code>를 설정합니다. no-cache는 캐싱을 하지 않겠다는 말이 아니라, <strong>서버에게 새로운 컨텐츠가 있는지를 묻는 역할</strong>을 합니다. 그래서 새로운 컨텐츠가 있다면 그것을 다운로드합니다.</p>
<p>또한 <code>ETags</code>(Entity Tag)를 사용합니다. 위에서 <code>no-cache</code>를 통해 서버에게 새로운 데이터가 있는지 확인한다고 했지만, 반드시 함께 사용해야 하는 속성이 바로 <code>ETags</code>입니다. <code>ETags</code>은 특정 리소스에 대한 토큰을 발급합니다. 그래서 해당 토큰값을 가지고 있다가, 컨텐츠가 변경되면 새로운 토큰을 발급합니다. 토큰 값을 비교하면서, 서로 다른 토큰값이 발견되면 컨텐츠에 변경이 생겼다는 것을 알아차리는 방식입니다.</p>
<p>🤔 <strong>no-cache와 no-store</strong>
이름은 비슷하지만 두 값의 동작은 좀 다릅니다.</p>
<ul>
<li><p><strong>no-cache</strong>
캐시는 저장하지만 사용하려고 할 때마다 서버에 재검증 요청을 보내야 합니다.</p>
</li>
<li><p><strong>no-store</strong>
캐시를 절대로 해서는 안 되는 리소스일 때 사용합니다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/1e88c987-d46f-40c3-9141-506cc6593ded/image.png" alt=""></p>
<p>위 사진을 보게 되면 서버에서 발급받은 ETags를 브라우저는 가지고 있다가 리소스를 요청합니다. 서버 쪽에서 ETags를 확인했더니 일치하는 토큰입니다. 즉, 변경 사항이 없었다는 것입니다. <code>304</code> 상태코드와 함께 응답을 보냅니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/9b76bf8e-4cea-4e65-a98b-b0fa920408f1/image.png" alt=""></p>
<p>위 사진은 토큰이 일치하지 않은 경우입니다. 브라우저가 가지고 있던 ETags와 함께 리소스를 요청했는데, 서버 쪽에서 확인을 해보니 토큰이 달랐던 겁니다. 그래서 상태코드 <code>200</code>와 함께 새로운 ETags와 변경된 리소스를 함께 응답으로 보내줍니다.</p>
<p>이것이 바로 mutable한 리소스에 대한 캐싱 전략입니다.</p>
<blockquote>
<p>서버는 리소스 요청과 함께 온 ETags를 받고 변경 사항이 없다면 304 상태 코드와 캐싱된 리소스를 보내준다 <br > 만약 변경 사항이 있다면 200 상태 코드와 함께 변경된 리소스를 응답으로 보내준다.</p>
</blockquote>
<h4 id="immutable한-리소스">immutable한 리소스</h4>
<p>immutable한 리소스의 대표적인 것에는 이미지 파일이 있습니다. 우리가 이미지 파일을 내부적으로 변경시킬 일은 없을 것입니다. 또한 파일 이름을 통해서 버전관리를 하는 파일들도 immutable한 리소스에 포함됩니다. 우리가 vscode에서 파일 이름을 변경하면 git은 전 파일이 delete되고 새로운 파일이 추가된 것처럼 인식하는 것을 봐도 그렇습니다. immutable한 리소스에 대한 캐싱 전략을 알아봅시다.</p>
<p><code>Cache-Control</code>에 <code>max-age</code>를 설정해줍니다. 만약 1년을 캐싱하도록 하고 싶다면 <code>max-age: 31536000</code>로 설정하면 됩니다. 또 다른 방법으로는 <code>expires</code> 헤더를 사용하는 것입니다. <code>max-age</code>는 어느정도 시간동안 캐싱하겠다고 나타낸다면, <code>expires</code>는 특정 날짜까지 캐싱을 하겠다고 명시하는 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/442ee7b6-ee50-4a6b-b81f-3aabc7743727/image.png" alt=""></p>
<p>위와 같이 한 번 받아온 리소스의 유효 기간이 지나기 전이라면, 브라우저는 서버에 요청을 보내지 않고 디스크 또는 메모리에서만 캐시를 읽어와 계속 사용합니다.</p>
<p>🤔 <strong>캐시의 유효 기간이 지나면 어떻게 될까?</strong>
캐시의 유효 기간이 끝나면 캐시가 완전히 사라질까요? 그렇지는 않습니다. 대신 브라우저는 서버에 <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests">조건부 요청(Conditional request)</a>을 통해 캐시가 유효한지 재검증(Revalidation)을 수행합니다.</p>
<p>재검증 결과, 브라우저가 가지고 있는 캐시가 유효하다면 서버는 304 상태코드로 매우 빠르게 리소스 응답이 가능합니다. 만약 캐시가 유효하지 않다면 200 상태 코드와 함께 최신 값을 내려받을 수 있도록 합니다.</p>
<h3 id="캐싱-방법2-메모리-캐시와-디스크-캐시">캐싱 방법2: 메모리 캐시와 디스크 캐시</h3>
<p>실제로 어떻게 캐싱을 적용하기 전에 메모리 캐시와 디스크 캐시에 대해 알아봐야겠습니다. </p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/94105f37-2603-4d93-87ea-592e208d5c06/image.png" alt=""></p>
<p>우선 그 전에, 캐싱이 잘 되고 있는가를 확인하려면 네트워크 탭에서 위와 같이 (memory cache) 혹은 (disk cache)라고 적혀 있습니다.</p>
<p>자세히 보면 특징이 있습니다. memory cache의 경우 리소스를 다운 받는데 걸린 시간이 0ms, disk cache의 경우에는 2ms 혹은 3ms인 것을 확인할 수 있습니다. 무슨 차이가 있길래 그런 걸까요?</p>
<h4 id="memory-cache">memory cache</h4>
<p>memory cache의 경우, 리로스를 메모리(RAM)에 저장해 둡니다. 때문에 훨씬 빠르지만, 휘소성이 있습니다. 브라우저가 닫히기 전까지는 메모리에 캐싱되어 있다가 사용됩니다.</p>
<h4 id="disk-cache">disk cache</h4>
<p>disk cache의 경우는 영구적입니다. 즉, 휘소성이 없습니다. 이는 유저의 디스크에 저장되어 있다가, 리소스를 요청하면 디스크로부터 가져오기 때문에 메모리보다는 느립니다.</p>
<p>그래서 브라우저를 껐다가 바로 다시 켰을 때는 모든 데이터가 disk cache에서 가져오고, 그 이후에 새로고침을 하게 되면 몇몇 데이터는 memory cache에서 가져오기 시작할 것입니다.</p>
<h3 id="🤩-적용해보자---nextjs에서는-기본적으로-지원-2">🤩 적용해보자 - Next.js에서는 기본적으로 지원</h3>
<p>Next.js에서 이미 이미지까지 캐싱하도록 지원을 하고 있습니다. 기본적으로 static 폴더에 들어가 있는 모든 파일들은 자동으로 캐싱됩니다. 또한 동적으로 불러오는 이미지들의 경우에도 캐싱이 되고 있습니다. <code>.next/cache</code> 폴더에서 /image 폴더에 들어가보면, 캐싱되어 있는 external 이미지들을 확인할 수 있었습니다. 요청했던 사이즈별로 이미지가 캐싱되어 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/94ac171e-977c-41a2-98d8-8070cf94ebc6/image.png" alt=""></p>
<h1 id="13버전의-image-컴포넌트-개선">13버전의 Image 컴포넌트 개선</h1>
<p>Next.js 13 버전이 나오면서 많은 부분들이 변경되었다고들 합니다. 여기에는 Image 컴포넌트도 포함되는데요! 공식문서에 13 버전 업데이트 노트에 <a href="https://nextjs.org/blog/next-13#nextimage">Image 컴포넌트의 변경</a>에 대해서도 중요하게 다루고 있습니다.</p>
<blockquote>
<p>The new Image component</p>
</blockquote>
<ul>
<li>Ships less client-side JavaScript</li>
<li>Easier to style and configure</li>
<li>More accessible requiring <code>alt</code> tags by default</li>
<li>Aligns with the Web platform</li>
<li>Faster because native lazy loading doesn&#39;t require hydration</li>
</ul>
<p>위 내용만 보면, 어떻게 변경되었길래 JS를 덜 쓰게 되었으며, 렌더링 방식이 어떻게 변경되었길래 이전엔 hydration이 필요했고 지금은 필요하지 않을까요?</p>
<h2 id="실제-렌더링-시의-차이점">실제 렌더링 시의 차이점</h2>
<p>간단하게 Next.js에서 두 가지 컴포넌트를 렌더링해보겠습니다.</p>
<blockquote>
<p>📌 참고
13버전 이전의 Image 컴포넌트는 <code>&#39;next/legacy/image</code>로 이동했습니다.</p>
</blockquote>
<pre><code class="language-tsx">import Image from &#39;next/image&#39;
import LegacyImage from &#39;next/legacy/image&#39;

export default function Home() {
  return (
    &lt;main&gt;
      &lt;label&gt;Image(13)&lt;/label&gt;
      &lt;Image
        src=&#39;/images/test.png&#39;
        alt=&#39;test&#39;
        width={300}
        height={300}
      /&gt;
      &lt;label&gt;Legacy&lt;/label&gt;
      &lt;LegacyImage
        src=&#39;/images/test.png&#39;
        alt=&#39;test&#39;
        width={300}
        height={300}
      /&gt;
    &lt;/main&gt;
  )
}</code></pre>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/af51898a-c2a1-4f80-b225-80efe96fa824/image.png" alt=""></p>
<p>겉으로 보기에는 동일했습니다. 그러나 차이점은 DOM tree에서 발견할 수 있었는데요.</p>
<h3 id="dom-tree에서의-차이">DOM tree에서의 차이</h3>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/87eeecbd-ac74-48ba-a683-d8d2174f75a7/image.png" alt=""></p>
<p><code>legacy</code>의 경우에는 이미지 DOM의 상단에 <code>&lt;span&gt;</code> 태그가 존재합니다. 그리고 <code>&lt;span&gt;</code> 태그 하위에 <code>&lt;span&gt;</code> 그리고 <code>&lt;img&gt;</code> 가 있습니다. 이것들은 무슨 역할일까요?</p>
<h3 id="12버전의-image">12버전의 Image</h3>
<h4 id="cls-해결">CLS 해결</h4>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/7901b021-e829-4a81-9145-f25690b13378/image.png" alt=""></p>
<p>Next.js12까지는 <code>Image</code> 컴포넌트를 사용할 때 span 태그로 감싸고 투명한 <code>png</code>를 로드합니다. 이때 투명한 이미지의 크기를 결정하기 위해서 Image 컴포넌트의 <code>width</code>, <code>height</code>를 필수 props로 전달해야 합니다.그리고 이미지가 로딩되면 투명 이미지에 <code>absolute</code> 속성을 준 진짜 <code>img</code> 태그가 보이는 형태로 만들어져 있습니다.</p>
<p>CLS를 해결하기 위해서 이미지가 로딩되기 전에 투명 이미지로 레이아웃을 잡아놓고 이미지가 로딩되면 화면에 보이도록 한 것입니다.</p>
<h3 id="13버전의-image">13버전의 Image</h3>
<p>아까 잠깐 보았을 때, 13버전의 <code>Image</code> 컴포넌트를 사용했을 때는 이전 버전과 다르게 <code>img</code> 태그 하나만 화면에 렌더링된 것을 확인할 수 있었습니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/d2f34e9d-c887-4c5b-b9df-93402effe446/image.png" alt=""></p>
<h4 id="cls-해결-1">CLS 해결</h4>
<p>📄 <a href="https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/get-img-props.ts#L231">next.js/packages/next/src/shared/lib/get-img-props.ts</a></p>
<pre><code class="language-tsx">if (!fill) {
  if (!widthInt &amp;&amp; !heightInt) {
    widthInt = staticImageData.width
    heightInt = staticImageData.height
  } else if (widthInt &amp;&amp; !heightInt) {
    const ratio = widthInt / staticImageData.width
    heightInt = Math.round(staticImageData.height * ratio)
  } else if (!widthInt &amp;&amp; heightInt) {
    const ratio = heightInt / staticImageData.height
    widthInt = Math.round(staticImageData.width * ratio)
  }
}</code></pre>
<p><code>width</code>, <code>height</code> 값이 있다면 해당 값을 사용해서 비율을 맞추게 됩니다. <code>width</code>, <code>height</code>가 둘 중 하나의 값만 있다면 비율을 계산해서 값을 얻습니다.</p>
<p>해당 값을 img에 넘기면 <code>width</code>, <code>height</code> 크기를 잡아 놓기 때문에 layout shift가 생기지 않습니다.</p>
<p>🤔 <strong>그럼 13버전 이전에는 width랑 height가 어떻게 처리 되었길래?</strong>
아래는 legacy Image 컴포넌트에 해당하는 DOM tree 내용입니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/7f5fc9dd-8379-4ea5-871e-2b1a2c53b37b/image.png" alt=""></p>
<p>13버전과 달리, 보게 되면 어디에도 제가 입력했던 <code>width=300</code>과 <code>height=300</code>을 확인할 수 없었습니다.
Image 컴포넌트를 사용하면 Next.js는 지정한 <code>width</code>와 <code>height</code>    를 기반으로 필요한 크기의 이미지를 동적으로 생성하는데, 이때 생성한 이미지는 실제 DOM에 추가되는 것이 아니라, 생성된 이미지의 url을 src 속성에 할당하여 브라우저에 로드합니다.</p>
<p>결론적으로, 내부적으로 처리하고, 최적화된 이미지를 로드할 때 겉에 <code>&lt;span&gt;</code> 태그를 하나 더 두어서 투명 레이아웃으로 layout shift를 방지하려고 했던 것 같습니다.</p>
<p>반면, 13버전에서는 img의 기본 property인 <code>width</code>와 <code>height</code> 로 크기를 잡아놓는 방법으로 layout shift를 방지합니다.</p>
<h2 id="hydration이-불필요">Hydration이 불필요</h2>
<p>이 내용은 <code>lazy loading</code>과 관련이 있는데요. next13 이전까지는 intersection observer를 사용하여 구현했습니다. next13 버전부터는 기본 property인 loading으로 구현 방법을 변경하여 JS 사용량을 줄였습니다. 따라서 hydration이 불필요해졌다는 말이였습니다.</p>
<h2 id="next13-버전-이후의-image-컴포넌트-변경-사항-정리">next13 버전 이후의 Image 컴포넌트 변경 사항 정리</h2>
<p>Next.js 13에서는 Image 컴포넌트의 로딩 방식이 변경되었고 특히 CLS를 해결하는 방식과 lazy loading을 구현하는 방식이 변경되었습니다.</p>
<ol>
<li><p>lazy loading
intersection observer를 사용하다가 img의 기본 property인 loading으로 구현 방법 변경하여 JS 사용량이 줄어들었고, <strong>이 덕분에 hydration 불필요</strong></p>
</li>
<li><p>CLS 방지
span으로 감싸고 투명 이미지를 불러와서 absolute로 이미지 위치리를 잡는 것을 제거, img 기본 property인 width와 height를 사용해서 구현, <strong>이 덕분에 스타일링 간편</strong></p>
</li>
</ol>
<h2 id="nextjs는-모든-이미지를-최적화해주진-않아요">Next.js는 모든 이미지를 최적화해주진 않아요</h2>
<p>Next.js의 이미지 최적화 모듈의 코드 일부분을 보면 다음과 같습니다.</p>
<pre><code class="language-ts">const vector = VECTOR_TYPES.includes(upstreamType)
const animate =
      ANIMATABLE_TYPES.includes(upstreamType) &amp;&amp; isAnimated(upstreamBuffer)

if (vector || animate) {
  return { buffer: upstreamBuffer, contentType: upstreamType, maxAge }
}</code></pre>
<p>SVG와 같은 vector 이미지, 그리고 GIF와 같은 상대적으로 복잡하고 최적화에 오래 걸리는 애니메이션 이미지의 경우에서는 기본적으로 최적화 기능을 제공하지 않고 바로 응답을 내려주게 되어 있습니다.</p>
<h2 id="nextjs가-권장하는-sharp-라이브러리">Next.js가 권장하는 sharp 라이브러리</h2>
<p>Next.js에서는 sharp 라이브러리를 사용할 것을 권장하고 있는데요.</p>
<blockquote>
<p><a href="https://nextjs.org/docs/messages/sharp-missing-in-production">Sharp Missing in Production</a></p>
</blockquote>
<h3 id="nextjs는-sharp를-import해서-설치-유무를-확인합니다">Next.js는 sharp를 import해서 설치 유무를 확인합니다</h3>
<p>이미지 최적화 모듈을 초기화할 때, <code>sharp</code>를 import함으로써 <code>sharp</code>의 설치 여부를 확인하고 이후에 동작하는 로직에서 <code>sharp</code> 변수를 기준으로 동작하는 방식으로 코드가 작성되어 있습니다.</p>
<p>바로 아래에는 <code>sharp</code> 가 없을 경우, warning도 show하도록 되어있죠!</p>
<pre><code class="language-ts">let sharp: typeof import(&#39;sharp&#39;) | undefined

try {
  sharp = require(process.env.NEXT_SHARP_PATH || &#39;sharp&#39;)
  if (sharp &amp;&amp; sharp.concurrency() &gt; 1) {
    // Reducing concurrency should reduce the memory usage too.
    // We more aggressively reduce in dev but also reduce in prod.
    // https://sharp.pixelplumbing.com/api-utility#concurrency
    const divisor = process.env.NODE_ENV === &#39;development&#39; ? 4 : 2
    sharp.concurrency(Math.floor(Math.max(cpus().length / divisor, 1)))
  }
} catch (e) {
  // Sharp not present on the server, Squoosh fallback will be used
}

let showSharpMissingWarning = process.env.NODE_ENV === &#39;production&#39;</code></pre>
<h3 id="nextjs는-기본-이미지-최적화-모듈로-squoosh를-사용해요">Next.js는 기본 이미지 최적화 모듈로 Squoosh를 사용해요</h3>
<p>Next.js는 <code>Squoosh</code>를 기본 이미지 최적화 모듈로 사용하고 있습니다. <code>Squoosh</code>를 기본으로 사용하는 이유는 빠르게 설치할 수 있고 개발 환경에 적합하기 때문이라고 합니다. </p>
<blockquote>
<p>⭐️ 그런데, 운영 환경에서는 <code>sharp</code>를 사용하는 것을 매우 강력하게 권장하고 있죠!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/2ea5afa2-8a97-4433-9031-25d21aab4bfa/image.png" alt=""></p>
<p><a href="https://github.com/lovell/sharp">sharp 라이브러리</a>의 소개를 보게 되면, 다양한 크기의 JPEG, PNG, WebP, GIF, AVIF와 같은 이미지들을 더 작은 크기로, 그리고 웹에 진화적으로 변환해 주는 매우 빠른 속도의 모듈이라고 설명하고 있습니다.</p>
<h3 id="sharp와-squoosh-성능-비교">sharp와 Squoosh 성능 비교</h3>
<p>제가 레퍼런스로 보고 있는 <a href="https://oliveyoung.tech/blog/2023-06-09/nextjs-image-optimization/">올리브영의 테크블로그</a>에 의하면 다음과 같습니다.</p>
<p>코드는 동일하고 <code>sharp</code> 라이브러리를 추가해서 Next.js 서버를 구동한 휘 설치 전/후 이미지 최적화 결과를 비교합니다.</p>
<p><strong>PNG → Webp 변환</strong></p>
<p>Webp 파일로 변환했을 때의 모습입니다. 원본은 1.9MB의 이미지 파일입니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/9a207867-959f-4728-b3e2-2b31bbcc61d5/image.png" alt=""></p>
<p>이미지 크기를 비교했을 때, Squoosh는 17.1KB, sharp는 16.9KB로 크기를 감소했습니다.</p>
<p>크기를 비교했을 때는 큰 차이가 없지만, 응답 속도를 비교한다면 Squoosh는 228ms, sharp는 64ms입니다. sharp를 사용했을 때 약 3~4배 정도 빠르게 응답을 받을 수 있었습니다.</p>
<p><strong>PNG → AVIF 변환</strong></p>
<p>AVIF 파일로 변환했을 때의 모습입니다. 원본은 1.9MB의 이미지 파일입니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/d4e17eca-dc1e-477c-8862-1bb92b80187f/image.png" alt=""></p>
<p>이미지 크기를 비교했을 때, Squoosh는 10.8KB로, sharp는 13.1KB로 크기를 감소했습니다.</p>
<p>이번에도 크기를 비교했을 때는 큰 차이가 없지만, 응답 속도를 비교한다면 Squoosh는 1.24s, sharp는 202ms입니다. sharp를 사용했을 때 약 6배 정도로 빠르게 응답을 받을 수 있었습니다.</p>
<blockquote>
<p>⭐️ 속도 면에서 상당한 차이가 있었다. sharp를 사용하지 않을 이유가 없다..!</p>
</blockquote>
<h1 id="결론">결론</h1>
<p>Next.js는 최적화 전략을 이미 많이 제공하고 있어 간편하게 사용이 가능했습니다. 그렇지만 이번 포스팅을 통해 Next가 무엇을 위해 최적화 전략을 제공하며 어떤 내용을 포함하고 있는지 알아볼 수 있었습니다. 뿐만 아니 next@13 버전의 Image 컴포넌트 개선에는 어떤 내용들이 포함되어 있는지까지도 알아보았습니다. 앞으로 어떤 더 나은 최적화 전략을 적용해 나갈지 기대가 되기도 합니다 :)</p>
<h1 id="참고-문서">참고 문서</h1>
<ul>
<li><a href="https://velog.io/@yesbb/Nextjs%EC%97%90%EC%84%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0">yesbb-Next.js에서 이미지 최적화하기</a></li>
<li><a href="https://dev.to/arrofirezasatria/creating-blur-placeholder-images-using-next-js-and-plaiceholder-5ckh">Creating blur placeholder images using Next JS and Plaiceholder</a></li>
<li><a href="https://toss.tech/article/smart-web-service-cache">toss tech - 웹 서비스 캐시 똑똑하게 다루기</a></li>
<li><a href="https://velog.io/@devohda/%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-Next.js-13-%EB%B2%84%EC%A0%84%EC%9D%98-Image-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EA%B0%9C%EC%84%A0">devohda-코드로 알아보는 Next.js 13 버전의 Image 컴포넌트 개선</a></li>
<li><a href="https://oliveyoung.tech/blog/2023-06-09/nextjs-image-optimization/">올리브영 테크블로그 - NEXT.JS의 이미지 최적화는 어떻게 동작하는가?</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 렌더링 동작 원리 with Hydration]]></title>
            <link>https://velog.io/@doeunnkimm_/Next.js%EC%9D%98-Hydration</link>
            <guid>https://velog.io/@doeunnkimm_/Next.js%EC%9D%98-Hydration</guid>
            <pubDate>Sat, 08 Jul 2023 06:09:38 GMT</pubDate>
            <description><![CDATA[<h1 id="nextjs-">Next.js ?</h1>
<p>Next는 CSR인 React를 SSR(Server-side Rendering) 방식으로 구현할 수 있도록 도와주는 프레임워크입니다.</p>
<h2 id="ssr과-csr의-차이">SSR과 CSR의 차이</h2>
<p>SSR과 CSR의 렌더링 과정을 이미지와 함께 알아 보려고 합니다. 
둘다 4단계로 이루어져 있습니다. 각각 몇 단계에서 화면을 볼 수 있으며, 몇 단계에서 상호작용이 가능해지는지를 생각해보면 더욱 차이점을 극명하게 알 수 있습니다 :)</p>
<h3 id="ssrserver-side-rendering">SSR(Server-side Rendering)</h3>
<p>서버 쪽에서 렌더링 준비를 끝마친 상태로 클라이언트에게 전달하는 방식입니다.</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/ac454333-ac22-4a8e-a57a-aa8340b3ffc8/image.png" alt=""></p>
<p>⭐️ 위 내용 중에서 가장 중요한 부분은 <strong>렌더될 준비를 끝마친 상태로 HTML 응답을 클라이언트에게</strong> 보내는 것! 그것을 가지고 브라우저는 Viewale한 페이지를 바로 렌더링합니다.</p>
<h4 id="🤔-브라우저가-렌더링을-바로-했다">🤔 브라우저가 렌더링을 바로 했다?</h4>
<p>브라우저가 말하는 렌더링의 의미는 HTML, CSS, JS 파일을 받아와 이를 일고 파싱해서 실행한 결과물로 화면을 그려내는 과정입니다. 그러나 서버 사이드 렌더링에 있는 렌더링의 뜻은 이와는 좀 다른데요.</p>
<h4 id="🤔-서버-사이드에서의-렌더링">🤔 서버 사이드에서의 렌더링</h4>
<p>서버 사이드 렌더링에서의 렌더링은 <strong>HTML 파일 내에 내용이 있느냐 없느냐</strong> 입니다. <strong>내용이 있다면, 렌더링이 된 것</strong>입니다.
이렇게 서버는 HTML 파일 내에 내용이 모두 있으므로 브라우저는 바로 페이지를 렌더링합니다. 덕분에 사용자는 처음부터 빈화면을 보지 않을 수 있습니다. </p>
<p>JS는 어디있냐구요? 브라우저는 렌더 가능한 HTML을 받아 렌더링한 이후에 JS파일을 다운 받습니다. 이렇게 브라우저가 해당 파일을 실행시키면, 페이지의 상호작용까지도 가능해지는 것입니다. </p>
<p>물론, JS파일이 읽히기 전에 렌더링되어 보여지는 HTML이 있기 때문에 콘텐츠를 볼 수 있지만, 사이트를 조작할 수는 없습니다. 다만, 이때의 사용자 조작을 기억하고 있어 JS까지 성공적으로 컴파일 되었다면, 기억하고 있던 조작이 실행되고, 웹 페이지는 상호작용이 가능한 상태가 됩니다.</p>
<h3 id="csrclient-side-rendering">CSR(Client-side Rendering)</h3>
<p>SSR과 달리 렌더링이 클라이언트 쪽에서 일어납니다. 즉, 서버는 요청을 받으면 클라이언트에 HTML와 JS파일을 보내줍니다. 클라이언트는 그것을 받아 렌더링을 시작합니다. </p>
<blockquote>
<p>📌 참고
처음 접속 시에는 HTML과 JS 파일이 우선적으로 보내지고, 그 후 CSS나 폰트 파일, 이미지 파일들 같은 리소스들은 추가적으로 로드됩니다. </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/f629bda6-7633-425f-9672-a8af718ce9c1/image.png" alt=""></p>
<p>CSR은 마지막 4단계에서 화면을 볼 수 있고 상호작용할 수 있습니다.</p>
<p>그 이유는 서버가 HTML 파일을 줄 때, 렌더 준비가 되지 않은 파일이기 때문인데요. 즉 HTML 파일 안에는 아무런 내용이 없다는 것입니다. 그 내용은 JS파일을 받아 실행을 시켜야 그제서야 만들어집니다.</p>
<p>CRA를 해보면 <code>index.js</code> 에 <code>React.createElement</code>라는 메서드가 있습니다. 이는 JS에서 HTML 태그를 생성하는 것입니다.</p>
<p>특히, <code>index.html</code> 파일의 바디 태그 안에 <code>&lt;div id=&quot;root&quot;&gt;&lt;/div&gt;</code>와 같이 div 태그 하나만 존재하고 안에는 아무 내용이 없습니다. 이렇게 아무 것도 없는 상태로 전달되므로 유저는 처음에 빈 화면을 보게 되는 것입니다.</p>
<p>그러나 이후, 브라우저가 추가적으로 JS 파일을 다운받고 실행하면 그때가 되어서야 index.js에서 root 태그를 화면에 렌더링 즉, 그려주게 되는 것입니다.</p>
<h4 id="🤩-아-이제-ssr의-렌더링될-준비가-된-html이란-것이-뭔지-알겠다">🤩 아 이제 SSR의 렌더링될 준비가 된 HTML이란 것이 뭔지 알겠다</h4>
<p>SSR은 <code>index.html</code> 파일 내에 화면에 그려내는 코드들이 이미 작성되어 있기 때문에 div 태그만 달랑 있는 CSR과 달리 브라우저가 HTML을 바로 받은 시점인 2단게에서 Viewable할 수 있는 것입니다!</p>
<h3 id="ssr-csr-차이">SSR, CSR 차이</h3>
<h4 id="1-웹-페이지의-로딩-시간">1. 웹 페이지의 로딩 시간</h4>
<p>웹 페이지의 로딩의 종류는 2가지로 나누어 볼 수 있습니다.</p>
<ul>
<li><p>첫 페이지 로딩 시간
CSR: HTML, CSS, 모든 스크립트들을 한번에 불러온다
SSR: 필요한 부분의 HTML과 스크립트만 불러온다.
⭐️ SSR이 더 빠르다</p>
</li>
<li><p>나머지 페이지 로딩 시간
첫 페이지를 로딩한 후, 사이트의 다른 곳으로 이동하는 식의 동작을 가정해 봅시다.
CSR: 이미 첫 페이지 로딩 시 나머지 부분을 구성하는 코드를 받아왔으므로 빠르다
SSR: 첫 페이지를 로딩한 것처럼 페이지 이동 시마다 동일하게 미리 그려진 HTML 파일 보내고 그 이후에 JS 보내느 과정
⭐️ CSR이 더 빠르다</p>
</li>
</ul>
<h4 id="2-seo">2. SEO</h4>
<p>검색 엔진은 자동화된 로봇인 &#39;크롤러&#39;로 웹 사아트를 읽어들입니다.</p>
<p>CSR은 최초로 불러온 HTML 파일의 내용이 비어있다고 했었죠? JS가 로드된 후에야 동적으로 root 안의 내용을 채우는 방식이었습니다.
따라서 웹 크롤러가 각 사이트를 돌아다니며 조사를 하는 상황이라고 가정하면, 최초로 웹 크롤러에게는 비어있는 root만 보여지게 되는 것이죠!</p>
<blockquote>
<p>웹 크롤러는 정적인 HTML의 내용을 먼저 수집하여 색인한다.</p>
</blockquote>
<p>반면 SSR은 애초에 서버 사이드에서 내용을 채운 상태로 클라이언트로 넘어오기 때문에 크롤러에 대응하기 용이합니다.</p>
<h4 id="3-서버-자원-사용">3. 서버 자원 사용</h4>
<p>SSR이 서버 자원을 더 많이 사용합니다. 클라이언트가 페이지를 이동한다거나 하면 우선 페이지가 그려지긴 하지만, 인터랙티브한 데이터가 필요하다면 브라우저 → 프론트 서버 → 백엔드 서버 → 데이터베이스를 거쳐 데이터를 가져온 후, 브라우저가 데이터가 그려지는 과정을 반복하게 됩니다. </p>
<p>매번 서버에 요청을 하기 때문에 서버 부하 문제가 발생할 수 있습니다.</p>
<h2 id="nextjs란-react-기반-프레임워크">Next.js란 React 기반 프레임워크</h2>
<p>React는 기본적으로 CSR 방식을 사용하는데, SSR을 사용하고 싶다면 개발자가 직접 환경을 구성해야 합니다.</p>
<p>Next.js는 직접 환경을 구성할 필요 없이, SSR, SSG을 쉽게 사용할 수 있도록 도와주는 React 기반 프레임워크입니다.</p>
<h3 id="🤔-nextjs--ssr">🤔 Next.js !== SSR</h3>
<p>&#39;Next.js가 SSR로 동작한다&#39; 라고만 알고 있어 SSR의 단점을 알게 되면 Next.js에 대한 의구심이 드는데..</p>
<p>사실 Next.js는 <span style="color:#0C956C">SPA이며 SSG를 기본으로 사용하고, SSR을 사용</span>할 수 있습니다.</p>
<p>⭐️ Next.js는 기본적으로 SSG를 사용하기 때문에 빌드 시점에만 서버 사이드에서 pre-render한 파일들을 보내주고, 그 이후에는 CSR로 페이지를 이동하는 것입니다.</p>
<p>⭐️ SSR의 경우 매 요청마다 추가적인 리소스를 불러오는 것입니다.</p>
<p>결론적으로는, Next.js는 CSR을 사용하여 페이지 이동을 처리합니다. 필요한 경우에만 서버에 추가적인 데이터를 요청합니다. 따라서 SSR을 사용하더라고 페이지 이동 시마다 HTML과 JS파일을 전체적으로 다시 불러오는 것은 아니며, 필요한 데이터의 업데이트만 수행하게 됩니다.</p>
<p>이를 통해 빠른 페이지 전환과 효율적인 네트워크 사용이 가능한 것입니다.</p>
<blockquote>
<p>📌 Next.js가 가지고 있는 가장 강력한 장점은 Pre-rendering과 CSR의 장점을 모두 사용할 수 있게 해준다는 것입니다.</p>
</blockquote>
<h2 id="nextjs는-어떻게-렌더링-되는가">Next.js는 어떻게 렌더링 되는가?</h2>
<h3 id="pre-rendering">pre-rendering</h3>
<p>Next.js는 <strong>모든 페이지를 미리 렌더링(pre-render)</strong> 합니다.
→ 각 페이지의 HTML을 미리 생성해 둔다.</p>
<p>생성된 HTML은 해당 페이지에 필요한 최소한의 자바스크립트 코드와 연결되게 됩니다. 그 후 브라우저에 의해 페이지가 로드되면, 그때 자바스크립트 코드가 실행되어 페이지와 유저가 상호작용할 수 있게 되는 것입니다.</p>
<blockquote>
<p>📌 HTML에 JS가 연결되는 것을 <strong>Hydration</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/024ba372-bfac-48fb-90f1-20f5c8c7d9be/image.png" alt=""></p>
<p>Next.js에서 미리 렌더링 하는 방식은 2가지로 나뉩니다. <strong>이 2가지는 HTML이 생성되는 시점이 다릅니다.</strong></p>
<ol>
<li><p>SSG(Static-site Generation)
빌드 타임에 HTML이 생성되어 매 요청마다 이를 재사용. 즉, 빌드 시점 이후에는 서버에게 따로 요청X
→ 데이터가 바뀌지 않는 블로그 글, 상품 정보 페이지 등에서 사용한다</p>
</li>
<li><p>SSR(Server-side Rendering)
매 요청마다 HTML을 생성. 여기서의 &#39;매 요청&#39;은 웹 사이트의 페이지를 접속하거나 페이지를 새로고침할 때 발생하는 요청을 말한다. 
SSR 방식에서는 클라이언트의 각 요청마다 서버가 해당 페이지의 데이터와 리소스를 가져와서 HTML을 동적으로 생성
→ 최신 콘텐츠를 제공해야 할 때 사용</p>
</li>
</ol>
<h3 id="코드-뜯어보며-알아보는-nextjs-렌더링-과정">코드 뜯어보며 알아보는 Next.js 렌더링 과정</h3>
<p>Next.js가 먼저 Server를 거친 후에 클라이언트에서 렌더링되는 것은 알겠지만... 어떤 코드들을 거쳐서 실제 브라우저에서 볼 수 있는 것일까요?</p>
<blockquote>
<p><a href="https://github.com/vercel/next.js">vercel/next.js</a></p>
</blockquote>
<h4 id="🤔-서버에서-render하고-반환하는-과정">🤔 서버에서 render하고 반환하는 과정</h4>
<p>우선 Next.js가 렌더링(→HTML에 내용을 채우는 과정)부터 알아봐야겠습니다 :)</p>
<hr>
<p>1️⃣ 📄 <a href="https://github.com/vercel/next.js/blob/canary/packages/next/src/server/render.tsx">next.js/packages/next/src/server/render.tsx</a></p>
<pre><code class="language-tsx">const renderDocument = async () =&gt; {
    // ...

    async function loadDocumentInitialProps(
      renderShell?: (
        _App: AppType,
        _Component: NextComponentType
      ) =&gt; Promise&lt;ReactReadableStream&gt;
    ) {
      const Body = ({ children }: { children: JSX.Element }) =&gt; {
        return inAmpMode ? children : &lt;div id=&quot;__next&quot;&gt;{children}&lt;/div&gt;
      }

      // ...
      const { App: EnhancedApp, Component: EnhancedComponent } =
          enhanceComponents(options, App, Component)

      // ...
      const renderPage: RenderPage = async (
        options: ComponentsEnhancer = {}
      ): Promise&lt;RenderPageResult&gt; =&gt; {
         // ...
        const html = await renderToString(
          &lt;Body&gt;
            &lt;AppContainerWithIsomorphicFiberStructure&gt;
              {renderPageTree(EnhancedApp, EnhancedComponent, {
                ...props,
                router,
              })}
            &lt;/AppContainerWithIsomorphicFiberStructure&gt;
          &lt;/Body&gt;
        )
        return { html, head }
      }
        //...
      return {
        bodyResult,
        documentElement,
        head,
        headTags: [],
        styles,
      }
}</code></pre>
<p>우선 Body라는 컴포넌트를 선언해서 <code>&lt;div id=&quot;__next&quot;&gt;&lt;/div&gt;</code>를 만들어 children을 주입하고 있습니다.</p>
<p>실제로 next.js로 만든 웹 사이트에 들어가면 아래처럼 확인할 수 있었습니다.</p>
<div align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/e0f9c216-d46e-423e-8a6e-7968ef3184bd/image.png" width="70%" /></div>

<ul>
<li><code>loadDocumentInitialProps</code> : 초기 페이지 렌더링에 필요한 컴포넌트들을 향상(기능적으로 확장하거나 성능을 최적화하는 과정)시키고, 페이지를 렌더링하는 데 사용하는 함수</li>
<li><code>renderPage</code> : <code>EnhancedApp</code>과 <code>EnhancedComponent</code>를 사용하여 페이지 컴포넌트를 렌더링하고, renderToString 함수를 통해 해당 페이지의 HTML을 생성.</li>
<li><code>loadDocumentInitialPropsreturn</code>의 return 값 : 페이지 렌더링 후 최종적으로 생성된 문서에 대한 다양한 정보 포함 </li>
</ul>
<p>결론적으로, <code>renderDocument</code>는 <code>App</code>과 <code>Component</code>를 통해 DOM에 필요한 다양한 정보를 return 하고 있었습니다.</p>
<hr>
<p>2️⃣ 그 다음으로는, <code>renderDocument</code>가 호출되는 부분을 찾아가 보았습니다.</p>
<pre><code class="language-tsx">  const documentResult = await getTracer().trace(
    RenderSpan.renderDocument,
    {
      spanName: `render route (pages) ${renderOpts.pathname}`,
      attributes: {
        &#39;next.route&#39;: renderOpts.pathname,
      },
    },
    async () =&gt; renderDocument()
  )</code></pre>
<p>호출하여 <code>documentResult</code>가 DOM에 대한 정보를 들고 있다고 파악했습니다.</p>
<hr>
<p>3️⃣ 그 다음은, <code>documentResult</code>가 어디서에서 쓰이는지 찾아가 보았습니다.</p>
<pre><code class="language-tsx">const htmlProps: HtmlProps = {
    __NEXT_DATA__: {
      // ...
      head: documentResult.head,
      headTags: documentResult.headTags,
      styles: documentResult.styles,
      // ...
  }

  const document = (
    &lt;AmpStateContext.Provider value={ampState}&gt;
      &lt;HtmlContext.Provider value={htmlProps}&gt;
        {documentResult.documentElement(htmlProps)}
      &lt;/HtmlContext.Provider&gt;
    &lt;/AmpStateContext.Provider&gt;
  )

 const documentHTML = await getTracer().trace(
    RenderSpan.renderToString,
    async () =&gt; renderToString(document)
  )
</code></pre>
<p>documentResult의 <code>documentElement</code>는 HTML 문서의 최상위 요소에 해당하는 DOM 요소 즉, <code>&lt;html&gt;</code>를 나타냅니다.</p>
<p>renderDocument 함수에서 <code>documentElement</code>는 HTML 문서의 최상위 요소에 대한 참조를 제공하므로, 필요에 따라 요소를 수정하거나 다른 작업을 수행할 수 있습니다. 예를 들어 내부에 추가적인 메타데이터를 추가할 수도 있겠죠?</p>
<p>결론적으로, DOM을 구성하기 위한 정보들이 담겨있는 <code>htmlProps</code>를 <code>documentElement</code>로 전달함으로써 HTML 문서의 구조와 속성을 구성하고 있는 것을 확인할 수 있었습니다.</p>
<p>최종적으로는, document를 string으로 변환하여 <code>documentHTML</code>을 만들어 내는 것까지 코드로 확인했습니다.</p>
<h4 id="🤔-클라이언트에서-이를-받아-렌더링하는-과정">🤔 클라이언트에서 이를 받아 렌더링하는 과정</h4>
<p>📄 <a href="https://github.com/vercel/next.js/blob/canary/packages/next/src/client/next.ts">next.js/packages/next/src/client/next.ts</a></p>
<pre><code class="language-typescript">initialize({})
  .then(() =&gt; hydrate())
  .catch(console.error)</code></pre>
<p>우선 <code>inialize()</code>가 진행된 다음에 <code>hydrate()</code>를 실행하는 것을 확인할 수 있었습니다. 각각의 코드를 살펴봅시다 :)</p>
<p>📄 <a href="https://github.com/vercel/next.js/blob/canary/packages/next/src/client/index.tsx">next.js/packages/next/src/client/index.tsx</a></p>
<pre><code class="language-tsx">export async function initialize(opts: { webpackHMR?: any } = {}): Promise&lt;{
  assetPrefix: string
}&gt; {
  // ...
  initialData = JSON.parse(
    document.getElementById(&#39;__NEXT_DATA__&#39;)!.textContent!
  )
  window.__NEXT_DATA__ = initialData

  const prefix: string = initialData.assetPrefix || &#39;&#39;

  appElement = document.getElementById(&#39;__next&#39;)
  return { assetPrefix: prefix }
}</code></pre>
<p><code>initialize()</code>는 서버에서 렌더링한 HTML에서 <code>__NEXT_DATA__</code>를 id로 갖는 엘리먼트의 컨텐츠를 브라우저의 전역객체 <code>window.__NEXT_DATA__</code>로 저장합니다. 그리고 환경에 맞게 prefix를 반환합니다.</p>
<blockquote>
<p>📌 Next.js에서의 prefix
정적 자원(이미지, CSS, 폰트 등)의 경로를 지정하는 데 사용되는 옵션
이를 통해 정적 자원읙 경로를 설정하고 해당 자원에 접근할 수 있게 된다.</p>
</blockquote>
<pre><code class="language-tsx">export async function hydrate(opts?: { beforeRender?: () =&gt; Promise&lt;void&gt; }) {
   // ...
  const renderCtx: RenderRouteInfo = {
    App: CachedApp,
    initial: true,
    Component: CachedComponent,
    props: initialData.props,
    err: initialErr,
  }

  render(renderCtx)
}</code></pre>
<p><code>hydrate()</code>는 실행하려는 페이지의 에러가 있는지 확인 및 validate 체크를 하고 없다면 렌더링할 때 필요한 컨텍스트(라우터, App, Component, initialProps 등)를 <code>render()</code>의 인자로 넘겨줍니다.</p>
<pre><code class="language-tsx">async function render(renderingProps: RenderRouteInfo): Promise&lt;void&gt; {
    // ...
    await doRender(renderingProps)
}

function doRender(input: RenderRouteInfo): Promise&lt;any&gt; {
  // ...
  renderReactElement(appElement!, (callback) =&gt; (
    &lt;Root callbacks={[callback, onRootCommit]}&gt;
      {process.env.__NEXT_STRICT_MODE ? &lt;React.StrictMode&gt;{elem}&lt;/React.StrictMode&gt; : elem}
    &lt;/Root&gt;
  ));
}</code></pre>
<p><code>doRender()</code>를 따라가다보면, <code>renderReactElement()</code>를 실행시키고 있었습니다.</p>
<pre><code class="language-tsx">let shouldHydrate: boolean = true; // 첫 렌더에서는 항상 true이다

function renderReactElement(domEl: HTMLElement, fn: (cb: () =&gt; void) =&gt; JSX.Element): void {
  //...
  const reactEl = fn(shouldHydrate ? markHydrateComplete : markRenderComplete);

  // ...
  if (shouldHydrate) {
    ReactDOM.hydrate(reactEl, domEl);
    shouldHydrate = false;
  } else {
    ReactDOM.render(reactEl, domEl);
  }
}
</code></pre>
<p>드이어 React에서 렌더해주는 <code>ReactDOM.render()</code>와 <code>ReactDOM.hydrate()</code>를 확인할 수 있었습니다.🥹</p>
<p>🤔 <strong>render()</strong></p>
<pre><code class="language-tsx">ReactDOM.render(element, container, [callback])</code></pre>
<ul>
<li>element: 화면에 그려진 React element (집어넣어 줄 요소)</li>
<li>container: React element를 해당 container DOM에 렌더링 (구체적인 위치)</li>
<li>callback: 렌더링 후 반환되는 값을 돌려주는 콜백 함수</li>
</ul>
<p>CRA하게 되면 <code>index.js</code>에 다음 코드를 쉽게 볼 수 있습니다.</p>
<pre><code class="language-tsx">import App from &#39;./App&#39;;

ReactDOM.render(&lt;App /&gt;, document.getElementById(&#39;root&#39;));</code></pre>
<p>즉, <code>&lt;App/&gt;</code> 컴포넌트를 root라는 id를 가지고 있는 엘리먼트 내부로 넣어주어 페이지를 렌더링 해주고 있습니다.</p>
<p>🤔 <strong>hyrate()</strong></p>
<pre><code class="language-tsx">ReactDOM.hydrate(element, container, [callback])</code></pre>
<p>기본적으로 <code>render()</code>와 동일하지만, ReactDOMServer로 렌더링된 HTML에 이벤트 리스터(자바스크립트 코드)를 연결해주기 위해 사용됩니다. </p>
<p>서버 사이드를 통해 이미 HTML에는 엘리먼트들이 채워져 있죠? 따라서 다시 render 해줄 필요 없이 hydrate를 통해 기존 마크업에 이벤트 리스너를 붙여주는 과정입니다.</p>
<h1 id="hydration">Hydration</h1>
<p>위 과정들을 정리해봅시다 😃</p>
<p>Next.js는 서버에서 HTML을 문자열로 가져온 후에, 클라이언트에서 서버로부터 보내준 HTML을 <code>render()</code>, <code>hydrate()</code>하여 브라우저에 렌더링 했습니다. 이 일련의 과정을 Hydration이라고 합니다!</p>
<blockquote>
<p>📌 Hydration 사전적 정의
[명사] 수분 공급</p>
</blockquote>
<p>서버의 데이터가 클라이언트의 DOM과 결합하는 과정을 빗대어 hydrate라는 단어로 정의된 것 같습니다.</p>
<p>React는 클라이언트 렌더링만 있어, 유저에게 보여줄 HTML, CSS 그리고 자바스크립트 모두 render() 함수를 이용해 생성하여, 모든 리소스를 한번에 렌더링합니다.</p>
<p>반면, Next.js는 서버에서 보여줄 HTML 컨텐츠를 미리 렌더링(내용을 채워서)하여 가져오기 때문에 render() 함수로 HTML 뼈대만 렌더하고, <code>hydrate()</code>를 통해 서버에서 받아온 HTML에 유저가 상호작용할 수 있는 이벤트 리스너(JS파일)을 연결하는 것입니다.</p>
<p>HTML에 JS파일(수분💦)을 주입한다고 해서 hydrate라고 이해할 수 있겠습니다!</p>
<h2 id="nextjs가-hydration하기까지의-과정-정리">Next.js가 Hydration하기까지의 과정 정리</h2>
<ol>
<li>서버에서 전달된 HTML 수신</li>
<li>클라이언트 측 렌더링 (<code>render()</code>)
클라이언트는 수신된 HTML을 우선 렌더링하고, 인터렉션을 위한 JS파일을 로드한다.</li>
<li><code>hydrate()</code> 호출
전달된 HTML에 이벤트 핸들러를 연결</li>
<li>클라이언트 측 렌더링 완료
<code>hydrate</code> 과정이 완료되면 클라이언트에서 페이지의 렌더링과 인터렉션을 관리할 수 있게 된다.</li>
</ol>
<h1 id="참고-문서">참고 문서</h1>
<ul>
<li><a href="https://velog.io/@minuk3508/SEOReact%EC%99%80-SEO">minuk3508 - SEO) React와 SEO</a></li>
<li><a href="https://velog.io/@hanei100/TIL-SSR-vs-CSR-%EC%B0%A8%EC%9D%B4">hanei100 - SSR vs CSR 차이</a></li>
<li><a href="https://velog.io/@sj_dev_js/Next.js-%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B1%B0%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83">js_dev_js - Next.js에 대한 거의 모든 것</a></li>
<li><a href="https://www.howdy-mj.me/next/hydrate">howdy-mj.me - Next.js의 렌더링 과정(Hydrate) 알아보기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hooks를 최적화하여 사용하기]]></title>
            <link>https://velog.io/@doeunnkimm_/Hooks%EB%A5%BC-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EC%97%AC-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@doeunnkimm_/Hooks%EB%A5%BC-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EC%97%AC-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 07 Jul 2023 15:27:49 GMT</pubDate>
            <description><![CDATA[<h1 id="hook">Hook</h1>
<ul>
<li>React에서 기존에 사용하던 Class형 컴포넌트에서 사용되던 메소드들 없이도</li>
<li>함수형 컴포넌트에서 Hook을 통해 상태 관리와 여러 기능을 사용할 수 있도록 만든 기능</li>
</ul>
<h2 id="탄생-배경">탄생 배경</h2>
<ul>
<li>React 컴포넌트는 클래스형 컴포넌트 / 함수형 컴포넌트로 나뉜다.</li>
<li>기존의 개발 방식은 일반적으로 함수형 컴포넌트를 주로 사용하되<ul>
<li>state나 Life Cycle Method를 사용해야 할 때에만 클래스형 컴포넌트를 사용하는 방식이었다.</li>
<li>이유는 어려운 클래스 문법, 어려운 축소, 어려운 로직의 재사용성 등등</li>
</ul>
</li>
<li>이러한 단점이 있음에도, state나 Life Cycle Method를 사용하기 위해서는 클래스형 컴포넌트 사용을 해야만…</li>
</ul>
<blockquote>
<p>⭐ Hooks가 등장하고 함수형 컴포넌트에서도 state와 Life Cycle Method 사용이 가능해졌다.</p>
</blockquote>
<ul>
<li>덕분에 클래스형 컴포넌트의 단점을 극복 + 상태 관리 + 생명주기 함수 사용까지도 가능해진 것!</li>
</ul>
<h2 id="state-life-cycle-method">State? Life Cycle Method?</h2>
<h3 id="state">State</h3>
<ul>
<li>컴포넌트 내에서 관리되는 데이터<ul>
<li>컴포넌트의 동작과 상호작용을 제어하고</li>
<li>컴포넌트가 렌더링될 때마다 변경되는 값을 저장하는 데 사용</li>
<li>state는 컴포넌트 내에서 변경 가능</li>
</ul>
</li>
<li>state는 컴포넌트의 동적인 부분을 나타내며, 사용자 상호작용, 서버로부터의 데이터 로딩, 시간에 따른 변화 등과 같은 변동 사항을 표현하기 위해 사용</li>
</ul>
<p>⭐ state는 렌더링의 트리거하는 주요한 역할을 한다.</p>
<ul>
<li><p>state가 변경되지 않는다면, 불필요한 렌더링을 피하고, 성능을 개선하기 위해 UI 업데이트 수행 X</p>
</li>
<li><p>setState를 통해 React에게 상태 변경을 알리도록 되어 있다.</p>
<p>  → 그래서 직접 값을 변경한 경우 렌더링 X </p>
</li>
</ul>
<h3 id="life-cycle-method생명주기-메서드">Life Cycle Method(생명주기 메서드)</h3>
<ul>
<li>컴포넌트가 브라우저상에 나타나고, 업데이트되고, 사라지게 될 때 호출하는 메서드들</li>
<li>아래는 클래스형 컴포넌트의 생명주기 메서드들</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/2ffbde90-d7ca-41e4-abea-aed75d8c8e91/image.png" alt=""></p>
<ul>
<li>함수형 컴포넌트에서는 아래와 같이 사용되고 있다.</li>
</ul>
<pre><code>| 분류 | 클래스형 컴포넌트 | 함수형 컴포넌트 |
| --- | --- | --- |
| Mounting | constructor() | 함수형 컴포넌트 내부 |
| Mounting | render() | return() |
| Mounting | componentDidMount() | ueEffect() |
| Updating | componentDidUpdate() | useEffect() |
| UnMounting | componentWillUnmount() | useEffect() |</code></pre><h1 id="state-최적화를-위한-방법">State 최적화를 위한 방법</h1>
<h2 id="state를-최적화한다는-개념">State를 최적화한다는 개념</h2>
<ul>
<li>state는 렌더링의 트리거하는 주요한 역할을 한다고 했었다.
  ⭐ 이 말은 곧, UI를 업데이트하는 작업과 연관</li>
</ul>
<blockquote>
<p>⭐ UI 업데이트하는 데 비용이 많이 드는 DOM 작업 수를 최소화하는 것이 필요</p>
</blockquote>
<h2 id="state-업데이트-최적화가-중요한-이유">State 업데이트 최적화가 중요한 이유</h2>
<p>⭐ 성능 향상과 직접적인 연관</p>
<ol>
<li>불필요한 렌더링 방지<ul>
<li>화면에 변화가 없는데도 불필요하게 컴포넌트를 다시 그리는 작업을 의미</li>
<li>불필요한 렌더링은 불필요한 리소스 사용을 초래할 수 있다</li>
<li>동일한 데이터를 중복해서 서버에게 요청하는 경우 네트워크 대역폭 낭비 및 서버의 부하까지도 이어질 수 있다</li>
</ul>
</li>
<li>가상 DOM 비교 최소화<ul>
<li>가상 DOM 비교는 성능에 영향을 주는 계산적인 비용이 따르는 작업</li>
<li>따라서 state 업데이트를 최적화하여 가상 DOM 비교를 최소화하면 React의 업데이트 성능 향상</li>
</ul>
</li>
</ol>
<p>목표는 불필요한 렌더링을 최소화하는 것, 이제 방법을 알아보자</p>
<h2 id="방법1-independent-child-careless-parent">방법1. Independent child, Careless parent</h2>
<h3 id="render-waterfall">Render Waterfall</h3>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/b1dee85b-2734-4190-a655-f0d641d06bf6/image.png" alt=""></p>
<ul>
<li>구성 요소의 부모가 렌더링되면 모든 자식도 렌더링이 된다.</li>
<li>부모 컴포넌트로 인한 복잡한 자식 컴포넌트들로 인해 불필요하게 자식 컴포넌트들까지 렌더링되는 경우</li>
</ul>
<h3 id="상태는-독립된-작은-부분에서만-관리하자">상태는 독립된 작은 부분에서만 관리하자</h3>
<ul>
<li>특정 컴포넌트랑만 연관이 있는 상태를 부모 컴포넌트에서 관리할 경우</li>
<li>해당 상태와 관련이 없는 자식 컴포넌트까지도 리렌더링을 하게 된다</li>
</ul>
<p>⭐ 부모 컴포넌트는 자식 컴포넌트의 상태 변경에 신경쓰지 않고, 자식 컴포넌트가 자체적으로 상태를 관리하도록</p>
<p>→ 자식 컴포넌트의 상태 변경과 관련된 로직을 신경쓰지 않고,
→ 자식 컴포넌트를 렌더링하는 역할에 집중</p>
<p><img src="https://velog.velcdn.com/images/doeunnkimm_/post/b3996e2f-4ccd-425c-b8ec-53677f8be5d8/image.png" alt=""></p>
<p>📌 <strong>실험 내용</strong></p>
<p>✔️ 부모 컴포넌트 - Child1 &amp; Child2가 있다.
1️⃣ 상황1 : Child1와만 관련이 되어있는 상태를 부모 컴포넌트에서 관리
2️⃣ 상황2 : Child1와만 관련이 있는 상태를 Child1에서만 관리</p>
<p>※ Child2를 통해 불필요한 렌더링으로 인한 성능 차이를 유의미하게 측정하기 위해 Child2 컴포넌트에는 고화질의 사진이 포함되어 있다.</p>
<p>⚙️ 성능 측정은 <code>Profiler</code> 를 통해 진행 → Render duration 확인</p>
<table>
<thead>
<tr>
<th align="center">상황1</th>
<th align="center">상황2</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/90958ed5-a28f-4130-ba92-72781b687cfc/image.gif" alt=""></td>
<td align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/c53b7621-38d8-4412-b88e-e362a95811d9/image.gif" alt=""></td>
</tr>
<tr>
<td align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/d5af92c3-4885-42de-bc76-444e3f0ba435/image.png" alt=""></td>
<td align="center"><img src="https://velog.velcdn.com/images/doeunnkimm_/post/4904608b-c1b8-4d25-ac84-864c00497ab6/image.png" alt=""></td>
</tr>
<tr>
<td align="center">1.5ms</td>
<td align="center">0.8ms</td>
</tr>
</tbody></table>
<p>예상대로, 불필요한 리렌더링을 줄여 렌더링 시간을 줄일 수 있었다.</p>
<h2 id="방법2-minimal-states-minimal-render">방법2. Minimal states, Minimal render</h2>
<ul>
<li>최소한으로 state를 선언하도록 해야 한다.</li>
<li>한 state로 파생되어 사용될 수 있는 값들이 존재한다면<ul>
<li>따로 state를 선언하는 방법이 아니라</li>
<li>기존 state를 가지고 화면을 렌더링할 수 있도록 해야 한다.</li>
</ul>
</li>
</ul>
<pre><code class="language-jsx">// Bad
const [count, setCount] = useState(0)
const [isEven, setIsEven] = useState(false)
const [isPrime, setIsPrime] = useState(false)
const [isPositive, setIsPositive] = useState(false)
const [isMultipleOfFive, setIsMultipleOfFive] = useState(false)

return (
    &lt;&gt;
      &lt;h1&gt;To Much State 😈&lt;/h1&gt;
      &lt;h3&gt;count: {count}&lt;/h3&gt;
      &lt;h3&gt;Is Even : {isEven ? &#39;Yes&#39; : &#39;No&#39;}&lt;/h3&gt;
      &lt;h3&gt;Is Prime : {isPrime ? &#39;Yes&#39; : &#39;No&#39;}&lt;/h3&gt;
      &lt;h3&gt;Is Positive: {isPositive ? &#39;Yes&#39; : &#39;No&#39;}&lt;/h3&gt;
      &lt;h3&gt;Is Multiple of Five: {isMultipleOfFive ? &#39;Yes&#39; : &#39;No&#39;}&lt;/h3&gt;
      &lt;hr /&gt;
      &lt;button onClick={increment}&gt;증가&lt;/button&gt;
    &lt;/&gt;
  )

// Good
const [count, setCount] = useState(0)

return (
    &lt;&gt;
      &lt;h1&gt;Minimal State 👶🏻&lt;/h1&gt;
      &lt;h3&gt;count: {count}&lt;/h3&gt;
      &lt;h3&gt;Is Even : {count % 2 === 0 ? &#39;Yes&#39; : &#39;No&#39;}&lt;/h3&gt;
      &lt;h3&gt;Is Prime : {count % 2 !== 0 ? &#39;Yes&#39; : &#39;No&#39;}&lt;/h3&gt;
      &lt;h3&gt;Is Positive: {count &gt; 0 ? &#39;Yes&#39; : &#39;No&#39;}&lt;/h3&gt;
      &lt;h3&gt;Is Multiple of Five: {count % 5 === 0 ? &#39;Yes&#39; : &#39;No&#39;}&lt;/h3&gt;
      &lt;hr /&gt;
      &lt;button onClick={increment}&gt;증가&lt;/button&gt;
    &lt;/&gt;
  )</code></pre>
<h2 id="방법3-reactmemo">방법3. React.memo</h2>
<ul>
<li>부모 컴포넌트 - Child1, Child2, Child3 컴포넌트가 있다고 했을 때</li>
<li>부모 컴포넌트에서 관리하고 있는 state를 Child1과 Child2에게만 넘겨주어야 한다 하면</li>
<li>Child3는 부모 컴포넌트에서 state 업데이트가 일어났다고 한들 다시 그려질 필요가 없다</li>
<li>이럴 때 Child3에 React.memo를 적용해주면 불필요한 리렌더링을 막아줄 수 있다.</li>
</ul>
<h1 id="memoization-꼭-필요한가">Memoization 꼭 필요한가?</h1>
<h2 id="memoization">Memoization</h2>
<ul>
<li>메모이제이션은 메모리 공간을 더 많이 사용하는 대가로 컴퓨터 프로그램 속도를 높이는 데 사용되는 최적화 기술</li>
<li>메모이제이션을 통한 속도 향상은 동일한 매개 변수가 제공될 때 결과의 반복 계산을 피한다.</li>
<li>대신에 캐시된 결과를 사용한다.</li>
<li>캐시된 결과가 추가 공간을 차지하기 때문에 메모리 공간 사용량 증가</li>
</ul>
<h2 id="react에서-memoization">React에서 Memoization</h2>
<ul>
<li>복잡한 구성 요소를 다시 렌더링하는 경우 → 성능 문제 → 사용자 경험에 영향</li>
<li>동일한 입력으로 다시 실행할 때마다 값을 다시 계산하지 않고 캐시된 값을 사용하고 싶다면<ul>
<li>ex. props가 이전과 동일하다면 굳이 다시 처음부터 화면을 그릴 필요가 없음</li>
</ul>
</li>
<li>초기 렌더링 결과를 캡처하고 나중에 사용할 수 있도록 메모리에 캐시 가능</li>
</ul>
<p>⭐ 웹 성능 향상에 도움</p>
<h2 id="react에서-memoization을-활용하면-유용한-경우">React에서 Memoization을 활용하면 유용한 경우</h2>
<ul>
<li>주로 복잡한 계산, 연산, 데이터 변화 등의 경우에 유용</li>
<li>불필요한 연산을 줄이고 성능을 향상시키는 데 도움</li>
</ul>
<ol>
<li><strong>계산 비용이 높은 연산</strong><ul>
<li>계산 비용이 높은 연산을 수행해야 하는 경우</li>
<li>메모이제이션을 사용하여 연산 결과를 캐시 가능</li>
<li>이를 통해 동일한 입력에 대해 다시 계산하지 않고 이전에 계산된 결과를 재사용 가능</li>
<li>그런데, 수천 개의 항목에 대해 루프를 수행하거나 팩토리얼 계산을 수행하지 않는 한 비용이 많이 들지 않을 수 있다</li>
</ul>
</li>
<li><strong>렌더링 성능 최적화</strong><ul>
<li>컴포넌트의 state나 props가 변경되지 않았다면</li>
<li>이전 결과를 다시 계산하지 않고 이전에 계산된 결과를 재사용하여 불필요한 렌더링 방지</li>
</ul>
</li>
</ol>
<h2 id="react에서-모든-것을-메모해야-할까-no">React에서 모든 것을 메모해야 할까? “No!”</h2>
<blockquote>
<p>⭐ 메모이제이션은 무료가 아니다.</p>
</blockquote>
<p>⭐ 무분별하거나 과도한 메모이제이션은 그만한 가치가 없을 수 있다.</p>
<ul>
<li>메모이제이션을 추가할 때 3가지 주요 비용이 발생<ol>
<li><strong>메모리 사용량 증가</strong><ul>
<li>너무 많은 것을 메모하면 메모리 사용량 관리에 어려움</li>
<li>메모리가 부족해지면 컴포넌트의 렌더링과 상태 업데이트에 소요되는 시간 증가할 수 있다</li>
</ul>
</li>
<li><strong>메모리 누수 가능성</strong><ul>
<li>메모이제이션은 결과를 캐시하여 재사용</li>
<li>이를 관리하지 않고 사용하는 경우 메모리 누수가 발생할 수 있다</li>
<li>캐시된 결과가 더 이상 필요하지 않은 경우에도 계속해서 메모리에 남아있게 되는 경우</li>
</ul>
</li>
<li><strong>메모이제이션을 위한 추가적인 코드로, 코드 복잡성 증가</strong><ul>
<li>메모이제이션은 캐시를 관리하고 관련된 종속성을 처리하는 추가적인 로직을 도입하게 된다</li>
<li>너무 많이 사용하면 코드를 이해하기 어려워질 수 있고, 디버깅과 유지보수에 어려움이 생길 수 있다</li>
</ul>
</li>
</ol>
</li>
</ul>
<h2 id="어떤-경우에-메모이제이션을-피해야-할까">어떤 경우에 메모이제이션을 피해야 할까?</h2>
<ol>
<li><strong>최적화하려는 계산의 비용이 크지 않은 경우</strong><ul>
<li>이러한 경우 메모이제이션 할 때 발생하는 오버헤드가 이점보다 클 수 있다.</li>
<li>React 공식 홈페이지에서는 <a href="https://react.dev/reference/react/useMemo#how-to-tell-if-a-calculation-is-expensive">1ms 이상 걸리는 경우 메모</a>해두는 것이 좋다고 이야기</li>
</ul>
</li>
<li><strong>메모이제이션이 필요한지 확실하지 않은 경우</strong><ul>
<li>우선 없이 작업을 하고, 문제가 발생하면 점진적으로 최적화를 적용하는 방향이 올바르다</li>
</ul>
</li>
<li><strong>의존성 배열이 너무 자주 변경되는 경우</strong><ul>
<li>계산되는 경우가 많으면 성능적인 이점을 얻을 수 없다</li>
</ul>
</li>
</ol>
<h1 id="참고-문서">참고 문서</h1>
<ul>
<li><a href="https://ko.legacy.reactjs.org/docs/hooks-intro.html">React - Hook</a></li>
<li><a href="https://green-grapes.tistory.com/entry/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85Hook%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90">[React] 리액트 훅(Hook)에 대해서 알아보자</a></li>
<li><a href="https://legacy.reactjs.org/docs/state-and-lifecycle.html">React - State and Lifecycle</a></li>
<li><a href="https://adjh54.tistory.com/43">[React] 함수형 컴포넌트 생명주기(lifecycle) 이해하기</a></li>
<li><a href="https://www.codementor.io/blog/react-optimization-5wiwjnf9hj">21 Performance Optimization Techniques for React Apps</a></li>
<li><a href="https://www.toptal.com/react/optimizing-react-performance">Efficient React Components: A Guide to Optimizing React Performance</a></li>
<li><a href="https://www.developerway.com/posts/react-re-renders-guide">React re-renders guide: everything, all at once</a></li>
<li><a href="https://reactjs-kr.firebaseapp.com/docs/lifting-state-up.html">React - State 끌어올리기</a></li>
<li><a href="https://blog.goncharov.page/react-lifting-state-up-is-killing-your-app">React: Lifting state up is killing your app</a></li>
<li><a href="https://www.bitovi.com/blog/how-and-when-to-memoize-your-react-application">How and When tk Memoize Your React Application</a></li>
<li><a href="https://www.syncfusion.com/blogs/post/what-is-memoization-in-react.aspx">What is Memoization in React?</a></li>
<li><a href="https://www.toptal.com/react/react-memoization">Heavy Computation Made Lighter: React Memoization</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Lazy Loading과 Code Splitting]]></title>
            <link>https://velog.io/@doeunnkimm_/Lazy-Loading%EA%B3%BC-Code-Splitting</link>
            <guid>https://velog.io/@doeunnkimm_/Lazy-Loading%EA%B3%BC-Code-Splitting</guid>
            <pubDate>Fri, 07 Jul 2023 15:10:04 GMT</pubDate>
            <description><![CDATA[<h1 id="lazy-loading--초기-로딩-시간을-단축하자">lazy loading | 초기 로딩 시간을 단축하자</h1>
<h2 id="lazy-loading이란">Lazy Loading이란?</h2>
<ul>
<li>페이지 내에서 <strong>실제로 필요로 할 때까지 리소스의 로딩을 미루고</strong>,  실제로 화면에 보여질 필요가 있을 때 로딩을 할 수 있도록</li>
<li>즉, 내가 미리 결정해둔 조건이 충족될 때까지 UI 렌더링을 차단할 수 있다</li>
</ul>
<h2 id="lazy-loading을-사용하는-이유">Lazy Loading을 사용하는 이유</h2>
<ul>
<li>⭐ 웹 성능 향상</li>
<li>Lazy Loading이 웹 성능 향상에 도움이 되는 이유들<ol>
<li>초기 로드 시간 최소화<ul>
<li>초기 로드 시 필요하지 않은 리소스들(JS파일, 이미지 파일, …)은 나중에 필요한 시점에 로드함으로써, 초기 로딩 시간을 단축</li>
</ul>
</li>
<li>자원 사용 최적화<ul>
<li>웹 페이지에 있는 모든 리소스를 한번에 로드하면 사용자가 실제로 필요로 하는 리소스가 아니더라도 로드</li>
<li>하지만 lazy loading을 적용하면, 필요한 리소스만 로드하여 리소스 사용 최적화 가능</li>
<li>사용자가 스크롤링하거나 특정 이벤트가 발생했을 때 필요한 리소스들을 동적으로 로드 가능</li>
</ul>
</li>
<li>사용자 경험 개선<ul>
<li>lazy loading을 사용하면 초기 로딩 시간을 최소화, 필요한 리소스를 적시에 로드하여 사용자 경험 향상</li>
<li>페이지가 빠르게 로드되고, 사용자가 필요한 요소를 볼 수 있는 순간부터 콘텐츠를 제공할 수 있기 때문</li>
</ul>
</li>
</ol>
</li>
</ul>
<h2 id="lazy-loading---code-splitting---suspense">Lazy Loading - Code Splitting - Suspense</h2>
<ul>
<li>초기에 페이지에 접속하면 웹펙으로부터 번들링된 js 파일을 받는다</li>
<li>이때 번들링에는 모든 컴포넌트 포함<ul>
<li>여기에 fetching까지 이루어진다면, 사용자가 초기에 웹페이지를 보는 시점이 매우 늦어질 것</li>
</ul>
</li>
<li>lazy loading은 코드를 분할해서 모든 컴포넌트가 번들링된 파일을 받는 것이 아니라 필요할 때 동적으로 컴포넌트를 불러오는 것</li>
<li>컴포넌트를 동적으로 import 하려면 Promise의 상태를 처리해야 한다</li>
<li>초기 렌더 시간이 오래걸리는 문제를 해결 가능</li>
<li>lazy loading을 적용한 페이지를 동적으로 import할 때, Suspense가 이를 감지해 fallback 컴포넌트를 대신 보여준다</li>
</ul>
<h2 id="code-splitting">Code Splitting</h2>
<h3 id="react에서-code-splitting코드-분할이란">React에서 Code Splitting(코드 분할)이란?</h3>
<ul>
<li>Code Splitting은 Webpack같은 번들러에서 지원하는 기능</li>
<li><strong>런타임에 동적으로 로드</strong>할 수 있는 여러 번들을 생성 가능</li>
<li>코드 분할은 React.lazy 및 Suspense 같은 도구를 사용하여 의존성을 느리게 로드하고, 사용자가 필요할 때만 로드하는 것도 가능</li>
</ul>
<blockquote>
<p>⭐ 초기 로딩 시간을 단축하기 위해 사용</p>
</blockquote>
<h3 id="react에서-코드-분할을-하는-방법">React에서 코드 분할을 하는 방법</h3>
<ol>
<li><p><strong>Dynamic imports (동적 import)</strong></p>
<ul>
<li><p>리소스를 동적으로 로드하는 기능</p>
</li>
<li><p>필요한 시점에 필요한 리소스만 로드하여 성능을 향상</p>
</li>
<li><p>초기 번들 크기를 줄이고 필요한 리소스만 비동기적으로 로드 → 초기 로딩 속도 향상</p>
<pre><code class="language-jsx">import(&#39;./MyComponent&#39;).then((module) =&gt; {
const MyComponent = module.default;
// 모듈 로드가 완료된 후에 실행될 코드
});</code></pre>
</li>
</ul>
</li>
</ol>
<ol start="2">
<li><p><strong>React.lazy</strong></p>
<ul>
<li><p>React의 내장함수</p>
</li>
<li><p>동적으로 컴포넌트를 로드하기 위해 사용</p>
</li>
<li><p>Lazy Loading을 구현하기 위해 사용</p>
</li>
<li><p>필요한 컴포넌트가 화면에 나타나기 전까지 로딩을 지연</p>
<pre><code class="language-jsx">const OtherComponent = React.lazy(() =&gt; import(&#39;./OtherComponent&#39;));</code></pre>
</li>
<li><p>React.lazy는 Dynamic import를 호출하는 함수를 인자로 가진다 → Promise 반환 대기</p>
</li>
</ul>
</li>
<li><p><strong>Route-based code splitting (경로 기반 코드 분할)</strong></p>
<ul>
<li><p>특정 경로로 이동할 때 해당 경로에 필요한 모듈만 로드</p>
</li>
<li><p>React.lazy를 사용하여 경로 기반 코드 분할을 수행 가능</p>
<pre><code class="language-jsx">import React, { Suspense, lazy } from &#39;react&#39;;
import { BrowserRouter as Router, Route, Routes } from &#39;react-router-dom&#39;;

const Home_ = lazy(() =&gt; import(&#39;./routes/Home&#39;));
const About_ = lazy(() =&gt; import(&#39;./routes/About&#39;));

const App = () =&gt; (
&lt;Router&gt;
  &lt;Suspense fallback={&lt;div&gt;Loading...&lt;/div&gt;}&gt;
    &lt;Routes&gt;
      &lt;Route exact path=&quot;/&quot; element={&lt;Home/&gt;}/&gt;
      &lt;Route exact path=&quot;/about&quot; element={&lt;About/&gt;}/&gt;
    &lt;/Routes&gt;
  &lt;/Suspense&gt;
&lt;/Router&gt;
);
export default App;</code></pre>
</li>
</ul>
</li>
</ol>
<h2 id="suspense">Suspense</h2>
<ul>
<li>React.lazy 혹은 Data Fetching처럼 비동기식으로 컨텐츠가 로드되는 동안 대체 표시</li>
</ul>
<pre><code class="language-jsx">&lt;Suspense fallback={&lt;Loading /&gt;}&gt;
  &lt;Albums /&gt;
&lt;/Suspense&gt;</code></pre>
<ul>
<li>하위 항목에 필요한 모든 코드와 데이터가 로드될 때까지 로딩 fallback을 표시</li>
<li>렌더링할 준비가 될 때까지 React는 가장 가까운 Suspense 경계를 전환<ul>
<li>대체 요소인 구성 요소를 표시</li>
</ul>
</li>
<li>그런 다음, 데이터가 로드되면 React는 fallback을 숨기고 불러온 데이터를 기반으로 렌더링</li>
</ul>
<h2 id="참고-문서">참고 문서</h2>
<ul>
<li><a href="https://legacy.reactjs.org/docs/code-splitting.html">Code splitting</a></li>
<li><a href="https://www.geeksforgeeks.org/code-splitting-in-react/">Code Splitting in React</a></li>
<li><a href="https://www.turing.com/kb/how-to-split-bundle-using-code-splitting-in-reactjs">How Do You Split Bundle Using Code Splitting in ReactJS</a></li>
<li><a href="https://velog.io/@syoung125/WIL-2021.11-1st">syoung125-Image Lazy Loading 구현하기</a></li>
<li><a href="https://velog.io/@bnb8419/Suspense%EC%99%80-lazy-Loading">bnb8419-React Suspense와 lazy Loading</a></li>
<li><a href="https://react.dev/reference/react/Suspense">react.dev-Suspense</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>