<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>hooninedev.com</title>
        <link>https://hooninedev.com</link>
        <description>프론트엔드 개발자 이지훈(후니)의 기술 블로그. React, TypeScript, Next.js 등 웹 개발 기록과 학습 노트를 공유합니다.</description>
        <lastBuildDate>Mon, 20 Apr 2026 11:18:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>ko</language>
        <copyright>All rights reserved 2026, 이지훈</copyright>
        <item>
            <title><![CDATA[도메인 모델]]></title>
            <link>https://hooninedev.com/260418</link>
            <guid isPermaLink="false">https://hooninedev.com/260418</guid>
            <pubDate>Sat, 18 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[이번 포스팅에서는 도메인(Domain), 도메인 모델(Domain Model), 도메인 오브젝트 모델(Domain Object Model), 도메인 오브젝트(Domain Object)에 대한 이야기를 해보려고 한다. 필자는 프론트엔드 개발을 하면서 "도메인"이라는 단어를 꽤 자주 접해왔다. 코드 리뷰에서 "이건 도메인 로직이니까 컴포넌트 밖으로 빼세요", ...]]></description>
            <content:encoded><![CDATA[<p>이번 포스팅에서는 <strong>도메인(Domain)</strong>, <strong>도메인 모델(Domain Model)</strong>, <strong>도메인 오브젝트 모델(Domain Object Model)</strong>, **도메인 오브젝트(Domain Object)**에 대한 이야기를 해보려고 한다.</p>
<p>필자는 프론트엔드 개발을 하면서 "도메인"이라는 단어를 꽤 자주 접해왔다. 코드 리뷰에서 "이건 도메인 로직이니까 컴포넌트 밖으로 빼세요", 아키텍처 논의에서 "도메인 모델을 먼저 정의합시다" 같은 말들이 오간다. 그런데 막상 "도메인이 정확히 뭔데?"라고 물어보면 명쾌하게 대답하기가 쉽지 않다. (솔직히 필자도 한동안 그냥 분위기에 맞춰 끄덕였었다)</p>
<p>더 혼란스러운 것은 "도메인 모델"과 "도메인 오브젝트"와 "도메인 오브젝트 모델"이 서로 어떻게 다른지, 그리고 이 개념들이 백엔드가 아닌 <strong>프론트엔드</strong>에서는 어떤 의미를 갖는지에 대한 정리된 글이 많지 않다는 점이다. 이 글에서는 각 개념의 정확한 정의부터 시작해서, 프론트엔드에서 도메인 로직을 어떻게 분리하고 추상화하는 것이 적합한지까지 예시와 함께 정리해보려 한다.</p>
<hr>
<h2 id="도메인domain이란-무엇인가"><a class="anchor" href="#도메인domain이란-무엇인가">도메인(Domain)이란 무엇인가?</a></h2>
<p>가장 기초적인 질문부터 시작해보자. <strong>도메인</strong>이란 무엇인가?</p>
<p>Eric Evans는 그의 저서 *"Domain-Driven Design: Tackling Complexity in the Heart of Software"(2003)*에서 도메인을 다음과 같이 정의한다.</p>
<blockquote>
<p>"A sphere of knowledge, influence, or activity."
(지식, 영향력, 또는 활동의 영역)</p>
</blockquote>
<p>쉽게 말해, <strong>프로그래밍으로 해결하고자 하는 문제 영역</strong> 그 자체가 도메인이다. 세금 신고 서비스를 만든다면 "세금 신고"가 도메인이고, 보험 청구 플랫폼을 만든다면 "보험 청구"가 도메인인 것이다. 도메인은 코드가 아니다. 소프트웨어 이전에 존재하는 현실 세계의 문제 영역이다.</p>
<p>프론트엔드 개발자에게 이것은 어떤 의미일까? 우리가 만드는 UI는 결국 이 도메인을 사용자에게 보여주고 조작할 수 있게 해주는 **창(window)**이다. 토스인컴 같은 세금 환급 서비스를 개발한다면, 소득 유형, 경비율, 소득공제, 세액공제, 환급액이라는 도메인 개념을 UI로 표현하는 것이다. 따라서 프론트엔드 개발자도 자신이 다루는 도메인을 깊이 이해해야 한다. UI 컴포넌트를 잘 그리는 것 못지않게, **"이 서비스가 해결하는 문제가 무엇인지"**를 아는 것이 중요하다는 뜻이다.</p>
<p>그런데 "세금"이라는 도메인 하나만 해도, 들여다보면 내부에 수많은 하위 도메인이 존재한다. 종합소득세 계산 파이프라인만 봐도 이렇다.</p>
<pre><code>총수입금액 → 필요경비 차감 → 종합소득금액
  → 소득공제 → 과세표준 → 세율 적용 → 산출세액
    → 세액공제 → 결정세액 → 기납부세액 차감
      → 납부 or 환급
</code></pre>
<p>이 파이프라인의 각 단계가 독자적인 규칙과 데이터를 가진 하위 도메인이다. "세금"이라는 하나의 큰 도메인 안에 소득(Income), 공제(Deduction), 세액(Tax), 신고(Filing)라는 세부 도메인들이 얽혀 있는 것이다. 이것을 코드로 어떻게 나눠야 하는지가 바로 도메인 모델링의 핵심 질문이다.</p>
<hr>
<h2 id="도메인-모델domain-model이란"><a class="anchor" href="#도메인-모델domain-model이란">도메인 모델(Domain Model)이란?</a></h2>
<p>그렇다면 도메인 모델은 무엇일까? 도메인 그 자체와 도메인 "모델"은 어떻게 다른 것일까?</p>
<p>Martin Fowler는 *"Patterns of Enterprise Application Architecture"(2002)*에서 도메인 모델을 이렇게 정의한다.</p>
<blockquote>
<p>"An object model of the domain that incorporates both behavior and data."
(행위와 데이터를 모두 포함하는 도메인의 객체 모델)</p>
</blockquote>
<p>Eric Evans의 정의도 살펴보자.</p>
<blockquote>
<p>"A system of abstractions that describes selected aspects of a domain and can be used to solve problems related to that domain."
(도메인의 선택된 측면을 기술하는 추상화 체계로, 해당 도메인과 관련된 문제를 해결하는 데 사용될 수 있다.)</p>
</blockquote>
<p>핵심은 **"선택적 추상화"**라는 점이다. 도메인 모델은 현실 세계의 모든 것을 담지 않는다. 영화감독이 현실의 모든 장면을 담지 않고 이야기에 필요한 장면만 선택하듯, 도메인 모델도 <strong>해결하려는 문제에 필요한 측면만 골라서 구조화</strong>한 것이다.</p>
<p>여기서 중요한 점이 하나 있다. 도메인 모델은 반드시 코드일 필요가 없다. 화이트보드에 그린 다이어그램일 수도 있고, 팀원들 머릿속에 공유된 멘탈 모델(Mental Model)일 수도 있다. 한국어로 정리된 Hudi의 블로그에서도 이를 명확히 짚고 있다 — "도메인 모델이라는 용어 자체는 엄밀하게는 소프트웨어와는 별개의 개념"이라고 말이다.</p>
<h3 id="도메인-모델과-데이터-모델은-다르다"><a class="anchor" href="#도메인-모델과-데이터-모델은-다르다">도메인 모델과 데이터 모델은 다르다</a></h3>
<p>프론트엔드 개발자가 특히 혼동하기 쉬운 부분이 있다. API 응답의 JSON 구조를 보고 "이게 도메인 모델이군"이라고 생각하는 것이다. 하지만 이는 **데이터 모델(Data Model)**이지, 도메인 모델이 아니다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>도메인 모델</th>
<th>데이터 모델</th>
</tr>
</thead>
<tbody>
<tr>
<td>목적</td>
<td>비즈니스 개념과 규칙을 표현</td>
<td>저장/전송 구조를 정의</td>
</tr>
<tr>
<td>언어</td>
<td>비즈니스 용어 (과세표준, 세액공제, 환급액)</td>
<td>기술 용어 (string, number, array)</td>
</tr>
<tr>
<td>포함 요소</td>
<td>데이터 + 행위(규칙)</td>
<td>데이터 구조만</td>
</tr>
<tr>
<td>프론트엔드 예시</td>
<td>"과세표준 1,400만원 이하 구간은 세율 6%"</td>
<td><code>{ taxableBase: number, taxRate: number }</code></td>
</tr>
</tbody>
</table>
<p>데이터 모델은 "어떤 형태로 데이터가 오가는가"를 정의하고, 도메인 모델은 "이 데이터가 비즈니스적으로 무엇을 의미하고 어떤 규칙을 따르는가"를 정의한다. 이 둘을 구분하지 못하면, 컴포넌트가 API 응답 구조에 직접 의존하게 되어 백엔드 스키마가 바뀔 때마다 프론트엔드 전체가 흔들리는 상황이 벌어진다.</p>
<hr>
<h2 id="도메인-오브젝트domain-object란"><a class="anchor" href="#도메인-오브젝트domain-object란">도메인 오브젝트(Domain Object)란?</a></h2>
<p>도메인 모델이 개념적 추상화라면, <strong>도메인 오브젝트</strong>는 그 개념이 코드로 구현된 실체이다.</p>
<p>Code with Jason의 정리가 가장 직관적이다.</p>
<blockquote>
<p>"Any object in my object model that also exist as a concept in my domain model I would call a domain object."
(내 객체 모델에 있는 객체 중 내 도메인 모델에도 개념으로 존재하는 것을 도메인 오브젝트라고 부른다.)</p>
</blockquote>
<p>즉, 도메인 모델에서 "종합소득"이라는 개념이 있고, 코드에서 <code>Income</code>이라는 타입이 있다면, 이 <code>Income</code>이 바로 도메인 오브젝트인 것이다. 모든 코드 객체가 도메인 오브젝트인 것은 아니다. <code>HttpClient</code>, <code>LocalStorageAdapter</code>, <code>useDebounce</code> 같은 것들은 기술적 도구이지 도메인 개념이 아니다.</p>
<h3 id="entity와-value-object"><a class="anchor" href="#entity와-value-object">Entity와 Value Object</a></h3>
<p>Evans는 도메인 오브젝트를 크게 두 가지로 분류한다. Fowler는 이를 "Evans Classification"이라고 부른다.</p>
<p><strong>Entity(엔티티):</strong></p>
<blockquote>
<p>"Objects that have a distinct identity that runs through time and different representations."
(시간과 다양한 표현을 관통하는 고유한 정체성을 가진 객체)</p>
</blockquote>
<p>세금 신고서(TaxFiling), 납세자(Taxpayer), 보험 계약(Policy)처럼 고유한 ID로 식별되는 객체이다. 속성이 바뀌더라도 같은 ID면 같은 엔티티이다. 신고서의 공제 항목이 수정되어도 같은 신고서인 것처럼.</p>
<p><strong>Value Object(값 객체):</strong></p>
<blockquote>
<p>"Objects that matter only as the combination of their attributes. Two value objects with the same values for all their attributes are considered equal."
(속성의 조합으로만 의미를 가지는 객체. 모든 속성 값이 같으면 동일한 것으로 간주한다.)</p>
</blockquote>
<p>금액(Money), 세율(TaxRate), 과세 구간(TaxBracket)처럼 값 자체가 의미인 객체이다. "세율 6%"는 어디에서 쓰이든 "세율 6%"일 뿐이다.</p>
<p>프론트엔드에서 이 구분이 왜 중요할까? TypeScript로 예시를 들어보자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// Entity - ID로 식별된다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  id</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  taxpayerName</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  taxYear</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  status</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 같은 ID면 같은 신고서</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> isSameFiling</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">a</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">b</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> a.id </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> b.id;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// Value Object - 값의 조합으로 식별된다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Money</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  amount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  currency</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "KRW"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "USD"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 모든 속성이 같으면 같은 값</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> isSameMoney</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">a</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Money</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">b</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Money</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  a.amount </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> b.amount </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> a.currency </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> b.currency;</span></span></code></pre></figure>
<p>Entity는 ID 기반 비교, Value Object는 속성 기반 비교. 이 구분을 명확히 하면 상태 관리에서 "이 데이터가 같은 건지 다른 건지"를 판단하는 로직이 자연스럽게 정리된다. 리스트에서 아이템을 갱신할 때 Entity라면 ID로 찾아서 교체하고, Value Object라면 불변 교체(immutable replace)를 하는 식이다.</p>
<hr>
<h2 id="도메인-오브젝트-모델domain-object-model이란"><a class="anchor" href="#도메인-오브젝트-모델domain-object-model이란">도메인 오브젝트 모델(Domain Object Model)이란?</a></h2>
<p>자, 여기까지 읽으면 자연스럽게 떠오르는 질문이 하나 있다. "도메인 모델"과 "도메인 오브젝트"는 알겠는데, <strong>도메인 오브젝트 모델</strong>은 또 뭔가?</p>
<p>이 용어는 앞의 세 개념을 연결하는 다리 역할을 한다.</p>
<blockquote>
<p>"The place where my domain model turns into actual code is in the object model."
(내 도메인 모델이 실제 코드로 전환되는 곳이 바로 객체 모델이다.)
— Code with Jason</p>
</blockquote>
<p><strong>도메인 오브젝트 모델 = 도메인 모델의 코드 구현체</strong>이다. 개념적 추상화(도메인 모델)를 프로그래밍 언어의 타입, 클래스, 함수로 표현한 것이 도메인 오브젝트 모델이고, 그 안의 개별 타입이나 인스턴스가 도메인 오브젝트인 것이다.</p>
<p>(참고로, 웹 개발에서 흔히 접하는 DOM(Document Object Model)과는 완전히 다른 개념이다. Wikipedia에서도 이 둘이 별개임을 명시하고 있다.)</p>
<p>네 가지 개념의 관계를 세금 도메인으로 정리하면 다음과 같다.</p>
<pre><code>도메인 (Domain)
  → 문제 영역 그 자체. "종합소득세 신고"

도메인 모델 (Domain Model)
  → 문제 영역의 선택적 추상화. "소득-경비-공제-세액-환급" 계산 파이프라인

도메인 오브젝트 모델 (Domain Object Model)
  → 도메인 모델의 코드 구현 전체. Income, Deduction, TaxCalculation, Filing 타입들과 그 관계

도메인 오브젝트 (Domain Object)
  → 구현된 개별 객체. 특정 납세자의 TaxCalculation 인스턴스 하나
</code></pre>
<p>이것은 추상에서 구체로 내려가는 계층이다. 도메인이 가장 넓고, 도메인 오브젝트가 가장 구체적이다.</p>
<hr>
<h2 id="프론트엔드에서-도메인-로직은-어디에-있어야-하는가"><a class="anchor" href="#프론트엔드에서-도메인-로직은-어디에-있어야-하는가">프론트엔드에서 도메인 로직은 어디에 있어야 하는가?</a></h2>
<p>개념 정의는 여기까지로 하고, 이제 실전 이야기를 해보자. 프론트엔드에서 도메인 로직은 <strong>어디에</strong> 있어야 하는 걸까?</p>
<p>Khalil Stemmler는 처음에 "비즈니스 로직은 프론트엔드에 속하지 않는다"고 주장했다가, 이후 입장을 수정하며 이렇게 말했다.</p>
<blockquote>
<p>"Pretty much everything we're doing architecturally on the backend, we could and should be doing on the frontend."
(백엔드에서 아키텍처적으로 하고 있는 거의 모든 것을, 프론트엔드에서도 할 수 있고 해야 한다.)</p>
</blockquote>
<p>필자도 이 입장에 동의한다. 물론 프론트엔드가 비즈니스 로직의 **단일 진실 공급원(Single Source of Truth)**이 되어서는 안 된다. 그것은 백엔드의 역할이다. 하지만 프론트엔드에도 <strong>프론트엔드만의 도메인 로직</strong>이 분명히 존재한다. 세금 서비스처럼 "사용자가 입력한 정보에 따라 실시간으로 예상 환급액을 보여줘야 하는" 경우에는 더욱 그렇다.</p>
<h3 id="흔히-발생하는-안티패턴-도메인-로직이-컴포넌트에-섞인-경우"><a class="anchor" href="#흔히-발생하는-안티패턴-도메인-로직이-컴포넌트에-섞인-경우">흔히 발생하는 안티패턴: 도메인 로직이 컴포넌트에 섞인 경우</a></h3>
<p>종합소득세 미리보기 화면을 예로 들어보자. 사용자가 소득 정보를 입력하면 예상 세액을 실시간으로 보여주는 기능이다. 아래는 흔히 볼 수 있는, 도메인 로직과 UI 로직이 뒤섞인 코드이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxPreviewPage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">grossIncome</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setGrossIncome</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">expenseRate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setExpenseRate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0.646</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 단순경비율</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">personalDeductions</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setPersonalDeductions</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 본인 1명</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 비즈니스 규칙: 종합소득금액 = 총수입 - 필요경비</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> totalIncome</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> grossIncome </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> grossIncome </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">*</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> expenseRate;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 비즈니스 규칙: 인적공제 1인당 150만원</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> deductionAmount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> personalDeductions </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">*</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1_500_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> taxableBase</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Math.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">max</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, totalIncome </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> deductionAmount);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 비즈니스 규칙: 8단계 누진세율</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> calculatedTax </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (taxableBase </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 14_000_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    calculatedTax </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> taxableBase </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">*</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0.06</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (taxableBase </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 50_000_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    calculatedTax </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> taxableBase </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">*</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0.15</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> -</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1_260_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (taxableBase </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 88_000_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    calculatedTax </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> taxableBase </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">*</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0.24</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> -</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 5_760_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (taxableBase </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 150_000_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    calculatedTax </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> taxableBase </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">*</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0.35</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> -</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 15_440_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    calculatedTax </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> taxableBase </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">*</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0.38</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> -</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 19_940_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 비즈니스 규칙: 기납부세액 (3.3% 원천징수)</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> prepaidTax</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> grossIncome </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">*</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0.033</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> refundOrPayment</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> prepaidTax </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> calculatedTax;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">input</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{grossIncome} </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">onChange</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">e</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> setGrossIncome</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(e.target.value))} /></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">p</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>종합소득금액: {totalIncome.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toLocaleString</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()}원&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">p</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">p</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>과세표준: {taxableBase.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toLocaleString</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()}원&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">p</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">p</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>산출세액: {Math.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">floor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(calculatedTax).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toLocaleString</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()}원&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">p</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">p</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>기납부세액: {Math.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">floor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(prepaidTax).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toLocaleString</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()}원&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">p</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">p</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>{refundOrPayment </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> ?</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> '환급 예상액'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> :</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> '납부 예상액'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}: {Math.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">abs</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(Math.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">floor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(refundOrPayment)).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toLocaleString</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()}원&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">p</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이 코드의 문제가 보이는가? "인적공제 1인당 150만원", "8단계 누진세율", "3.3% 원천징수"라는 <strong>세법에 의해 정해진 비즈니스 규칙</strong>이 React 컴포넌트 안에 직접 박혀 있다. 세법이 매년 개정되는데, 이런 규칙이 컴포넌트에 흩어져 있으면 개정 시 수정해야 할 곳을 찾아 헤매게 된다. (토스인컴의 QA팀이 환급 플로우만으로 35개 이상의 E2E 시나리오를 운영한다는 사실을 떠올려보면, 이런 규칙이 컴포넌트에 박혀 있을 때의 테스트 비용을 짐작할 수 있다) emewjin의 블로그에서도 이 상황을 정확히 지적한다 — "이게 뷰 로직인지 비즈니스 로직인지 구분하기 어려울 정도로 엉킨다."</p>
<h3 id="도메인-로직-분리-순수-함수로-추출하기"><a class="anchor" href="#도메인-로직-분리-순수-함수로-추출하기">도메인 로직 분리: 순수 함수로 추출하기</a></h3>
<p>Alex Bespoyasov의 Clean Architecture 접근법에서 핵심 원칙을 빌려오자. 도메인 로직은 <strong>프레임워크에 의존하지 않는 순수 함수</strong>로 분리하는 것이다.</p>
<blockquote>
<p>"The domain is the core that distinguishes one application from another. You can think of the domain as something that won't change if we move from React to Angular."
(도메인은 하나의 애플리케이션을 다른 것과 구별하는 핵심이다. React에서 Angular로 옮기더라도 변하지 않는 것이라고 생각하면 된다.)</p>
</blockquote>
<p>위의 세금 계산 예시를 리팩토링해보자.</p>
<p><strong>1단계: 도메인 타입 정의</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// domain/tax.ts</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Income</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  grossAmount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;       </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 총수입금액</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  expenseRate</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;       </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 경비율 (단순/기준)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Deductions</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  personalCount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;     </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 인적공제 대상 인원</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  pensionPaid</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;       </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 연금보험료</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  additionalDeductions</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 기타 소득공제</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxCalculation</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  totalIncome</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;       </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 종합소득금액</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  deductionAmount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;   </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 소득공제 합계</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  taxableBase</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;       </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 과세표준</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  calculatedTax</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;     </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 산출세액</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  prepaidTax</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;        </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 기납부세액</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  refundOrPayment</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;   </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 환급(+) 또는 납부(-)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p><strong>2단계: 도메인 로직을 순수 함수로 분리</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// domain/tax.ts (이어서)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PERSONAL_DEDUCTION_PER_PERSON</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1_500_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> WITHHOLDING_RATE</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0.033</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> TAX_BRACKETS</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  { limit: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">14_000_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,  rate: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0.06</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, progressiveDeduction: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  { limit: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">50_000_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,  rate: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0.15</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, progressiveDeduction: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1_260_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  { limit: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">88_000_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,  rate: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0.24</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, progressiveDeduction: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">5_760_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  { limit: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">150_000_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, rate: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0.35</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, progressiveDeduction: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">15_440_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  { limit: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">300_000_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, rate: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0.38</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, progressiveDeduction: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">19_940_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  { limit: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">500_000_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, rate: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0.40</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, progressiveDeduction: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">25_940_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  { limit: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1_000_000_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, rate: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0.42</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, progressiveDeduction: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">35_940_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  { limit: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Infinity</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,    rate: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0.45</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, progressiveDeduction: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">65_940_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculateTotalIncome</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">income</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Income</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> income.grossAmount </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> income.grossAmount </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">*</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> income.expenseRate;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculateDeductions</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">deductions</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Deductions</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    deductions.personalCount </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">*</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PERSONAL_DEDUCTION_PER_PERSON</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> +</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    deductions.pensionPaid </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    deductions.additionalDeductions</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculateTaxableBase</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">totalIncome</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">deductionAmount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Math.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">max</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, totalIncome </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> deductionAmount);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculateTax</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">taxableBase</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> bracket</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> TAX_BRACKETS</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">find</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">b</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> taxableBase </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> b.limit)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Math.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">floor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(taxableBase </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">*</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> bracket.rate </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> bracket.progressiveDeduction);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculatePrepaidTax</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">grossIncome</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Math.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">floor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(grossIncome </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">*</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> WITHHOLDING_RATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculateRefund</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">prepaidTax</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">calculatedTax</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> prepaidTax </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> calculatedTax;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> computeFullTax</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">income</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Income</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">deductions</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Deductions</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxCalculation</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> totalIncome</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculateTotalIncome</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(income);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> deductionAmount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculateDeductions</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(deductions);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> taxableBase</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculateTaxableBase</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(totalIncome, deductionAmount);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> calculatedTax</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculateTax</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(taxableBase);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> prepaidTax</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculatePrepaidTax</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(income.grossAmount);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> refundOrPayment</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculateRefund</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(prepaidTax, calculatedTax);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { totalIncome, deductionAmount, taxableBase, calculatedTax, prepaidTax, refundOrPayment };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p><strong>3단계: 컴포넌트는 도메인 로직을 "사용"만 한다</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// ui/TaxPreviewPage.tsx</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { computeFullTax } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> '../domain/tax'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxPreviewPage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">income</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setIncome</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Income</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>({ grossAmount: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, expenseRate: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0.646</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> });</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">deductions</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setDeductions</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Deductions</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    personalCount: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, pensionPaid: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, additionalDeductions: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> result</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> computeFullTax</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(income, deductions);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">IncomeForm</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{income} </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">onChange</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{setIncome} /></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">DeductionForm</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{deductions} </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">onChange</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{setDeductions} /></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">TaxResultSummary</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> result</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{result} /></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>무엇이 달라졌는가?</p>
<ol>
<li><strong>8단계 누진세율 테이블</strong>(<code>TAX_BRACKETS</code>)이 한 곳에 모여 있어 세법 개정 시 이 파일만 수정하면 된다</li>
<li><strong>계산 파이프라인</strong>이 <code>computeFullTax</code> 한 함수로 응집되어, 전체 흐름이 한눈에 보인다</li>
<li><strong>컴포넌트는 "어떻게 보여줄까"에만 집중</strong>한다. 세율이 바뀌어도 컴포넌트를 수정할 필요가 없다</li>
<li>React에서 Vue로 마이그레이션한다 해도, <code>domain/tax.ts</code>는 <strong>한 글자도 바꿀 필요 없다</strong></li>
</ol>
<h3 id="테스트가-쉬워진다"><a class="anchor" href="#테스트가-쉬워진다">테스트가 쉬워진다</a></h3>
<p>도메인 로직이 분리되면 테스트는 놀랍도록 단순해진다. 세금 도메인에서는 <strong>계산의 정확성이 곧 사용자의 돈</strong>이기 때문에 이 점이 특히 중요하다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// domain/tax.test.ts</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">describe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'calculateTax'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  it</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'1,400만원 이하는 6% 세율'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">calculateTax</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10_000_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">600_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  it</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'5,000만원 이하는 15% 세율 - 누진공제 126만원'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">calculateTax</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">40_000_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">4_740_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 40M * 0.15 - 1,260,000</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  it</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'과세표준 0원이면 세액도 0원'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">calculateTax</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">describe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'computeFullTax'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  it</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'프리랜서 3,000만원 소득, 단순경비율 64.6%의 환급액을 정확히 계산한다'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> income</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { grossAmount: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">30_000_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, expenseRate: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0.646</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> };</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> deductions</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { personalCount: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, pensionPaid: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, additionalDeductions: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> };</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> result</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> computeFullTax</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(income, deductions);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 종합소득금액: 30M - 30M * 0.646 = 10,620,000</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(result.totalIncome).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10_620_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 과세표준: 10,620,000 - 1,500,000 = 9,120,000</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(result.taxableBase).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9_120_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 산출세액: 9,120,000 * 0.06 = 547,200</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(result.calculatedTax).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">547_200</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 기납부세액: 30M * 0.033 = 990,000</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(result.prepaidTax).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">990_000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 환급액: 990,000 - 547,200 = 442,800</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(result.refundOrPayment).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">442_800</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span></code></pre></figure>
<p>React Testing Library도, <code>render</code>도, <code>screen.getByText</code>도 필요 없다. 순수 함수에 입력을 넣고 출력을 확인하면 된다. 우아한형제들 기술블로그에서도 SCM 플랫폼 프론트엔드 개발 사례를 통해, 도메인 단위 테스트가 컴포넌트 분리 기준을 자연스럽게 잡아주고, 테스트 코드가 문서 역할까지 한다는 점을 보여주고 있다.</p>
<hr>
<h2 id="빈약한-도메인-모델anemic-domain-model을-경계하라"><a class="anchor" href="#빈약한-도메인-모델anemic-domain-model을-경계하라">빈약한 도메인 모델(Anemic Domain Model)을 경계하라</a></h2>
<p>여기서 한 가지 주의할 점이 있다. Martin Fowler는 **빈약한 도메인 모델(Anemic Domain Model)**을 안티패턴으로 강하게 비판했다.</p>
<blockquote>
<p>"The fundamental horror of this anti-pattern is that it's so contrary to the basic idea of object-oriented design; which is to combine data and process together."
(이 안티패턴의 근본적인 공포는, 데이터와 프로세스를 결합하라는 객체지향 설계의 기본 사상에 정면으로 반한다는 것이다.)</p>
</blockquote>
<blockquote>
<p>"In essence the problem with anemic domain models is that they incur all of the costs of a domain model, without yielding any of the benefits."
(본질적으로 빈약한 도메인 모델의 문제는, 도메인 모델의 비용은 모두 지불하면서 이점은 하나도 얻지 못한다는 것이다.)</p>
</blockquote>
<p>프론트엔드에서 빈약한 도메인 모델은 이런 모습이다. 세금 신고(Filing) 도메인을 예로 들어보자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 빈약한 도메인 모델의 예</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 타입은 있지만, 행위(규칙)가 없다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  id</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  status</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'draft'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'submitted'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'reviewing'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'completed'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'amended'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  taxYear</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  filingType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'regular'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'late'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'amendment'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  determinedTax</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 비즈니스 규칙이 여기저기 흩어져 있다</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// utils/filingHelpers.ts</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> canAmendFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// hooks/useFilingActions.ts</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useFilingActions</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() { </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* canAmendFiling를 또 구현하거나 import... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// components/FilingDetail.tsx</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingDetail</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filing</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 또 다른 곳에서 수정 가능 여부를 직접 판단...</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> isEditable</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> filing.status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'draft'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // ...</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>타입은 정의했지만 규칙이 타입과 분리되어 여러 파일에 흩어져 있다. 이러면 "경정청구 가능 조건이 변경됩니다"라는 요구사항이 들어왔을 때, 수정해야 할 곳이 어딘지 찾아다녀야 한다. (속된 말로 지뢰 찾기다)</p>
<p><strong>개선된 방향은 행위를 도메인과 함께 두는 것이다.</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// domain/filing.ts</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  id</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  status</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  taxYear</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  filingType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  determinedTax</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingStatus</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'draft'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'submitted'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'reviewing'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'completed'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'amended'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'regular'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'late'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'amendment'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 도메인 규칙은 도메인 옆에 둔다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> canEdit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> filing.status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'draft'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> canSubmit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> filing.status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'draft'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> &#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> filing.determinedTax </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">>=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> canAmend</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> filing.status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'completed'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> &#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> filing.filingType </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'amendment'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getNextAvailableStatuses</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[] {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> transitions</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Record</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[]> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    draft: [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'submitted'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">],</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    submitted: [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'reviewing'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">],</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    reviewing: [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'completed'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">],</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    completed: [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'amended'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">],</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    amended: [],</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> transitions[filing.status];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이제 신고 관련 비즈니스 규칙은 <code>domain/filing.ts</code> 한 곳에서 관리된다. 어떤 컴포넌트에서든 <code>canAmend(filing)</code>를 호출하면 되고, 규칙이 바뀌면 이 파일 하나만 수정하면 된다.</p>
<hr>
<h2 id="api-응답과-도메인-모델-사이의-변환-계층"><a class="anchor" href="#api-응답과-도메인-모델-사이의-변환-계층">API 응답과 도메인 모델 사이의 변환 계층</a></h2>
<p>실무에서 한 가지 더 고려해야 할 것이 있다. 백엔드 API 응답 구조와 프론트엔드의 도메인 모델이 <strong>항상 일치하지는 않는다</strong>는 점이다. 세금 서비스에서는 특히 그렇다. 국세청 홈택스 연동 데이터는 약어와 코드값으로 가득 차 있다.</p>
<p>emewjin의 블로그에서는 class를 활용해 서버 데이터를 모델로 관리하면서 이런 이점을 발견했다고 기록한다 — "서버로부터 받아온 값과 각 필드에 대한, 뷰 로직과는 독립적인 로직을 한 곳에서 관리"할 수 있게 되었다고. 동시에 "백엔드에서 <code>name</code>이 <code>realName</code>으로 바뀌어도, 모델만 수정하면 된다"는 실용적 장점도 있었다.</p>
<p>TypeScript에서 class 없이도 이런 변환 계층을 만들 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// infrastructure/mappers/incomeMapper.ts</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// API 응답 타입 (홈택스 연동 데이터 구조)</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HometaxIncomeResponse</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  tot_amt</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;          </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 총수입금액</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  inc_tp_cd</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;        </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 소득유형코드</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  biz_cd</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;           </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 업종코드</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  exp_rt</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;           </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 경비율</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  wh_tax_amt</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;       </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 원천징수세액</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  pyr_nm</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;           </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 지급자명</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  tx_yr</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;            </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 귀속연도</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 도메인 타입 (프론트엔드가 정의한 구조)</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> type</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { Income } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> '../domain/tax'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> toIncome</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">response</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HometaxIncomeResponse</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Income</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    grossAmount: response.tot_amt,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    expenseRate: response.exp_rt,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> mapIncomeType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">code</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> IncomeType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> typeMap</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Record</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">IncomeType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    '40'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'business'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,       </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 사업소득</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    '60'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'employment'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,     </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 근로소득</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    '61'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'pension'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,        </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 연금소득</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    '80'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'other'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,          </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 기타소득</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> typeMap[code] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">??</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'other'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이렇게 하면 API 응답의 <code>tot_amt</code>, <code>inc_tp_cd</code> 같은 약어, 코드값 기반 분류 등을 프론트엔드 도메인에 맞게 <strong>한 곳에서</strong> 변환한다. 홈택스 API의 필드명이 바뀌어도 mapper 함수 하나만 수정하면 된다. Roseline의 블로그에서도 이 접근을 "API와 UI의 연결을 효과적으로 설계"하는 방법으로 소개하고 있다.</p>
<hr>
<h2 id="유틸-함수와-도메인-로직-그-애매한-경계"><a class="anchor" href="#유틸-함수와-도메인-로직-그-애매한-경계">유틸 함수와 도메인 로직, 그 애매한 경계</a></h2>
<p>도메인 로직을 분리하다 보면 반드시 부딪히는 질문이 있다. <strong>"이거 유틸 함수 아닌가?"</strong></p>
<p>예를 들어 아래 두 함수를 보자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 이건 유틸인가, 도메인 로직인가?</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> formatCurrency</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">amount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> `${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">amount</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toLocaleString</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">()</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}원`</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 이건?</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculateTax</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">taxableBase</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> bracket</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> TAX_BRACKETS</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">find</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">b</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> taxableBase </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> b.limit)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Math.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">floor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(taxableBase </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">*</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> bracket.rate </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> bracket.progressiveDeduction);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p><code>formatCurrency</code>는 숫자를 문자열로 바꾸는 순수한 <strong>표현(Presentation) 로직</strong>이다. "원"이라는 단위를 붙이고 천 단위 콤마를 찍는 것은 비즈니스 규칙이 아니라, 사용자에게 어떻게 보여줄지에 관한 것이다. 반면 <code>calculateTax</code>는 "8단계 누진세율 적용"이라는 <strong>세법에 근거한 비즈니스 규칙</strong>을 담고 있다. 이것은 UI가 없어도 동일하게 적용되어야 하는 도메인의 규칙이다.</p>
<p>필자가 실무에서 사용하는 판단 기준은 이것이다.</p>
<blockquote>
<p><strong>"이 로직이 사라지면 비즈니스가 깨지는가, 화면만 깨지는가?"</strong></p>
</blockquote>
<p>비즈니스가 깨진다면 도메인 로직이고, 화면만 깨진다면 표현 로직이다. 이 질문 하나로 대부분의 경계는 구분할 수 있다.</p>
<table>
<thead>
<tr>
<th>판단 기준</th>
<th>도메인 로직</th>
<th>유틸/표현 로직</th>
</tr>
</thead>
<tbody>
<tr>
<td>없으면 뭐가 깨지나?</td>
<td>세금 계산 오류</td>
<td>화면이 이상해짐</td>
</tr>
<tr>
<td>프레임워크가 바뀌면?</td>
<td>그대로 유지</td>
<td>바뀔 수 있음</td>
</tr>
<tr>
<td>기획서에 명시되어 있나?</td>
<td>"과세표준 × 세율 - 누진공제"</td>
<td>"금액은 콤마 구분"</td>
</tr>
<tr>
<td>백엔드에도 같은 로직이?</td>
<td>있거나 있어야 함</td>
<td>없음 (프론트만의 관심사)</td>
</tr>
</tbody>
</table>
<p>하지만 현실은 이렇게 깔끔하지 않다. 가장 까다로운 것은 <strong>도메인 로직처럼 생겼는데 실은 표현 로직인 경우</strong>이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// domain/filing.ts 에 넣었는데... 이게 정말 도메인인가?</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getStatusBadgeColor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">status</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> colors</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Record</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    draft: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'gray'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    submitted: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'blue'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    reviewing: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'yellow'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    completed: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'green'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    amended: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'purple'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> colors[status];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getStatusDisplayText</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">status</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> labels</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Record</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    draft: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'작성 중'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    submitted: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'제출 완료'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    reviewing: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'검토 중'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    completed: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'신고 완료'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    amended: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'경정청구'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> labels[status];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p><code>getStatusBadgeColor</code>와 <code>getStatusDisplayText</code>는 <code>FilingStatus</code>라는 도메인 개념을 사용하지만, 하는 일은 <strong>화면 표현</strong>이다. 배지 색상이 바뀌어도 비즈니스는 전혀 깨지지 않는다. 이런 함수를 <code>domain/filing.ts</code>에 넣으면 도메인 모듈이 점점 비대해지고, 진짜 도메인 로직(<code>canAmend</code>, <code>canSubmit</code>)과 표현 로직이 뒤섞이게 된다.</p>
<h3 id="도메인-모델과-viewmodel의-분리"><a class="anchor" href="#도메인-모델과-viewmodel의-분리">도메인 모델과 ViewModel의 분리</a></h3>
<p>이 문제를 해결하는 실용적인 방법이 있다. <strong>같은 도메인 폴더 안에 ViewModel을 별도 파일로 분리</strong>하는 것이다. <code>.ui.ts</code>보다 <code>.viewModel.ts</code>라는 네이밍을 쓰면, MVVM 패턴의 ViewModel 개념과 자연스럽게 연결된다 — "도메인 데이터를 화면에 맞게 변환하는 계층"이라는 역할이 이름에서 바로 드러나기 때문이다.</p>
<pre><code>domains/
└── filing/
    ├── filing.ts              # 도메인 모델 + 도메인 로직
    ├── filing.viewModel.ts    # ViewModel (표현 변환 계층)
    ├── filing.test.ts         # 도메인 로직 테스트
    └── filingMapper.ts        # API ↔ 도메인 변환
</code></pre>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// domains/filing/filing.ts — 순수 도메인</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  id</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  status</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  taxYear</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  filingType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  determinedTax</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingStatus</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'draft'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'submitted'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'reviewing'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'completed'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'amended'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'regular'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'late'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'amendment'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> canEdit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> filing.status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'draft'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> canAmend</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> filing.status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'completed'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> &#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> filing.filingType </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'amendment'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// domains/filing/filing.viewModel.ts — 표현 관심사</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> type</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { FilingStatus, FilingType } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> './filing'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getStatusBadgeColor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">status</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> colors</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Record</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    draft: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'gray'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    submitted: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'blue'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    reviewing: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'yellow'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    completed: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'green'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    amended: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'purple'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> colors[status];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getStatusDisplayText</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">status</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> labels</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Record</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    draft: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'작성 중'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    submitted: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'제출 완료'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    reviewing: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'검토 중'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    completed: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'신고 완료'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    amended: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'경정청구'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> labels[status];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getFilingTypeLabel</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">type</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> labels</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Record</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">FilingType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    regular: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'정기 신고'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    late: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'기한 후 신고'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    amendment: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'경정청구'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> labels[type];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>핵심은 <strong>의존 방향</strong>이다. <code>filing.viewModel.ts</code>는 <code>filing.ts</code>를 import하지만, <code>filing.ts</code>는 <code>filing.viewModel.ts</code>를 절대 import하지 않는다. 도메인은 표현을 모르고, 표현이 도메인을 알고 있는 구조이다. 이것이 Robert C. Martin이 말한 의존성 규칙(Dependency Rule)의 축소판이라고 볼 수 있다.</p>
<p>같은 폴더에 두는 이유가 있다. 토스 프론트엔드 챕터에서 강조하는 원칙 — "함께 변경되는 파일을 같은 디렉토리에 배치하라"를 따른 것이다. <code>FilingStatus</code> 타입에 새로운 값(예: <code>'rejected'</code>)이 추가되면 <code>filing.ts</code>와 <code>filing.viewModel.ts</code> 모두 수정해야 한다. 같은 폴더에 있으니 수정 범위가 한눈에 보인다.</p>
<hr>
<h2 id="도메인-모델의-경계와-응집"><a class="anchor" href="#도메인-모델의-경계와-응집">도메인 모델의 경계와 응집</a></h2>
<p>도메인 로직을 분리하는 것만큼 중요한 것이 <strong>경계를 어디에 그을 것인가</strong>이다. 필자가 실무에서 자주 마주치는 경계 판단 문제를 몇 가지 정리해보겠다.</p>
<h3 id="데이터의-경계-이-필드는-누구의-것인가"><a class="anchor" href="#데이터의-경계-이-필드는-누구의-것인가">데이터의 경계: 이 필드는 누구의 것인가?</a></h3>
<p>프론트엔드에서 다루는 데이터는 대략 네 가지 출처에서 온다.</p>
<ol>
<li><strong>서버 데이터</strong>: API 응답으로 받은 것 (<code>filing.status</code>, <code>income.grossAmount</code>)</li>
<li><strong>파생 데이터</strong>: 서버 데이터로부터 계산된 것 (<code>taxableBase</code>, <code>refundAmount</code>)</li>
<li><strong>UI 상태</strong>: 화면 제어를 위한 것 (<code>isModalOpen</code>, <code>selectedTab</code>)</li>
<li><strong>사용자 입력</strong>: 폼에서 입력 중인 것 (<code>inputAmount</code>, <code>selectedDeductions</code>)</li>
</ol>
<p>이 네 가지를 하나의 타입에 섞으면 도메인 모델이 오염된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 안티패턴: 모든 것이 섞인 타입</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 서버 데이터 (도메인)</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  id</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  status</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  determinedTax</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 파생 데이터 (도메인)</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  refundAmount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  canAmend</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // UI 상태 (표현)</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  isExpanded</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  activeStep</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 임시 상태</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  editingDeductions</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Deduction</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이 타입은 도메인 개념과 UI 상태와 임시 데이터가 한 바구니에 담겨 있다. <code>activeStep</code>이 바뀔 때마다 신고 도메인이 갱신되는 셈이다. (폼 단계가 바뀌는 것은 비즈니스 이벤트가 아니다)</p>
<p>개선 방향은 경계에 따라 타입을 분리하는 것이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 도메인 모델 — 비즈니스 개념만</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  id</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  status</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  taxYear</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  filingType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  determinedTax</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// UI 상태 — 화면 제어만</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingFormViewState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  activeStep</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  expandedSections</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 사용자 입력 — 폼 상태만</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> DeductionEditForm</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  personalCount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  selectedDeductionIds</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[];</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  customExpenseAmount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이렇게 하면 각 타입이 <strong>하나의 변경 이유</strong>만 가진다. 도메인 타입은 세법이 바뀔 때만, UI 상태는 화면 설계가 바뀔 때만, 폼 상태는 입력 UX가 바뀔 때만 수정된다.</p>
<h3 id="응집의-기준-함께-변하는-것을-함께-두어라"><a class="anchor" href="#응집의-기준-함께-변하는-것을-함께-두어라">응집의 기준: "함께 변하는 것을 함께 두어라"</a></h3>
<p>Evans의 DDD에서 **Aggregate(집합체)**라는 개념이 있다. "관련된 객체들의 클러스터를 하나의 단위로 다루는 것"이다. 프론트엔드에서 이 개념을 그대로 적용할 필요는 없지만, 핵심 원칙은 빌려올 만하다 — <strong>함께 변하는 데이터와 규칙은 함께 둔다.</strong></p>
<p>세금 서비스를 예로 들면, <code>Income</code>(소득)과 <code>ExpenseRate</code>(경비율)는 항상 함께 변한다. 소득 유형이 바뀌면 적용 경비율도 바뀌고, 종합소득금액 계산도 영향을 받는다. 따라서 이것들은 하나의 파일 <code>domain/tax.ts</code>에 응집시킨다.</p>
<p>반면, <code>TaxFiling</code>(신고서)은 세액 계산과 별개로 변할 수 있다. 신고서의 상태 전이 규칙이 바뀌어도 세율 계산 로직은 영향을 받지 않는다. 따라서 <code>domain/filing.ts</code>로 분리하는 것이 맞다.</p>
<pre><code>이렇게 묻자: "A가 변할 때 B도 반드시 변해야 하는가?"
  → Yes: 같은 모듈에 둔다 (Income + ExpenseRate + TaxBracket)
  → No: 분리한다 (Tax 계산 ↔ Filing 상태관리)
</code></pre>
<hr>
<h2 id="class-vs-함수형-도메인-응집의-두-가지-표현"><a class="anchor" href="#class-vs-함수형-도메인-응집의-두-가지-표현">Class vs 함수형: 도메인 응집의 두 가지 표현</a></h2>
<p>여기까지 읽으면 한 가지 근본적인 질문이 떠오를 수 있다. 지금까지의 예시는 모두 <code>interface</code> + 순수 함수 조합이었는데, <strong>Class로 도메인을 표현하면 응집이 더 자연스럽지 않은가?</strong></p>
<p>맞는 말이다. Class 기반으로 도메인을 표현하면 데이터와 행위가 하나의 객체에 묶이기 때문에, 응집이 코드 구조에서 바로 드러난다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// Class 기반: 행위가 데이터에 귀속된다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFilingModel</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  constructor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    public</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> readonly</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209"> id</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    public</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> readonly</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209"> status</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    public</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> readonly</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209"> taxYear</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    public</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> readonly</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209"> filingType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FilingType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    public</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> readonly</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209"> determinedTax</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  ) {}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  canEdit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'draft'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  canAmend</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'completed'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> &#x26;&#x26;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.filingType </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'amendment'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  canSubmit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'draft'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> &#x26;&#x26;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.determinedTax </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">>=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 사용: 주어가 명확하다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFilingModel</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'F-001'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'completed'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2025</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'regular'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">547200</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">filing.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">canAmend</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();  </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// "이 신고서가 경정청구 가능한가?" — 자연어에 가깝다</span></span></code></pre></figure>
<p><code>filing.canAmend()</code>는 마치 자연어를 읽는 것처럼 직관적이다. 주어(filing)와 동사(canAmend)가 명확하게 결합되어 있다. 마치 <code>jihoon.eat('감자탕')</code>이라고 쓰면 "지훈이가 감자탕을 먹는다"가 바로 읽히는 것과 같다.</p>
<p>반면 함수형 스타일에서는 이렇게 된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 함수형: 행위가 데이터 밖에 있다</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">canAmend</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(filing);           </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// "경정청구가능한가(신고서를)"</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">eat</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'jihoon'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'감자탕'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);     </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// "먹는다(지훈을, 감자탕을)"</span></span></code></pre></figure>
<p>주어와 동사의 결합이 느슨해진다. <code>canAmend</code>라는 함수가 <code>TaxFiling</code>과 관련된다는 것은 파일을 열어보거나 타입 시그니처를 확인해야 알 수 있다. 같은 파일에 <code>canAmend(filing)</code>, <code>canEdit(filing)</code>, <code>calculateTax(taxableBase)</code> 같은 함수들이 섞여 있으면, 어떤 함수가 어떤 도메인에 속하는지 한눈에 파악하기 어려워질 수 있다.</p>
<h3 id="그렇다면-프론트엔드에서는-class를-써야-하는가"><a class="anchor" href="#그렇다면-프론트엔드에서는-class를-써야-하는가">그렇다면 프론트엔드에서는 Class를 써야 하는가?</a></h3>
<p>솔직히 말하면, 답은 **"상황에 따라 다르다"**이다. 하지만 필자의 경험상 React + TypeScript 환경에서는 Class가 만능이 아닌 현실적 이유들이 있다.</p>
<p><strong>1. React 상태 관리와의 마찰</strong></p>
<p>emewjin의 블로그에서도 정확히 이 문제를 지적하고 있다 — "리덕스 스토어에 저장시 인스턴스는 저장할 수 없다. 때문에 매번 <code>classToPlain</code>으로 풀어서 넣어주고, 다시 class로 만들어주어야 했다."</p>
<p>React의 상태 관리는 기본적으로 <strong>Plain Object</strong>를 전제로 설계되어 있다. <code>useState</code>, <code>useReducer</code>, Redux, Zustand 모두 직렬화 가능한(serializable) 객체를 다룬다. Class 인스턴스를 상태에 넣으면 직렬화/역직렬화 과정에서 메서드가 소실되고, <code>JSON.stringify</code> → <code>JSON.parse</code> 사이클에서 일반 객체로 퇴화한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 이런 상황이 발생한다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">filing</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFilingModel</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'F-001'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'draft'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2025</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'regular'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 상태 업데이트 후 filing은 여전히 TaxFilingModel 인스턴스인가?</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// Redux DevTools에서 직렬화할 때는?</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// React Server Component에서 전달할 때는?</span></span></code></pre></figure>
<p><strong>2. 불변성 보장의 어려움</strong></p>
<p>React는 상태 변경을 **참조 비교(referential equality)**로 감지한다. Class 인스턴스의 프로퍼티를 직접 수정하면 React가 변경을 감지하지 못한다. 매번 새 인스턴스를 생성해야 하는데, 그러면 Class의 이점인 "캡슐화된 상태 변경"이 무색해진다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// Class의 캡슐화 철학과 React의 불변성 철학이 충돌한다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> DeductionList</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // Class 철학: 내부 상태를 직접 변경</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  addDeduction</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">item</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Deduction</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.items.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">push</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);  </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// React가 변경을 감지 못함!</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // React에 맞추려면: 매번 새 인스턴스를 반환</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  addDeduction</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">item</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Deduction</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> DeductionList</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> DeductionList</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.items, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]);  </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 그럼 Class의 의미가...?</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<h3 id="함수형에서-응집을-확보하는-전략"><a class="anchor" href="#함수형에서-응집을-확보하는-전략">함수형에서 응집을 확보하는 전략</a></h3>
<p>그렇다면 함수형 스타일에서 <code>eat('jihoon', '감자탕')</code> 같은 느슨한 응집 문제를 어떻게 개선할 수 있을까? 필자가 효과적이라고 느낀 방법 세 가지를 소개한다.</p>
<p><strong>전략 1: 모듈 네임스페이스로 응집하기</strong></p>
<p>가장 직관적인 방법이다. 파일(모듈) 자체를 도메인 단위로 만들고, import 시 네임스페이스를 활용한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// domain/filing.ts</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> canEdit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> canAmend</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> canSubmit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span></span></code></pre></figure>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 사용하는 쪽</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> *</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> as</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> FilingModel </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> '../domain/filing'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">FilingModel.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">canEdit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(filing);      </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// "FilingModel의 canEdit" — 소속이 명확</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">FilingModel.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">canAmend</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(filing);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">FilingModel.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">canSubmit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(filing);</span></span></code></pre></figure>
<p><code>FilingModel.canAmend(filing)</code>은 <code>filing.canAmend()</code>만큼은 아니지만, 최소한 이 함수가 Filing 도메인에 속한다는 것이 코드에서 바로 드러난다. 함수가 여러 도메인에 걸쳐 섞일 위험도 없어진다.</p>
<p><strong>전략 2: 첫 번째 인자를 도메인 주체로 통일하기</strong></p>
<p>함수형에서 응집을 표현하는 또 다른 컨벤션이 있다. <strong>첫 번째 인자를 항상 "행위의 주체"로 둔다.</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 일관된 규칙: 첫 번째 인자 = 행위의 주체</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> canEdit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> canAmend</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculateTotalIncome</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">income</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Income</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> calculateTax</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">taxableBase</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span></span></code></pre></figure>
<p>이 컨벤션을 따르면 <code>canAmend(filing)</code>은 "filing에 대해 canAmend를 물어본다"로 읽힌다. Unix의 파이프라인 사고방식(<code>data |> transform</code>)과도 일맥상통한다. 사실 Go 언어의 메서드 리시버가 정확히 이 패턴이고, Rust의 <code>impl</code> 블록에서 <code>self</code>를 첫 인자로 받는 것도 같은 발상이다.</p>
<p><strong>전략 3: 도메인 객체 생성 함수(Factory)로 행위를 묶기</strong></p>
<p>Class의 응집력이 그리울 때 사용할 수 있는 패턴이다. 팩토리 함수가 도메인 객체와 그 행위를 한 번에 반환한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// domain/filing.ts</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createFilingModel</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaxFiling</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    ...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">data,</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    canEdit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data.status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'draft'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    canSubmit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data.status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'draft'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> &#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data.determinedTax </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">>=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    canAmend</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data.status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'completed'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> &#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data.filingType </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'amendment'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 사용</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> filing</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createFilingModel</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(rawFiling);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">filing.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">canAmend</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();    </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// Class처럼 읽히면서도 Plain Object다</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">filing.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">canEdit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// JSON.stringify도 되고, 스프레드도 되고, React 상태에도 들어간다</span></span></code></pre></figure>
<p>이 패턴은 Class의 표현력(<code>filing.canAmend()</code>)과 함수형의 실용성(Plain Object, 직렬화 가능)을 동시에 얻는다. 물론 매번 함수 객체를 새로 만드는 비용이 있지만, 프론트엔드에서 다루는 데이터 규모에서는 성능 문제가 되는 경우가 거의 없다.</p>
<h3 id="어떤-전략을-선택할까"><a class="anchor" href="#어떤-전략을-선택할까">어떤 전략을 선택할까?</a></h3>
<p>정리하면 이렇다.</p>
<table>
<thead>
<tr>
<th>전략</th>
<th>응집 표현</th>
<th>적합한 상황</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>import * as FilingModel</code></td>
<td><code>FilingModel.canAmend(filing)</code></td>
<td>도메인 함수가 많고, 여러 곳에서 import할 때</td>
</tr>
<tr>
<td>첫 인자 = 주체 컨벤션</td>
<td><code>canAmend(filing)</code></td>
<td>팀 전체가 컨벤션에 합의할 수 있을 때</td>
</tr>
<tr>
<td>Factory 함수</td>
<td><code>filing.canAmend()</code></td>
<td>Class의 표현력이 필요하지만 React 호환이 중요할 때</td>
</tr>
</tbody>
</table>
<p>필자의 경험상, <strong>전략 1과 2를 기본으로 가져가고, 특별히 응집이 중요한 핵심 도메인에만 전략 3을 적용</strong>하는 것이 균형 잡힌 접근이다. 완벽한 응집을 위해 모든 곳에 Factory를 도입하는 것은 또 다른 과잉 설계가 될 수 있다.</p>
<hr>
<h2 id="어디까지-분리할-것인가--실용적-기준"><a class="anchor" href="#어디까지-분리할-것인가--실용적-기준">어디까지 분리할 것인가? — 실용적 기준</a></h2>
<p>Clean Architecture 글을 읽다 보면 3~4개 레이어를 나누고 Port/Adapter를 정의하는 이상적인 구조가 나온다. 하지만 현실에서 모든 프로젝트에 이 구조를 적용하는 것은 과잉 설계(over-engineering)가 될 수 있다. Bespoyasov 본인도 이 비용을 인정한다 — "시간 소모, 과도한 코드량, 온보딩 어려움"이 있다고.</p>
<p>필자가 생각하는 실용적 기준은 다음과 같다.</p>
<h3 id="최소한-이것만은-하자"><a class="anchor" href="#최소한-이것만은-하자">최소한 이것만은 하자</a></h3>
<ol>
<li><strong>도메인 타입을 API 응답 타입과 분리</strong>한다. <code>interface</code>든 <code>type</code>이든, 프론트엔드가 사용하는 도메인 개념을 별도 파일로 정의한다.</li>
<li><strong>비즈니스 규칙이 포함된 로직은 컴포넌트 밖으로 뺀다.</strong> <code>domain/</code> 폴더가 아니어도 좋다. 중요한 것은 React에 의존하지 않는 순수 함수로 만드는 것이다.</li>
<li><strong>API 응답 → 도메인 모델 변환을 한 곳에서 한다.</strong> Mapper 함수든, Zod 스키마든, 그 한 곳만 수정하면 변경이 전파되지 않는 구조를 만든다.</li>
</ol>
<h3 id="프로젝트가-복잡해지면-추가로"><a class="anchor" href="#프로젝트가-복잡해지면-추가로">프로젝트가 복잡해지면 추가로</a></h3>
<ol start="4">
<li><strong>Bounded Context 단위로 폴더를 나눈다.</strong> 토스 프론트엔드 챕터에서도 "함께 변경되는 파일을 같은 디렉토리에 배치하라"는 원칙을 강조한다. 도메인 단위로 폴더를 나누면 import 경로가 도메인 경계를 자연스럽게 드러낸다.</li>
<li><strong>Use Case 계층을 도입한다.</strong> 도메인 로직의 조합이 복잡해지면, "소득 정보 조회 → 경비율 적용 → 공제 항목 계산 → 세액 산출 → 환급액 확정"이라는 시나리오를 하나의 함수로 묶는 Application 계층이 필요해진다.</li>
</ol>
<pre><code>src/
├── domains/
│   ├── tax/
│   │   ├── tax.ts                  # 세액 계산 도메인 (세율, 공제, 계산 파이프라인)
│   │   ├── tax.viewModel.ts        # 세액 표현 (금액 포맷, 구간 라벨)
│   │   ├── tax.test.ts             # 세액 계산 테스트
│   │   └── incomeMapper.ts         # 홈택스 API ↔ 도메인 변환
│   ├── filing/
│   │   ├── filing.ts               # 신고 상태 도메인 (상태 전이, 권한)
│   │   ├── filing.viewModel.ts     # 신고 표현 (상태 배지, 라벨)
│   │   ├── filing.test.ts
│   │   └── filingMapper.ts
│   └── deduction/
│       ├── deduction.ts            # 공제 항목 도메인 (자격 조건, 한도)
│       └── deduction.viewModel.ts
├── hooks/                           # React 의존 로직
├── components/                      # UI 컴포넌트
└── api/                             # API 호출
</code></pre>
<p>세금이라는 하나의 도메인 안에서도 <strong>세액 계산(tax)</strong>, <strong>신고 관리(filing)</strong>, **공제 항목(deduction)**이 각각 독립된 하위 도메인으로 분리된다. 세율이 바뀌어도 신고 상태 전이 로직은 영향을 받지 않고, 공제 항목이 추가되어도 신고서 제출 플로우는 그대로이다. 이것이 Bounded Context의 실전적 적용이다.</p>
<hr>
<h2 id="마무리"><a class="anchor" href="#마무리">마무리</a></h2>
<p>정리하면, <strong>도메인</strong>은 우리가 해결하려는 문제 영역이고, <strong>도메인 모델</strong>은 그 문제를 선택적으로 추상화한 개념 체계이며, <strong>도메인 오브젝트 모델</strong>은 그 개념 체계를 코드로 구현한 것이고, <strong>도메인 오브젝트</strong>는 그 구현 안의 개별 객체이다.</p>
<p>그리고 이 개념들을 프론트엔드에서 실천한다는 것은, 단순히 폴더를 나누는 것이 아니라 <strong>여러 겹의 경계를 의식적으로 판단하는 것</strong>이다. "이건 비즈니스 규칙인가, 표현 로직인가?" "이 데이터는 도메인 상태인가, UI 상태인가?" "이 함수의 응집은 충분한가?" — 이런 질문을 습관적으로 던지는 것만으로도 코드의 구조는 자연스럽게 좋아진다.</p>
<p>물론 모든 프로젝트에 Clean Architecture의 레이어를 다 갖출 필요는 없다. 단순한 CRUD 앱에 4개 레이어를 나누고 모든 도메인에 Factory 패턴을 적용하는 것은 배보다 배꼽이 더 큰 격이다. Class의 우아한 응집과 함수형의 실용적 유연함 사이에서 정답은 프로젝트의 복잡도와 팀의 컨텍스트가 결정한다.</p>
<p>정답은 없다. 하지만 적어도 **"도메인이 무엇인지 모르고 코드를 짜는 것"**과 <strong>"도메인을 인식하고, 경계를 판단하고, 의식적으로 분리하는 것"</strong> 사이에는 분명한 차이가 있다. 이 글을 읽는 독자 분들도 자신의 프로젝트에서 "여기서 도메인은 뭘까, 그리고 이 코드는 어디에 있어야 할까?"라는 질문을 한 번쯤 던져보시길 바란다.</p>
<hr>
<h3 id="참고-자료"><a class="anchor" href="#참고-자료">참고 자료</a></h3>
<ul>
<li>Eric Evans, <em>Domain-Driven Design: Tackling Complexity in the Heart of Software</em> (2003) — 도메인, 도메인 모델, Entity, Value Object 정의</li>
<li>Martin Fowler, <em>Patterns of Enterprise Application Architecture</em> (2002) — <a href="https://martinfowler.com/eaaCatalog/domainModel.html">Domain Model 패턴</a>, <a href="https://martinfowler.com/bliki/AnemicDomainModel.html">Anemic Domain Model</a>, <a href="https://martinfowler.com/bliki/EvansClassification.html">Evans Classification</a></li>
<li>Robert C. Martin, <em>Clean Architecture</em> (2017) — <a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html">The Clean Architecture</a></li>
<li>Code with Jason — <a href="https://www.codewithjason.com/difference-domains-domain-models-object-models-domain-objects/">Domains, Domain Models, Object Models, Domain Objects</a></li>
<li>Khalil Stemmler — <a href="https://khalilstemmler.com/articles/typescript-domain-driven-design/ddd-frontend/">Does DDD Belong on the Frontend?</a></li>
<li>Alex Bespoyasov — <a href="https://bespoyasov.me/blog/clean-architecture-on-frontend/">Clean Architecture on Frontend</a></li>
<li>Hudi — <a href="https://hudi.blog/domain-domain-model-domain-object/">혼란스러운 도메인, 도메인 모델, 도메인 객체 용어 정리</a></li>
<li>Roseline — <a href="https://roseline.oopy.io/dev/frontend-architecture-domain-modeling-strategy-for-ui-development">프론트엔드 아키텍처: UI 개발을 위한 도메인 모델링 전략</a></li>
<li>emewjin — <a href="https://emewjin.github.io/model/">프론트엔드 개발에서 서버 데이터를 모델로 관리한다는 것</a></li>
<li>우아한형제들 기술블로그 — <a href="https://techblog.woowahan.com/8942/">단위 테스트로 복잡한 도메인의 프론트 프로젝트 정복하기</a></li>
<li>토스 — <a href="https://frontend-fundamentals.com/">Frontend Fundamentals</a></li>
<li>토스인컴 — <a href="https://toss.tech/article/income-qa-e2e-automation">빠른 속도에서 품질을 지키기 위한 E2E 자동화 여정</a></li>
</ul>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>아키텍처</category>
            <category>DDD</category>
        </item>
        <item>
            <title><![CDATA[Toss Frontend Fundamentals 모의고사 2회차 리팩토링 후기]]></title>
            <link>https://hooninedev.com/260328</link>
            <guid isPermaLink="false">https://hooninedev.com/260328</guid>
            <pubDate>Sat, 28 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[이번 포스팅에서는 Toss Frontend Fundamentals 모의고사 2회차에 참여하며 진행한 리팩토링 경험에 대한 이야기를 해보려고 한다. 평소 코드 리뷰나 리팩토링에 관심이 있었던 필자는, 토스에서 공개한 Frontend Fundamentals 모의고사라는 흥미로운 형식의 과제를 진행하게되었다. 과제는 회의실 예약 앱이 주어지고, 이를 리팩토링하는...]]></description>
            <content:encoded><![CDATA[<p>이번 포스팅에서는 Toss Frontend Fundamentals 모의고사 2회차에 참여하며 진행한 리팩토링 경험에 대한 이야기를 해보려고 한다.</p>
<p>평소 코드 리뷰나 리팩토링에 관심이 있었던 필자는, 토스에서 공개한 Frontend Fundamentals 모의고사라는 흥미로운 형식의 과제를 진행하게되었다. 과제는 회의실 예약 앱이 주어지고, 이를 리팩토링하는 과제였다. 테스트 코드가 함께 제공되어 있어서, 리팩토링 과정에서 기능이 깨지지 않았는지 검증할 수 있는 안전망이 갖추어져 있었다.</p>
<p>결과적으로 2일동안 리팩토링을 진행했고, 그 과정에서 느낀 점들을 정리해보려 한다.</p>
<hr>
<h2 id="코드를-처음-마주했을-때"><a class="anchor" href="#코드를-처음-마주했을-때">코드를 처음 마주했을 때</a></h2>
<p>필자가 코드를 처음 열었을 때 가장 먼저 한 일은 <strong>테스트 스펙을 읽는 것</strong>이었다. 테스트 코드는 이 애플리케이션이 무엇을 해야 하는지를 가장 정직하게 알려주는 문서이기 때문이다. <code>App.easy.spec.tsx</code>와 <code>App.hard.spec.tsx</code>를 훑으며 애플리케이션의 전체 요구사항을 파악했다.</p>
<p>그다음으로 실제 코드를 살펴보았는데, 눈에 들어온 것은 두 개의 모놀리식 컴포넌트였다.</p>
<ul>
<li><code>ReservationStatusPage</code> 는 400여줄의 컴포넌트로 날짜 선택, 타임라인 시각화, 예약 상세 툴팁, 내 예약 목록, 취소 기능이 하나의 파일에 전부 들어가 있었다.</li>
<li><code>RoomBookingPage</code> 는 300여줄의 컴포넌트로 필터, 방 목록, 예약 생성 로직, URL 파라미터 동기화가 하나로 얽혀 있었다.</li>
</ul>
<p>필자는 코드를 읽으면서 "개선이 필요하다"는 판단 이전에, 먼저 <strong>코드의 특성을 분류</strong>하는 데 집중했다. 어떤 코드가 도메인 정보를 담고 있고, 어떤 코드가 유틸리티 성격이며, 어떤 코드가 순수한 UI 레이어인지를 구분하는 것이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 도메인 정보: 장비 라벨, 타임 슬롯 등 비즈니스 상수</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> EQUIPMENT_LABELS</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Record</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  tv: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'TV'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, whiteboard: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'화이트보드'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, video: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'화상장비'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, speaker: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'스피커'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 유틸리티: 날짜 포맷, 시간 변환</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> formatDate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">date</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Date</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> timeToMinutes</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">time</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 서버 상태: 인라인 useQuery, useMutation 호출</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">rooms</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [] } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useQuery</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'rooms'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">], getRooms);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">reservations</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [] } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useQuery</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'reservations'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, date], () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getReservations</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(date));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// UI + 비즈니스 로직 혼재: 필터링, 정렬, 충돌 감지가 JSX 사이에 산재</span></span></code></pre></figure>
<p>이런 특성 분류를 해두니, 어디부터 손을 대야 할지가 자연스럽게 보이기 시작했다. 각 코드 영역에 간단한 주석을 달아두며 개선 방향을 메모했다. (마치 지금 회사에 입사했을 때 jquery 기반 프로젝트를 마이그레이션 하는 것과 비슷한 기분이었다)</p>
<p>그렇다면 어디서부터 손을 대야 할까?</p>
<hr>
<h2 id="리팩토링-전략-수립"><a class="anchor" href="#리팩토링-전략-수립">리팩토링 전략 수립</a></h2>
<p>필자는 다음과 같은 순서로 리팩토링을 진행하기로 계획했다.</p>
<ol>
<li><strong>서버 코드 핸들링</strong> : query, mutation 분리</li>
<li><strong>도메인 로직 분리</strong> : Equipment, Room, Reservation 모델</li>
<li><strong>타입 선언</strong> : 도메인 모델 기반의 타입 체계 정리</li>
<li><strong>유틸 함수 분리</strong> : 날짜 포맷, 타임라인 계산 등</li>
<li><strong>UI 레이어 분리</strong> : 컴포넌트를 관심사별 호흡 단위로 나누기</li>
<li><strong>추상화 및 관심사 분리</strong> : 에러/로딩 처리, 쿼리 키 관리</li>
</ol>
<p>이 순서를 선택한 이유는 <strong>의존 방향의 바깥에서 안쪽으로</strong> 진행하기 위해서다. 인프라(서버 코드, 유틸)부터 정리하고, 도메인 모델을 확립한 뒤, 마지막으로 UI를 다듬는 것이다. 만약 UI 컴포넌트를 먼저 분리하면, 아직 정리되지 않은 도메인 로직과 쿼리 코드를 여러 컴포넌트에 걸쳐 옮겨 다녀야 하는 상황이 생길 수 있다.</p>
<p>전략을 세웠으니, 이제 하나씩 실행에 옮겨보자.</p>
<hr>
<h2 id="서버-코드와-유틸부터-정리하기"><a class="anchor" href="#서버-코드와-유틸부터-정리하기">서버 코드와 유틸부터 정리하기</a></h2>
<h3 id="날짜-표시-유틸-분리"><a class="anchor" href="#날짜-표시-유틸-분리">날짜 표시 유틸 분리</a></h3>
<p>가장 먼저 손을 댄 곳은 <code>formatDate</code> 함수였다. 두 페이지에서 동일한 함수가 각각 인라인으로 정의되어 있었기 때문이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// utils/formatYYYYMMDD.ts</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> formatYYYYMMDD</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">date</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Date</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> year</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> date.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getFullYear</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> month</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> String</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(date.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getMonth</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">padStart</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'0'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> date</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> String</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(date.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getDate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">padStart</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'0'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> `${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">year</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}-${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">month</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}-${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">date</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}`</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>작은 변경이지만, 리팩토링의 첫 커밋으로서 중요한 의미가 있었다. 가장 독립적이고 사이드이펙트가 적은 부분부터 건드려서, 테스트가 여전히 통과하는지 확인하는 <strong>워밍업</strong> 같은 것이다.</p>
<h3 id="react-query-훅-분리"><a class="anchor" href="#react-query-훅-분리">React Query 훅 분리</a></h3>
<p>다음으로 컴포넌트 내부에 직접 작성되어 있던 <code>useQuery</code>, <code>useMutation</code> 호출을 별도의 파일로 분리했다. <code>queryOptions</code> 패턴을 활용하여 쿼리 설정을 재사용 가능한 단위로 만들었다.</p>
<p>이 과정에서 <code>remotes.ts</code>에 있던 API 응답 타입도 명시적으로 정의했다. 기존에는 <code>any</code>로 흘러가던 타입들이 <code>GetRoomsResponse</code>, <code>GetReservationsResponse</code> 등으로 명확해졌다.</p>
<p>인프라 레이어를 정리했으니, 이제 도메인 모델로 눈을 돌려보자.</p>
<hr>
<h2 id="도메인-모델-분리"><a class="anchor" href="#도메인-모델-분리">도메인 모델 분리</a></h2>
<p>리팩토링에서 가장 중요한 전환점은 <strong>도메인 모델을 별도의 <code>models/</code> 디렉토리로 분리</strong>한 것이었다.</p>
<p>기존 코드에서는 <code>EQUIPMENT_LABELS</code>, <code>TIME_SLOTS</code> 같은 비즈니스 상수가 컴포넌트 파일 상단에 선언되어 있었다. <code>Room</code>이나 <code>Reservation</code>의 타입도 서버 핸들러(<code>_tosslib/server/types.ts</code>)에만 존재하고, 클라이언트 코드에서는 <code>any</code>에 가까운 상태로 사용되고 있었다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// models/equipment.ts</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> EQUIPMENT_LABELS</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  tv: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'TV'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, whiteboard: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'화이트보드'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, video: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'화상장비'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, speaker: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'스피커'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Equipment</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> keyof</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> typeof</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> EQUIPMENT_LABELS</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> ALL_EQUIPMENT</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Object.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">keys</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">EQUIPMENT_LABELS</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Equipment</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[];</span></span></code></pre></figure>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// models/reservation.ts</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Room</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  id</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  name</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  floor</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  capacity</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  equipment</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Equipment</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Reservation</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  id</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  roomId</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  date</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  start</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  end</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  attendees</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  equipment</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Equipment</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>도메인 모델 분리가 왜 중요한가? 비즈니스 로직이 UI 컴포넌트에 종속되어 있으면, 해당 로직을 변경할 때 컴포넌트의 렌더링 로직까지 함께 들여다봐야 한다. 반면 <code>models/</code> 디렉토리에 독립적으로 존재하면, 비즈니스 규칙의 변경이 UI와 분리된 채로 이루어질 수 있다. 물론 완벽한 분리란 현실적으로 어렵지만, 최소한 <strong>"이 로직은 여기 있을 것이다"라고 예측할 수 있는 구조</strong>를 만드는 것이 핵심이다.</p>
<p>도메인 모델을 분리했다면, 이제 UI는 얼마나 가벼워질 수 있을까?</p>
<hr>
<h2 id="컴포넌트-해체-작업"><a class="anchor" href="#컴포넌트-해체-작업">컴포넌트 해체 작업</a></h2>
<h3 id="reservationstatuspage"><a class="anchor" href="#reservationstatuspage">ReservationStatusPage</a></h3>
<p>이 작업이 가장 극적인 변화를 만들어낸 커밋이자 시간을 많이 소요했다. 385줄의 모놀리식 컴포넌트를 다음과 같이 분리했다.</p>
<pre><code>ReservationStatusPage/
├── index.tsx                    # 페이지 레벨
└── components/
    ├── DateSelector.tsx         # 날짜 선택 UI
    ├── ReservationTimeline.tsx  # 타임라인
    └── MyReservation.tsx        # 내 예약 목록 + 취소
</code></pre>
<p>분리 기준은 <strong>"이 코드가 독립적으로 의미를 가지는가"</strong> 였다. 타임라인 시각화는 날짜에 따른 예약 데이터를 받아 그리드를 그리는 독립적인 관심사다. 내 예약 목록은 사용자의 예약 데이터를 조회하고 취소하는 독립적인 관심사다. 이들이 한 파일에 있을 이유는 없었다.</p>
<p>분리 후 <code>index.tsx</code>는 <strong>조율자(orchestrator)</strong> 역할만 하게 되었다. 상태 관리, 메시지 표시, 하위 컴포넌트 조합을 담당할 뿐, 실제 데이터 페칭이나 렌더링 디테일은 하위 컴포넌트에 위임했다.</p>
<h3 id="roombookingpage"><a class="anchor" href="#roombookingpage">RoomBookingPage</a></h3>
<p>예약 페이지도 동일한 원칙으로 분리했다.</p>
<pre><code>RoomBookingPage/
├── index.tsx                    # 페이지 레벨
├── components/
│   ├── BookingFilter.tsx        # 날짜, 시간, 인원, 장비, 층 UI
│   └── AvailableRoomList.tsx    # 예약 가능 방 목록
└── hooks/
    └── useBookingParams.ts      # URL searchParams 기반 상태 관리
</code></pre>
<p>이 과정에서 한 가지 흥미로운 선택이 있었다. 초기에는 <code>react-hook-form</code> + <code>zod</code>를 도입해서 폼 유효성 검증을 시도했다. 그런데 최종적으로는 이를 제거하고 커스텀 훅 <code>useBookingParams</code>로 대체했다. 이 결정에 대해서는 뒤에서 더 자세히 이야기하겠다.</p>
<p>여기까지 읽으면 자연스럽게 떠오르는 질문이 하나 있다. 과연 어디까지 추상화해야 할까?</p>
<hr>
<h2 id="추상화의-적정선"><a class="anchor" href="#추상화의-적정선">추상화의 적정선</a></h2>
<p>이 섹션이 필자가 이번 모의고사에서 가장 많이 고민한 부분이다.</p>
<h3 id="중첩-조건문-어디까지-풀어야-할까"><a class="anchor" href="#중첩-조건문-어디까지-풀어야-할까">중첩 조건문, 어디까지 풀어야 할까</a></h3>
<p>방 예약 가능 여부를 판단하는 로직에는 여러 조건이 결합되어 있다. 수용 인원이 충분한지, 필요한 장비가 있는지, 선호 층수가 맞는지, 시간이 겹치지 않는지. 원본 코드에서는 이 모든 조건이 하나의 <code>filter</code> 콜백 안에 인라인으로 작성되어 있었다.</p>
<p>필자는 이를 <code>models/roomFilter.ts</code>로 추출하면서, 각 조건을 <strong>이름이 있는 함수</strong>로 분리했다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> isEnoughCapacity</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">room</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Room</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">attendees</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> room.capacity </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">>=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> attendees;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> hasRequiredEquipment</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">room</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Room</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">equipment</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Equipment</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[]) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  equipment.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">every</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">eq</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> room.equipment.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">includes</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(eq));</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> isOnPreferredFloor</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">room</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Room</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">floor</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  floor </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> ||</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> room.floor </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> floor;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> hasNoTimeConflict</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">room</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Room</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reservations</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Reservation</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[], </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">date</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">start</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">end</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  !</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">reservations.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">some</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reservation</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> reservation.roomId </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> room.id </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> reservation.date </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> date </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> reservation.start </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> end </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> reservation.end </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> start);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> filterAvailableRooms</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">rooms</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Room</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[], </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reservations</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Reservation</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[], </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">params</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Params</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Room</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[] {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> rooms</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">filter</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">room</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =></span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      isEnoughCapacity</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(room, params.attendees) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      hasRequiredEquipment</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(room, params.equipment) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      isOnPreferredFloor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(room, params.floor) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      hasNoTimeConflict</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(room, reservations, params.date, params.startTime, params.endTime)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    )</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">sort</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">a</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">b</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (a.floor </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> b.floor) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> a.floor </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> b.floor;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> a.name.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">localeCompare</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(b.name);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>여기서 핵심은 <strong>추상화할 수 있는 이름이 명확한 경우에만 함수로 분리</strong>했다는 점이다. <code>isEnoughCapacity</code>, <code>hasRequiredEquipment</code> 같은 이름은 구현을 보지 않아도 무엇을 하는지 예측할 수 있다. 만약 이름이 <code>processRoomConditions</code>처럼 모호해질 수밖에 없다면, 그 추상화는 오히려 읽는 사람에게 인지 부하를 줄 수 있다.</p>
<p>물론 이것이 정답이라는 의미는 아니다. 다만 필자의 판단 기준은 <strong>"이 함수 이름만 보고 동작을 예측할 수 있는가?"</strong> 였다. 예측 가능하다면 추상화하고, 그렇지 않다면 인라인으로 두는 것이 오히려 가독성에 도움이 된다고 생각했다.</p>
<h3 id="searchparams-vs-form-state"><a class="anchor" href="#searchparams-vs-form-state">searchParams vs form state</a></h3>
<p>예약 필터 상태를 어디에 둘 것인가도 상당히 고민한 부분이었다. 원본 코드에서는 <code>useState</code>로 각 필터 값을 관리하면서, <code>useEffect</code>로 URL searchParams와 동기화하고 있었다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 원본: useState + useEffect 동기화 방식</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">date</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setDate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(searchParams.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'date'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">||</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> formatDate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Date</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()));</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">startTime</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setStartTime</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(searchParams.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'startTime'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">||</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> ''</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// ... 6개의 개별 상태</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> params</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Record</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {};</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (date) params.date </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> date;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // ... 모든 상태를 searchParams에 동기화</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  setSearchParams</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(params, { replace: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, [date, startTime, endTime, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]);</span></span></code></pre></figure>
<p>필자는 처음에 <code>react-hook-form</code> + <code>zod</code>를 도입해서 폼으로 관리하는 방식을 시도했다. 하지만 결국 이를 제거하고 <strong>searchParams를 단일 진실 공급원(Single Source of Truth)으로</strong> 사용하는 <code>useBookingParams</code> 훅으로 대체했다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// useBookingParams: searchParams가 곧 상태</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useBookingParams</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">searchParams</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setSearchParams</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSearchParams</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> params</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useMemo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">BookingParams</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    date: searchParams.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'date'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">||</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> formatYYYYMMDD</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Date</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    startTime: searchParams.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'startTime'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">||</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> ''</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // ...</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }), [searchParams]);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> updateParam</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useCallback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">K</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> keyof</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> BookingParams</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">key</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> K</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> BookingParams</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">K</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    setSearchParams</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">prev</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">      // 기존 파라미터 병합 후 업데이트</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> result;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }, { replace: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, [setSearchParams]);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { params, updateParam };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이 결정의 핵심 근거는 <strong>"상태가 별개로 움직이는 것이 적합하지 않다"</strong> 는 판단이었다. <code>useState</code>와 <code>searchParams</code>가 각각 상태를 가지면, 동기화 시점에 따라 불일치가 발생할 수 있다. 반면 searchParams만을 상태로 사용하면, URL이 곧 애플리케이션 상태가 되어 동기화 문제 자체가 사라진다. 사용자가 URL을 공유하면 동일한 필터 상태가 재현되는 것은 덤이다.</p>
<p>다른 참가자의 후기에서도 비슷한 고민을 발견할 수 있었다. <strong>"URL searchParams를 단일 진실 공급원으로 통일했다", "개별 필터 props를 하나의 <code>filter</code> 객체로 통합하는 접근을 택했다."</strong> 표현 방식은 다르지만, <strong>"흩어진 상태를 하나의 개념으로 묶어야 한다"</strong> 는 문제 인식은 동일했다.</p>
<hr>
<h2 id="안정성"><a class="anchor" href="#안정성">안정성</a></h2>
<h3 id="suspense와-errorboundary"><a class="anchor" href="#suspense와-errorboundary">Suspense와 ErrorBoundary</a></h3>
<p>컴포넌트 구조가 확정된 후에 에러/로딩 처리를 추가했다. 순서가 중요한 이유는, 경계(Boundary)를 어디에 설정할지는 컴포넌트 트리가 결정된 후에야 판단할 수 있기 때문이다.</p>
<p><code>react-error-boundary</code> 라이브러리를 활용하여, 각 독립적인 데이터 페칭 단위마다 <code>ErrorBoundary</code>와 <code>Suspense</code>를 감쌌다. 타임라인이 실패해도 내 예약 목록은 정상적으로 보여야 하고, 그 반대도 마찬가지이기 때문이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* 각 영역이 독립적으로 에러/로딩을 처리 */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">ErrorBoundary</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FallbackComponent</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{ErrorFallback} </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resetKeys</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{[date]}></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Suspense</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> fallback</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Loading</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> message</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"예약 현황을 불러오는 중..."</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> />}></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">ReservationTimeline</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> date</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{date} /></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;/</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Suspense</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">ErrorBoundary</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">ErrorBoundary</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FallbackComponent</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{ErrorFallback}></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Suspense</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> fallback</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Loading</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> message</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"내 예약을 불러오는 중..."</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> />}></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">MyReservation</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> onCancel</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{handleCancel} /></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;/</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Suspense</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">ErrorBoundary</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span></code></pre></figure>
<h3 id="query-key-중앙-관리"><a class="anchor" href="#query-key-중앙-관리">Query Key 중앙 관리</a></h3>
<p>리팩토링 과정에서 쿼리 훅을 분리하다 보니, query key가 여러 파일에 분산되는 문제가 생겼다. mutation의 <code>onSuccess</code>에서 invalidation할 때 어떤 key를 써야 하는지 추적하기 어려워진 것이다.</p>
<p><code>@lukemorales/query-key-factory</code>를 도입하여 쿼리 키를 중앙에서 관리하도록 변경했다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// queries/queryKeys.ts</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> roomKeys</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createQueryKeys</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'rooms'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  list: { queryKey: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">queryFn</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> remotes.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getRooms</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> reservationKeys</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createQueryKeys</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'reservations'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  list</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">date</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({ queryKey: [date], </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">queryFn</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> remotes.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getReservations</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(date) }),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  my: { queryKey: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">queryFn</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> remotes.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getMyReservations</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span></code></pre></figure>
<p>이렇게 하면 <code>useSuspenseQueries({ queries: [roomKeys.list, reservationKeys.list(date)] })</code> 형태로 사용할 수 있어, 쿼리 키와 페칭 함수가 항상 함께 이동한다. 또한 route 경로도 <code>PATHS</code> 상수로 추출하여, 문자열 하드코딩을 제거했다.</p>
<hr>
<h2 id="출제자의-의도는-무엇이었을까"><a class="anchor" href="#출제자의-의도는-무엇이었을까">출제자의 의도는 무엇이었을까</a></h2>
<p>리팩토링을 마치고 나서, 필자는 한 발 물러서서 생각해보았다. 이 모의고사가 평가하고자 한 것은 무엇이었을까?</p>
<p>다른 참가자들의 후기를 읽으며 흥미로운 공통점을 발견했다. 거의 모든 후기에서 <strong>"코드는 읽는 것이 아니라 예측하는 것이다"</strong> 라는 문장이 등장했다. 우리의 뇌는 코드를 한 줄 한 줄 해석하는 것이 아니라, 경험에서 쌓인 패턴을 기반으로 예측하며 읽는다는 것이다. 그리고 그 예측이 어긋날 때 인지 부하가 급격히 증가한다.</p>
<p>이 관점에서 보면, 모의고사가 평가하는 것은 단순한 코딩 능력이 아니라 <strong>"동료가 읽을 코드를 얼마나 예측 가능하게 만들 수 있는가"</strong> 라는 협업 역량인 것이다. (출제자와 동료의 마음을 읽는 것이야말로 진정한 소프트웨어 엔지니어의 역량일지도 모른다)</p>
<p>다른 참가자들의 후기들을 살펴보니, <strong>"남이 작성한 코드를 이해하는 것은 쉽지 않다", "인터페이스를 먼저 설계하는 것이 중요하지만, 방대한 기존 코드 앞에서는 그 접근이 흔들릴 수 있다"</strong> 는 내용이 공감이 되었다. 필자 역시 비슷한 경험을 했다. 기존 코드가 이미 동작하고 있으면, 그 구조를 합리화하려는 유혹이 생긴다. "이미 돌아가는 코드인데 굳이?"라는 생각 말이다. 하지만 모의고사의 핵심은 바로 그 유혹을 넘어서, <strong>"이 코드를 내가 아닌 다른 누군가가 읽었을 때 얼마나 빨리 파악할 수 있는가, 본인의 인지로 문제를 판단하고 해결해나갈 수 있는지"</strong> 를 기준으로 판단하는 것이었다.</p>
<hr>
<h2 id="리팩토링에서-배운-것"><a class="anchor" href="#리팩토링에서-배운-것">리팩토링에서 배운 것</a></h2>
<p><strong>리팩토링 순서가 결과를 좌우한다.</strong> 바깥(인프라)에서 안쪽(UI)으로 진행하는 것이 중간에 꼬이지 않는 안전한 경로였다. 유틸과 도메인 모델이 정리된 상태에서 컴포넌트를 분리하니, 각 컴포넌트가 무엇에 의존하는지가 명확했다.</p>
<p><strong>추상화의 판단 기준은 "이름"이다.</strong> 함수나 변수로 추출했을 때 그 이름이 동작을 설명할 수 있다면 추상화할 가치가 있다. 이름이 모호해질 수밖에 없다면, 인라인이 오히려 나은 선택일 수 있다.</p>
<p><strong>상태의 위치가 곧 아키텍처다.</strong> 같이 이동해야 하는 상태는 같은 곳에 두어야 한다. <code>useState</code>와 <code>searchParams</code>를 동기화하는 것보다, searchParams 하나만을 진실 공급원으로 사용하는 것이 구조적으로 더 건강하다.</p>
<hr>
<h2 id="마무리"><a class="anchor" href="#마무리">마무리</a></h2>
<p>과제를 마치고 2명의 동료와 이야기를 나눠보았다. 혼자 코드를 들여다볼 때는 보이지 않던 것들이, 대화를 통해 생각을 풀어나가는 과정에서 드러나기 시작했다. 필자가 당연하다고 넘겼던 구조적 선택에 "왜 그렇게 했어?"라는 질문이 들어오는 순간, 미처 의식하지 못했던 판단의 빈틈이 보이는 것이다.</p>
<p>AI가 코드 작성과 리뷰에 드는 시간을 획기적으로 줄여주고 있는 것은 사실이다. 하지만 그럼에도 코드 리뷰와 데일리 미팅이 여전히 중요하다고 생각하는 이유가 바로 이런 경험에 있다. AI는 코드의 정합성을 검증해줄 수 있지만, <strong>"네가 놓친 관점은 이거야"</strong> 라고 짚어주는 것은 결국 같은 맥락을 공유하는 동료의 몫이다. 내가 보지 못한 부분에 대한 발견, 그리고 그 발견을 통한 프로덕트의 안정화. 이것이 협업의 본질이 아닐까.</p>
<p>문제를 풀어나가는 과정속에 코드를 작성에는 정답은 없다. 같은 모의고사를 풀었던 다른 참가자들도 각자 다른 경로를 택했고, 각자의 근거를 가지고 있었다. 중요한 것은 <strong>"왜 이렇게 짰는가"에 대해 설명할 수 있는 것</strong>이다. 이 글을 읽는 분들도 한 번쯤 자신의 코드를 처음 보는 사람의 시선으로 바라보기를 권한다. 그 시선 하나가 코드의 품질을 결정하는 가장 강력한 기준이 될 수 있을 것이다.</p>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>React</category>
            <category>리팩토링</category>
        </item>
        <item>
            <title><![CDATA[추상화]]></title>
            <link>https://hooninedev.com/260201</link>
            <guid isPermaLink="false">https://hooninedev.com/260201</guid>
            <pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[이번 포스팅에서는 프로그래밍에서의 추상화, 그리고 추상화 관점에서 좋은 코드를 작성하는 방법에 대한 이야기를 해보려고 한다. 필자는 프론트엔드 개발을 하면서 "이 로직을 어디까지 분리해야 하지?", "이 컴포넌트를 어떤 단위로 쪼개야 하지?"라는 고민을 수도 없이 해왔다. 처음에는 단순히 공통된 부분을 뽑아내면 그게 추상화라고 생각했다. 반복되는 코드를 함...]]></description>
            <content:encoded><![CDATA[<p>이번 포스팅에서는 프로그래밍에서의 추상화, 그리고 추상화 관점에서 좋은 코드를 작성하는 방법에 대한 이야기를 해보려고 한다.</p>
<p>필자는 프론트엔드 개발을 하면서 "이 로직을 어디까지 분리해야 하지?", "이 컴포넌트를 어떤 단위로 쪼개야 하지?"라는 고민을 수도 없이 해왔다. 처음에는 단순히 공통된 부분을 뽑아내면 그게 추상화라고 생각했다. 반복되는 코드를 함수로 만들고, 비슷한 컴포넌트의 공통점을 추출해서 하나로 합치는 것. 그런데 이렇게 만들어진 코드가 시간이 지나면서 오히려 손대기 어려운 괴물이 되어가는 경험을 몇 번 하고 나니, 추상화라는 것에 대해 다시 생각해보게 되었다.</p>
<p>이 글에서는 추상화의 본질이 무엇인지, 그리고 프론트엔드 개발에서 추상화를 어떻게 활용해야 좋은 코드를 만들 수 있는지에 대해 필자가 고민해온 내용들을 정리해보려 한다.</p>
<hr>
<h2 id="추상과-추상화-이-단어가-의미하는-것"><a class="anchor" href="#추상과-추상화-이-단어가-의미하는-것">추상과 추상화, 이 단어가 의미하는 것</a></h2>
<p>본격적인 이야기에 앞서, "추상"과 "추상화"라는 단어가 프로그래밍에서 정확히 무엇을 의미하는지 짚고 넘어가려 한다. 이 두 단어는 비슷해 보이지만 성격이 꽤 다르다.</p>
<p>**추상(Abstract)**은 상태이자 속성이다. "이것은 추상적이다"라고 말할 때, 그것은 구체적인 세부사항이 생략되어 있고 핵심적인 개념만 남아있는 상태를 뜻한다. Java나 TypeScript에서 <code>abstract</code> 키워드가 붙은 클래스나 메서드가 바로 이런 의미이다. 아직 구체적인 구현이 채워지지 않은, 본질적인 형태만 정의된 미완성의 설계도인 것이다.</p>
<p>**추상화(Abstraction)**는 과정이자 행위이다. 복잡한 대상에서 핵심적인 특징만 남기고 불필요한 세부사항을 제거하여 단순화하는 과정 자체를 말한다. 중요한 것은, 추상화는 "대충 뭉뚱그리는 것"이 아니라 <strong>각 수준에서 정확하게 역할을 정의하는 행위</strong>라는 점이다.</p>
<p>일상에서 "추상적"이라는 말은 흔히 "모호하다"는 뉘앙스로 쓰인다. 하지만 프로그래밍에서의 추상화는 정반대이다. Edsger Dijkstra는 이렇게 말한 적이 있다.</p>
<blockquote>
<p>"추상화의 목적은 모호해지는 것이 아니라, 절대적으로 정확할 수 있는 새로운 의미 수준을 만드는 것이다."</p>
</blockquote>
<p>MIT의 John V. Guttag도 비슷한 맥락에서 이렇게 정의했다.</p>
<blockquote>
<p>"추상화의 본질은 주어진 맥락에서 관련 있는 정보를 보존하고, 관련 없는 정보를 잊는 것이다."</p>
</blockquote>
<p>결국 추상과 추상화를 구분해서 이해하면 이렇다. 추상은 "핵심만 남은 상태"이고, 추상화는 "핵심만 남기는 과정"이다. 우리가 코드를 설계할 때 하는 일은 바로 이 추상화, 즉 복잡한 구현에서 핵심적인 인터페이스만 남기고 나머지를 감추는 과정인 것이다.</p>
<p>그렇다면 이런 추상화가 프로그래밍에서 왜 필요한 걸까?</p>
<hr>
<h2 id="추상화는-왜-필요한가"><a class="anchor" href="#추상화는-왜-필요한가">추상화는 왜 필요한가</a></h2>
<p>추상화가 프로그래밍에서 필요한 근본적인 이유는 의외로 단순하다. <strong>더 복잡한 것을 만들기 위해서</strong>이다.</p>
<p>우리가 매일 사용하는 React만 해도 그렇다. 컴포넌트 하나를 렌더링하기 위해 내부적으로는 Virtual DOM 생성, 재조정(Reconciliation), 실제 DOM 조작이라는 복잡한 과정이 일어난다. 하지만 우리는 그런 걸 신경 쓰지 않고 JSX를 작성하면 된다. React가 이 복잡한 과정을 추상화해주었기 때문이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 이 한 줄 뒤에는 VDOM 생성, diffing, DOM 패치라는 복잡한 과정이 숨어있다.</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 하지만 우리는 그걸 몰라도 UI를 만들 수 있다.</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">UserProfile</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> name</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"jihoon"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> /></span></span></code></pre></figure>
<p>예전에는 Webpack을 직접 설정하는 것이 프론트엔드 개발자의 일상이었지만, 지금은 Next.js나 Vite 같은 프레임워크가 번들링 설정을 추상화해버렸다. 덕분에 우리는 번들러의 내부 동작을 몰라도 애플리케이션을 개발할 수 있게 되었고, 그 시간에 비즈니스 로직이나 사용자 경험 같은 더 고차원적인 문제에 집중할 수 있게 되었다.</p>
<p>결국 추상화의 핵심 가치는 이것이다. 복잡한 것을 감추어 단순하게 보이도록 만들고, 각자가 자신의 영역에만 집중할 수 있게 해주는 것. 이 덕분에 우리는 혼자서 모든 것을 이해하지 않아도 점점 더 거대하고 복잡한 소프트웨어를 만들어낼 수 있게 된 것이다.</p>
<p>그렇다면 추상화가 이렇게 좋은 것이라면, 무조건 많이 하면 좋은 걸까?</p>
<hr>
<h2 id="추상화의-본질은-맥락을-줄여주는-것이다"><a class="anchor" href="#추상화의-본질은-맥락을-줄여주는-것이다">추상화의 본질은 맥락을 줄여주는 것이다</a></h2>
<p>많은 개발자들이 추상화를 "공통된 부분을 추려내는 것"이라고 인식하고 있다. 틀린 말은 아니지만, 이것은 추상화를 수행하는 기법 중 하나일 뿐 추상화의 본질을 설명해주지는 못한다.</p>
<p>필자가 생각하는 추상화의 본질은 **"코드를 읽는 사람이 알아야 할 맥락을 적절한 수준으로 줄여주는 것"**이다.</p>
<p>간단한 예시를 보자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Order</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">id</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">status</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "pending"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "completed"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">amount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> orders</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Order</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  { id: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"a"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, status: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"pending"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, amount: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  { id: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"b"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, status: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"completed"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, amount: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">5000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  { id: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"c"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, status: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"completed"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, amount: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">8000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">];</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> total </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">for</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> i </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; i </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> orders.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">length</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; i</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">++</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (orders[i].status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "completed"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    total </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> orders[i].amount;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이 코드를 읽는 개발자는 반복문의 초기화·조건·증감을 파악하고, 인덱스로 원소에 접근하고, 조건을 확인해 분기하고, 외부의 누적 변수가 어떻게 갱신되는지까지 전부 머릿속에 담아야 한다. 정작 이 코드가 하려는 일은 **"완료된 주문들의 합계를 구한다"**는 한 문장인데, 그 한 문장을 이해하기 위해 네 가지 다른 맥락을 동시에 끌어안고 있는 셈이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> total</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> orders</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">filter</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">order</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> order.status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "completed"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">reduce</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">sum</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">order</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> sum </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> order.amount, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span></code></pre></figure>
<p><code>filter</code>와 <code>reduce</code>라는 추상화 덕분에 개발자는 "완료된 주문만 골라낸다"와 "금액을 누적한다"는 두 가지 의도만 따라가면 된다. 인덱스 관리, 누적 변수의 선언과 갱신이라는 맥락은 코드 표면에서 사라졌다.</p>
<p>여기서 한 걸음 더 나아갈 수도 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> total</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> sumCompletedOrders</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(orders);</span></span></code></pre></figure>
<p>이제 코드를 읽는 사람은 이 계산이 배열 순회를 통해 이루어진다는 사실조차 알 필요가 없다. "완료된 주문들의 총액을 구한다"는 비즈니스 의도만 남아있을 뿐이다. 코드가 <strong>어떻게(How) 계산되는지</strong>가 아니라 <strong>무엇을(What) 계산하는지</strong>에 집중할 수 있게 된 것이다.</p>
<p>이 관점에서 보면, 우리가 매일 작성하는 React 코드도 수많은 추상화의 조합이라는 사실을 알 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { css } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "@emotion/css"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { format } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "date-fns"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TodayHeader</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> now</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Date</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// Date 객체 생성이라는 복잡한 과정을 추상화</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> formatted</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> format</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(now, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"yyyy-MM-dd"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 날짜 포맷팅 로직을 추상화</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">h1</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      className</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">css</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">`</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">        font-size: 1.8rem;</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">      `</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    ></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      {</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">" "</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      {</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* CSS-in-JS 처리 과정을 추상화 */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      Today is {formatted}</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">h1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// React.createElement를 추상화</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 그리고 위 모든 것을 다시 추상화</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">TodayHeader</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> />;</span></span></code></pre></figure>
<p>만약 <code>emotion</code>, <code>date-fns</code>, <code>react</code>의 내부 코드가 전부 이 컴포넌트 파일에 펼쳐져 있다면 어떨까? 어디서부터 읽어야 할지, 어떤 부분이 비즈니스 로직이고 어떤 부분이 라이브러리 코드인지 구분하기 어려울 것이다. 추상화가 각 영역의 맥락을 적절히 감춰주었기 때문에 우리는 "오늘 날짜를 보여준다"는 본질에만 집중할 수 있는 것이다.</p>
<p>그렇다면 실제로 코드를 설계할 때, 추상화를 어떤 방향으로 접근해야 할까?</p>
<hr>
<h2 id="추상화-수준이-높다-낮다는-것"><a class="anchor" href="#추상화-수준이-높다-낮다는-것">추상화 수준이 높다, 낮다는 것</a></h2>
<p>추상화를 이야기할 때 빠질 수 없는 개념이 **추상화 수준(Level of Abstraction)**이다. 코드의 추상화 수준이 "높다" 혹은 "낮다"는 것은 도대체 무슨 뜻일까?</p>
<p><strong>추상화 수준이 낮은 코드</strong>는 컴퓨터가 수행하는 구체적인 절차에 가까운 코드이다. 문자열을 직접 파싱하고, 인덱스로 배열을 순회하고, 바이트를 조작하는 것. <strong>어떻게(How)</strong> 동작하는지가 적나라하게 드러나 있다.</p>
<p><strong>추상화 수준이 높은 코드</strong>는 비즈니스 도메인이나 문제 영역의 언어로 표현된 코드이다. <code>checkout(cart)</code>, <code>approveRequest(id)</code>, <code>publishArticle(draft)</code> 같은 것. <strong>무엇을(What)</strong> 하는지가 드러나 있고, 어떻게 하는지는 감춰져 있다.</p>
<p>Robert C. Martin은 <em>Clean Code</em>에서 이 개념을 **"함수당 하나의 추상화 수준(One Level of Abstraction per Function)"**이라는 원칙으로 정리했다. 하나의 함수 안에 높은 수준과 낮은 수준의 코드가 섞여 있으면, 읽는 사람은 "이게 핵심 로직인가, 아니면 세부 구현인가?"를 매 줄마다 판단해야 하기 때문이다.</p>
<p>실제 코드로 보면 이 문제가 명확해진다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 추상화 수준이 뒤섞인 함수</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">async</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> registerUser</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">name</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">email</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">password</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 높은 수준: 입력 검증</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">name </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">||</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> !</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">email) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">throw</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Invalid input"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 낮은 수준: 이메일 정규식 직접 검사</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> emailRegex</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> /</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">^</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">[A-Za-z0-9+_.-]</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#DBEDFF;--shiki-light:#032F62">@(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">.</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#DBEDFF;--shiki-light:#032F62">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">$</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">emailRegex.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">test</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(email)) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">throw</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Invalid email"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 낮은 수준: 비밀번호 해싱 직접 구현</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> encoder</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TextEncoder</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> data</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> encoder.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">encode</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(password);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> hashBuffer</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> crypto.subtle.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">digest</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"SHA-256"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, data);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> hashedPassword</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> btoa</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    String.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">fromCharCode</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Uint8Array</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(hashBuffer)),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 높은 수준: 사용자 저장</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> user</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { name, email, password: hashedPassword };</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  await</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> userRepository.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">save</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(user);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 높은 수준: 환영 이메일 발송</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  await</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> sendWelcomeEmail</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(user);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이 함수를 읽는 사람은 "사용자 등록 흐름"이라는 높은 수준의 맥락을 따라가다가, 갑자기 정규식 패턴과 해시 버퍼 조작이라는 낮은 수준의 맥락으로 끌려 내려간다. 그러다 다시 <code>userRepository.save</code>라는 높은 수준으로 점프한다. 이렇게 추상화 수준이 오르락내리락하면, 읽는 사람의 머릿속도 함께 오르락내리락하게 된다.</p>
<p>같은 함수를 추상화 수준을 맞춰서 다시 작성하면 이렇게 된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 추상화 수준이 일관된 함수</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">async</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> registerUser</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">name</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">email</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">password</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  validateInput</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(name, email);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> hashedPassword</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> hashPassword</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(password);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> user</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createUser</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(name, email, hashedPassword);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  await</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> userRepository.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">save</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(user);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  await</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> notificationService.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">sendWelcomeEmail</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(user);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>모든 문장이 같은 높이의 추상화 수준에서 이야기하고 있다. 이메일 검증이 어떤 정규식을 쓰는지, 비밀번호 해싱이 어떤 알고리즘을 쓰는지는 각각의 하위 함수가 책임진다. 이 함수를 읽는 사람은 오직 "사용자 등록의 전체 흐름"이라는 하나의 맥락에만 집중하면 된다.</p>
<p>Martin은 이것을 **"내리막 규칙(The Stepdown Rule)"**이라고도 부른다. 코드를 위에서 아래로 읽을 때, 마치 신문 기사처럼 위에는 큰 그림이, 아래로 내려갈수록 세부사항이 나와야 한다는 것이다.</p>
<p>Kent Beck도 <em>Smalltalk Best Practice Patterns</em>에서 동일한 원칙을 <strong>Composed Method 패턴</strong>으로 제시했다.</p>
<blockquote>
<p>"프로그램을 하나의 식별 가능한 작업을 수행하는 메서드들로 나누고, 하나의 메서드 안의 모든 연산은 같은 추상화 수준에 있어야 한다."</p>
</blockquote>
<p>결국 이 모든 이야기가 하나로 귀결된다. <strong>하나의 함수는 하나의 추상화 수준에서만 이야기해야 한다.</strong> 이것만 지켜도 코드의 가독성은 눈에 띄게 달라진다.</p>
<p>그렇다면 추상화의 방향은 어떻게 잡아야 할까? 구체적인 것에서 시작해야 할까, 추상적인 것에서 시작해야 할까?</p>
<hr>
<h2 id="공통점-추출이-아닌-부품의-조립으로-생각하기"><a class="anchor" href="#공통점-추출이-아닌-부품의-조립으로-생각하기">공통점 추출이 아닌, 부품의 조립으로 생각하기</a></h2>
<p>OOP에서 흔히 말하는 "구체적인 것들의 공통점을 뽑아내어 추상적인 것을 정의하라"는 가이드라인이 있다. 이 접근법 자체가 틀린 것은 아니지만, 필자는 이 방식에 너무 얽매이면 현재의 요구사항에 갇힌 설계를 만들어낼 위험이 있다고 생각한다.</p>
<p>예를 들어보자. 요구사항에 A, B, C라는 버튼이 있고, 세 버튼 모두 파란색 둥근 모양이며 차이점은 라벨 텍스트뿐이라고 하자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 공통점을 뽑아낸 설계</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> BlueRoundButton</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({ </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">label</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">label</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> className</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"blue round"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>{label}&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre></figure>
<p>현재 요구사항은 완벽하게 충족된다. 그런데 며칠 뒤 기획자가 말한다.</p>
<blockquote>
<p>"B 버튼의 색상을 변경할 수 있게 해주세요."</p>
</blockquote>
<p>이 순간 <code>BlueRoundButton</code>이라는 이름부터 어색해진다. 색상 prop을 추가하면 되긴 하지만, 애초에 "파란색 둥근 버튼"이라는 구체적인 공통점에서 출발한 설계가 변경에 취약했던 것이다. (이 정도는 양반이다. 현실에서는 "버튼을 둥글게 말고 네모로 바꿔주세요"가 날아온다)</p>
<p>이런 상황이 반복되다 보면 자연스럽게 깨닫게 되는 것이 있다. <strong>구체적인 요구사항에서 공통점을 뽑아내는 방식은, 추상화된 결과물마저도 현재의 요구사항만을 반영하게 되기 쉽다</strong>는 것이다.</p>
<p>그래서 필자가 선호하는 접근은 반대 방향이다. 구체적인 것에서 추상을 뽑아내는 것이 아니라, <strong>추상적인 부품들을 먼저 생각하고 이것들을 조립하여 구체적인 것을 만드는 방식</strong>이다.</p>
<p>토스트 알림 컴포넌트를 만든다고 생각해보자. 처음 요구사항은 단순하다. "저장이 완료되면 하단에 짧은 안내 메시지를 띄워주세요." 공통점 추출 방식으로 접근하면 이렇게 된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 현재 요구사항의 공통 특성에서 출발한 설계</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ToastProps</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  message</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  hasAction</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  actionLabel</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  onAction</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Toast</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({ </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">message</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">hasAction</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">actionLabel</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">onAction</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ToastProps</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> className</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"toast"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">span</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>{message}&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">span</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    {hasAction </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{onAction}>{actionLabel}&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>}</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span></code></pre></figure>
<p>현재 요구는 완벽히 다뤄진다. 그런데 며칠 뒤 기획자가 "성공 / 경고에 따라 왼쪽에 아이콘을 넣어주세요"라고 말한다. <code>hasIcon</code>, <code>iconName</code>이 추가된다. 곧이어 "업로드 진행률 바가 붙은 토스트도 필요해요"라는 요구가 날아온다. <code>progress</code> prop이 또 하나 늘어난다. 이 과정을 몇 번 반복하고 나면 <code>Toast</code>는 열 개가 넘는 props를 가진 채, <strong>이 조합은 되고 저 조합은 안 되는</strong> 숨겨진 규칙까지 암기해야 하는 컴포넌트가 된다. (그리고 대개 그 규칙은 주석으로도 남지 않는다)</p>
<p>애초에 **"토스트는 이런 모양이어야 한다"는 현재 시점의 구체적 상(像)**에서 출발한 설계가 변경에 취약했던 것이다.</p>
<p>부품 조립 방식으로 접근하면 이야기가 달라진다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 부품을 조립하는 설계</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Toast</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({ </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">children</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PropsWithChildren</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> className</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"toast"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>{children}&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Toast.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Icon</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({ </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">name</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">name</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "check"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "warn"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "info"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  /* ... */</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Toast.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Message</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({ </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">children</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PropsWithChildren</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">span</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>{children}&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">span</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Toast.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Action</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  children,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  onClick,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PropsWithChildren</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;{ </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }>) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{onClick}>{children}&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Toast.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Progress</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({ </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  /* ... */</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 필요한 부품만 골라 조립한다</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast.Message</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>저장되었습니다&#x3C;/</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast.Message</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast.Icon</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> name</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"check"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> /></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast.Message</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>업로드 완료&#x3C;/</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast.Message</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast.Action</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{undo}>실행 취소&#x3C;/</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast.Action</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast.Progress</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0.4</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} /></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast.Message</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>파일 전송 중...&#x3C;/</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast.Message</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Toast</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span></code></pre></figure>
<p>"토스트는 무언가를 담는 얕은 컨테이너"라는 변하지 않는 본질과, "그 안에 무엇이 담기는가"라는 변하기 쉬운 구체가 분리되었다. 이제 <code>Toast</code> 내부를 건드리지 않고도 새로운 부품을 얼마든지 추가하거나, 기존 부품을 새로운 조합으로 배치할 수 있다. prop 조합의 유효성 규칙도 사라진다. 단순히 <strong>넣고 싶은 것을 넣으면 되는 것</strong>이다.</p>
<p>물론 경험이 많은 개발자라면 "처음부터 IoC로 설계하면 되는 거 아니냐"고 할 수 있다. 맞는 말이다. 하지만 그런 판단이 가능한 이유는 과거에 수많은 삽질을 통해 "어디가 변경되기 쉬운 부분인지"에 대한 감각이 쌓였기 때문이다. (쉽게 말해 짬바다)</p>
<p>이런 감각이 아직 부족한 상황이라면, "이 기능은 어떤 부품들로 이루어져 있고, 각 부품은 어떻게 조립되어야 하는가"라는 질문에서 출발하는 것이 변경에 열린 설계를 만들어내기 훨씬 수월하다.</p>
<p>여기까지 읽으면 자연스럽게 떠오르는 질문이 하나 있다. 그렇다면 부품을 어떤 기준으로 나누고, 외부에 어떻게 표현해야 할까?</p>
<hr>
<h2 id="좋은-추상화를-위한-세-가지-원칙"><a class="anchor" href="#좋은-추상화를-위한-세-가지-원칙">좋은 추상화를 위한 세 가지 원칙</a></h2>
<h3 id="1-표현에-대해-고민하기"><a class="anchor" href="#1-표현에-대해-고민하기">1. 표현에 대해 고민하기</a></h3>
<p>추상화된 모듈의 가장 중요한 덕목은, 소스코드를 까보지 않아도 동작을 유추할 수 있어야 한다는 것이다. Kent Beck은 이것을 <strong>"의도를 드러내는 이름(Intention-Revealing Name)"</strong> 패턴이라고 불렀고, 이름을 간결하게 지을 수 없다면 추상화 자체를 다시 고민해봐야 한다고 말했다.</p>
<p>우리가 이를 위해 사용할 수 있는 도구는 크게 두 가지다. <strong>이름</strong>과 <strong>타입</strong>이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 이름과 타입만으로 동작을 유추할 수 있는 함수</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> addDays</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">date</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Date</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">amount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Date</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 도대체 뭘 하는 건지 알 수 없는 함수</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> add</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">a</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">b</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span></code></pre></figure>
<p><code>addDays</code>는 이름만 봐도 날짜에 일수를 더한다는 것을 알 수 있고, <code>Date</code>를 받아서 <code>Date</code>를 반환한다는 타입 정보가 이를 뒷받침한다. 내부에서 <code>Date.prototype.getDate</code>를 어떻게 다루는지 같은 구현 세부사항은 몰라도 된다.</p>
<p>반면 <code>add(a: any, b: any): any</code>는? 뭘 더하는 건지, 결과가 뭔지 전혀 감을 잡을 수 없다. 결국 소스코드를 까봐야만 사용할 수 있게 되고, 추상화의 장점을 잃어버리는 것이다.</p>
<p>이때 이름을 짓는 방식 자체가 추상화 수준을 반영한다는 점에 주목할 필요가 있다. 프로그래밍에서 함수 이름은 대체로 <strong>동사 + 명사</strong> 조합으로 이루어지는데, 어떤 동사를 선택하느냐에 따라 그 함수가 어느 수준의 추상화에서 동작하는지가 드러난다.</p>
<p>추상화 수준이 <strong>낮은</strong> 쪽에서 자주 쓰이는 동사들이 있다. <code>parse</code>, <code>encode</code>, <code>decode</code>, <code>serialize</code>, <code>read</code>, <code>write</code>, <code>push</code>, <code>pop</code> 같은 것들이다. 이 단어들은 데이터의 물리적인 변환이나 자료구조의 직접적인 조작을 암시한다.</p>
<p>중간 수준에서는 <code>fetch</code>, <code>save</code>, <code>load</code>, <code>validate</code>, <code>convert</code>, <code>transform</code> 같은 동사들이 등장한다. 기술적인 동작이지만, 그 동작의 의도가 어느 정도 드러나는 수준이다.</p>
<p>추상화 수준이 <strong>높은</strong> 쪽에서는 <code>register</code>, <code>checkout</code>, <code>approve</code>, <code>publish</code>, <code>submit</code> 같은 동사들이 쓰인다. 이 단어들은 비즈니스 도메인의 언어이다. 내부에서 어떤 기술적 절차가 일어나는지는 전혀 드러나지 않고, <strong>사용자의 행위나 비즈니스 프로세스</strong>만이 표현된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 낮은 수준: 기술적 동작이 드러남</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> parseJSON</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">text</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> object</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> encodeBase64</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Uint8Array</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 중간 수준: 의도가 드러나되 기술적 맥락이 남아있음</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> fetchUserById</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">id</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">User</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> validateEmail</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">email</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 높은 수준: 비즈니스 의도만 드러남</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> registerUser</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">form</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> RegistrationForm</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">User</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> approveLeaveRequest</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">requestId</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span></code></pre></figure>
<p>Robert C. Martin은 <em>Clean Code</em>에서 이에 대해 **"짧고 수수께끼 같은 이름보다 길고 서술적인 이름이 낫다"**고 말했다. 또한 **"하나의 개념에는 하나의 단어를 사용하라"**는 원칙도 제시했는데, 같은 맥락의 동작에 <code>fetch</code>, <code>retrieve</code>, <code>get</code>을 혼용하면 읽는 사람이 "이 셋이 다른 동작인가?"라고 혼란을 겪게 되기 때문이다.</p>
<p>이 원칙은 React 컴포넌트와 훅의 네이밍에도 그대로 적용된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 컴포넌트: 추상화 수준에 따라 이름의 구체성이 달라진다</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Button</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> />                </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 낮은 수준: 범용 UI 프리미티브</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">SearchInput</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> />           </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 중간 수준: UI + 맥락이 결합</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">SubmitOrderButton</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> />     </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 높은 수준: 비즈니스 의도가 이름에 담김</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 이벤트 핸들러: on*은 선언적 인터페이스, handle*은 구현</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Form</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> onSubmit</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{handleSubmit} />           </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// on = 외부에 노출하는 이름</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> handleSubmit</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FormData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// handle = 내부 구현의 이름</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  await</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> registerUser</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(data);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 훅: use + 목적을 나타내는 이름</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> user</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useAuth</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();                    </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 인증 상태를 관리하는 훅</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">items</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setItems</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useCartItems</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();  </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 장바구니 항목을 관리하는 훅</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">isOpen</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">toggle</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useModal</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();     </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 모달 상태를 관리하는 훅</span></span></code></pre></figure>
<p>Jeff Atwood는 <em>Coding Horror</em>에서 <code>Manager</code>라는 접미사의 문제를 지적한 적이 있다. <code>UrlManager</code>라는 이름은 URL을 풀링하는 건지, 검증하는 건지, 생성하는 건지 전혀 알 수 없다. <code>UrlBuilder</code>, <code>UrlValidator</code>, <code>UrlPool</code>처럼 구체적인 역할을 드러내는 이름이 훨씬 낫다. 이름이 모호하다는 것은, 그 모듈의 책임 자체가 모호하다는 신호일 수 있는 것이다.</p>
<p>결국 좋은 이름이란 <strong>그 코드가 어떤 추상화 수준에서 동작하는지를 읽는 사람에게 즉시 알려주는 이름</strong>이다.</p>
<h3 id="2-입력의-자유도를-의도적으로-설계하기"><a class="anchor" href="#2-입력의-자유도를-의도적으로-설계하기">2. 입력의 자유도를 의도적으로 설계하기</a></h3>
<p>추상화된 모듈을 설계할 때 자주 마주치는 고민이 있다. "기능을 어디까지 열어줄 것인가?"이다. 이 결정에 따라 모듈을 사용하는 개발자의 경험이 크게 달라진다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 기능이 닫힌 컴포넌트</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Button</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({ </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">children</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PropsWithChildren</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>{children}&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 기능이 완전히 열린 컴포넌트</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Button</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">props</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ComponentProps</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"button"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">props} />;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre></figure>
<p>첫 번째 버튼은 <code>children</code>만 받을 수 있다. <code>onClick</code>도, <code>type</code>도, <code>disabled</code>도 설정할 수 없다. 대신 사용하는 사람은 고민할 것이 없다.</p>
<p>두 번째 버튼은 <code>button</code> 엘리먼트의 모든 속성을 받을 수 있다. 자유도가 높지만, 사용하는 입장에서는 수십 개의 prop 중 뭘 써야 할지 고민하게 된다. 필자는 이런 상황을 **"컴포넌트가 개발자에게 고민을 강요한다"**고 표현한다.</p>
<p>정답은 없다. 다만 모듈의 목적과 사용자에 따라 적절한 수준을 찾아야 한다. 디자인 시스템의 기본 버튼이라면 제한된 Props로 일관성을 지키는 것이 나을 수 있고, 범용 유틸리티 컴포넌트라면 유연하게 열어주는 것이 나을 수 있다.</p>
<p>비유하자면 신디사이저와 디지털 피아노의 차이와 같다. 오디오 전문가에게는 파형을 직접 다룰 수 있는 신디사이저가 적합하지만, 단순히 피아노를 치고 싶은 사람에게는 기능이 제한된 디지털 피아노가 더 좋은 경험을 제공한다. <strong>사용자가 원리를 알 필요가 없는 상황에서 넓은 입력을 제공하면 혼란만 가중되고, 반대로 다양한 상황에 대응해야 하는데 입력을 제한하면 사용처가 막혀버린다.</strong></p>
<h3 id="3-추상화의-단위를-적절하게-유지하기"><a class="anchor" href="#3-추상화의-단위를-적절하게-유지하기">3. 추상화의 단위를 적절하게 유지하기</a></h3>
<p>추상화의 단위, 즉 "어디까지를 하나의 모듈로 묶을 것인가"도 중요한 고민이다.</p>
<p>프론트엔드에서 흔히 보는 안티패턴 중 하나는 Custom Hook의 과도한 추출이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 이 훅은 단 하나의 컴포넌트에서만 사용된다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useUserProfileData</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">user</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setUser</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">loading</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setLoading</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    fetchUser</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(setUser)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">finally</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> setLoading</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">false</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, []);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { user, loading };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre></figure>
<p>하나의 컴포넌트에서만 사용되는 로직을 굳이 훅으로 분리하면, 오히려 코드를 읽는 사람이 두 개의 파일을 오가며 맥락을 파악해야 한다. 추상화가 맥락을 줄여주기는커녕 늘려버린 것이다.</p>
<p>반대로, 하나의 훅에 너무 많은 것을 담는 것도 문제다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 관련 없는 관심사가 하나의 훅에 뒤섞여 있다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useEverything</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> auth</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useAuth</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> theme</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useTheme</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> analytics</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useAnalytics</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> toast</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useToast</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // ...20줄 더</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { auth, theme, analytics, toast </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* ... */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre></figure>
<p>이런 "God Hook"은 테스트하기 어렵고, 한 가지를 수정하면 관련 없는 부분까지 영향을 받을 수 있다.</p>
<p>React 공식 문서에서도 이런 말을 하고 있다.</p>
<blockquote>
<p>"처음부터 Hook으로 분리해야 한다는 압박을 느낄 필요는 없다."</p>
</blockquote>
<p>추상화의 적절한 단위를 판단하는 기준은 **"이 분리가 코드를 읽는 사람의 맥락을 실제로 줄여주는가?"**이다. 만약 분리한 결과 오히려 맥락이 분산되어 파악이 어려워진다면, 그 추상화는 아직 때가 아닌 것이다.</p>
<hr>
<h2 id="가능하면-추상화를-하지-말자"><a class="anchor" href="#가능하면-추상화를-하지-말자">가능하면 추상화를 하지 말자</a></h2>
<p>여기까지 읽으면 "그래서 추상화를 언제 해야 하는 건데?"라는 질문이 남을 수 있다. 필자의 생각은 이렇다. <strong>기본 전제는 추상화를 하지 않는 것이어야 한다.</strong></p>
<p>이것은 필자만의 생각이 아니다. Sandi Metz는 이렇게 말했다.</p>
<blockquote>
<p>"중복은 잘못된 추상화보다 훨씬 비용이 싸다. (Duplication is far cheaper than the wrong abstraction.)"</p>
</blockquote>
<p>명확한 추상화 신호가 보이지 않는 한, 코드를 있는 그대로 두는 것이 잘못된 추상화를 만들어놓고 나중에 풀어헤치는 것보다 저점이 높다. 잘못된 추상화가 만들어지는 과정은 대략 이렇다.</p>
<ol>
<li>코드 A와 코드 B에서 비슷한 패턴이 보인다.</li>
<li>"DRY 원칙이니까 공통 함수로 뽑아야지!" 하고 추상화한다.</li>
<li>코드 C에서도 비슷한 패턴이 보여서 같은 함수를 쓰되, 약간 다른 동작을 위해 매개변수를 하나 추가한다.</li>
<li>코드 D, E에서도 사용하게 되면서 조건문과 매개변수가 계속 늘어난다.</li>
<li>이제 이 함수는 모든 곳에서 사용되지만, 아무도 건드리기 두려워하는 코드가 된다.</li>
</ol>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 처음에는 단순했던 함수가...</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> formatUserName</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">user</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> User</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> `${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">user</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">.</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">firstName</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">} ${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">user</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">.</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">lastName</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}`</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 요구사항이 추가될 때마다 매개변수가 늘어나고...</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> formatUserName</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  user</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> User</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  includeMiddleName</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  format</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "full"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "short"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "initials"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  locale</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  honorific</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (format </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "initials"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    /* ... */</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (includeMiddleName </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> user.middleName) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    /* ... */</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (honorific </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> locale </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "ko"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    /* ... */</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // ...끝없는 분기</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre></figure>
<p>이런 상황에 빠졌다면, Sandi Metz가 제안하는 해법은 명확하다. 추상화된 코드를 각 사용처에 다시 인라인하고, 각 사용처에서 불필요한 코드를 제거한 뒤, 깨끗해진 상태에서 진짜 공통점이 보이면 그때 다시 추상화한다. <strong>"가장 빠른 전진은 뒤로 돌아가는 것이다."</strong></p>
<p>그렇다면 추상화를 해야 할 때는 언제인가? 필자가 느끼는 <strong>추상화 신호</strong>는 대략 이런 것들이다.</p>
<ul>
<li><strong>일관성이 깨지고 있다.</strong> 똑같은 로직인데 어떤 컴포넌트에서는 인라인으로, 어떤 컴포넌트에서는 별도 함수로 분리되어 있다. 같은 계산 로직이 사방에 흩어져 있다.</li>
<li><strong>내부 구조가 외부에 불필요하게 노출되고 있다.</strong> 외부에서 알 필요 없는 구현 세부사항을 호출자가 일일이 다루고 있다.</li>
<li><strong>자신의 행위가 계속 노출되고 있다.</strong> 모듈이 자기 내부의 절차를 숨기지 못하고, 사용하는 쪽에서 그 절차를 그대로 따라가야 한다.</li>
</ul>
<p>문제는, 이런 신호를 감지하는 것 자체는 대체로 단순한데, <strong>실제로는 이 신호를 무시하고 더 "중요한" 요구사항을 충족하는 데 집중하게 되기 쉽다</strong>는 것이다. 일정에 쫓기거나 기능 구현에 몰두하다 보면, "일단 동작하니까 나중에 정리하자"가 되고, 그 나중은 좀처럼 오지 않는다.</p>
<p>여기서 한 가지 더 중요한 것은 <strong>일관된 추상화 기준을 유지하는 것</strong>이다. 코드베이스 안에서 같은 종류의 로직이 어떤 곳에서는 인라인으로, 어떤 곳에서는 함수로, 또 어떤 곳에서는 커스텀 훅으로 분리되어 있다면, 새로 코드를 읽는 사람은 "이 차이에 의도가 있는 건가?"라고 혼란스러워한다. 추상화를 하든 안 하든, 팀 안에서 기준이 일관되어야 한다.</p>
<p>Joel Spolsky가 2002년에 제시한 **"새는 추상화의 법칙(The Law of Leaky Abstractions)"**도 이 맥락에서 기억해둘 만하다.</p>
<blockquote>
<p>"모든 비자명한 추상화는 어느 정도 새는 법이다."</p>
</blockquote>
<p>TCP는 불안정한 네트워크를 안정적인 연결처럼 보이게 추상화하지만, 케이블이 끊기면 그 추상화는 깨진다. React는 UI 업데이트를 선언적으로 추상화하지만, 리렌더링 최적화를 위해서는 결국 내부 동작을 이해해야 한다. 완벽한 추상화는 존재하지 않기에, 추상화를 할 때는 **"이 추상화가 깨졌을 때 사용자가 대응할 수 있는가?"**까지 고민해야 한다.</p>
<p>Spolsky의 말처럼 <strong>"추상화는 우리의 작업 시간을 절약해주지만, 학습 시간을 절약해주지는 않는다."</strong></p>
<hr>
<h2 id="추상화는-체화의-영역이다"><a class="anchor" href="#추상화는-체화의-영역이다">추상화는 체화의 영역이다</a></h2>
<p>필자가 동료와 추상화에 대해 이야기를 나눈 적이 있는데, 그때 나온 이야기가 인상 깊었다. 추상화 신호를 감지하고, 적절한 시점에 적절한 수준으로 분리하는 것은 결국 <strong>감각의 영역</strong>이라는 것이다.</p>
<p>물론 앞서 이야기한 원칙들, 추상화 수준을 맞추고, 이름을 잘 짓고, 입력 자유도를 설계하는 것은 분명히 중요하다. 하지만 실제로 코드를 작성하는 순간에 이런 원칙들을 하나하나 떠올리면서 "이걸 분리할까 말까?"를 따지는 접근은 오히려 흐름을 깨뜨릴 수 있다. 마치 시합 중에 잽을 던질 때 팔꿈치 각도를 의식하면 오히려 타이밍을 놓치는 것처럼, 코드를 짤 때도 추상화는 의식적 판단이 아닌 자연스러운 감각에서 나와야 하는 것이다.</p>
<p>코드를 쭉 짜다가 뭔가 거부반응이 올라오는 순간이 있다. "이 로직이 여기 있으면 안 되는 것 같은데", "이 컴포넌트가 너무 많은 것을 알고 있는 것 같은데"라는 느낌. 그 느낌이 바로 추상화 신호이고, 이것을 자연스럽게 감지하고 반응할 수 있는 것이 체화인 것이다.</p>
<p>다만 이 감각은 하루아침에 만들어지지 않는다. 수많은 패턴을 학습하고, 다양한 코드를 읽고, 직접 삽질을 겪어봐야 비로소 **"이건 분리해야 할 것 같은데"**라는 감이 자연스럽게 올라오기 시작한다. 나중에 동료가 "왜 이걸 분리했어?"라고 물었을 때, "이건 OO이기 때문에 분리했어"라고 자연스럽게 설명할 수 있다면 그것이 체화된 것이다.</p>
<p>어떤 분야든 마찬가지인 것 같다. 외워서 잘하려고 하면 오히려 판단이 어려워지고, 결국은 큰 흐름만 가져가되 디테일은 자연스럽게 채워져야 한다. 그리고 그 자연스러움은 결국 평소에 쌓아놓은 다양한 패턴과 경험에서 나오는 것이다.</p>
<hr>
<h2 id="마치며"><a class="anchor" href="#마치며">마치며</a></h2>
<p>정리하면, 프로그래밍에서 추상화란 복잡한 것을 감추어 단순해 보이게 만들고, 코드를 읽는 사람이 필요한 맥락에만 집중할 수 있게 해주는 행위이다.</p>
<p>좋은 추상화를 위해 기억해둘 것들을 다시 한번 짚어보자면 이렇다.</p>
<ul>
<li><strong>기본 전제는 추상화를 하지 않는 것이다.</strong> 명확한 신호가 보일 때 비로소 분리하자.</li>
<li>하나의 함수는 <strong>하나의 추상화 수준</strong>에서만 이야기하자.</li>
<li>이름과 타입으로 동작을 충분히 <strong>표현</strong>하여, 소스코드를 까보지 않아도 사용할 수 있게 만들자.</li>
<li>입력의 자유도를 모듈의 목적과 사용자에 맞게 <strong>의도적으로 설계</strong>하자.</li>
<li>잘못된 추상화 위에 덧붙이기보다, <strong>풀어헤치고 다시 시작하는 용기</strong>를 갖자.</li>
<li>그리고 이 모든 것을 의식하지 않아도 자연스럽게 나올 수 있도록 <strong>다양한 패턴을 체화</strong>하자.</li>
</ul>
<p>물론 이 글에서 필자가 이야기한 것이 정답은 아니다. 추상화의 적절한 수준은 비즈니스 상황, 팀의 구성, 프로젝트의 성격에 따라 달라질 수 밖에 없다. 다만 한 가지 변하지 않는 것이 있다면, 추상화의 궁극적인 목적은 <strong>사람이 이해하기 좋은 코드를 만드는 것</strong>이라는 점이다.</p>
<p>이 글을 읽는 분들도 각자의 코드베이스에서 "이 추상화가 정말 맥락을 줄여주고 있는가?"라는 질문을 한번 던져보시길 바란다. 그 질문 하나만으로도 코드를 바라보는 시각이 조금은 달라질 것이라 생각한다.</p>
<hr>
<h2 id="참고한-자료"><a class="anchor" href="#참고한-자료">참고한 자료</a></h2>
<p>이 글은 여러 원전과 선행 아티클에서 많은 영감을 받았다. 직접 인용한 구절의 출처와, 사고의 틀을 잡는 데 도움이 된 글들을 함께 남긴다.</p>
<ul>
<li>Evan Moon, <a href="https://evan-moon.github.io/2023/01/15/what-is-abstract/">「추상, 그리고 추상화」</a> — 이 글의 전체적인 구성과 **"추상화는 맥락을 줄이는 것"**이라는 관점은 이 아티클에서 가장 큰 영향을 받았다.</li>
<li>박서진, 「좋은 코드를 위한 나의 선택, 신중하게 코드 작성하기」 (Toss SLASH 23 발표) — "공통점 추출"이 아닌 **"부품의 조립"**으로 접근하라는 관점은 이 발표에서 출발했다.</li>
<li>Robert C. Martin, <em>Clean Code</em> — "함수당 하나의 추상화 수준", Stepdown Rule, 의도를 드러내는 네이밍 규칙(2장, 3장).</li>
<li>Kent Beck, <em>Smalltalk Best Practice Patterns</em> — Composed Method / Intention-Revealing Name 패턴.</li>
<li>Sandi Metz, <a href="https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction">“The Wrong Abstraction”</a> — 잘못된 추상화가 쌓여가는 과정과, 풀어헤치고 다시 시작하는 용기에 대한 논의.</li>
<li>Joel Spolsky, <a href="https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/">“The Law of Leaky Abstractions”</a> — 완벽한 추상화는 존재하지 않는다는 관점.</li>
<li>Edsger W. Dijkstra, "The Humble Programmer" (1972 ACM Turing Award Lecture) — 추상화의 목적에 대한 고전적 정의.</li>
<li>John V. Guttag, <em>Introduction to Computation and Programming Using Python</em> — 추상화의 본질에 대한 정의.</li>
<li>React 공식 문서, <a href="https://react.dev/learn/reusing-logic-with-custom-hooks">Reusing Logic with Custom Hooks</a> — Hook 분리의 압박에 대한 가이드.</li>
<li>Jeff Atwood, <a href="https://blog.codinghorror.com/i-shall-call-it-somethingmanager/">“i-shall-call-it-somethingmanager”</a> (Coding Horror) — <code>Manager</code> 접미사의 모호함에 대한 지적.</li>
</ul>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>설계</category>
            <category>추상화</category>
        </item>
        <item>
            <title><![CDATA[저랑 오픈소스 같이 공부하실분~]]></title>
            <link>https://hooninedev.com/250625</link>
            <guid isPermaLink="false">https://hooninedev.com/250625</guid>
            <pubDate>Wed, 25 Jun 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[사내에서 성능 병목을 겪거나, 라이브러리의 버전 관리를 하다 보면 자연스럽게 오픈소스의 코어 로직을 탐색하게 된다. 불과 몇 달 전까지만 해도 오픈소스 코드를 읽는 것 자체가 벅찼지만, AI의 발전 덕분에 (물론 내 실력이 향상된 것도 있지만) 자체가 달라졌고, 지금은 훨씬 쉽게 탐구할 수 있게 되었다. 예전에는 내가 필요한 부분만 빠르게 훑고 넘어가는 일...]]></description>
            <content:encoded><![CDATA[<p>사내에서 성능 병목을 겪거나, 라이브러리의 버전 관리를 하다 보면 자연스럽게 오픈소스의 코어 로직을 탐색하게 된다.
불과 몇 달 전까지만 해도 오픈소스 코드를 읽는 것 자체가 벅찼지만, <strong>AI의 발전</strong> 덕분에 <del>(물론 내 실력이 향상된 것도 있지만)</del> 자체가 달라졌고, 지금은 훨씬 쉽게 탐구할 수 있게 되었다.</p>
<p><img src="/content/250625/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<p>예전에는 내가 필요한 부분만 빠르게 훑고 넘어가는 일이 많았지만, 최근엔 코어 기능을 응용하거나 프로젝트에 직접 접목해야 하는 일이 잦아지면서 <strong>더 깊이 있는 이해와 분석이</strong> 필요해졌다.</p>
<p>아마 이 글을 읽는 많은 분들이 단순한 관심을 넘어서, <strong>실질적인 오픈소스 기여를</strong> 목표로 하고 있으리라 생각한다. 그래서 이 글은 단순한 맞춤법 수정이나 사소한 버그 픽스가 아닌, 내가 어떤 방식으로 AI를 활용해 오픈소스의 핵심 로직을 탐구하고, <strong>구조적 이해를 바탕으로</strong> 한 <strong>의미 있는 기여를 위한 접근 방법에 초점을 맞추어 정리</strong>해보려 한다.</p>
<hr>
<h2 id="ai를-활용한-오픈소스-탐구"><a class="anchor" href="#ai를-활용한-오픈소스-탐구">AI를 활용한 오픈소스 탐구</a></h2>
<p><strong>“AI를 활용해 오픈소스를 공부한다”</strong> 고 하면 막연하게 느껴질 수 있다.</p>
<p>실제로 오픈소스 코드베이스는 방대한 경우가 많아, 모든 내용을 AI에게 학습시키는 건 아직 불가능하다. (가벼운 용량의 라이브러리면 가능할 것 같지만..)</p>
<p>그래서 나는 아래와 같은 계획을 세우고, 그에 맞춰 AI를 효율적으로 활용해왔다.(요즘 claude 4.0 모델을 사용하고있다.)</p>
<pre><code>1. 관심 있는 라이브러리를 포크한다.

2. 핵심 로직 한 부분을 선정하고, 관련 로직들을 탐색하며 구조를 파악한다.

3. 명확한 구조를 보여주는 로직을 중심으로 AI에게 학습시킨다.

4. AI와의 대화를 통해 메인테이너의 설계 의도와 방향성을 추정한다.

5. 이해되지 않는 부분은 질문 리스트로 정리한다.

6. 각 질문에 대해 가설을 세우고, AI에게 흐름 설명을 요청하며 검증한다.

7. 이해한 내용이 타당하다면, 실제 개선 PR을 위한 브랜치를 만들어 기여를 시도한다.
</code></pre>
<p>이렇게 보면 이해가 되지 않는 부분이 많을 것 같아 가장 최근에 공부하고 기여해본 <strong>TanStack Route 라우팅 탐색 최적화 (<a href="https://github.com/TanStack/router/pull/4513">PR #4513</a>)</strong> 을 가지고 이야기해보겠다.</p>
<hr>
<h3 id="관심-있는-라이브러리-선택해보자"><a class="anchor" href="#관심-있는-라이브러리-선택해보자">관심 있는 라이브러리 선택해보자.</a></h3>
<p>가장 먼저 관심 가지고 깊게 분석해볼 라이브러리를 선택하는 것이 첫 번째다. 나는 다음의 기준을 우선순위로 두었다.</p>
<ul>
<li>회사에서 실제로 사용 중인 라이브러리</li>
<li>사내에서 사용하는 버전과 공식 최신 버전 간의 간극이 큰 라이브러리</li>
<li>유사 기능을 가진 다른 라이브러리들과의 구조 비교가 가능한 라이브러리</li>
</ul>
<p>나는 최근 회사에서 <code>react-router</code>를 사용하고 있었는데, 앞으로 <code>tanstack-router</code>로 교체해보는 것이 더 나은 선택일 수 있을지 고민하게 되었다.
계기는 우연히 본 Reddit 게시글이었다. (물론 새로운 라이브러리가 나올때마다 쉽게 바꾸고 대응할 수 없겠지만, 관심은 가져야한다고 생각한다.)</p>
<ul>
<li><a href="https://www.reddit.com/r/reactjs/comments/1iatblk/react_router_v7_has_to_be_a_psyop/">React Router v7이 Remix의 구조적 영향을 크게 받았으며, 점점 Remix 중심의 방향으로 진화하고 있다는 내용.</a></li>
<li><a href="https://www.reddit.com/r/reactjs/comments/1klp8cr/react_router_v7_or_tanstack_router/">React Router v7과 TanStack Router의 차이점이 무엇인가요?</a></li>
</ul>
<p>이 내용이 궁금해져 react-router의 버전 히스토리를 살펴보았고, 그 대안으로 떠오른 tanstack-router가 눈에 들어왔다.
보다 명확한 비교와 판단을 위해 tanstack/router 레포지토리를 포크하고 본격적인 분석을 시작했다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="md" data-theme="github-dark github-light"><code data-language="md" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">TanStack 라이브러리 성장 속도가 엄청 빠르다.</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">처음 tanstack/query 만 서버상태관리를 위해 사용했다.</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">그러다 정신차리고 보니, router, store, db, form 등 라이브러리들을 쏟아내고있다.</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">모든 라이브러리를 알지 못해도 현재 사용하고있는 아래 라이브러리의 코어 로직은 탐구해봐야겠다고 생각했다.</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> react-router</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> 클라이언트 상태관리 라이브러리(zustand, jotai, recoil)</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> react-hook-form</span></span></code></pre></figure>
<hr>
<h3 id="핵심-로직-한-부분을-선정하고-공부해보자"><a class="anchor" href="#핵심-로직-한-부분을-선정하고-공부해보자">핵심 로직 한 부분을 선정하고, 공부해보자.</a></h3>
<p>처음부터 코어 로직들을 다 살펴보겠다는 목표를 가지고 탐구를 시작했다.</p>
<p>하지만 코어 로직이 상당히 비대하고, 깊은 이해도를 가지기에는 많은 시간이 소비가 되어 원래의 목표를 달성하기 전에 절망에 빠져버렸다. 그래서 내가 중점으로 살펴볼 핵심 로직을 정하고 그 로직을 중심으로 연결된 로직들을 살펴보는 방식을 취했다.</p>
<p>내가 tanstack-router를 살펴보려 했을 때 가장 먼저 궁금했던 것은 성능 병목과 구조적 효율성이었다. 특히 다음 세 가지가 주요 관찰 포인트였다.</p>
<ul>
<li>TanStack Router는 성능 측면에서 얼마나 최적화되어 있을까?</li>
<li>기존 react-router와 비교했을 때, 제공하는 메서드나 API의 호환성이 충분할까?</li>
<li>실무에서 마이그레이션을 진행한다면 비용이 얼마나 들까?</li>
</ul>
<p>이 중 가장 먼저 확인해야 할 것은 라우팅 트리 매칭 방식이었고, 이에 관련된 <strong><a href="https://github.com/TanStack/router/blob/640ac95fa7582753e5464b2950af8ad1f6e3179b/packages/router-core/src/router.ts#L1075">matchRoutesInternal</a>을</strong> 집중적으로 탐색하게 되었다.</p>
<hr>
<h3 id="학습한-로직을-중심으로-ai에게-학습시킨다"><a class="anchor" href="#학습한-로직을-중심으로-ai에게-학습시킨다">학습한 로직을 중심으로 AI에게 학습시킨다.</a></h3>
<p>코드를 분석하다 보니 <code>matchRoutesInternal</code> 함수가 라우트 트리를 전부 순회하며 매칭하는 구조로 작성되어 있다는 점이 눈에 띄었다.</p>
<p>메인 로직과 연관된 함수들을 보았다. <code>getMatchedRoutes</code> 함수를 통해 경로 매칭하고 내부에 파라미터 파싱 및 검증, 검색 파라미터 검증 로직들이 서로 연관되어있다.</p>
<p>이 방식은 트리 깊이가 깊거나, 라우트 수가 많아질수록 성능 병목이 발생할 수 있는 구조라고 생각해보았다. 그래서 학습한 로직과 <code>matchRoutesInternal</code> 함수와 연관된 로직들을 AI 에게 학습시켜보았다.</p>
<hr>
<h3 id="메인테이너의-설계-의도와-방향성-파악하기"><a class="anchor" href="#메인테이너의-설계-의도와-방향성-파악하기">메인테이너의 설계 의도와 방향성 파악하기</a></h3>
<p>기존 로직을 AI와 함께 분석하며, 왜 현재 구조가 이런 방식으로 설계되었는지, 이 구조가 어떤 문제를 풀기 위해 존재하는지 메인테이너의 입장에서 고민해보았다.</p>
<p>단순히 “어떤 부분이 효율적이고, 어떤 부분이 비효율적이다”라고 판단하는 것이 아니라, 이 방식이 왜 선택되었고, 어떤 조건에서 성능 문제가 생기는지를 파악하는 것이 중요했다.</p>
<p><strong>Claude 4.0 모델</strong> 을 아래처럼 활용해보았다.</p>
<div style="display: flex; gap: 10px;">
  <img src="/content/250625/3.png" alt="이미지 3" style="width: 60%" />
  <img src="/content/250625/4.png" alt="이미지 4" style="width: 40%" />
</div>
<hr>
<h3 id="이해되지-않는-부분은-질문-리스트로-정리해보기"><a class="anchor" href="#이해되지-않는-부분은-질문-리스트로-정리해보기">이해되지 않는 부분은 질문 리스트로 정리해보기</a></h3>
<p>분석 도중 다음과 같은 의문이 생겼다.</p>
<ul>
<li>“왜 전체 트리를 매번 순회하는가?”</li>
<li>“자식 노드를 가지는 경우에도 모든 경로를 전수조사해야 하는 구조인가?”</li>
<li>“이 방식은 현재 구조에서 최적의 선택일까, 아니면 단순한 구현상의 디폴트인가?”</li>
</ul>
<p>이러한 질문 리스트를 바탕으로, 더 효율적인 구조로 대체할 수 있을지 가설을 세우며 AI 를 활용해 더 깊이있게 <code>matchRoutesInternal</code> 을 탐구해보았다.</p>
<hr>
<h3 id="가설-검증해보기"><a class="anchor" href="#가설-검증해보기">가설 검증해보기</a></h3>
<p>이후 나는 다음과 같은 가설을 세웠다.</p>
<blockquote>
<p><strong>매번 트리를 순회하지 않고, Trie 기반의 정적 라우트 매칭 구조로 바꾼다면 성능을 개선할 수 있지 않을까?</strong></p>
</blockquote>
<p>이 아이디어를 바탕으로 RouteTrie 구조를 새로 작성하고, 기존의 matchRoutesInternal 로직을 대체해 성능을 개선하는 방안을 구현했다.</p>
<p>또한 자체적으로 테스트 파일을 구성해 가설을 검증했고, 아래 개선사항을 확인했다. (내가 작성했던 테스트는 PR 올리는 과정에 삭제했는데, 메인테이너가 추가 테스트 파일을 작성해 PR 에 추가해줬다.)</p>
<ul>
<li>A → B → A와 같이 이전 페이지로 빠르게 돌아오는 경우, A 페이지 데이터가 캐시에 유지</li>
<li>동시 네비게이션 시 사용자가 링크를 빠르게 여러 번 클릭할 때 발생할 수 있는 데이터 손실을 방지</li>
<li>페이지 데이터를 새로 고치는 도중 다른 페이지로 이동했다가 돌아올 때 캐시된 데이터가 유지</li>
</ul>
<hr>
<h3 id="검증이-옳다면-실제-개선-pr을-위한-브랜치를-만들어보자"><a class="anchor" href="#검증이-옳다면-실제-개선-pr을-위한-브랜치를-만들어보자">검증이 옳다면 실제 개선 PR을 위한 브랜치를 만들어보자.</a></h3>
<p>검증 결과 성능 병목이 실제로 존재했고, 내가 제안한 구조가 이를 개선할 수 있다는 확신이 들었다.
이에 개선된 로직을 기반으로 PR을 올렸고, 메인테이너가 이를 받아들여 최종적으로 공식 반영되었다.</p>
<hr>
<h2 id="마무리하며"><a class="anchor" href="#마무리하며">마무리하며</a></h2>
<p>이 글에서는 어떤 코드를 어떻게 수정했는지를 설명하려는 것이 아니다. 더 중요한 것은, 이런 흐름으로 오픈소스를 탐구하고 이해하는 방식이 나한테 많은 도움이 되었다는 것을 알려주고 싶었다.</p>
<p>처음에는 단순한 의문에서 출발했지만, <strong>핵심 로직을 직접 탐색하고, AI를 활용해 설계를 파악하며, 작은 가설을 세워 검증하고, 실제 기여까지 연결해보는 일련의 과정</strong>을 거치면서 단순한 학습 이상의 깊이 있는 이해와 개발자로서의 성장을 경험할 수 있었다.</p>
<p>이런 방식으로 지난 2개월 동안 여러 오픈소스 라이브러리들을 분석하고, 그 중 일부는 실제로 개선하며 기여의 보람도 느낄 수 있었다.</p>
<ul>
<li>TanStack DB refetch 로직 개선 (<a href="https://github.com/TanStack/db/pull/122">PR #122</a>)</li>
<li>TanStack Store selector 의존성 개선 (<a href="https://github.com/TanStack/store/pull/198">PR #198</a>)</li>
</ul>
<p>그리고 요즘 제일 많이 탐구하고 있는 toss 의 오픈소스들</p>
<p><img src="/content/250625/5.png" alt="5.png" loading="lazy" decoding="async"></p>
<hr>
<p>오픈소스를 분석해보는 것은 엄청 어렵고, 거창한게 아니다.</p>
<p>내가 사용하는 도구가 왜 이렇게 만들어졌는지에 대한 <strong>‘왜’에서 출발해, 작은 질문과 개선 의지를 가지고 접근</strong>해본다면 누구나 만들고 기여해볼 수 있다고 생각한다.</p>
<hr>
<p>이 글을 작성한 이유는 혼자 공부하다 보니 너무 어렵고, 이게 적합한 개선인지 이 부분에 대해 다른 사람들은 어떻게 생각하는지 궁금해졌기 때문이다.</p>
<p>누군가와 함께 특정 라이브러리의 구조를 뜯어보고, 토론하고, 분석하며 함께 기여하는 과정을 만들 수 있다면 얼마나 좋을까?</p>
<hr>
<p><img src="/content/250625/6.png" alt="6.png" loading="lazy" decoding="async"></p>
<hr>
<p><strong>그래서 아래에 해당하면 연락한번 주셔도 좋겠습니다! 같이 공부하고 분석해보면 좋을것같다.</strong> <a href="https://www.linkedin.com/in/jiji-hoon96">링크드인</a>, <a href="mailto:jihoon7705@gmail.com">jihoon7705@gmail.com</a> 또는 댓글로 연락 주셔도 좋습니다!</p>
<ul>
<li>오픈소스에 관심 있지만 어디서 시작할지 막막한 분</li>
<li>프론트엔드 직무에 밀접한 코어 라이브러리에 흥미 있는 분</li>
<li>구조와 설계에 대한 분석을 좋아하는 분</li>
<li>AI를 활용한 오픈소스 학습법에 관심 있는 분</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>TypeScript</category>
        </item>
        <item>
            <title><![CDATA[as const 한 줄로 만드는 타입 안정성]]></title>
            <link>https://hooninedev.com/250620</link>
            <guid isPermaLink="false">https://hooninedev.com/250620</guid>
            <pubDate>Fri, 20 Jun 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[프론트엔드 실무에서 as const를 단순히 타입 추론을 돕는 보조 수단이 아니라, 불변성 선언과 동시에 타입의 리터럴화를 통해 안정성과 자동완성을 극대화할 수 있는 툴로 유용하게 사용하고 있다. 이번 글에서는 실무에서 as const의 활용 사례를 중심으로 활용과 원리까지 깊이 있게 다뤄보려 한다. as const as const는 TypeScript에서...]]></description>
            <content:encoded><![CDATA[<p>프론트엔드 실무에서 <code>as const</code>를 단순히 타입 추론을 돕는 보조 수단이 아니라, <strong>불변성 선언과 동시에 타입의 리터럴화를 통해 안정성과 자동완성을 극대화</strong>할 수 있는 툴로 유용하게 사용하고 있다.</p>
<p>이번 글에서는 실무에서 <code>as const</code>의 활용 사례를 중심으로 활용과 원리까지 깊이 있게 다뤄보려 한다.</p>
<hr>
<h2 id="as-const"><a class="anchor" href="#as-const">as const</a></h2>
<p><code>as const</code>는 TypeScript에서 <strong>값을 리터럴 타입으로 고정(literal narrowing)</strong> 하기 위한 단언문이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> button</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "primary"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 타입: string</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> button</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "primary"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> as</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 타입: "primary"</span></span></code></pre></figure>
<p>배열이나 객체에 쓰면 해당 구조를 읽기 전용(readonly)으로 만들면서 각 요소의 타입을 리터럴로 고정한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> status</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"idle"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"loading"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 타입: readonly ["idle", "loading"]</span></span></code></pre></figure>
<p>이처럼 <strong>값을 변경 불가능한 상수로 명시</strong>하고, 타입도 좁게 고정되도록 만드는 것이 핵심이다.</p>
<hr>
<h3 id="tanstack-query--query-key-안정성-확보"><a class="anchor" href="#tanstack-query--query-key-안정성-확보">TanStack Query – query key 안정성 확보</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> queryKey</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"user"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, userId] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useQuery</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ queryKey, queryFn });</span></span></code></pre></figure>
<p>여기서 <code>as const</code>를 쓰지 않으면 queryKey는 <code>(string | number)[]</code>로 추론되어 타입 안정성이 떨어진다.</p>
<p>반면 <code>as const</code>를 쓰면 queryKey가 정확히 <code>readonly ["user", string]</code>이 되어, <strong>query key가 고정된 식별자로 취급</strong>되고, 캐싱/비교/인밸리데이션에서도 더 안정적으로 작동한다.</p>
<hr>
<h3 id="react-router--라우팅과-같은-객체-구조의-키값-고정"><a class="anchor" href="#react-router--라우팅과-같은-객체-구조의-키값-고정">React Router – 라우팅과 같은 객체 구조의 키값 고정</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> router</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createBrowserRouter</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  { path: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"/home"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, element: &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">HomePage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> /> },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  { path: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"/about"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, element: &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">AboutPage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> /> }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span></code></pre></figure>
<p>React Router는 내부적으로 라우트 배열을 순회하면서 타입을 추론하려고 하기 때문에, <code>as const</code>로 선언해주면 각 path의 리터럴 값(<code>"/home"</code>, <code>"/about"</code>)을 정확히 추론할 수 있다.</p>
<p>이를 통해 <strong>라우트 기반 자동완성, 타입 기반 네비게이션 제어</strong>가 가능해진다.</p>
<hr>
<h2 id="as-const를-쓰지-않아야-할-때"><a class="anchor" href="#as-const를-쓰지-않아야-할-때"><code>as const</code>를 쓰지 않아야 할 때</a></h2>
<p>무조건 좁은 타입이 좋은 건 아니다. 다음과 같은 경우는 오히려 <code>as const</code>를 쓰지 않는 것이 낫다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> status </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "idle"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> as</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span></code></pre></figure>
<p>이렇게 하면 <code>status</code>는 절대 다른 문자열을 가질 수 없기 때문에, 동적으로 상태를 변경해야 하는 경우엔 적절치 않다.</p>
<hr>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">type</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "create"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "delete"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {}</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"create"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> as</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 불필요</span></span></code></pre></figure>
<p>함수 시그니처가 리터럴 타입을 받는 경우, 이미 문자열 리터럴을 넣으면 타입이 자동으로 좁혀진다. 명시적 <code>as const</code>는 오히려 코드만 장황해질 수 있다.</p>
<hr>
<h2 id="내부-동작-원리--const-assertion의-타입화"><a class="anchor" href="#내부-동작-원리--const-assertion의-타입화">내부 동작 원리 – const assertion의 타입화</a></h2>
<p>TypeScript에서 <code>as const</code>는 해당 값은 <strong>readonly</strong>로 간주되고, 가능한 한 좁은 타입으로 <strong>리터럴 추론</strong>을 한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> obj</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { a: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, b: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"x"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 타입: { readonly a: 1; readonly b: "x"; }</span></span></code></pre></figure>
<p>이는 일종의 <strong>const assertion</strong>으로, 값 자체는 JS에서 변하지 않지만 타입도 함께 고정시키는 것이 특징이다.</p>
<p>배열, 객체, 튜플 등에 모두 적용 가능하며, <strong>유형 안정성(type safety)을 명시적으로 선언하는 수단</strong>이다.</p>
<hr>
<h3 id="기술적으로-조금-더-깊게-들어가면"><a class="anchor" href="#기술적으로-조금-더-깊게-들어가면">기술적으로 조금 더 깊게 들어가면</a></h3>
<p>TypeScript는 <code>as const</code>를 만나면 해당 값 전체를 readonly로 처리하며, 내부 속성 하나하나를 리터럴 타입으로 고정한다.</p>
<p>이는 내부적으로 <code>const T: { readonly a: 1, readonly b: "x" }</code> 같은 형태로 변환된다.</p>
<p>제네릭 함수에서 <code>as const</code>된 인자를 넘기면, 추론된 타입이 정확한 리터럴 형태로 유지되기 때문에 타입 제한, 자동완성, 추론 정확도가 크게 향상된다.</p>
<p>특히 readonly 튜플로 바뀐다는 점에서, 배열 기반 추론(예: query key 등)에 매우 큰 이점을 준다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> key</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"user"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Key</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> key; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// readonly ["user", 1]</span></span></code></pre></figure>
<p>만약 <code>as const</code> 없이 선언했다면 <code>Key</code>는 <code>(string | number)[]</code>이 되었을 것이고, 타입 정합성은 깨졌을 것이다.</p>
<hr>
<h2 id="enum과-비슷하지만-다르다"><a class="anchor" href="#enum과-비슷하지만-다르다"><code>enum</code>과 비슷하지만 다르다.</a></h2>
<p><code>as const</code>와 <code>enum</code>은 모두 <strong>값 집합을 고정시키는</strong> 용도로 사용되지만, 아래와 같은 차이가 있다.</p>
<hr>
<table>
<thead>
<tr>
<th>항목</th>
<th><code>as const</code></th>
<th><code>enum</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>런타임 존재 여부</td>
<td>값 그 자체</td>
<td>객체 생성됨</td>
</tr>
<tr>
<td>타입 유추 방식</td>
<td>리터럴 타입으로 추론</td>
<td>명시적 타입 선언 필요</td>
</tr>
<tr>
<td>Tree-shaking</td>
<td>가능</td>
<td>어려움</td>
</tr>
<tr>
<td>JS 출력 코드</td>
<td>없음</td>
<td>존재 (실제 객체 생성)</td>
</tr>
</tbody>
</table>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Mode</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">typeof</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> MODES</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)[</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">];</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> MODES</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"light"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"dark"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">enum</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ModeEnum</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  Light</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "light"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  Dark</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "dark"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>대부분의 경우 <strong>불필요한 런타임 비용 없이 타입만 고정하고 싶을 때는 <code>as const</code>가 더 효율적</strong>이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>TypeScript</category>
        </item>
        <item>
            <title><![CDATA[함수 참조 제대로 알자.]]></title>
            <link>https://hooninedev.com/250617</link>
            <guid isPermaLink="false">https://hooninedev.com/250617</guid>
            <pubDate>Tue, 17 Jun 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[React에서 useCallback은 성능 최적화를 위해 자주 사용된다. 하지만 useCallback의 사용 자체에만 집중하다 보면, 정작 단순한 함수 참조 유지라는 기본 개념을 잊고 작업하게 된다. 그리고 시간이 지난 뒤, 왜 이 함수는 계속 새로 생성되는 거지? 라는 의문을 갖게 된다. 이번 글에서는 회사에서 useCallback을 사용하던 중, 함수 ...]]></description>
            <content:encoded><![CDATA[<p>React에서 <code>useCallback</code>은 성능 최적화를 위해 자주 사용된다. 하지만 <code>useCallback</code>의 사용 자체에만 집중하다 보면, 정작 단순한 함수 참조 유지라는 기본 개념을 잊고 작업하게 된다. 그리고 시간이 지난 뒤, <strong>왜 이 함수는 계속 새로 생성되는 거지?</strong> 라는 의문을 갖게 된다.</p>
<p>이번 글에서는 회사에서 <code>useCallback</code>을 사용하던 중, <strong>함수 참조에 대한 개념을 간과한 채 디버깅에 시간을 낭비했던 경험을 반성하는 의미로 내용을 정리해보았다.</strong></p>
<p><img src="/content/250617/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<hr>
<h2 id="usecallback"><a class="anchor" href="#usecallback">useCallback</a></h2>
<p>React 컴포넌트가 리렌더링될 때, 함수 선언도 함께 다시 실행된다. 이는 매번 <strong>새로운 메모리 참조를 가진 함수 객체가 생성된다는 뜻</strong>이다. 이렇게 생성된 함수가 <code>useEffect</code>의 의존성 배열(deps)에 포함되어 있다면, 의도치 않은 재실행이 발생할 수 있다. 또 자식 컴포넌트에 props로 해당 함수를 전달할 경우, 참조가 바뀌면서 memoization이 무효화되어 <strong>불필요한 리렌더링</strong>이 일어날 수 있다.</p>
<p>이때 <code>useCallback</code>은 지정한 <code>deps</code>가 변경되지 않는 한 함수의 참조를 유지해, <strong>불필요한 연산과 리렌더링을 방지</strong>할 수 있도록 도와준다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> memoizedFn</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useCallback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(fn, deps);</span></span></code></pre></figure>
<hr>
<h3 id="참조reference"><a class="anchor" href="#참조reference">참조(reference)</a></h3>
<p>자바스크립트에서 <code>함수는 객체다.</code> 따라서 다음 두 코드는 <code>완전히 다른 동작</code>을 한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> handleClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useCallback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'dd'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, []);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  &#x3C;></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    &#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">button onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{handleClick}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">1번</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">button</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    &#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">button onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{() => </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">handleClick</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">2번</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">button</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  &#x3C;/></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span></code></pre></figure>
<p>위 코드를 보면, <code>onClick={handleClick}</code>은 <code>useCallback</code>에 의해 <strong>메모이제이션된 함수 참조를 그대로 전달</strong>한다.</p>
<p>반면, <code>onClick={() => handleClick()}</code>은 렌더링될 때마다 <strong>새로운 함수 객체</strong>를 생성한다.</p>
<p>즉, useCallback으로 만든 함수를 불필요하게 감싸는 순간, 참조 유지의 이점은 사라진다.</p>
<ul>
<li>
<p>onClick={handleClick}은 불필요한 리렌더링을 방지할 수 있다. (정상)</p>
</li>
<li>
<p>onClick={() => handleClick()}은 useCallback의 이점을 완전히 무효화한다. (잘못된 사용)</p>
</li>
</ul>
<hr>
<h2 id="자식-컴포넌트에-영향이-있을까"><a class="anchor" href="#자식-컴포넌트에-영향이-있을까">자식 컴포넌트에 영향이 있을까?</a></h2>
<p>React는 props가 바뀌었는지를 Object.is()로 비교한다. 따라서 () => handleClick()은 매번 새로운 함수 객체이기 때문에 아래 상황에서는 Child가 매번 리렌더링된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Child onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{() => </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">handleClick</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/></span></span></code></pre></figure>
<p>반면, onClick={handleClick}은 useCallback에 의해 참조가 유지되므로, Child가 리렌더링되지 않는다.</p>
<hr>
<h3 id="objectis"><a class="anchor" href="#objectis">Object.is()</a></h3>
<p>React는 함수형 컴포넌트가 리렌더링될 때 props가 이전과 달라졌는지를 판단하기 위해 Object.is()를 사용한다. 특히 React.memo, useMemo, useCallback 등 메모이제이션 관련 최적화 기법에서 이 비교 방식은 매우 중요한 역할을 한다.</p>
<p><code>Object.is(value1, value2)</code>는 두 값이 <strong>같은 값인지(SameValue 알고리즘)</strong> 를 비교하는 JavaScript 내장 함수로, ===와 매우 유사하지만 몇 가지 미묘한 차이가 있다.</p>
<p><code>NaN === NaN</code> 에서 ===는 false를 반환하지만 Object.is()는 true를 반환한다. 그리고 <code>+0 === -0</code> 에서 ===는 true를 반환하지만 Object.is()는 false를 반환한다.</p>
<p>즉, Object.is()는 보다 정밀한 동일성 비교를 수행하고, NaN, +0, -0 등의 케이스에서도 오동작하지 않도록 설계된 함수다. 그래서 React 에서 <code>Object.is()</code>를 사용하여 props가 이전과 달라졌는지를 판단한다.</p>
<hr>
<h2 id="브라우저엔진-관점에서의-최적화"><a class="anchor" href="#브라우저엔진-관점에서의-최적화">브라우저/엔진 관점에서의 최적화</a></h2>
<p>모던 자바스크립트 엔진(V8 등)은 함수 객체를 반복적으로 생성하고 해제하는 과정에서 <strong>GC(가비지 컬렉션)</strong> 비용이 증가할 수 있다. 특히 다음과 같은 경우에 성능 저하가 발생할 수 있다</p>
<ul>
<li>컴포넌트 리렌더링 시 <strong>새로운 함수 객체가 매번 생성</strong>되는 경우</li>
<li>렌더된 DOM에 <strong>이벤트 핸들러가 대량으로 등록</strong>되는 경우</li>
<li>내부적으로 <strong>클로저 환경이 계속해서 새롭게 생성</strong>되어 메모리 누수가 발생할 가능성이 있는 경우</li>
</ul>
<p>이는 브라우저 엔진이 매 렌더마다 생성된 함수를 개별적인 클로저 컨텍스트와 함께 추적해야 하기 때문이다. GC 타이밍이 UI 업데이트와 맞물릴 경우 프레임 드롭이나 UI 랙으로 이어질 수 있다.</p>
<p>그래서 <strong>useCallback을 통해 함수 참조를 고정하고, 컴포넌트 외부에서 클로저 생성을 피한 구조를 최적화한다</strong>.</p>
<hr>
<h3 id="그럼-언제---fn을-써도-괜찮을까"><a class="anchor" href="#그럼-언제---fn을-써도-괜찮을까">그럼 언제 () => fn()을 써도 괜찮을까?</a></h3>
<p>useCallback이 항상 정답은 아니다. 함수 내부에서 <strong>최신 props나 상태(state)</strong> 를 참조해야 하는 경우, 외부에 고정된 함수를 쓰면 stale closure 문제가 발생할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">button onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{() => </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">doSomething</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">id</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">삭제</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">button</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span></code></pre></figure>
<p>예를 들어 id가 렌더링 시점에 따라 바뀌는 값이라면, useCallback으로 감싸도 이 id가 stale(오래된 값)일 수 있다.</p>
<hr>
<h3 id="react-19의-useevent"><a class="anchor" href="#react-19의-useevent">React 19의 useEvent</a></h3>
<p>React 19 이전에는 ahooks의 useMemoizedFn이 유사한 역할을 수행할 수 있는 방법이 있었다. (적극적으로 사용되지 않았던 것 같다.)</p>
<p>React 19에서는 useEvent 훅이 공식화되었다. 이 훅은 이벤트 핸들러에서 최신 상태나 props를 안정적으로 참조할 수 있도록 돕는다. 내부적으로는 stable reference를 유지하되, 내부 로직에서는 항상 최신 값을 읽도록 구현되어 있다.</p>
<hr>
<h2 id="결론"><a class="anchor" href="#결론">결론</a></h2>
<p>디버깅을 하다 보면, 평소에 잘 알고 있다고 생각했던 훅이나 개념들이 실제로는 완전히 이해되지 않았던 부분이라는 걸 깨닫게 된다.</p>
<p>그래서 문제의 명확한 원인을 빠르게 파악하기 위해서는, 익숙한 개념일수록 다시 한번 정확히 이해하고 있는지 점검해보는 태도가 반드시 필요하다는 것을 다시 한번 느꼈다.</p>
<h2 id="참고-자료"><a class="anchor" href="#참고-자료">참고 자료</a></h2>
<ul>
<li><a href="https://arc.net/l/quote/bwcierlc">React 공식 문서: Hooks FAQ - useCallback</a></li>
<li><a href="https://v8.dev/blog/">V8 엔진의 함수 생성 및 GC 정책: v8.dev</a></li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>React</category>
        </item>
        <item>
            <title><![CDATA[2년차 불안함]]></title>
            <link>https://hooninedev.com/250528</link>
            <guid isPermaLink="false">https://hooninedev.com/250528</guid>
            <pubDate>Wed, 28 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[내가 잘하고 있는 걸까? 어떤 것부터 시작해야 하지? 프론트엔드 개발자로 일하며, 나는 매일 스스로의 부족함을 마주한다. 그리고 미래를 위해 갖춰야 할 역량은 끝도 없이 쏟아진다. 불안함에 동아리와 스터디를 시작했다. 좋은 동료들을 만난 만큼, 자연스럽게 나를 그들과 비교하게 되었다. 어떤 이는 디자인 시스템에 관심이 많아 직접 구축하며 UI 개발에 깊이 ...]]></description>
            <content:encoded><![CDATA[<h3 id="내가-잘하고-있는-걸까"><a class="anchor" href="#내가-잘하고-있는-걸까">내가 잘하고 있는 걸까?</a></h3>
<h3 id="어떤-것부터-시작해야-하지"><a class="anchor" href="#어떤-것부터-시작해야-하지">어떤 것부터 시작해야 하지?</a></h3>
<hr>
<hr>
<p>프론트엔드 개발자로 일하며, 나는 매일 스스로의 부족함을 마주한다.
그리고 미래를 위해 갖춰야 할 역량은 끝도 없이 쏟아진다.</p>
<p>불안함에 동아리와 스터디를 시작했다. 좋은 동료들을 만난 만큼, 자연스럽게 나를 그들과 <strong>비교</strong>하게 되었다.</p>
<ul>
<li>어떤 이는 디자인 시스템에 관심이 많아 직접 구축하며 UI 개발에 깊이 몰입한다.</li>
<li>또 다른 이는 DevOps와 인프라에 관심을 갖고 Docker와 쿠버네티스를 학습한다.</li>
<li>어떤 이는 UX를 넘어 DX를 고민하며 직접 라이브러리를 만들기도 한다.</li>
</ul>
<p>그들과 나를 비교할수록, <strong>나는 한없이 깊은 심해 속으로 가라앉는다.</strong> 불안속에서 허우적대다 문득 정신을 차리고, 하나씩 해보기로 결심했다.</p>
<hr>
<p>운이 좋게도 나는 빠르게 취업할 수 있었다.</p>
<p>하지만 독학으로 <strong>'당장 필요한 기술'만</strong> 익혔던 나는 실무에서 기초 실력의 부족함을 크게 느꼈다. 그래서 다시 JavaScript부터 차근차근 공부하기 시작했다.</p>
<p>공부와 실무를 병행하며 사내 프로젝트를 진행하다 보니, 자연스럽게 <strong>프로덕트 안정화와 기능 개발</strong>에 관심이 생겼다.
오랜 시간 동안 다양한 라이브러리를 도입하고, 아키텍처 구조를 고민하며 컨벤션을 직접 기획했다.
이 모든 것을 바탕으로 대규모 리팩토링을 진행했고, 프론트엔드 안정화를 위해 밤낮을 고민했다.</p>
<p>작은 불편을 줄이기 위해 시작한 일이었지만, 점차 <strong>'더 개선할 수 있는 일은 무엇일까?'</strong> 를 스스로 묻기 시작했다.</p>
<hr>
<h3 id="proof-of-concept-기반-dx-개선"><a class="anchor" href="#proof-of-concept-기반-dx-개선">Proof of Concept 기반 DX 개선</a></h3>
<p>작게 시작한 사내 개선은 어느새 내 욕심이 되어 있었고, 그만큼 더 깊은 학습을 필요로 했다. JS조차 끝이 없는 세계지만, 완벽하지 않은 상태에서도 나는 다음 단계(Infra와 DevOps)로 나아갔다.</p>
<p>Docker로 이미지를 만들고, CI/CD 환경을 구성하기 위한 공부를 시작했고, 공부한 내용을 바탕으로 사내에서 PoC를 진행했다. 몇 주간의 노력 끝에 눈에 보이는 성과를 만들어냈지만, 곧 의문이 들기 시작했다.</p>
<p><strong>“이게 정말 효과적인 개선일까? 내가 제대로 해낸 게 맞을까?”</strong></p>
<p>앞서 배포환경에 대한 회고도 진행하지 못한채 새로운 문제들이 생겼다.</p>
<p>사내에서는 반복적인 문법 오류로 인한 이슈들이 끊임없이 발생했고, 각기 다른 개발자들이 일관성 없는 코드를 작성하며, 수정과 에러가 이어졌다.</p>
<p><strong>“이걸 어떻게 줄일 수 있을까?”</strong></p>
<p>그쯤 여러가지 라이브러리를 찾아보았고, 특히 <strong><a href="https://github.com/toss/es-toolkit">Toss의 es-toolkit</a>를</strong> 관심있게 살펴보았다.</p>
<p>그리고 <strong>사내에서 사용하는 문법을 통용하는 내부 패키지를 만들어보자</strong>는 결정을 내렸다. 그렇게 다시, 또 하나의 시도를 시작했다.</p>
<p>배우고 있는 JS, 성능 최적화, 추상화, 클라우드, 인프라 개념들이 정리가 안된채 머릿속을 휘저었지만, 진행하던 공부를 멈추고 프로젝트를 개발했고, 실사용까지 완료했다.</p>
<p>그 결과, 사내 패키지를 기반으로 코드 작성이 일관되면서 코드 리뷰가 수월해졌고, 개별 함수 단위로 구성된 내부 유틸 패키지를 통해 tree-shaking 부담도 줄었다.외부 라이브러리 의존도를 낮추면서, 신규 프로젝트 투입 시 학습 비용과 초기 에러 발생률도 줄어들었다</p>
<p>표면적으로는 성공이었다. 하지만 조직내에서 내 역할에 대한 검증을 해줄 리더의 부재로, 제대로 검증받을 기회는 없었다.</p>
<p>그래도 <strong>내가 문제를 찾고, 주도적으로 도전해볼 수 있다는 자신감</strong> 을 얻었다.</p>
<p>물론 여전히 어설프고, 새 개념이 등장하면 어색하고 두려운 나지만, 그 불안은 학습으로 어느 정도 극복할 수 있다는 사실도 함께 깨달았다.</p>
<hr>
<h3 id="어느덧-3년-차"><a class="anchor" href="#어느덧-3년-차">어느덧 3년 차.</a></h3>
<p>프론트엔드라는 직무뿐 아니라, 세상의 모든 직무는 끝없이 배워야 하는 길 위에 있다. 나는 그동안 두 번의 이직 준비를 했다. 한 번은 좋은 결과가 있었고, 지금은 많은 고민 속에 새로운 도전을 진행하고 있다.</p>
<p>두려움 속에서 멈춰도 괜찮다. 계획을 세우는 것도, 무작정 시작하는 것도 좋다. <strong>다만 실패를 두려워해서, 아무것도 하지 않는 것만은 피했으면 좋겠다.</strong></p>
<p>그동안 나는 수많은 책을 사고, 인강을 듣고, 스터디를 운영하고, 클라우드 자격증을 준비하며 시도했다. 그 과정에서 많은 시도들을 끝까지 이어가지 못했다. (물론, 일부는 <strong>‘끝’</strong> 이 어디인지조차 명확하지 않기에 단정짓기도 어렵다.)</p>
<p>그때는 그런 나를 <strong>‘실패했다’</strong> 고 생각했다. 하지만 그건 실패가 아니라, <strong>‘그저 과정이었다’</strong> 는 것을.</p>
<hr>
<h3 id="sipe"><a class="anchor" href="#sipe">SIPE</a></h3>
<p>올해 초, 나는 <strong>‘SIPE’</strong> 라는 동아리 활동을 마무리했다. <del>(물론 DND, DevOcean 등 다양한 동아리 활동도 진행했지만)</del></p>
<p>그곳에서 멋진 사람들을 만나며 <strong>자극받기도</strong> 했고, 비교 속에서 <strong>열등감을 느끼기도 했다.</strong></p>
<p>동아리는 나에게 큰 동기였고, 주도적으로 스터디와 프로젝트를 운영하며 성장의 체감도 분명했다.</p>
<p>하지만 지나친 의존을 끊기 위해 동아리를 그만두기로 결심했을 때, <strong>“혼자서도 잘할 수 있을까?”</strong> 하는 불안이 밀려왔다.</p>
<p>그리고 벌써 4개월이 지났다. 코딩 테스트와 TypeScript 공부는 자주 멈췄지만, 코드를 쓰고, 책을 읽고, 아키텍처와 도메인 분리를 고민하며 오픈소스 기여도 해보았다.</p>
<p>그동안 내가 느낀 것들을 정리해보면, 아주 단순하다.</p>
<blockquote>
<ul>
<li>무언가를 처음부터 잘하는 사람은 없다.</li>
<li>한 가지만 잘하는 것도 좋지만(물론 이 방법이 내가 생각하는 성공의 지름길이겠지만), 필요한 것을 찾아 다양하게 해보는 것도 충분히 멋지다</li>
<li>단, 자주 나를 객관화하고, 부족한 점을 정리해보자.</li>
<li>그리고 계속해보자. 끈기 있게. 포기하지 말자.</li>
</ul>
</blockquote>
<hr>
<h3 id="지금-어떤-선택을-하든-어떤-고민을-하든-불안한-건-너무나-당연하다"><a class="anchor" href="#지금-어떤-선택을-하든-어떤-고민을-하든-불안한-건-너무나-당연하다">지금 어떤 선택을 하든, 어떤 고민을 하든, 불안한 건 너무나 당연하다.</a></h3>
<p>올해의 절반이 지나가는 이 시점, 도전의 좌절과 빠르게 흘러간 시간에 회의감이 들 수도 있다.</p>
<p>하지만, 그것들은 결코 나쁜 시간이 아니다. 나이키에서 자주 본 문구처럼, <strong>Just do it.</strong> 그저 해보자.</p>
<p>그러면 언젠가 우리는, 지금보다 훨씬 더 나은 사람이 되어 있을 것이다.</p>
<hr>
<h3 id="이-글을-마무리-하며"><a class="anchor" href="#이-글을-마무리-하며">이 글을 마무리 하며</a></h3>
<p>하루를 마칠 때면 스스로에게 이렇게 말한다.</p>
<p><strong>“짧은 시간이지만, 정말 많은 걸 해냈어. 오늘도 수고했어.”</strong></p>
<hr>
<p>그리고 아침이 되면, 나에게 말한다.</p>
<p><strong>"그만 자고 일어나"</strong></p>
<hr>
<p>나는 <strong>끊임없이 불안을 느끼는 사람이지만, 그만큼 계속 무엇인가 하는 사람</strong>이다.</p>
<hr>
<h2 id="그래-나는-t-다"><a class="anchor" href="#그래-나는-t-다">그래. 나는 <strong>T</strong> 다.</a></h2>
<hr>
<p>올해 절반을 보내며 스스로에 대한 정리로 글을 마무리 해보겠다.</p>
<h3 id="내가-느끼는-부족함"><a class="anchor" href="#내가-느끼는-부족함">내가 느끼는 부족함</a></h3>
<ul>
<li>논리적인 대화</li>
<li>어려운 코드 읽기</li>
<li>프로그래밍 언어, CS 지식</li>
<li>한 가지 목표를 끝까지 끌고 가는 끈기</li>
<li>비즈니스 도메인 분리 및 아키텍처 설계를 위한 코드 작성 역량</li>
</ul>
<h3 id="올해-남은-시간-동안-내가-해보고-싶은-일"><a class="anchor" href="#올해-남은-시간-동안-내가-해보고-싶은-일">올해 남은 시간 동안 내가 해보고 싶은 일</a></h3>
<ul>
<li>발표 많이 하기</li>
<li>오픈 소스 많이 읽고, 생산성 높여주는 라이브러리 주도적 개발해보기</li>
<li><strong>코드 리뷰 많이 하기 (부족하지만 코드리뷰 요청 해주시면 열심히 해보겠다..)</strong></li>
<li><strong>‘추상화와 분리’</strong> 를 고려해서 구현 많이 해보기 (이런 주제에 관심이 있으시면 언제든지 연락해주세요)</li>
<li>건강 챙기기 (제일 중요한데 제일 못한다. 나는 돼지다.)</li>
</ul>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>회고</category>
        </item>
        <item>
            <title><![CDATA[React Fiber 완전 정복]]></title>
            <link>https://hooninedev.com/250520</link>
            <guid isPermaLink="false">https://hooninedev.com/250520</guid>
            <pubDate>Tue, 20 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[이번 포스팅에서는 React의 심장이라 할 수 있는 Fiber 아키텍처에 대한 이야기를 해보려고 한다. 필자가 React를 처음 접했을 때, "Fiber" 라는 단어는 면접 단골 질문 정도로만 인식되었다. "React의 렌더를 위해 작업 단위를 나눠서 처리"라는 한 줄짜리 정의를 외우고, 그게 전부인 줄 알았다. 하지만 실제로 React의 소스코드를 들여다...]]></description>
            <content:encoded><![CDATA[<p>이번 포스팅에서는 React의 심장이라 할 수 있는 <strong>Fiber 아키텍처</strong>에 대한 이야기를 해보려고 한다.</p>
<p>필자가 React를 처음 접했을 때, <strong>"Fiber"</strong> 라는 단어는 면접 단골 질문 정도로만 인식되었다. "React의 렌더를 위해 작업 단위를 나눠서 처리"라는 한 줄짜리 정의를 외우고, 그게 전부인 줄 알았다. 하지만 실제로 React의 소스코드를 들여다보기 시작하면서, Fiber가 단순한 개념이 아니라 React 렌더링의 <strong>모든 것</strong>을 관장하는 런타임 아키텍처라는 사실을 깨닫게 되었다.</p>
<blockquote>
<p>그때 React 소스코드를 처음 열었을 때의 충격은 아직도 잊을 수 없다. "이게... 다 뭐지?" 싶었다.</p>
</blockquote>
<p>이 글에서는 "Fiber가 뭐예요?"라는 질문에 "작업 단위를 나눠서 처리하는 거요"라고 대답하는 수준을 넘어, Fiber가 <strong>왜</strong> 탄생했고, <strong>어떻게</strong> 설계되었으며, 그 구조가 React의 Concurrent Features를 <strong>어떻게</strong> 가능하게 만드는지까지 깊이 있게 파헤쳐 보려 한다.</p>
<hr>
<h2 id="왜-fiber가-등장했을까"><a class="anchor" href="#왜-fiber가-등장했을까">왜 Fiber가 등장했을까?</a></h2>
<p>이 질문에 답하려면 먼저 Fiber 이전의 세상, 즉 React 15까지 사용되던 <strong>Stack Reconciler</strong>가 어떤 문제를 가지고 있었는지 이해해야 한다.</p>
<p>Stack Reconciler는 이름 그대로 <strong>재귀(recursive) 호출 기반</strong>의 재조정 엔진이었다. 컴포넌트 트리를 위에서 아래로 재귀적으로 순회하면서, 한 번 렌더링을 시작하면 전체 트리를 끝까지 처리해야만 멈출 수 있었다. 이것은 마치 전화 통화 중에 상대방이 말을 끝낼 때까지 절대 끊을 수 없는 것과 같은 상황이었다. (상대방이 3시간짜리 인생 상담을 시작했는데 중간에 끊을 수 없다고 생각해보라. 끔찍하다.)</p>
<p>Stack Reconciler은 구체적으로 다음과 같은 한계가 있었다.</p>
<ul>
<li><strong>렌더링 중 중단 불가</strong>: 모든 트리를 한 번에 처리해야 했으므로, 복잡한 UI에서는 메인 스레드가 수십~수백 밀리초 동안 점유되었다</li>
<li><strong>우선순위 개념 부재</strong>: 사용자가 버튼을 클릭하든, 백그라운드 데이터가 갱신되든, 모든 업데이트가 동일한 방식으로 처리되었다</li>
<li><strong>애니메이션/제스처 대응 어려움</strong>: 60fps를 유지하려면 한 프레임당 약 16ms 안에 모든 작업이 끝나야 하는데, 재귀 렌더링은 이를 보장할 수 없었다</li>
<li><strong>에러 발생 시 전체 앱 중단</strong>: 컴포넌트 트리 어딘가에서 에러가 발생하면 전체 앱이 멈추는 문제가 있었다</li>
</ul>
<p>이러한 한계를 극복하기 위해 React 팀은 작업을 <strong>쪼개고</strong>, <strong>우선순위를 매기고</strong>, 필요하면 <strong>중단하고 재개</strong>할 수 있는 새로운 실행 모델을 고민했다. 그리고 그 결과물이 바로 <strong>React Fiber</strong>인 것이다.</p>
<p>Andrew Clark가 작성한 <a href="https://github.com/acdlite/react-fiber-architecture">react-fiber-architecture</a> 문서는 이 설계의 핵심 사상을 담고 있으며, Fiber를 이해하는 데 가장 중요한 참고 자료다. (이 문서를 작성하고 얼마 안 되어 React 팀에 합류한 것으로 보인다.)</p>
<hr>
<h2 id="stack-vs-fiber"><a class="anchor" href="#stack-vs-fiber">Stack vs Fiber</a></h2>
<p>그렇다면 Stack Reconciler와 Fiber Reconciler는 코드 수준에서 어떻게 다른 것일까?</p>
<h3 id="재귀-기반의-stack-reconciler"><a class="anchor" href="#재귀-기반의-stack-reconciler">재귀 기반의 Stack Reconciler</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> renderComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">component</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> element</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> component.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">render</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  element.props.children.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">forEach</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">child</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> renderComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(child)); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 재귀 호출</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>Stack 방식은 이렇게 자식 컴포넌트를 만나면 <strong>즉시 재귀 호출</strong>로 들어간다. 이 방식의 문제는 JavaScript의 콜 스택(call stack)에 직접 의존한다는 점이다. 재귀 호출이 깊어지면 콜 스택에 프레임이 쌓이고, 이 모든 프레임이 해소될 때까지 브라우저의 메인 스레드는 다른 일을 할 수 없다.</p>
<p>쉽게 말해, 콜 스택이 비워질 때까지 브라우저는 <strong>꼼짝도 할 수 없는</strong> 상태가 되는 것이다.</p>
<video width="640" height="480" controls>
  <source src="/content/250520/stack.mov" type="video/mp4">
</video>
<p>위 영상을 보면 Stack Reconciler가 렌더링하는 동안 메인 스레드가 완전히 블로킹되는 모습을 확인할 수 있다.</p>
<hr>
<h3 id="반복-기반의-fiber-reconciler"><a class="anchor" href="#반복-기반의-fiber-reconciler">반복 기반의 Fiber Reconciler</a></h3>
<p>Fiber는 재귀를 <strong>반복(iterative loop)</strong> 으로 대체했다. 콜 스택 대신 <strong>자체적인 가상 스택</strong>을 메모리 위에 구현한 것이다. 각 Fiber 노드가 곧 하나의 "스택 프레임"이 되며, 이 노드들은 JavaScript 객체(힙 메모리)에 존재하기 때문에 언제든 중단하고 나중에 다시 이어갈 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> performWork</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">deadline</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  while</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (nextUnitOfWork </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> deadline.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">timeRemaining</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 5</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    nextUnitOfWork </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> performUnitOfWork</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(nextUnitOfWork);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  requestIdleCallback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(performWork); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 나눠서 실행</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>위 코드는 Fiber의 초기 개념 모델을 보여준다. 핵심은 <code>while</code> 루프 안에서 한 번에 하나의 작업 단위(unit of work)만 처리하고, 시간이 부족하면 루프를 빠져나와 브라우저에게 제어권을 돌려준다는 것이다.</p>
<p>(초기에는 <code>requestIdleCallback</code>을 활용하는 방식이었지만, 실제 React는 이를 사용하지 않는다. 이유는 뒤에서 자세히 다룬다.)</p>
<video width="640" height="480" controls>
  <source src="/content/250520/fiber.mov" type="video/mp4">
</video>
<p>Fiber 방식에서는 렌더링 중에도 사용자 이벤트(버튼 클릭, 타이핑 등)에 즉시 반응할 수 있다. 작업을 잘게 쪼개 실행하기 때문에, 브라우저가 숨 쉴 틈이 생기는 것이다.</p>
<p>두 방식의 차이를 직접 체험해보고 싶다면 <strong><a href="https://animated-lollipop-2b6cbb.netlify.app/" target="_blank" rel="noopener noreferrer">여기를</a></strong> 클릭하면 된다. Stack Reconciler와 Fiber Reconciler의 동작 차이를 눈으로 확인할 수 있다.</p>
<p>이것이 바로 Andrew Clark이 문서에서 강조한 Fiber의 핵심 목표다.</p>
<ul>
<li><strong>작업을 일시 중지하고 나중에 다시 돌아올 수 있다</strong></li>
<li><strong>서로 다른 유형의 작업에 우선순위를 부여할 수 있다</strong></li>
<li><strong>이전에 완료된 작업을 재사용할 수 있다</strong></li>
<li><strong>더 이상 필요 없는 작업을 중단할 수 있다</strong></li>
</ul>
<hr>
<h2 id="fiber-node-내부-구조"><a class="anchor" href="#fiber-node-내부-구조">Fiber Node 내부 구조</a></h2>
<p>여기까지 읽으면 자연스럽게 떠오르는 질문이 하나 있다. "그래서 Fiber 노드는 내부적으로 어떻게 생겼는가?"</p>
<p>React 팀은 Fiber의 내부 구현에 대한 공식 문서를 별도로 제공하지 않고 있다. 하지만 Andrew Clark의 <a href="https://github.com/acdlite/react-fiber-architecture">react-fiber-architecture</a> 문서와 실제 React 소스코드(<code>ReactFiber.js</code>)를 통해 그 구조를 파악할 수 있다.</p>
<p>Fiber 노드를 필자는 <strong>작업 지시서(Work Order)</strong> 에 비유하고 싶다. 공장에서 제품을 조립할 때, 각 작업 지시서에는 "이 부품이 어떤 종류인지", "어떤 재료를 사용하는지", "다음에 어떤 작업을 해야 하는지", "우선순위는 어떤지"가 적혀 있다. Fiber 노드도 마찬가지다.</p>
<hr>
<h3 id="reactelement와-fibernode"><a class="anchor" href="#reactelement와-fibernode">ReactElement와 FiberNode</a></h3>
<p>Fiber를 이해하려면 먼저 <strong>ReactElement</strong>와 <strong>FiberNode</strong>를 구분해야 한다. 이 둘은 자주 혼동되지만 전혀 다른 존재다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// ReactElement — React.createElement()가 반환하는 가벼운 객체</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ReactElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  type</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 문자열(HTML 태그) 또는 함수(컴포넌트)</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  props</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    [</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">key</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">    children</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ReactElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  key</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  ref</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  _owner</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FiberNode</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>ReactElement는 UI의 <strong>설계도</strong>에 불과하다. "이런 컴포넌트를 이런 props로 렌더링해달라"는 요청서일 뿐, 실제 렌더링 로직이나 상태는 담고 있지 않다.</p>
<p>반면 <strong>FiberNode</strong>는 이 설계도를 바탕으로 React가 내부적으로 생성하는 <strong>런타임 작업 단위</strong>다. ReactElement에는 없는 <code>tag</code>, <code>stateNode</code>, <code>child/sibling/return</code>, <code>memoizedState</code>, <code>updateQueue</code>, <code>lanes</code> 같은 필드들이 여기에 존재한다.</p>
<p>React가 ReactElement의 <code>type</code>을 보고 FiberNode를 생성할 때, <strong>tag</strong> 값이 결정된다.</p>
<ul>
<li><code>type</code>이 함수이고 <code>prototype.isReactComponent</code>가 있으면 → <code>tag = ClassComponent(1)</code></li>
<li><code>type</code>이 함수이면 → <code>tag = FunctionComponent(0)</code></li>
<li><code>type</code>이 문자열(<code>"div"</code> 등)이면 → <code>tag = HostComponent(5)</code></li>
</ul>
<p><strong>tag</strong>는 FiberNode의 종류를 나타내는 숫자 상수다. <code>ReactWorkTags.js</code>에 정의되어 있으며, <code>FunctionComponent(0)</code>, <code>ClassComponent(1)</code>, <code>HostRoot(3)</code>, <code>HostComponent(5)</code>, <code>HostText(6)</code> 등 약 25가지 이상의 태그가 존재한다. React는 이 tag 값을 기반으로 <code>beginWork</code>에서 어떤 처리 로직을 실행할지 결정하는 것이다.</p>
<p><strong>type</strong>은 재조정(reconciliation) 과정에서 핵심적인 역할을 한다. React가 이전 렌더링의 Fiber와 새 엘리먼트를 비교할 때 <strong>가장 먼저 확인하는 것</strong>이 바로 type이다. (이 값은 ReactElement에서 FiberNode로 그대로 전달된다.)</p>
<ul>
<li>이전에도 <code>div</code>였고 이번에도 <code>div</code>라면, React는 해당 Fiber 노드를 <strong>재사용</strong>하여 props만 업데이트한다</li>
<li>이전에는 <code>div</code>였는데 이번에는 <code>span</code>으로 바뀌었다면, React는 기존 Fiber를 <strong>버리고</strong> 새 Fiber를 생성한다</li>
</ul>
<p><strong>key</strong> 역시 ReactElement에서 FiberNode로 전달되는 값으로, 주로 리스트(배열) 렌더링 시에 사용된다. key가 없으면 React는 리스트 아이템의 순서가 변경되었을 때 어떤 아이템이 어디로 이동했는지 정확히 알 수 없다. 이로 인해 불필요한 DOM 조작이 발생하거나, 컴포넌트의 내부 상태가 의도치 않게 유지 또는 소실될 수 있다.</p>
<hr>
<h3 id="child-sibling-return"><a class="anchor" href="#child-sibling-return">child, sibling, return</a></h3>
<p>React Fiber가 재귀 대신 반복을 사용할 수 있는 비밀이 바로 여기에 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="js" data-theme="github-dark github-light"><code data-language="js" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> 부모</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">자식1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">/>, &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">자식2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">/>];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p><strong>child</strong>는 컴포넌트의 render가 반환한 <strong>첫 번째</strong> 자식 요소를 가리킨다. 위 예제에서는 <code>&#x3C;자식1/></code>이 해당된다. <strong>sibling</strong>은 동일한 부모를 가진 <strong>다음 형제</strong> 요소를 의미한다. <code>&#x3C;자식1/></code>의 sibling은 <code>&#x3C;자식2/></code>이다. <strong>return</strong>은 현재 Fiber 노드의 처리가 끝난 뒤 <strong>되돌아갈 부모</strong> Fiber를 가리킨다. <code>&#x3C;자식1/></code>과 <code>&#x3C;자식2/></code>의 return은 모두 <code>부모</code>이다.</p>
<p>이 세 필드가 만들어내는 구조는 <strong>단일 연결 리스트(Singly Linked List) 형태의 트리</strong>다. 일반적인 트리 구조에서는 자식 배열(<code>children[]</code>)을 두는 것이 직관적이지만, Fiber는 의도적으로 이를 피했다.</p>
<p>왜일까? 배열 기반의 자식 구조에서는 순회를 위해 인덱스를 관리해야 하고, 중간에 중단했다가 재개할 때 "어디까지 처리했는지"를 별도로 추적해야 한다. 반면 linked list 구조에서는 현재 노드의 참조만 기억하면 언제든 이어서 순회할 수 있다. 이것이 Fiber가 <strong>중단과 재개</strong>를 자연스럽게 지원할 수 있는 구조적 기반인 것이다.</p>
<p>React는 이 구조를 기반으로 깊이 우선 탐색(DFS) 순서로 노드를 순회한다. <code>child</code>를 따라 내려가고(beginWork), 리프 노드에 도달하면 <code>sibling</code>을 확인하고, 형제가 없으면 <code>return</code>을 따라 올라가는(completeWork) 방식이다.</p>
<hr>
<h3 id="pendingprops와-memoizedprops"><a class="anchor" href="#pendingprops와-memoizedprops">pendingProps와 memoizedProps</a></h3>
<p><strong>pendingProps</strong>는 해당 Fiber가 처리를 시작할 시점에 전달된 <strong>새로운 props</strong>를 의미하며, <strong>memoizedProps</strong>는 이전 렌더링에서 처리가 완료된 <strong>이전 props</strong>를 나타낸다.</p>
<p>이 두 값이 동일하다면, React는 "이 컴포넌트에는 변경이 없다"고 판단하여 이전 렌더링 결과를 그대로 재사용할 수 있다. 이것이 바로 <strong>bailout 최적화</strong>의 핵심 메커니즘이다.</p>
<p>마찬가지로 <strong>memoizedState</strong>는 해당 Fiber의 훅(hooks) 상태를 저장하며, <strong>updateQueue</strong>는 아직 처리되지 않은 상태 업데이트(setState 호출들)를 연결 리스트로 관리한다.</p>
<hr>
<h3 id="statenode"><a class="anchor" href="#statenode">stateNode</a></h3>
<p><strong>stateNode</strong>는 Fiber 노드가 가리키는 <strong>실제 인스턴스</strong>를 참조한다.</p>
<ul>
<li><strong>HostComponent</strong>(div, span 등)의 경우: 실제 DOM 노드</li>
<li><strong>ClassComponent</strong>의 경우: 클래스 인스턴스</li>
<li><strong>HostRoot</strong>의 경우: FiberRoot 객체</li>
</ul>
<p>이 필드는 Fiber의 가상 세계와 브라우저의 실제 DOM을 연결하는 다리 역할을 한다.</p>
<hr>
<h2 id="더블-버퍼링-current-트리와-workinprogress-트리"><a class="anchor" href="#더블-버퍼링-current-트리와-workinprogress-트리">더블 버퍼링: current 트리와 workInProgress 트리</a></h2>
<p>Fiber를 이해하는 데 있어 빠뜨릴 수 없는 핵심 개념이 바로 **더블 버퍼링(Double Buffering)**이다.</p>
<p>이 개념을 이해하기 위해 게임 그래픽을 떠올려보자. 게임에서 화면을 그릴 때, 현재 화면에 직접 픽셀을 그리면 반쯤 그려진 프레임이 사용자에게 보이는 <strong>화면 깜빡임(tearing)</strong> 현상이 발생한다. 이를 방지하기 위해 게임 엔진은 <strong>두 개의 버퍼</strong>를 사용한다. 하나의 버퍼에 다음 프레임을 완전히 그린 후, 완성되면 화면에 표시되는 버퍼를 한 번에 교체하는 것이다.</p>
<p>React Fiber도 정확히 같은 전략을 사용한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="js" data-theme="github-dark github-light"><code data-language="js" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">currentFiber.alternate </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> workInProgressFiber;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">workInProgressFiber.alternate </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> currentFiber;</span></span></code></pre></figure>
<p><strong>current 트리</strong>는 현재 화면에 반영되어 있는 Fiber 트리. 사용자가 보고 있는 UI의 상태를 나타내고, <strong>workInProgress 트리</strong>는 다음 렌더링을 위해 백그라운드에서 준비 중인 Fiber 트리를 나타낸다.</p>
<p>두 트리는 <code>alternate</code> 속성으로 서로를 참조한다. 모든 변경 작업은 workInProgress 트리에서 수행되며, 작업이 완료되면 <code>root.current = finishedWork</code> 한 줄로 트리가 교체된다. 이전의 workInProgress가 새로운 current가 되고, 이전의 current는 다음 렌더링에서 workInProgress로 재활용되는 것이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="js" data-theme="github-dark github-light"><code data-language="js" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createWorkInProgress</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">current</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">pendingProps</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> workInProgress </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> current.alternate;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (workInProgress </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 최초 렌더: 새 Fiber를 생성하고 alternate를 연결</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    workInProgress </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createFiber</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(current.tag, pendingProps, current.key, current.mode);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    workInProgress.stateNode </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> current.stateNode; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// DOM 노드는 공유!</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    workInProgress.alternate </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> current;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    current.alternate </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> workInProgress;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 재렌더: 기존 alternate를 재사용, effect만 초기화</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    workInProgress.pendingProps </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> pendingProps;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    workInProgress.flags </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> NoFlags;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    workInProgress.subtreeFlags </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> NoFlags;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    workInProgress.deletions </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // lanes, child, memoizedState 등을 복사</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  workInProgress.childLanes </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> current.childLanes;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  workInProgress.child </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> current.child;</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // ...</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>여기서 핵심을 짚어보자. <code>stateNode</code>(실제 DOM 노드)는 current와 workInProgress 사이에서 <strong>공유</strong>된다. Fiber 객체를 매번 새로 만드는 것이 아니라, 기존 alternate를 재사용하면서 변경된 필드만 업데이트한다. 이 덕분에 매 렌더마다 가비지 컬렉션(GC) 부담 없이 효율적으로 트리를 구성할 수 있는 것이다.</p>
<p>만약 props나 state에 변경이 없다면? 해당 서브트리를 통째로 건너뛰는 <strong>bailout 최적화</strong>가 가능해진다. 게임의 더블 버퍼링이 프레임 단위의 최적화라면, Fiber의 더블 버퍼링은 <strong>컴포넌트 단위의 최적화</strong>까지 가능하게 만드는 것이다.</p>
<hr>
<h2 id="pendingworkpriority--lanes"><a class="anchor" href="#pendingworkpriority--lanes">pendingWorkPriority => Lanes</a></h2>
<p>그렇다면 Fiber는 어떻게 "이 작업이 더 중요하다"는 것을 판단하는 것일까?</p>
<h3 id="expirationtime의-한계"><a class="anchor" href="#expirationtime의-한계">expirationTime의 한계</a></h3>
<p>초기 Fiber는 <code>pendingWorkPriority</code>라는 숫자 기반의 우선순위를 사용했고, 이후 <code>expirationTime</code>이라는 단일 숫자로 발전했다. 만료 시간이 가까울수록 높은 우선순위를 의미했는데, 이 방식에는 근본적인 한계가 있었다.</p>
<p>단일 숫자로는 "이 업데이트는 A 그룹에 속하고, 저 업데이트는 B 그룹에 속한다"는 식의 <strong>유연한 분류가 불가능</strong>했기 때문이다. 예를 들어 사용자 입력과 Transition 업데이트가 동시에 발생했을 때, expirationTime 기반에서는 범위(range) 비교로만 분류할 수 있었고, 특정 업데이트만 선택적으로 처리하는 데 한계가 있었다.</p>
<h3 id="lane"><a class="anchor" href="#lane">Lane</a></h3>
<p>이 문제를 해결하기 위해 Andrew Clark이 <a href="https://github.com/facebook/react/pull/18796">PR #18796</a>에서 도입한 것이 <strong>Lane 시스템</strong>이다.</p>
<p>Lane을 이해하기 위해 <strong>고속도로</strong>를 떠올려보자. 고속도로에는 여러 차선(lane)이 있고, 각 차선은 서로 다른 용도를 가진다. 1차선은 추월 차선(긴급), 2차선은 주행 차선(일반), 갓길은 비상용이다. 각 차량(업데이트)은 자신의 성격에 맞는 차선에 배정되고, 고속도로 관리 시스템(스케줄러)은 어떤 차선의 차량을 먼저 통과시킬지 결정한다.</p>
<p>React의 Lane도 이와 같다. 각 업데이트에 <strong>비트 하나(lane)</strong> 를 할당하고, 비트 연산으로 그룹을 만들고 비교하는 것이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="js" data-theme="github-dark github-light"><code data-language="js" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 각 업데이트는 하나의 lane(단일 비트)을 가진다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> SyncLane</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">             /*  */</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0b0000000000000000000000000000010</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> InputContinuousLane</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  /*  */</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0b0000000000000000000000000001000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> DefaultLane</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">          /*  */</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0b0000000000000000000000000100000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> TransitionLane1</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">      /*  */</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0b0000000000000000000000100000000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> IdleLane</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">             /*  */</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0b0001000000000000000000000000000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 배치(batch)는 여러 비트의 OR 조합이다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> SyncUpdateLanes</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> SyncLane </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> InputContinuousLane </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> DefaultLane;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 특정 lane이 batch에 포함되는지 확인은 단순 비트 연산</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> isIncluded</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (lane </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> lanes) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span></code></pre></figure>
<p>총 31개의 lane이 31비트 정수에 들어가도록 설계되어 있는데, 이는 V8 엔진의 <strong>SMI(Small Integer)</strong> 최적화를 활용하기 위함이다. 31비트 이하의 정수는 V8에서 포인터 태깅으로 처리되어 힙 할당 없이 스택에서 직접 연산할 수 있다. 주요 lane의 우선순위는 <strong>낮은 비트일수록 높다</strong>.</p>
<p>이 구조 덕분에 React는 비트 연산 한 번으로 어떤 작업을 먼저 처리할지 결정할 수 있게 되었다. <code>getNextLanes()</code> 함수는 <code>pendingLanes</code>에서 가장 높은 우선순위의 lane 그룹을 골라내고, 중단된(suspended) lane은 건너뛰며, 데이터를 받은(pinged) lane은 우선 재시도하는 등의 정교한 스케줄링이 가능해진 것이다.</p>
<p>또한 <strong>기아 상태(starvation) 방지</strong>를 위해 각 lane에 만료 시간이 부여된다. Sync/InputContinuous는 250ms, Transition은 5000ms가 지나면 <code>expiredLanes</code>에 추가되어 동기적으로 강제 처리된다. 아무리 우선순위가 낮아도 영원히 무시당하지는 않는다는 뜻이다. (우선순위가 낮다고 영원히 무시당하면 그건 우선순위 시스템이 아니라 차별 시스템이다.)</p>
<hr>
<h2 id="fiber의-output"><a class="anchor" href="#fiber의-output">Fiber의 output</a></h2>
<p>여기까지 Fiber의 구조를 살펴봤다면, 이제 궁금해지는 것이 하나 있다. 이 Fiber 노드들이 어떻게 <strong>실제 DOM</strong>으로 변환되는 것일까?</p>
<p>output은 실제 DOM에 적용될 수 있는 구체적인 DOM 노드 정보를 의미한다. 여기서 중요한 구분이 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 사용자 정의 컴포넌트 — output 없음</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> 아바타</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">img</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> src</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"profile.jpg"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> />;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 호스트 컴포넌트 — output 생성</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">img</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> src</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"profile.jpg"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> /></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> className</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"프로필"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> /></span></span></code></pre></figure>
<p><strong>호스트 컴포넌트</strong>(div, span, img 등)만 실제 DOM 노드를 생성한다. 브라우저는 <code>&#x3C;아바타/></code>가 뭔지 모른다. 사용자 정의 컴포넌트는 추상화된 개념이기 때문에, 결국 호스트 컴포넌트로 분해되어야 브라우저가 이해할 수 있다.</p>
<p>이 과정을 좀 더 구체적으로 살펴보자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> 프로필</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> className</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"프로필"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">아바타</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> /></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">유저정보</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> /></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> 아바타</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">img</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> src</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"profile.jpg"</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> alt</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"프로필"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> />;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> 유저정보</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">h2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>홍길동&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">h2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">p</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>개발자&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">p</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이 컴포넌트들이 만들어내는 Fiber 트리와 output의 관계는 다음과 같다.</p>
<pre><code>프로필 (출력: 없음, 컴포넌트 함수)
  │
  └─► div.프로필 (출력: &#x3C;div class="프로필">...&#x3C;/div>)
       │
       ├─► 아바타 (출력: 없음, 컴포넌트 함수)
       │    │
       │    └─► img (출력: &#x3C;img src="profile.jpg" alt="프로필">)
       │
       └─► 유저정보 (출력: 없음, 컴포넌트 함수)
            │
            └─► div (출력: &#x3C;div>...&#x3C;/div>)
                 │
                 ├─► h2 (출력: &#x3C;h2>홍길동&#x3C;/h2>)
                 │
                 └─► p (출력: &#x3C;p>개발자&#x3C;/p>)
</code></pre>
<p>output 수집은 <strong>아래에서 위로</strong> 진행된다. 먼저 리프(호스트) 노드에서 DOM이 생성된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="js" data-theme="github-dark github-light"><code data-language="js" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 호스트 컴포넌트들이 실제 DOM 정보 생성</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">img_fiber.output </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createDOMElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'img'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  src: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'profile.jpg'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  alt: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'프로필'</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">h2_fiber.output </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createDOMElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'h2'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, {}, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'홍길동'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">p_fiber.output </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createDOMElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'p'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, {}, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'개발자'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span></code></pre></figure>
<p>그 다음 부모 호스트 컴포넌트가 자식들의 output을 수집한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="js" data-theme="github-dark github-light"><code data-language="js" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// div 노드가 자식들의 출력을 수집</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">유저정보_div_fiber.output </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createDOMElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'div'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, {}, [</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  h2_fiber.output,  </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// &#x3C;h2>홍길동&#x3C;/h2></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  p_fiber.output    </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// &#x3C;p>개발자&#x3C;/p></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 최상위 div가 모든 자식 출력을 수집</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">프로필_div_fiber.output </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createDOMElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'div'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, {className: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'프로필'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, [</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  img_fiber.output,           </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// &#x3C;img src="profile.jpg" alt="프로필"></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  유저정보_div_fiber.output   </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// &#x3C;div>&#x3C;h2>홍길동&#x3C;/h2>&#x3C;p>개발자&#x3C;/p>&#x3C;/div></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]);</span></span></code></pre></figure>
<p>마지막으로, 사용자 정의 컴포넌트는 자식의 output을 그대로 전달한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="js" data-theme="github-dark github-light"><code data-language="js" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 사용자 정의 컴포넌트는 자식의 출력을 위로 전달</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">아바타_fiber.output </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> img_fiber.output;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">유저정보_fiber.output </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> 유저정보_div_fiber.output;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">프로필_fiber.output </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> 프로필_div_fiber.output;</span></span></code></pre></figure>
<hr>
<h2 id="fiber의-스케줄링"><a class="anchor" href="#fiber의-스케줄링">Fiber의 스케줄링</a></h2>
<p>Fiber의 핵심 가치가 "작업을 나눌 수 있다"는 것이라면, 실제로 그 "나누기"를 수행하는 곳은 어디일까? 바로 <strong>Work Loop</strong>이다.</p>
<h3 id="work-loop-fiber-순회의-심장"><a class="anchor" href="#work-loop-fiber-순회의-심장">Work Loop: Fiber 순회의 심장</a></h3>
<p>React의 렌더링은 <code>ReactFiberWorkLoop.js</code>에 정의된 Work Loop에서 시작된다. React는 상황에 따라 두 가지 Work Loop를 사용한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="js" data-theme="github-dark github-light"><code data-language="js" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 동기 렌더링: 중단 없이 모든 Fiber를 처리</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> workLoopSync</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  while</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (workInProgress </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    performUnitOfWork</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(workInProgress);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 동시성 렌더링: 시간 제한 내에서 작업을 나누어 처리</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> workLoopConcurrent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">nonIdle</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (workInProgress </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> yieldAfter</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> now</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (nonIdle </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 25</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> :</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 5</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    do</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      performUnitOfWork</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(workInProgress);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">while</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (workInProgress </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> &#x26;&#x26;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> now</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> yieldAfter);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>두 함수의 차이를 주목하라. <code>workLoopSync</code>는 <code>workInProgress</code>가 <code>null</code>이 될 때까지 <strong>무조건</strong> 돈다. 반면 <code>workLoopConcurrent</code>는 <strong>시간 제한</strong>을 두고, 시간이 초과되면 루프를 빠져나온다.</p>
<p>여기서 흥미로운 것은 yield 간격의 차이다. Transition이나 Retry 같은 <strong>non-idle 작업(사용자가 체감할 수 있는 업데이트)</strong> 은 <strong>25ms</strong> 간격으로 양보하고, <strong>idle 작업(사용자가 아무것도 안 하고 있을 때 처리해도 되는 낮은 우선순위 작업)</strong> 은 <strong>5ms</strong> 간격으로 양보한다. non-idle 작업에 25ms를 부여하는 이유는 의도적으로 애니메이션을 약 30fps 수준으로 제한하여, transition 렌더링이 다른 작업을 기아 상태로 만드는 것을 방지하기 위함이다.</p>
<hr>
<h3 id="performunitofwork"><a class="anchor" href="#performunitofwork">performUnitOfWork</a></h3>
<p><code>performUnitOfWork</code>는 하나의 Fiber 노드를 처리하는 함수다. Fiber 순회의 핵심이 이 함수에 담겨 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="js" data-theme="github-dark github-light"><code data-language="js" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> performUnitOfWork</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">unitOfWork</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> current</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> unitOfWork.alternate;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> next</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> beginWork</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(current, unitOfWork, renderLanes);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  unitOfWork.memoizedProps </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> unitOfWork.pendingProps;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (next </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    workInProgress </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> next;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    completeUnitOfWork</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(unitOfWork);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p><code>beginWork</code>는 현재 노드를 처리하고 첫 번째 자식을 반환한다. 그리고 <code>pendingProps</code>를 <code>memoizedProps</code>로 확정하고 자식이 있으면 자식으로, 없으면 <code>completeUnitOfWork</code>를 호출한다</p>
<hr>
<h3 id="beginwork"><a class="anchor" href="#beginwork">beginWork</a></h3>
<p><code>beginWork</code>는 Fiber 노드를 위에서 아래로 순회하며, 각 노드에서 필요한 계산을 수행하는 함수다. <code>ReactFiberBeginWork.js</code>에 정의되어 있으며, 내부적으로 Fiber의 <code>tag</code>에 따라 거대한 <strong>switch문</strong>으로 분기한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="js" data-theme="github-dark github-light"><code data-language="js" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> beginWork</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">current</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">workInProgress</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">renderLanes</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // bailout 체크: props와 context가 변경되지 않았다면 스킵</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (current </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> oldProps</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> current.memoizedProps;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> newProps</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> workInProgress.pendingProps;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (oldProps </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> newProps </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> !</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">hasContextChanged</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> bailoutOnAlreadyFinishedWork</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(current, workInProgress, renderLanes);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  switch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (workInProgress.tag) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    case</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> FunctionComponent:</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> updateFunctionComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(current, workInProgress, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    case</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ClassComponent:</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> updateClassComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(current, workInProgress, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    case</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> HostComponent:</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> updateHostComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(current, workInProgress, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    case</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> SuspenseComponent:</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> updateSuspenseComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(current, workInProgress, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // ... 약 25가지 이상의 케이스</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>핵심은 맨 위의 <strong>bailout 체크</strong>다. props와 context가 이전과 동일하다면 <code>bailoutOnAlreadyFinishedWork</code>로 해당 서브트리를 통째로 건너뛴다. 이것이 React의 성능 최적화에서 가장 중요한 경로 중 하나다.</p>
<p><code>beginWork</code>의 반환값은 <strong>첫 번째 자식 Fiber</strong>이다. 자식이 있으면 그 자식이 다음 <code>workInProgress</code>가 되고, 없으면(<code>null</code>) <code>completeUnitOfWork</code>로 진입한다.</p>
<hr>
<h3 id="completework"><a class="anchor" href="#completework">completeWork</a></h3>
<p><code>completeWork</code>는 리프 노드에서 시작하여 부모 방향으로 올라가며 작업을 마무리하는 함수다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="js" data-theme="github-dark github-light"><code data-language="js" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> completeUnitOfWork</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">unitOfWork</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> completedWork </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> unitOfWork;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  do</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 1. completeWork로 현재 노드의 작업 마무리 (DOM 생성 등)</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    completeWork</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(current, completedWork, renderLanes);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 2. 형제가 있으면 형제로 이동 (다시 beginWork 시작)</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> siblingFiber</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> completedWork.sibling;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (siblingFiber </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      workInProgress </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> siblingFiber;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 3. 형제가 없으면 부모로 올라감</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    completedWork </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> completedWork.return;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    workInProgress </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> completedWork;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">while</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (completedWork </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p><code>completeWork</code>에서 수행하는 주요 작업은 다음과 같다.</p>
<ul>
<li><strong>HostComponent의 경우</strong>: 실제 DOM 노드를 생성(<code>createInstance</code>)하고, 자식 DOM들을 append한다. 이미 존재하는 DOM이라면 변경된 props를 수집하여 <code>updateQueue</code>에 저장한다.</li>
<li><strong><code>bubbleProperties()</code></strong>: 자식들의 flags를 <code>subtreeFlags</code>로 집계한다. 이 정보는 Commit Phase에서 서브트리 스킵 최적화에 사용된다.</li>
</ul>
<p>순회를 정리하면 이렇다. <strong>child를 따라 내려가고(beginWork) -> 리프에서 완료 후 sibling으로 이동 -> 형제가 없으면 return을 따라 올라감(completeWork)</strong>. 이것이 Fiber의 깊이 우선 탐색 순서인 것이다.</p>
<hr>
<h3 id="requestidlecallback을-버린-이유"><a class="anchor" href="#requestidlecallback을-버린-이유">requestIdleCallback을 버린 이유</a></h3>
<p>앞서 Fiber의 개념 모델에서 <code>requestIdleCallback</code>을 사용하는 코드를 보여줬는데, 실제 React는 이를 사용하지 않는다. 그 이유는 명확하다.</p>
<ul>
<li><strong>호출 빈도가 너무 낮다</strong> : 진정한 "유휴 시간(브라우저가 할 일이 없는 시간)"에만 호출되어, 바쁜 페이지에서는 React 작업이 무한정 지연될 수 있다. Dan Abramov도 "requestIdleCallback is called too infrequently to be useful for scheduling React work"라고 언급한 바 있다.</li>
<li><strong>브라우저 호환성 문제</strong> : Safari는 오랫동안 이를 구현하지 않았고, 브라우저마다 동작이 달랐다.</li>
<li><strong>20ms 상한</strong> : idle deadline의 상한이 있어 React가 원하는 수준의 예측 가능한 타이밍 제어가 불가능했다.</li>
</ul>
<p>그 다음으로 <code>requestAnimationFrame</code> + 프레임 예산 추정 방식을 시도했지만, React의 작업이 vsync(모니터가 수직 귀선을 완료한 시점에 맞춰 프레임 출력을 동기화하는 기술) 주기에 맞출 필요가 없다는 판단하에 이 역시 폐기되었다.</p>
<h3 id="messagechannel"><a class="anchor" href="#messagechannel">MessageChannel</a></h3>
<p>최종적으로 React는 <strong>MessageChannel</strong>을 선택했다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="js" data-theme="github-dark github-light"><code data-language="js" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> MessageChannel </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'undefined'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> channel</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MessageChannel</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  channel.port1.onmessage </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> performWorkUntilDeadline;</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  schedulePerformWorkUntilDeadline</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> channel.port2.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">postMessage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  schedulePerformWorkUntilDeadline</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(performWorkUntilDeadline, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>왜 <code>setTimeout</code>이 아닌 <code>MessageChannel</code>일까? <code>setTimeout</code>은 HTML 스펙에 따라 5회 이상 중첩되면 <strong>최소 4ms의 지연</strong>이 강제된다. 반면 <code>MessageChannel</code>은 이런 제한 없이 다음 이벤트 루프 틱에서 즉시 매크로태스크로 실행된다. 5ms 단위로 작업을 쪼개는 Fiber에게 4ms의 인위적 지연은 치명적이기 때문이다.</p>
<p>(5ms 중 4ms가 대기 시간이라면, 실질적으로 일하는 시간은 1ms뿐이다. 이건 워라밸이 아니라 그냥 밸이다.)</p>
<p>React의 Scheduler 패키지는 내부적으로 <strong>두 개의 min-heap(최소 힙)</strong> 을 관리한다.</p>
<pre><code>timerQueue (대기실)                    taskQueue (실행 대기열)
┌──────────────────┐                  ┌──────────────────┐
│ 아직 시작 시간이     │   startTime      │ 지금 실행 가능한     │
│ 안 된 태스크들       │ ──경과 시──→      │ 태스크들           │
│                  │                  │                  │
│ 정렬: startTime   │                  │ 정렬: expiration  │
│ (빠른 순)          │                  │ Time (임박한 순)   │
└──────────────────┘                  └──────────────────┘
</code></pre>
<p><strong>taskQueue</strong>는 "지금 당장 실행할 수 있는" 태스크들의 큐다. <code>expirationTime</code>(= startTime + timeout)이 작을수록, 즉 만료가 임박할수록 먼저 실행된다. <strong>timerQueue</strong>는 "아직 실행 시점이 오지 않은" 태스크들의 대기실이다. 현재 시간이 startTime을 넘는 순간 taskQueue로 이동한다.</p>
<p>그렇다면 expirationTime을 결정하는 timeout은 어떻게 정해질까? 각 업데이트의 우선순위(Priority Level)에 따라 고유한 timeout이 부여된다.</p>
<pre><code>우선순위          timeout        만료까지         예시
─────────────────────────────────────────────────────────
Immediate        -1ms          즉시 만료         flushSync
UserBlocking     250ms         0.25초           클릭, 입력
Normal           5,000ms       5초              일반 setState
Low              10,000ms      10초             startTransition
Idle             ~1,073,741,823ms  ~12.4일      오프스크린 렌더링
</code></pre>
<p><strong>Immediate</strong>는 생성 즉시 만료된다. taskQueue에 들어가자마자 최우선으로 실행되는 것이다. (태어나자마자 만료라니, 좀 서글픈 운명이긴 하다.) <strong>UserBlocking</strong>의 250ms는 사람이 "반응이 느리다"고 느끼는 임계치(100~300ms)에 맞춘 값이다. 클릭했는데 0.25초 안에 반응이 없으면 사용자는 불쾌해진다. <strong>Normal</strong>의 5초는 넉넉해 보이지만, 이는 "최악의 경우에도 반드시 처리한다"는 보장이다. 실제로는 앞선 작업이 끝나면 곧바로 실행된다. <strong>Idle</strong>의 약 12.4일은 사실상 무한이다. 다른 모든 작업이 끝나야 비로소 실행된다. (12일 동안 브라우저를 안 닫고 있을 일은 거의 없으니, 무한이라고 봐도 무방하다.)</p>
<p>이 timeout 값들은 동시에 <strong>기아 상태(starvation) 방지</strong> 메커니즘이기도 하다. 아무리 우선순위가 낮아도 timeout이 지나면 만료 상태가 되어 강제 실행된다. 높은 우선순위 작업이 계속 들어온다고 해서 낮은 우선순위 작업이 영원히 무시당하는 일은 없는 것이다.</p>
<p>Scheduler의 <code>shouldYieldToHost()</code>는 작업 시작 이후 경과 시간이 <code>frameInterval</code>(기본 <strong>5ms</strong>, <code>SchedulerFeatureFlags.js</code>에서 정의)을 초과했는지를 확인하여 메인 스레드에 제어권을 돌려줄지 결정한다.</p>
<hr>
<h2 id="render-phase와-commit-phase"><a class="anchor" href="#render-phase와-commit-phase">Render Phase와 Commit Phase</a></h2>
<p>지금까지 Fiber의 구조와 스케줄링을 살펴봤다. 이제 이 모든 것이 어떻게 조합되어 실제 UI 업데이트가 이루어지는지 전체 흐름을 정리해보자.</p>
<p>Fiber는 내부적으로 <strong>Render Phase</strong>와 <strong>Commit Phase</strong>라는 두 단계를 거친다. 이 분리는 React의 동시성 모델을 가능하게 만드는 핵심 설계인 것이다. Fiber의 작동 흐름을 직접 확인하고 싶다면 아래 이미지를 클릭하면 된다.</p>
<p><a href="https://storied-centaur-55230f.netlify.app/"><img src="/content/250520/2.png" alt="2.png" loading="lazy" decoding="async"></a></p>
<hr>
<h3 id="render-phase"><a class="anchor" href="#render-phase">Render Phase</a></h3>
<p>Render Phase는 UI에 <strong>어떤 변경이 필요한지 계산</strong>하는 단계다. 이 단계에서는 DOM에 실제로 아무런 영향을 주지 않는다. 그리고 가장 중요한 특성은 <strong>비동기적으로 중단 및 재개가 가능</strong>하다는 것이다.</p>
<p>이 단계는 앞서 살펴본 <code>beginWork</code>와 <code>completeWork</code> 중심으로 동작한다.</p>
<p><strong>beginWork(fiber)</strong> 에서는 각 Fiber의 타입(FunctionComponent, ClassComponent, HostComponent 등)에 따라 적절한 로직을 실행한다. 그리고 자식 Fiber 노드를 생성하고 연결한다. props가 이전과 동일하다면 memoization을 활용해 스킵할 수 있다(bailout)</p>
<p><strong>completeWork(fiber)</strong> 에서는 DOM 생성 작업이나 effect 정보를 준비한다. 그리고 <code>bubbleProperties()</code>를 통해 자식들의 flags를 <code>subtreeFlags</code>로 집계하고, 부모 방향으로 올라가며 정보를 보완한다</p>
<p>이 단계에서 DOM을 직접 수정하지 않기 때문에, 언제든 작업을 중단하고 나중에 다시 시작하더라도 사용자에게 불완전한 UI가 노출되지 않는다. 이것이 Concurrent 모드의 기반인 것이다.</p>
<hr>
<h3 id="subtreeflags"><a class="anchor" href="#subtreeflags">subtreeFlags</a></h3>
<p>Render Phase에서 각 Fiber에는 어떤 부수 효과(side effect)가 필요한지 <strong>비트 플래그</strong>로 기록된다. <code>ReactFiberFlags.js</code>에 정의된 주요 플래그를 살펴보자.</p>
<ul>
<li><code>Placement</code> : 새 노드를 DOM에 삽입</li>
<li><code>Update</code> : DOM 속성 업데이트 필요</li>
<li><code>ChildDeletion</code> : 자식 노드 삭제 필요</li>
<li><code>Ref</code> : ref 연결/해제 필요</li>
<li><code>Passive</code> : useEffect 콜백 실행 필요</li>
<li><code>Snapshot</code> : getSnapshotBeforeUpdate 실행</li>
<li><code>Callback</code> : 라이프사이클 콜백 실행</li>
</ul>
<p>이전 React(~16)에서는 <code>firstEffect</code> -> <code>nextEffect</code> -> <code>lastEffect</code>로 연결된 linked list를 사용하여 부수 효과가 있는 Fiber만 모아뒀다. 하지만 이 방식은 언마운트된 Fiber의 참조가 남아 <strong>메모리 누수</strong>를 일으키는 문제가 있었고, Suspense 같은 새로운 패턴을 효율적으로 처리하기 어려웠다.</p>
<p>React 17부터는 이 effect list를 제거하고 <strong>subtreeFlags 방식</strong>으로 전환했다(<a href="https://github.com/facebook/react/pull/19381">PR #19381</a>). <code>completeWork</code> 단계에서 <code>bubbleProperties()</code>가 자식들의 flags를 부모로 집계한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="js" data-theme="github-dark github-light"><code data-language="js" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> bubbleProperties</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">completedWork</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> subtreeFlags </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> NoFlags;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> child </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> completedWork.child;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  while</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (child </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    subtreeFlags </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> child.subtreeFlags;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    subtreeFlags </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> child.flags;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    child </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> child.sibling;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  completedWork.subtreeFlags </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> subtreeFlags;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이 구조의 가장 큰 장점은 Commit Phase에서 <strong>서브트리 전체를 스킵</strong>할 수 있다는 것이다. 만약 어떤 Fiber의 <code>subtreeFlags &#x26; MutationMask === NoFlags</code>라면, 그 서브트리에는 DOM 변경이 필요한 노드가 전혀 없으므로 통째로 건너뛸 수 있다. 이전의 linked list 방식에서는 불가능했던 최적화다.</p>
<hr>
<h3 id="commit-phase"><a class="anchor" href="#commit-phase">Commit Phase</a></h3>
<p>Commit Phase는 Render Phase에서 계산된 변경 사항을 <strong>실제 DOM에 반영</strong>하는 단계다. 이 단계는 <strong>항상 동기적</strong>으로 수행되며, 한 번 시작하면 끝까지 중단 없이 실행된다. 사용자가 반쯤 업데이트된 UI를 보게 되는 것을 방지하기 위함이다.</p>
<p>Commit Phase는 내부적으로 다음과 같은 세밀한 순서로 동작한다.</p>
<ol>
<li><strong>Before Mutation Phase</strong> : <code>commitBeforeMutationEffects()</code>
<ul>
<li>DOM이 변경되기 전에 현재 DOM 상태를 읽는다. <code>getSnapshotBeforeUpdate</code> 라이프사이클이 여기서 실행된다. 이 시점에서 <code>current</code> 트리는 아직 화면의 상태를 나타내므로, DOM의 스크롤 위치나 크기 같은 정보를 안전하게 캡처할 수 있다.</li>
</ul>
</li>
<li><strong>Mutation Phase</strong> : <code>commitMutationEffects()</code>
<ul>
<li><strong>실제 DOM 조작</strong>이 수행되는 단계다. 새 노드 삽입, 기존 노드 수정, 불필요한 노드 삭제가 모두 여기서 일어난다. <code>componentWillUnmount</code>도 이 시점에 실행되는데, 아직 <code>current</code>가 이전 트리를 가리키고 있으므로 이전 상태를 읽을 수 있기 때문이다.</li>
</ul>
</li>
<li><strong>트리 교체</strong> : <code>root.current = finishedWork</code>
<ul>
<li>더블 버퍼링의 핵심이다. workInProgress 트리가 current 트리로 승격된다. 이 교체가 Mutation 후, Layout 전에 실행되는 이유가 중요하다. <code>componentWillUnmount</code>는 <strong>이전 트리</strong>를 읽어야 하므로 Mutation 단계에서 실행되어야 하고, <code>componentDidMount</code>/<code>componentDidUpdate</code>는 <strong>새 트리</strong>를 읽어야 하므로 Layout 단계에서 실행되어야 하기 때문이다.</li>
</ul>
</li>
<li><strong>Layout Phase</strong> : <code>commitLayoutEffects()</code>
<ul>
<li>DOM 변경이 완료된 후, 새 DOM 상태를 기반으로 하는 작업들이 실행된다.
<ul>
<li><code>componentDidMount</code>, <code>componentDidUpdate</code> 실행</li>
<li><code>useLayoutEffect</code> 콜백 실행</li>
<li>이 시점에서 <code>current</code>는 이미 새 트리를 가리키므로, DOM을 읽으면 업데이트된 값을 얻을 수 있다</li>
</ul>
</li>
</ul>
</li>
<li><strong>Passive Effects</strong> (비동기)
<ul>
<li><code>useEffect</code>의 cleanup과 setup은 별도로 스케줄링되어 <strong>비동기적</strong>으로 실행된다. 이들은 DOM 변경에 의존하지 않는 부수 효과(데이터 패칭, 이벤트 구독 등)를 처리하기 위한 것이므로, 동기적으로 실행할 필요가 없다. 이를 비동기로 처리함으로써 브라우저가 먼저 화면을 그릴 수 있도록 양보하는 것이다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="concurrent-features와-fiber"><a class="anchor" href="#concurrent-features와-fiber">Concurrent Features와 Fiber</a></h2>
<p>지금까지 살펴본 Fiber의 모든 설계(더블 버퍼링, Lane 기반 우선순위, 중단 가능한 Work Loop)가 실제로 어떤 사용자 경험을 가능하게 만드는지, React 18 이후의 Concurrent Features를 통해 확인해보자.</p>
<h3 id="usetransition"><a class="anchor" href="#usetransition">useTransition</a></h3>
<p><code>startTransition(() => setState(...))</code>을 호출하면, 해당 업데이트에는 <code>TransitionLane</code>이 부여된다. 14개의 TransitionLane이 라운드로빈(작업을 순서대로 돌아가며 하나씩 배정하는 방식)으로 할당되어 충돌을 방지한다.</p>
<p>TransitionLane은 SyncLane이나 DefaultLane보다 우선순위가 낮기 때문에, 사용자 입력 같은 긴급 업데이트가 들어오면 transition 렌더링을 <strong>중단</strong>하고 긴급 업데이트를 먼저 처리할 수 있다. 이 동안 화면에는 <code>current</code> 트리(이전 상태)가 유지되고, transition은 workInProgress 트리에서 백그라운드로 진행된다.</p>
<p>여기서 더블 버퍼링의 가치가 빛난다. 중단된 transition 렌더링은 workInProgress 트리에만 영향을 미치고, 사용자가 보는 화면(current 트리)은 전혀 손상되지 않는 것이다.</p>
<p><code>isPending</code> 플래그는 이 transition이 아직 완료되지 않았음을 나타내어, 로딩 인디케이터를 보여주는 등의 처리가 가능하다.</p>
<hr>
<h3 id="usedeferredvalue"><a class="anchor" href="#usedeferredvalue">useDeferredValue</a></h3>
<p><code>useDeferredValue(value)</code>는 최초 렌더링에서는 전달된 <code>value</code>를 그대로 반환한다. 이후 렌더링에서 현재 렌더가 긴급한 경우, 이전의 memoized 값을 반환하고 TransitionLane으로 새로운 렌더를 스케줄링한다. 지연된 렌더링은 Transition과 마찬가지로 중단 가능하다</p>
<p>개념적으로는 <code>startTransition</code>과 유사하지만, 업데이트를 디스패치하는 쪽이 아니라 <strong>값을 수신하는 쪽</strong>에서 적용하는 차이가 있다. 검색 입력란의 텍스트는 즉시 반영하되, 검색 결과 목록의 렌더링은 지연시키는 것이 대표적인 사용 사례다.</p>
<hr>
<h3 id="suspense"><a class="anchor" href="#suspense">Suspense</a></h3>
<p>컴포넌트가 <code>&#x3C;Suspense></code> 내부에서 Promise를 throw하면 <code>throwException</code>이 이를 캐치하고 해당 Fiber를 <code>Incomplete</code>로 마킹한다. 그리고 <code>return</code> 체인을 따라 올라가며 가장 가까운 Suspense 경계를 탐색하고, Suspense 경계가 fallback UI를 표시하도록 전환한다. Promise가 resolve되면 <code>markRootPinged</code>로 해당 lane을 ping하고, React가 suspended 서브트리를 다시 렌더링한다</p>
<p>Concurrent 모드에서는 suspended 컴포넌트의 <strong>형제(sibling) 노드들을 계속 렌더링</strong>할 수 있어, 하나의 데이터 요청이 전체 트리의 렌더링을 차단하지 않는다. 이것이 가능한 이유는 Fiber의 linked list 구조 덕분에 sibling으로의 이동이 자유롭기 때문이다.</p>
<hr>
<h3 id="streaming-ssr과-selective-hydration"><a class="anchor" href="#streaming-ssr과-selective-hydration">Streaming SSR과 Selective Hydration</a></h3>
<p>React 18의 <code>renderToPipeableStream</code>은 Suspense 경계를 활용한다.</p>
<ul>
<li><strong>서버</strong>: Suspense 경계가 suspend되면, fallback HTML을 먼저 전송하고 데이터가 준비되면 나중에 <code>&#x3C;script></code> 태그로 실제 콘텐츠를 스트리밍한다</li>
<li><strong>클라이언트 (Selective Hydration)</strong>: 각 Suspense 경계가 <strong>독립적으로</strong> hydration될 수 있다. 사용자가 아직 hydration되지 않은 영역을 클릭하면, <code>SelectiveHydrationLane</code>을 통해 해당 경계의 hydration을 <strong>우선적으로</strong> 처리한 뒤 이벤트를 디스패치한다</li>
</ul>
<p>이 모든 것이 가능한 이유는 각 Suspense 경계가 독립적으로 스케줄링 가능한 Fiber 노드이기 때문이다. 결국 Fiber 아키텍처의 "작업을 나누고, 우선순위를 매기고, 중단/재개할 수 있다"는 핵심 설계가 이러한 기능들의 토대가 되는 것이다.</p>
<hr>
<h2 id="마치며"><a class="anchor" href="#마치며">마치며</a></h2>
<p>이 글에서 다룬 내용을 한 문장으로 요약하면, <strong>React Fiber는 재귀를 반복으로 바꾸고, 콜 스택을 힙으로 옮겨, 렌더링을 중단하고 재개할 수 있게 만든 아키텍처</strong>다.</p>
<p>이를 위해 linked list 기반의 트리 구조, 더블 버퍼링, Lane 기반 우선순위 시스템, MessageChannel 기반 스케줄러 등 수많은 정교한 설계가 조합되었다. 그리고 이 모든 것은 결국 <strong>사용자가 느끼는 UI의 반응성을 극대화하는 것</strong> 이라는 목표를 향하고 있다.</p>
<p>물론 Fiber의 내부 구현은 React 버전이 올라갈 때마다 계속 변화하고 있으며, 이 글에서 다룬 내용 역시 특정 시점의 스냅샷에 불과하다. 하지만 "작업을 나누고, 우선순위를 매기고, 중단하고 재개할 수 있다"는 Fiber의 핵심 철학만큼은 앞으로도 변하지 않을 것이라 생각한다.</p>
<p>이 글을 통해 React Fiber가 단순한 면접 키워드가 아닌, React의 모든 기능을 떠받치는 런타임 아키텍처라는 점이 전달되었기를 바란다. 정답은 없지만, 이 글을 읽는 독자분들도 소스코드를 직접 들여다보며 각자만의 이해를 쌓아가기를 바란다.</p>
<hr>
<h2 id="출처"><a class="anchor" href="#출처">출처</a></h2>
<ul>
<li><a href="https://github.com/acdlite/react-fiber-architecture">Andrew Clark — React Fiber Architecture</a></li>
<li><a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberWorkLoop.js">React 소스코드 — ReactFiberWorkLoop.js</a></li>
<li><a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js">React 소스코드 — ReactFiberBeginWork.js</a></li>
<li><a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberCompleteWork.js">React 소스코드 — ReactFiberCompleteWork.js</a></li>
<li><a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberLane.js">React 소스코드 — ReactFiberLane.js</a></li>
<li><a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberFlags.js">React 소스코드 — ReactFiberFlags.js</a></li>
<li><a href="https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiber.js">React 소스코드 — ReactFiber.js (createWorkInProgress)</a></li>
<li><a href="https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js">React 소스코드 — Scheduler.js</a></li>
<li><a href="https://github.com/facebook/react/blob/main/packages/scheduler/src/SchedulerFeatureFlags.js">React 소스코드 — SchedulerFeatureFlags.js</a></li>
<li><a href="https://github.com/facebook/react/pull/18796">PR #18796 — Initial Lanes implementation</a></li>
<li><a href="https://github.com/facebook/react/pull/19381">PR #19381 — Remove effect list (firstEffect/nextEffect)</a></li>
<li><a href="https://github.com/facebook/react/issues/7942">Issue #7942 — Fiber Principles: Contributing To Fiber</a></li>
<li><a href="https://github.com/reactwg/react-18/discussions/37">React 18 Working Group — New Suspense SSR Architecture (Discussion #37)</a></li>
<li><a href="https://github.com/reactwg/react-18/discussions/27">React 18 Working Group — Concurrent Scheduling Specifics (Discussion #27)</a></li>
<li><a href="https://github.com/reactwg/react-18/discussions/129">React 18 Working Group — useDeferredValue (Discussion #129)</a></li>
<li><a href="https://github.com/reactwg/react-18/discussions/130">React 18 Working Group — Selective Hydration (Discussion #130)</a></li>
<li><a href="https://react.dev/blog/2022/03/29/react-v18">React v18.0 Blog Post</a></li>
</ul>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>React</category>
        </item>
        <item>
            <title><![CDATA[Vite의 transformRequest()]]></title>
            <link>https://hooninedev.com/250507</link>
            <guid isPermaLink="false">https://hooninedev.com/250507</guid>
            <pubDate>Wed, 07 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Vite를 사용하다 보면, 개발 서버에서 모듈이 실시간으로 빠르게 갱신되는 경험을 하게 된다. 이 과정의 핵심에 있는 함수가 바로 transformRequest()다. 이번 글에서는 Vite 개발 서버 내부에서 이 함수가 어떤 역할을 하는지, 어떤 순서로 동작하는지, 그리고 실제 사용하면서 주의할 점은 무엇인지까지 정리해본다. TransformRequest...]]></description>
            <content:encoded><![CDATA[<p>Vite를 사용하다 보면, 개발 서버에서 모듈이 실시간으로 빠르게 갱신되는 경험을 하게 된다.<br>
이 과정의 핵심에 있는 함수가 바로 <code>transformRequest()</code>다.</p>
<p>이번 글에서는 Vite 개발 서버 내부에서 이 함수가 어떤 역할을 하는지, 어떤 순서로 동작하는지, 그리고 실제 사용하면서 주의할 점은 무엇인지까지 정리해본다.</p>
<hr>
<h2 id="transformrequest란"><a class="anchor" href="#transformrequest란">TransformRequest란?</a></h2>
<p>Vite의 개발 서버는 브라우저로부터의 HTTP 요청을 인터셉트하여, 요청된 모듈을 즉석에서 변환(transform) 후 제공한다. 이 핵심 과정을 담당하는 내부 함수가 바로 <code>transformRequest(url, serverContext)</code>이다. 이 함수는 Vite의 플러그인 시스템과 깊이 연관되어 있으며, 다음과 같은 정교한 파이프라인을 통해 동작한다.</p>
<hr>
<h3 id="1-모듈-식별-및-로딩-module-identification--loading"><a class="anchor" href="#1-모듈-식별-및-로딩-module-identification--loading">1. 모듈 식별 및 로딩 (Module Identification &#x26; Loading)</a></h3>
<ul>
<li>요청된 url을 기준으로 Vite의 모듈 해석 로직 (Module Resolution)을 통해 실제 파일 시스템 경로 또는 가상 모듈 ID를 확정한다.</li>
<li>확정된 ID를 사용하여 load 훅을 호출하는 플러그인을 통해 모듈의 원본 코드를 로드한다. 기본적으로는 파일 시스템에서 직접 파일을 읽어오지만, 플러그인을 통해 커스텀 로딩 로직을 구현할 수 있다.</li>
</ul>
<hr>
<h3 id="2-플러그인-기반-코드-변환-plugin-driven-code-transformation"><a class="anchor" href="#2-플러그인-기반-코드-변환-plugin-driven-code-transformation">2. 플러그인 기반 코드 변환 (Plugin-driven Code Transformation)</a></h3>
<ul>
<li>로드된 코드는 Vite에 등록된 플러그인들의 transform 훅 체인을 통과한다. 각 플러그인은 transform(code, id, options) 훅을 통해 코드를 분석하고 수정할 기회를 갖는다.</li>
<li>이 과정에서 Babel, PostCSS, SASS/LESS 전처리기 등 다양한 변환 작업이 순차적으로 적용될 수 있다. 플러그인은 코드 문자열뿐만 아니라 AST(Abstract Syntax Tree) 레벨에서의 조작도 수행할 수 있으며, 소스맵(source map) 생성 및 관리를 지원한다.</li>
<li>Vite는 이 과정에서 모듈 간의 의존성을 파악하여 모듈 그래프(Module Graph)를 구축하고, HMR(Hot Module Replacement)을 위한 데이터를 수집한다.</li>
</ul>
<hr>
<h3 id="3-내장-변환기-적용-built-in-transformers-application"><a class="anchor" href="#3-내장-변환기-적용-built-in-transformers-application">3. 내장 변환기 적용 (Built-in Transformers Application)</a></h3>
<ul>
<li>플러그인 변환 이후, 필요한 경우 Vite의 내장 변환기가 동작한다. 대표적으로 esbuild가 사용되어 TypeScript, JSX, TSX 파일을 매우 빠른 속도로 순수 JavaScript로 트랜스파일한다.</li>
<li>.css 파일의 경우, PostCSS 처리 및 CSS Modules, HMR을 위한 래핑(wrapping) 등이 이루어진다.</li>
<li>.vue 또는 .svelte 같은 SFC(Single File Component)는 해당 플러그인의 transform 훅에서 이미 처리되었을 가능성이 높지만, 이 단계에서 추가적인 Vite 내부 처리가 적용될 수 있다.</li>
</ul>
<hr>
<h3 id="4-결과-집계-및-반환-result-aggregation--return"><a class="anchor" href="#4-결과-집계-및-반환-result-aggregation--return">4. 결과 집계 및 반환 (Result Aggregation &#x26; Return)</a></h3>
<ul>
<li>모든 변환 과정을 거친 최종 코드(result.code)와 함께, 디버깅을 위한 소스맵(result.map), 그리고 플러그인들이 전달한 추가 메타데이터(result.meta)를 포함하는 객체를 반환한다.</li>
<li>이 결과는 HTTP 응답으로 브라우저에 전달되거나, 서버 사이드 렌더링(SSR) 시에는 서버 측 실행 환경에서 사용된다.</li>
</ul>
<p>그러면 아래 예시 코드를 활용해 Vite 개발 서버 환경에서 특정 모듈의 변환 결과를 확인해보자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> result</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> server.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">transformRequest</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"/src/main.ts"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  html: server.config.root </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "/index.html"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (result) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(result.code);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p><strong>server</strong>는 현재 실행 중인 ViteDevServer의 인스턴스를 가리킨다. 이 객체는 Vite 개발 서버의 다양한 기능과 설정에 접근할 수 있는 진입점 역할을 한다.</p>
<p><strong>server.transformRequest(url, options)</strong> 에서 url은 변환을 요청할 모듈의 URL 경로로, 예시에서는 <code>'/src/main.ts'</code>로, 프로젝트 소스 디렉토리 내의 main.ts 파일이다. 이 URL은 웹 브라우저가 요청하는 경로와 유사한 형태를 가진다.</p>
<p>options은 변환 과정에 영향을 줄 수 있는 추가적인 컨텍스트 정보를 제공하는 객체이다. 예시코드에서 <code>html: server.config.root + '/index.html'</code>로, 현재 변환 요청이 어떤 HTML 파일을 기준으로 이루어지는지를 명시한다. <code>server.config.root</code>는 Vite 프로젝트의 루트 디렉토리를 나타내며, 여기에 /index.html을 더해 일반적으로 프로젝트의 메인 HTML 파일 경로를 지정한다.</p>
<p>이 정보는 특정 플러그인이 HTML 컨텍스트에 따라 다르게 동작하거나, CSS <code>@import</code> 경로 해석, 혹은 SSR(Server-Side Rendering) 시 에셋 번들링 등에서 활용될 수 있다.</p>
<hr>
<h2 id="내부-구조를-코드와-함께-보기"><a class="anchor" href="#내부-구조를-코드와-함께-보기">내부 구조를 코드와 함께 보기</a></h2>
<p>Vite의 <a href="https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/transformRequest.ts"><code>transformRequest</code></a> 함수의 실제 구현은 HMR, 소스맵 처리, 캐싱 전략 등 다양한 요소가 고려되어 상당히 어렵고 정교하다. 그렇기에 핵심적인 처리 흐름을 간략화하여 살펴보자.</p>
<p>핵심은 <strong>"ID 해석 → 로드 → (플러그인 &#x26; esbuild) 변환 → 캐싱 및 반환"</strong> 의 흐름으로 요약할 수 있다. 이 과정을 통해 Vite는 요청된 모듈을 효율적으로 처리하고 빠른 개발 서버 성능을 제공한다. 이 부분을 명심하고 아래 설명을 따라가보자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 실제 transformRequest 함수의 시그니처 및 초기 캐시 확인 로직 (간략화)</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> async</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> transformRequest</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  environment</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> DevEnvironment</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  url</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  options</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TransformOptions</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {},</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TransformResult</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // (옵션 기본값 설정, 서버 닫힘 상태 확인 등의 전처리 로직이 여기에 위치합니다.)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  //    동일한 URL과 옵션에 대한 요청이 이미 처리 중인지 확인합니다.</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> cacheKey</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> `${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">options</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">.</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">html</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> ?</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "html:"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> :</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> ""}${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">url</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}`</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> pending</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> environment._pendingRequests.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(cacheKey);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (pending) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 이미 진행 중인 요청이 있고, 해당 요청이 현재 시점에서도 유효하다면</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> pending.request;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 새로운 변환 작업 시작</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> result;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<hr>
<h3 id="1단계--초기화-및-캐시-확인"><a class="anchor" href="#1단계--초기화-및-캐시-확인">1단계 : 초기화 및 캐시 확인</a></h3>
<p>요청 URL과 컨텍스트를 기반으로 캐시 키를 생성한다. 이미 동일한 요청이 진행 중인지 확인하고, 있다면 그 결과를 기다린다. 모듈이 마지막으로 무효화된 시간을 기준으로 최신 상태인지 확인한다.</p>
<p>실제 Vite 소스코드에서는 doTransform, loadAndTransform 등의 내부 함수로 분리되어 처리한다. 그리고 에러 처리, HMR을 위한 소프트 무효화(soft invalidation) 처리, 의존성 최적화 연동 등 훨씬 더 많은 세부 로직이 포함되어 있다.</p>
<hr>
<h3 id="2단계--url-정규화-및-모듈-id-해석"><a class="anchor" href="#2단계--url-정규화-및-모듈-id-해석">2단계 : URL 정규화 및 모듈 ID 해석</a></h3>
<p>URL에서 불필요한 쿼리(예: 타임스탬프)를 제거한다. 그리고 <code>pluginContainer.resolveId(url)</code>를 호출하여 플러그인을 통해 URL을 실제 파일 시스템 경로 또는 고유 ID로 변환한다. (예: '/src/main.ts' -> '/Users/username/my-project/src/main.ts')</p>
<p>이렇게 변환된 ID를 기준으로 모듈 그래프에서 기존 모듈 정보를 조회하고, 유효한 캐시가 있다면 반환한다.</p>
<hr>
<h3 id="3단계--코드-로드-load"><a class="anchor" href="#3단계--코드-로드-load">3단계 : 코드 로드 (Load)</a></h3>
<p><code>pluginContainer.load(id)</code>를 호출하여 등록된 플러그인들의 <code>load</code> 훅을 실행한다. 플러그인이 모듈 코드를 반환하면 그 코드를 사용하고, 그렇지 않으면 파일 시스템에서 직접 파일을 읽어온다. 로드된 코드에서 인라인 소스맵이 있다면 추출한다.</p>
<hr>
<h3 id="4단계--코드-변환-transform"><a class="anchor" href="#4단계--코드-변환-transform">4단계 : 코드 변환 (Transform)</a></h3>
<p><code>pluginContainer.transform(code, id, { inMap: map })</code>을 호출하여 등록된 모든 플러그인의 <code>transform</code> 훅이 순차적으로 실행되어 코드를 변환한다. (예: Vue SFC 처리, PostCSS 변환, 사용자 정의 변환 등)</p>
<p>TypeScript, JSX, TSX와 같은 파일은 Vite에 내장된 esbuild 플러그인 또는 관련 플러그인에 의해 JavaScript로 매우 빠르게 트랜스파일된다. 각 플러그인은 코드와 소스맵을 입력받아, 수정된 코드와 소스맵을 다음 플러그인으로 전달하거나 최종 결과로 반환한다.</p>
<hr>
<h3 id="5단계--소스맵-후처리"><a class="anchor" href="#5단계--소스맵-후처리">5단계 : 소스맵 후처리</a></h3>
<p>변환 과정에서 생성되거나 수정된 소스맵을 정규화한다. 원본 소스 내용을 소스맵에 포함시키거나(<code>injectSourcesContent</code>), 특정 소스 파일을 무시하는(<code>sourcemapIgnoreList</code>) 등의 후처리를 적용한다.</p>
<p>이 다음 단계에서 SSR 환경을 위한 추가 변환을 할 수 있다.</p>
<hr>
<h3 id="6단계--결과-생성-및-캐싱"><a class="anchor" href="#6단계--결과-생성-및-캐싱">6단계 : 결과 생성 및 캐싱</a></h3>
<p>최종적으로 변환된 코드, 소스맵, ETag(캐싱용) 등을 포함하는 결과 객체를 생성한다. 만약 모듈 처리 중에 해당 모듈이 무효화되지 않았다면, 이 결과를 모듈 그래프에 캐싱하여 다음 요청 시 빠르게 재사용할 수 있도록 한다.</p>
<hr>
<h2 id="transformrequest이-왜-중요한가"><a class="anchor" href="#transformrequest이-왜-중요한가">transformRequest이 왜 중요한가?</a></h2>
<p><code>transformRequest</code> 함수는 Vite의 핵심 기능인 모듈 핫 리로딩(HMR)을 가능하게 하는 중추적인 역할을 한다. 개발 중 코드가 변경되면, <code>transformRequest</code>는 해당 모듈만 신속하게 다시 읽어 변환하고 브라우저에 전달하여 즉각적인 화면 업데이트를 제공한다.</p>
<p>또한, 개발자가 특정 플러그인을 개발하거나 기존 플러그인의 동작을 디버깅할 때 매우 유용하다. 예를 들어, custom transform 훅이 예상대로 작동하는지 테스트하거나, TypeScript, JSX, Vue SFC와 같은 파일이 JavaScript로 어떻게 변환되는지 그 결과를 직접 추적하고 확인할 수 있다.</p>
<p>이처럼 <code>transformRequest</code>는 Vite의 빠른 개발 경험을 제공하고, 플러그인 생태계를 확장하며, 코드 변환 과정을 투명하게 만들어 개발자에게 강력한 분석 도구를 제공한다는 점에서 매우 중요하다.</p>
<hr>
<h2 id="연관-이슈-및-참고자료"><a class="anchor" href="#연관-이슈-및-참고자료">연관 이슈 및 참고자료</a></h2>
<ul>
<li><a href="https://github.com/vitejs/vite/issues/4898">Nuxt에서 transformRequest 관련 이슈</a><br>
→ SSR 환경에서 transform이 누락되는 버그 사례</li>
<li><a href="https://vite.dev/guide/api-javascript">Vite JavaScript API 문서</a><br>
→ createServer, transformRequest, ssrLoadModule 등의 공식 API 가이드</li>
<li><a href="https://esbuild.github.io/api/#transform">esbuild transform API</a><br>
→ TypeScript → JavaScript로 변환되는 과정 이해에 필수</li>
</ul>
<hr>
<h2 id="마치며"><a class="anchor" href="#마치며">마치며</a></h2>
<p><code>transformRequest()</code>는 Vite 개발 서버에서 가장 중요한 처리 로직 중 하나다.<br>
단순히 내부용 API로 보기보다는, <strong>플러그인 개발, 변환 디버깅, 성능 분석</strong> 시 활용할 수 있는 강력한 도구로 이해하면 좋다.</p>
<p>그리고 이러한 내부 API를 탐색하고 실험하는 과정은 번들러에 대한 이해도를 한층 높여준다.</p>
<p>다음엔 Vite의 <code>ssrLoadModule()</code>이나 <code>transformWithEsbuild()</code>도 함께 분석해보면 재미있을 것 같다.</p>
<hr>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
        </item>
        <item>
            <title><![CDATA[TS infer 에 대해서 깊이 탐구해보자]]></title>
            <link>https://hooninedev.com/250424</link>
            <guid isPermaLink="false">https://hooninedev.com/250424</guid>
            <pubDate>Thu, 24 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[요즘 Type Challenge 를 통해 TypeScript를 공부하고있는데 빈번하게, infer 을 자주 다루게 되었다. 단순하게, infer 가 어떻게 사용되는지 뿐 아니라, 어떤 목적으로 만들어지고 사용되고 동작하는지 알아보도록 하자. 특정 타입을 찾는 infer 위 예시 코드를 보고, infer의 역할을 추론해보자. infer라는 단어 자체가 '추론...]]></description>
            <content:encoded><![CDATA[<p>요즘 Type Challenge 를 통해 TypeScript를 공부하고있는데 빈번하게, infer 을 자주 다루게 되었다.</p>
<p><img src="/content/250424/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<p>단순하게, infer 가 어떻게 사용되는지 뿐 아니라, 어떤 목적으로 만들어지고 사용되고 동작하는지 알아보도록 하자.</p>
<hr>
<h1 id="특정-타입을-찾는-infer"><a class="anchor" href="#특정-타입을-찾는-infer">특정 타입을 찾는 infer</a></h1>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ElementType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">infer</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)[] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> :</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> never</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span></code></pre></figure>
<p>위 예시 코드를 보고, infer의 역할을 추론해보자.</p>
<p>infer라는 단어 자체가 '추론하다'라는 의미를 가지고 있어, 프로그래머 입장에서는 타입스크립트에서 자동으로 무언가를 추론하거나, let U = 추론된 값과 같이 변수를 선언하는 키워드로 오해할 수도 있다.</p>
<p>하지만 실제로 infer는 변수 선언 키워드가 아니라, 조건부 타입 내부에서만 사용되는 특수한 키워드로, TypeScript 컴파일러에게 '이 위치에서 타입을 추론하라'고 지시하는 역할을 한다.</p>
<p>즉, 프로그래머가 직접 값을 추론하는 것이 아니라, 컴파일러가 해당 위치의 타입을 자동으로 추론하도록 위임하는 구문이다.</p>
<hr>
<h2 id="what-is-infer"><a class="anchor" href="#what-is-infer">What is Infer?</a></h2>
<p>infer 키워드는 TypeScript의 조건부 타입(conditional types) 내에서만 사용할 수 있으며, 타입 매칭 과정에서 특정 위치의 타입을 타입 변수로 캡처하는 역할을 한다.</p>
<p>이는 복잡한 타입에서 특정 부분을 추출하여 재사용할 수 있게 해주는 강력한 타입 추론 메커니즘이다.</p>
<p>앞서 본 예시 코드를 보기 쉽게 바꿔놓으면 아래와 같다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> 결과타입</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> 패턴</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">infer</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> 대체타입</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span></code></pre></figure>
<p>이것을 글로 쉽게 풀어보면, infer U는 패턴 매칭 과정에서 U라는 타입 변수를 선언하고, 그 위치에 해당하는 실제 타입을 캡처하겠다는 의미다.</p>
<hr>
<h2 id="typescript-컴파일러-내부에서-어떻게-infer-를-처리할까"><a class="anchor" href="#typescript-컴파일러-내부에서-어떻게-infer-를-처리할까">TypeScript 컴파일러 내부에서 어떻게 Infer 를 처리할까?</a></h2>
<p>Infer는 컴파일러 내부에서 타입 추론을 위한 주요 역할은 다음과 같다.</p>
<ul>
<li>
<p>타입 분해 (Type Decomposition) : 함수, 튜플, 객체 등 복합 타입에서 특정 부분을 추출하여, 중첩된 타입 구조에서 내부 타입 접근 용이하다.</p>
</li>
<li>
<p>패턴 매칭 기반 타입 추론 : 구조적 패턴을 기반으로 타입 변수에 값 바인딩하여, 선언적 방식으로 복잡한 타입 관계 표현 가능</p>
</li>
<li>
<p>타입 변환 및 매핑 : 기존 타입을 기반으로 새로운 타입 생성</p>
</li>
</ul>
<p>컴파일러 내부 동작에 대한 내용들은 <a href="https://raw.githubusercontent.com/microsoft/TypeScript/refs/heads/main/src/compiler/checker.ts">Typescript Github</a>를 참고하여 작성되었다. 코드의 양도 많고, 어렵지만 직접 코드를 보는 것이 가장 좋을 것 같다. 찾아보실 분은 <code>src/compiler/checker.ts</code>를 탐구해보면 된다.</p>
<p>너무 어려우면 그냥 넘어가고, 요약만 살펴봐도 좋다.</p>
<hr>
<h3 id="조건부-타입-분석-및-초기화"><a class="anchor" href="#조건부-타입-분석-및-초기화">조건부 타입 분석 및 초기화</a></h3>
<p>컴파일러는 우선 조건부 타입을 분석하여 검사 타입(T), 확장 타입 패턴(extends 뒤의 타입), 그리고 true/false 결과 타입을 식별한다.</p>
<p>이 과정에서 infer 키워드가 포함된 타입 변수들을 찾아 타입 변수 환경(type variable environment)에 등록한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> processConditionalType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">node</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ConditionalTypeNode</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Type</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> checkType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getTypeFromTypeNode</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(node.checkType);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> extendsType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getTypeFromTypeNode</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(node.extendsType);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> trueType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getTypeFromTypeNode</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(node.trueType);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> falseType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getTypeFromTypeNode</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(node.falseType);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> inferTypeVariables</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> findInferTypeVariables</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(node.extendsType);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> typeVarEnvironment</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createTypeVarEnvironment</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(inferTypeVariables);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> resolveConditionalType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    checkType,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    extendsType,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    trueType,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    falseType,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    typeVarEnvironment,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>checkType은 조건부 타입의 검사 타입, extendsType은 확장 타입 패턴, trueType은 true 결과 타입, falseType은 false 결과 타입을 나타낸다.</p>
<p><code>inferTypeVariables</code>는 extendsType에서 infer 키워드가 포함된 타입 변수들을 찾고, <code>typeVarEnvironment</code>는 타입 변수 환경을 생성한다.</p>
<p><code>resolveConditionalType</code>은 조건부 타입을 해석하는 함수로, 타입 체커를 통해 패턴 매칭 알고리즘을 실행한다.</p>
<hr>
<h3 id="타입-패턴-매칭-및-추론-수행"><a class="anchor" href="#타입-패턴-매칭-및-추론-수행">타입 패턴 매칭 및 추론 수행</a></h3>
<p>이어 컴파일러는 검사 타입(T)이 확장 타입 패턴과 일치하는지 확인한다. 이 과정에서 infer 키워드가 위치한 부분에 대응하는 실제 타입을 캡처하여 타입 변수 환경에 저장한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> resolveConditionalType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  checkType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Type</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  extendsType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Type</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  trueType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Type</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  falseType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Type</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  typeVarEnv</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TypeVarEnvironment</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Type</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">isUnionType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(checkType)) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> distributeOverUnion</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(checkType, (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">type</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      resolveConditionalType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        type,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        extendsType,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        trueType,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        falseType,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        typeVarEnv.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">clone</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      ),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> isMatch</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> matchTypeWithPattern</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(checkType, extendsType, typeVarEnv);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> constraintsValid</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> validateTypeConstraints</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(typeVarEnv);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (isMatch </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> constraintsValid) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> substituteInferredTypes</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(trueType, typeVarEnv);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> falseType;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>위 코드로 패턴 매칭 및 타입 추론 프로세스를 살펴보자.</p>
<p><code>isUnionType</code> 을 활용해 검사 타입이 유니온 타입인지 확인하고, <code>distributeOverUnion</code>을 활용해 유니온 타입을 분산 처리하여 각 타입에 대해 조건부 타입을 재평가하고, 리턴한다.</p>
<p><code>matchTypeWithPattern</code>은 검사 타입이 확장 타입 패턴과 일치하는지 확인하고, <code>validateTypeConstraints</code>은 타입 제약 조건을 검사한다.</p>
<p><code>isMatch</code>와 <code>constraintsValid</code>이 true라면 <code>substituteInferredTypes</code>은 추론된 타입 변수를 적용하여 결과 타입을 결정하고, 그렇지 않다면 <code>falseType</code>을 리턴해 대체 타입을 변환한다.</p>
<hr>
<h3 id="통합된-타입-추론-프로세스"><a class="anchor" href="#통합된-타입-추론-프로세스">통합된 타입 추론 프로세스</a></h3>
<p>실제 매칭과 추론 과정에서 컴파일러는 타입 구조를 재귀적으로 분석하며, 다양한 타입 형태(객체, 배열, 함수 등)에 대한 패턴 매칭을 수행한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> matchTypeWithPattern</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  source</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Type</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  pattern</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Type</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  typeVarEnv</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TypeVarEnvironment</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (source </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> pattern) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">isInferTypeVar</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(pattern)) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> inferVar</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getInferTypeVar</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(pattern);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> constraints</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getTypeConstraints</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(inferVar);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">constraints </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">||</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> isTypeAssignableTo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(source, constraints)) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      typeVarEnv.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addInference</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(inferVar, source);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> false</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">isObjectType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(pattern) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> isObjectType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(source)) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> matchObjectTypes</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(source, pattern, typeVarEnv);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">isArrayType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(pattern)) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> matchArrayTypes</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(source, pattern, typeVarEnv);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">isFunctionType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(pattern)) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> matchFunctionTypes</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(source, pattern, typeVarEnv);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 기타 타입 형태에 대한 매칭</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // ...</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> false</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이 과정에서 직접 매칭을 검사하고, 패턴이 infer 타입 변수인 경우 타입을 캡처한다.</p>
<p>그리고 구조적 타입에 대한 재귀적 매칭을 수행하고 이어 기타 타입 형태에 대한 매칭을 차례대로 진행한다. 마지막으로 앞의 매칭들을 충족하지 못하면 매칭 실패인 <code>false</code>를 리턴한다.</p>
<hr>
<h2 id="너무-어렵다-각-단계를-실제-예시로-흐름을-살펴보자"><a class="anchor" href="#너무-어렵다-각-단계를-실제-예시로-흐름을-살펴보자">너무 어렵다. 각 단계를 실제 예시로 흐름을 살펴보자.</a></h2>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ReturnType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">args</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[]) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> infer</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> R</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> ?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> R</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> :</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> never</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> greet</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">name</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> `Hello, ${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">name</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}!`</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> GreetReturn</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ReturnType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> greet>;</span></span></code></pre></figure>
<p><code>ReturnType&#x3C;typeof greet></code> 평가를 시작한다.</p>
<p>여기서 조건부 타입 분석을 통해 검사 타입은 <code>typeof greet</code> 이 되고, 패턴은 <code>(...args: any[]) => infer R</code> 이 되는데 infer R 은 아직 추론되지 않음을 의미한다.</p>
<p>곧 이어 패턴 매칭 수행을 통해 <code>typeof greet</code>는 함수 타입이므로 패턴과 구조적 매칭 시도하고 함수 타입 매칭 중 반환 타입 위치에서 infer R을 만나 실제 반환 타입 string을 R에 캡처한다.</p>
<p>이로 인해 true 분기 결과 반환으로 <code>ReturnType&#x3C;typeof greet></code> 평가 결과는 string으로 결정된다. 만약 매칭이 실패해서 false 분기로 이어지면 never로 결정된다.</p>
<hr>
<hr>
<h2 id="여러-상황에서-infer-을-다뤄보자"><a class="anchor" href="#여러-상황에서-infer-을-다뤄보자">여러 상황에서 Infer 을 다뤄보자</a></h2>
<p>infer는 앞서 살펴본 컴파일러 내부 동작처럼 다양한 분기로 처리된다.</p>
<p>이 모든 분기 상황을 하나하나 다루기에는 너무 복잡하므로, 아래에서는 대표적인 상황별 예시 코드를 통해 infer의 활용법을 살펴보려 한다.</p>
<hr>
<h3 id="함수-반환-타입-추출"><a class="anchor" href="#함수-반환-타입-추출">함수 반환 타입 추출</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ReturnType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">args</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[]) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> infer</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> R</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> ?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> R</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> :</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> fetchData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FetchResult</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ReturnType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> fetchData>; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// Promise&#x3C;string></span></span></code></pre></figure>
<p>이 예시에서 ReturnType 유틸리티 타입은 함수 타입의 반환 타입을 추출한다.</p>
<p>T가 함수 타입인 경우, 패턴 <code>(...args: any[]) => infer R</code>에 매칭되고, 매칭된 infer R은 함수의 반환 타입 위치에서 실제 타입을 추론하여 캡처한다.</p>
<p>결과적으로 R(추론된 반환 타입)이 반환된다.</p>
<hr>
<h3 id="함수-매개변수-타입-추출"><a class="anchor" href="#함수-매개변수-타입-추출">함수 매개변수 타입 추출</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FirstParameter</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">first</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> infer</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">args</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[]) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  ?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  :</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> never</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> handleEvent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">id</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">event</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Event</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 구현부</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> IdType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FirstParameter</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> handleEvent>; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// number</span></span></code></pre></figure>
<p>이 예시에서 FirstParameter 유틸리티 타입은 함수의 첫 번째 매개변수 타입을 추출한다.</p>
<p>T가 함수 타입인 경우, 패턴 <code>first: infer U, ...args: any[]) => any</code>에 매칭되고, 매칭된 infer U는 첫 번째 매개변수 타입 위치에서 실제 타입을 추론하여 캡처한다.</p>
<p>결과적으로 U(추론된 첫 번째 매개변수 타입)이 반환된다.</p>
<hr>
<h3 id="조건부-타입-내-여러-infer을-사용"><a class="anchor" href="#조건부-타입-내-여러-infer을-사용">조건부 타입 내 여러 infer을 사용</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Unpacked</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">infer</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)[]</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  ?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">args</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[]) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> infer</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    ?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">infer</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      ?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T1</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Unpacked</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[]>; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// string</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T2</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Unpacked</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// number</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T3</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Unpacked</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>>; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// boolean</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T4</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Unpacked</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// string</span></span></code></pre></figure>
<p>이 예시에서 Unpacked 유틸리티 타입은 다양한 형태의 래퍼 타입에서 내부 타입을 추출한다.</p>
<ul>
<li>T가 배열 타입인 경우, 패턴 <code>infer U[]</code>에 매칭되어 배열 요소 타입 U를 추출한다.</li>
<li>T가 함수 타입인 경우, 패턴 <code>(...args: any[]) => infer U</code>에 매칭되어 반환 타입 U를 추출한다.</li>
<li>T가 Promise 타입인 경우, 패턴 <code>Promise&#x3C;infer U></code>에 매칭되어 Promise 결과 타입 U를 추출한다.</li>
<li>위 패턴에 매칭되지 않는 경우, 원본 타입 T를 그대로 반환한다.</li>
</ul>
<hr>
<h3 id="객체-속성-타입-추출"><a class="anchor" href="#객체-속성-타입-추출">객체 속성 타입 추출</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PropertyType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">K</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> keyof</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { [</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">P</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> in</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> K</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> infer</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  ?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  :</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> never</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> User</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  id</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  name</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  settings</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">theme</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "light"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "dark"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ThemeType</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PropertyType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">User</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"settings"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">], </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"theme"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 'light' | 'dark'</span></span></code></pre></figure>
<p>T가 K 키를 가진 객체 타입인 경우, 해당 속성의 타입 U를 추론한다. 여기서 중첩된 객체 속성도 접근 연산자(.)를 통해 추출할 수 있다.</p>
<hr>
<h3 id="튜틀-요소-타입-추출"><a class="anchor" href="#튜틀-요소-타입-추출">튜틀 요소 타입 추출</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FirstElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[]> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">infer</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[]] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> :</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> never</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> LastElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[]> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[], </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">infer</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> :</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> never</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Tuple</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">];</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> First</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FirstElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Tuple</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// string</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Last</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> LastElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Tuple</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// boolean</span></span></code></pre></figure>
<p>이 예시에서는 튜플의 첫 번째와 마지막 요소 타입을 추출하게 된다.</p>
<ul>
<li>FirstElement는 패턴 <code>[infer U, ...any[]]</code>를 사용하여 첫 번째 요소 타입을 추출한다.</li>
<li>LastElement는 패턴 <code>[...any[], infer U]</code>를 사용하여 마지막 요소 타입을 추출한다.</li>
</ul>
<hr>
<h3 id="유니온-타입-처리"><a class="anchor" href="#유니온-타입-처리">유니온 타입 처리</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ExtractNumberFromUnion</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> infer</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  ?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    ?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    :</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> never</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  :</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> never</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Union</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Numbers</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ExtractNumberFromUnion</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Union</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// number</span></span></code></pre></figure>
<p>분산적 조건부 타입의 특성을 활용하여 유니온 타입의 각 멤버에 대해 개별적으로 적용된다.</p>
<ul>
<li>첫 번째 조건부 타입<code>(T extends infer U)</code>은 항상 참이지만, 유니온 타입의 각 멤버를 개별적으로 U에 바인딩한다.</li>
<li>두 번째 조건부 타입<code>(U extends number)</code>은 U가 number인 경우에만 해당 타입을 유지한다.</li>
</ul>
<hr>
<h2 id="마무리"><a class="anchor" href="#마무리">마무리</a></h2>
<p>TypeScript의 infer 키워드는 처음 접했을 때 다소 난해하게 느껴질 수 있지만, 복잡한 타입 시스템을 다룰 때 강력한 도구가 된다고 생각한다. 특히 Type Challenge와 같은 타입 구현을 할 때 Infer 에 대한 개념을 확실하게 알고 있어야한다.</p>
<p>infer는 단순히 타입을 추론하는 것을 넘어, 타입 레벨 프로그래밍의 핵심 구성 요소로서 다음과 같은 이점을 제공한다.</p>
<ul>
<li>코드 재사용성 향상: 복잡한 타입 구조에서 필요한 부분만 추출하여 재사용</li>
<li>타입 안전성 강화: 런타임 에러 대신 컴파일 타임에 타입 관련 문제를 포착</li>
<li>표현력 증가: 복잡한 타입 관계를 더 명확하고 간결하게 표현</li>
</ul>
<p>일상적인 개발에서는 ReturnType, Parameters 같은 내장 유틸리티 타입을 통해 간접적으로 infer의 혜택을 누리게 되지만, 더 복잡한 타입 문제에 직면했을 때 직접 infer를 활용하는 능력이 필요할 것 같다.</p>
<p>타입 챌린지를 계속 풀어나가며 infer와 TypeScript의 타입 시스템에 더 깊이 익숙해지다 보면, 이전에는 불가능하다고 생각했던 타입 레벨의 문제들도 해결할 수 있을 것 같다.</p>
<p>지치지말고 계속해서 풀어보자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>TypeScript</category>
        </item>
        <item>
            <title><![CDATA[Verdaccio와 Lerna를 활용한 사내 유틸리티 라이브러리 구축기]]></title>
            <link>https://hooninedev.com/250404</link>
            <guid isPermaLink="false">https://hooninedev.com/250404</guid>
            <pubDate>Fri, 04 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[회사 내에서 프론트엔드 공통 유틸리티 모듈을 통합 관리하는 작업을 맡으면서, 다음과 같은 요구사항이 생겼다. 여러 레포지토리에 중복된 로직을 하나로 추출해 관리하고 싶다. 패키지 단위로 분리해 모노레포 형태로 운영하되, 유연한 버전 관리가 필요하다. 외부 공개 없이 사내에서만 배포 및 사용 가능한 NPM 패키지 레지스트리를 갖추고 싶다. CI/CD 파이프라...]]></description>
            <content:encoded><![CDATA[<p>회사 내에서 프론트엔드 공통 유틸리티 모듈을 통합 관리하는 작업을 맡으면서, 다음과 같은 요구사항이 생겼다.</p>
<ul>
<li>여러 레포지토리에 중복된 로직을 하나로 추출해 관리하고 싶다.</li>
<li>패키지 단위로 분리해 모노레포 형태로 운영하되, 유연한 버전 관리가 필요하다.</li>
<li>외부 공개 없이 사내에서만 배포 및 사용 가능한 NPM 패키지 레지스트리를 갖추고 싶다.</li>
<li>CI/CD 파이프라인에서도 안전하고 빠르게 배포되도록 자동화를 고려해야 한다.</li>
</ul>
<p>이 문제를 해결하기 위해 선택한 조합이 바로 <code>Lerna</code>와 <code>Verdaccio</code>다.</p>
<p>Lerna는 모노레포에서 패키지를 효과적으로 분리하고, 빌드/배포 파이프라인을 정리하는 데 탁월하다.</p>
<p>특히 <strong>independent versioning 전략</strong>을 활용하면 각 패키지의 변경사항만 추적하여 배포할 수 있어 유틸리티성 모듈 관리에 적합하다.</p>
<p>반면, Verdaccio는 사내 전용 NPM 레지스트리를 손쉽게 구축할 수 있는 도구로, 설정이 단순하며, Docker 기반으로 격리된 레지스트리를 운영할 수 있다. npmjs.org와의 proxy 연결도 가능해, 공용 패키지를 사내 캐싱하는 역할까지 수행할 수 있다.</p>
<p>이 글에서는 단순한 튜토리얼이 아닌, 실제 구축 경험을 바탕으로 <strong>설계 의도, 구조 구성, 보안 전략, 자동화 배포 흐름</strong>까지 기술적으로 깊게 다뤄볼 예정이다.</p>
<hr>
<h3 id="기술-선택의-이유"><a class="anchor" href="#기술-선택의-이유">기술 선택의 이유</a></h3>
<p>사내 유틸리티 라이브러리의 특성상, 아래와 같은 구조가 필요했다.</p>
<ul>
<li>프로젝트 간 코드 중복 제거 (ex. date formatter, error handler, toast utils 등)</li>
<li>빌드 후 개별 패키지로 publish, 각 프로젝트에서 scoped import로 사용</li>
<li>사내 프라이빗 레지스트리로 퍼블리싱 (Verdaccio)</li>
<li>자동화 배포 스크립트 (Lerna + Git tag 기반 publish)</li>
</ul>
<p>이를 고려했을 때 <code>Turborepo</code>, <code>Nx</code> 같은 최신 도구도 고려했지만,</p>
<ul>
<li>Nx는 workspace 내 모든 패키지가 동일한 버전 관리 체계를 요구하거나, monolithic CI 구조가 필요한 경우 더 적합하다고 생각</li>
<li>Lerna는 더 유연한 버전 전략을 제공하고, 다양한 팀이 패키지를 독립적으로 운영할 수 있게 도와줌</li>
<li>Verdaccio는 자체 인증과 스코프 제어를 통한 접근 제어가 가능</li>
</ul>
<p>그렇기에 Lerna + Verdaccio가 현재 상황에서 가장 합리적인 선택이었다.</p>
<hr>
<hr>
<hr>
<h2 id="lerna-기반-모노레포-아키텍처-구성"><a class="anchor" href="#lerna-기반-모노레포-아키텍처-구성">Lerna 기반 모노레포 아키텍처 구성</a></h2>
<p>사내 유틸리티 라이브러리는 장기적으로 확장 가능하고 모듈화된 구조여야 했다.</p>
<p>단순히 공통 함수들을 모아두는 수준을 넘어서, 각기 다른 목적의 기능들이 하나의 레포 안에서 독립적으로 유지보수 가능해야 했기 때문이다.</p>
<p>그래서 도입한 구조가 바로 <code>Lerna</code> 기반의 모노레포 아키텍처였다.</p>
<hr>
<h3 id="모노레포-패키지-구조-설계"><a class="anchor" href="#모노레포-패키지-구조-설계">모노레포 패키지 구조 설계</a></h3>
<p>모노레포에는 현재 두 개의 주요 패키지가 존재한다.</p>
<ul>
<li><code>@company/common-library</code>: 날짜 파싱, 타입 헬퍼, 유효성 검사 등 다양한 프론트엔드 프로젝트에서 재사용되는 범용 유틸리티 모듈</li>
<li><code>@company/napi</code>: 내부 시스템과 연동되는 네이티브 API wrapper 혹은 RPC 클라이언트 역할의 모듈</li>
</ul>
<p>이런 구조를 통해 <strong>도메인 별 패키지 책임을 분리하면서도</strong>, 공통 코드베이스 안에서 통합 관리가 가능하도록 구성했다.</p>
<pre><code>packages/
├── company-common-library/
├── company-napi/
</code></pre>
<p>패키지들은 모두 <code>packages/</code> 디렉토리 하위에 존재하며, 각각 독립적인 <code>package.json</code>, 빌드, 테스트 환경을 가진다.</p>
<br/>
<h3 id="lerna-설정-lernajson과-버전-관리"><a class="anchor" href="#lerna-설정-lernajson과-버전-관리">Lerna 설정 (<code>lerna.json</code>)과 버전 관리</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "version"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"independent"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "packages"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"packages/*"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">],</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "npmClient"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"pnpm"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>Lerna는 기본적으로 두 가지 버전 전략을 지원한다</p>
<ul>
<li><strong>fixed 모드</strong> : 모든 패키지가 같은 버전으로 관리됨</li>
<li><strong>independent 모드</strong> : 패키지별로 독립적인 버전 관리</li>
</ul>
<p>우리는 <code>independent</code> 모드를 선택했는데, 이 전략에서는 다음과 같은 방식으로 버전이 관리된다.</p>
<ul>
<li><code>lerna publish</code> 실행 시, git 히스토리를 기준으로 각 패키지별 변경 여부를 분석</li>
<li>변경이 감지된 패키지만 대상으로 <code>patch</code>, <code>minor</code>, <code>major</code> 중 하나로 버전 증가</li>
<li>해당 패키지의 <code>package.json</code> 버전을 올리고, 태그를 각각 생성</li>
<li>결과적으로 버전은 다음처럼 분기될 수 있다</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">company-common-library@</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1.0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">company-napi@</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1.0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span></span></code></pre></figure>
<p>이렇게 하게 되면 변경되지 않은 패키지를 불필요하게 배포하지 않아도 되고, 공통 유틸리티와 도메인별 모듈 간 릴리즈 흐름 분리가 쉬우며, 팀 간 병렬적인 개발/배포가 가능하다.</p>
<p><code>independent</code> 버전 전략에서는 각 패키지가 독립적으로 버전 업되기 때문에, <strong>패키지 간 참조 관계</strong>에 주의가 필요하다.</p>
<p>예를 들어 <code>@company/common-library</code>를 사용하는 <code>@company/napi</code>가 있을 경우, common-library가 새로 릴리즈되더라도, napi가 참조하는 버전 범위에 해당하지 않으면 업데이트가 반영되지 않는다.</p>
<p>이를 방지하기 위해서는 다음 두 가지를 고려해볼 수 있다.</p>
<hr>
<h4 id="version-range-설정---etc"><a class="anchor" href="#version-range-설정---etc">Version Range 설정 (<code>^</code>, <code>~</code>, etc.)</a></h4>
<p>보통 <code>^1.0.0</code>과 같이 major version 범위 내에서 자동 업데이트를 허용하는 방식이 사용된다.
이는 common-library가 <code>1.0.0 → 1.2.3</code>으로 변경되어도 napi가 자동으로 최신 버전을 참조할 수 있게 한다.
하지만 <code>1.0.0 → 2.0.0</code> 같은 breaking change는 자동으로 반영되지 않도록 막는다.</p>
<hr>
<h4 id="ci-테스트와-통합-빌드-검증"><a class="anchor" href="#ci-테스트와-통합-빌드-검증">CI 테스트와 통합 빌드 검증</a></h4>
<p>버전이 올라간 패키지가 다른 패키지에 영향을 주는 경우, 의존 패키지에서도 함께 테스트해야 한다.</p>
<p>CI 파이프라인에서는 전체 workspace를 대상으로 테스트를 실행하거나, <strong>Lerna의 affected strategy (--since, --scope)를</strong> 활용해 영향받는 패키지만 테스트하는 것도 방법이다.</p>
<p>여기서 Lerna의 affected strategy는 변경된 패키지에 대해서만 작업을 수행하기 위한 전략을 의미한다. <code>--since</code> 옵션을 통해 지정된 Git ref 이후 변경된 패키지에만 명령 적용할 수 있고, <code>--scope</code> 옵션을 통해 특정 패키지에만 명령을 적용할 수 있다.</p>
<br/>
<h3 id="pnpm-workspace와의-연동"><a class="anchor" href="#pnpm-workspace와의-연동">pnpm workspace와의 연동</a></h3>
<p><code>package.json</code> 루트에서는 <code>workspaces</code> 설정을 통해 pnpm과 Lerna의 패키지 인식 범위를 통일했다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"workspaces"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">  "packages/*"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span></code></pre></figure>
<p>pnpm은 Lerna와 달리 의존성을 루트에 hoist하지 않고 <code>.pnpm</code> 디렉토리에 격리해 설치한다.<br>
덕분에 패키지 간 충돌을 방지할 수 있다. 그리고 파일을 여러 위치에 <strong>복사</strong>하지 않고, 하나의 실제 파일을 여러 위치에서 <strong>참조</strong>하게 만드는 방식인 <strong>하드링크 기반 캐싱</strong>을 통해 빌드 속도까지 개선된다.</p>
<p>또한 <code>root</code> 패키지를 <code>file:</code>로 의존성 선언해 내부 참조 관계를 명시적으로 설정하였다</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"devDependencies"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "root"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"file:../../"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<br/>
<h3 id="패키지별-빌드-전략과-타입-관리"><a class="anchor" href="#패키지별-빌드-전략과-타입-관리">패키지별 빌드 전략과 타입 관리</a></h3>
<p>각 패키지는 vite + tsc 조합으로 빌드된다.</p>
<p>공통 모듈은 vite로 번들링된 ESM을 export하고, 동시에 <code>dts-bundle-generator</code>를 사용해 모든 타입 정의를 하나의 .d.ts 파일로 정리한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"build"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"del-cli build/**/* &#x26;&#x26; tsc &#x26;&#x26; vite build &#x26;&#x26; dts-bundle-generator ..."</span></span></code></pre></figure>
<p>dts-bundle-generator 이란?</p>
<ul>
<li>TypeScript로 작성된 라이브러리를 외부에 제공할 때, 여러 개의 .d.ts 파일 대신 하나의 진입점 타입 파일을 생성해주는 도구</li>
<li>내부적으로 모든 타입 의존성을 분석해 export 기준으로 병합</li>
<li>타입만 사용하는 외부 라이브러리(zod, type-fest 등)는 inline 방식으로 포함 가능</li>
<li>IDE에서 자동 완성, 타입 추론이 명확해지고, 사용자 입장에서도 import가 간결해지게 한다</li>
</ul>
<br/>
<p><code>exports</code> 필드는 타입, 브라우저, 기본 엔트리를 명시적으로 구분해 다양한 런타임에서도 타입 추론이 정확히 동작하도록 구성했다</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"exports"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "."</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "types"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"./dist/company-common-library.d.ts"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// TypeScript에서 import 시 참조할 타입 선언 파일</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "browser"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"./dist/company-common-library.js"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 브라우저 번들 환경에서 우선적으로 참조할 모듈</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "default"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"./dist/company-common-library.js"</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D"> // 기타 환경(Node.js 등)에서 사용할 기본 모듈</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>Node.js 13 이상에서는 exports 필드를 통해 <strong>패키지 외부에서 import 가능한 경로를 명시적으로 제한</strong>할 수 있다. 이는 단순히 entry point를 설정하는 게 아니라, 내부 파일 구조를 외부에 노출하지 않도록 보호하는 기능이기도 하다.</p>
<p>예를 들어 위 설정에서는 아래처럼만 import가 가능하고,</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { formatDate } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "@company/common"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span></code></pre></figure>
<p>직접 경로 접근은 불가능하다</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// ❌ ERR_PACKAGE_PATH_NOT_EXPORTED 오류 발생</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { formatDate } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "@company/common/utils/format"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span></code></pre></figure>
<br/>
<h3 id="테스트와-커버리지-전략"><a class="anchor" href="#테스트와-커버리지-전략">테스트와 커버리지 전략</a></h3>
<p>유닛 테스트는 <code>vitest</code>로 작성되며, 공통 유틸 함수의 안정성과 리팩토링을 보장하기 위한 커버리지를 기본으로 포함했다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"scripts"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "test"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"vitest"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "test:coverage"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"vitest --coverage"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p><code>__tests__/</code>와 <code>__bench__/</code> 디렉토리는 기능 테스트 외에도 퍼포먼스 비교 테스트를 병행하도록 설계되었다.<br>
이는 내부 util 함수들 간 병목 현상 탐지를 위한 구조이며, 런타임 성능 개선을 정량적으로 추적할 수 있게 한다.</p>
<hr>
<h2 id="verdaccio-기반-프라이빗-npm-레지스트리-구축"><a class="anchor" href="#verdaccio-기반-프라이빗-npm-레지스트리-구축">Verdaccio 기반 프라이빗 NPM 레지스트리 구축</a></h2>
<p>앞선 글에서 Lerna 기반의 모노레포 아키텍처를 통해 공통 유틸리티 라이브러리를 패키지 단위로 관리하는 구조를 살펴보았다.</p>
<p>하지만 이를 실제로 배포하고 사용할 수 있으려면 내부 전용 NPM 레지스트리가 필요하다.</p>
<br/>
<h3 id="왜-내부-전용이어야-했을까"><a class="anchor" href="#왜-내부-전용이어야-했을까">왜 내부 전용이어야 했을까?</a></h3>
<p>우리가 관리하는 유틸리티 패키지에는 비즈니스 로직이 직접 담기지 않더라도, <strong>사내 시스템 구조나 네이밍, 유효성 규칙</strong> 등 외부에 노출되면 민감할 수 있는 내부 도메인 정보가 간접적으로 포함된다.</p>
<p>또한, 사내 환경에 최적화된 설정 및 인터페이스를 포함하고 있기 때문에, 외부 레지스트리(NPM public registry)에서 공개적으로 운영하는 것이 적절하지 않다.</p>
<p>무엇보다도 외부 패키지와의 <strong>네이밍 충돌이나 dependency confusion</strong> 같은 공격 벡터를 차단하기 위해, 스코프가 명확히 정의된 사설 레지스트리를 통해 관리하는 것이 더 안전하고 예측 가능하다.</p>
<p>외부에 공개되지 않고, CI/CD와 연계되며, 접근 제어가 가능한 프라이빗 레지스트리를 구축하는 데 가장 적합한 도구는 바로 <strong>Verdaccio</strong>였다.</p>
<p>Verdaccio는 경량화된 Node.js 기반 NPM registry로, 손쉽게 설치하고 구성할 수 있으며, Docker 환경과도 잘 통합된다.</p>
<br/>
<h3 id="docker-기반-레지스트리-인프라-구성"><a class="anchor" href="#docker-기반-레지스트리-인프라-구성">Docker 기반 레지스트리 인프라 구성</a></h3>
<p>사내 인프라에는 다음과 같은 형태로 <code>verdaccio</code> 인스턴스를 구성하였다:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="yaml" data-theme="github-dark github-light"><code data-language="yaml" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D"># docker-compose.yml</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">version</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"3.7"</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">services</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">  verdaccio</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    image</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">hub.docker.company.net/verdaccio</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    container_name</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">verdaccio</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    restart</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">always</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    environment</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      - </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">VERDACCIO_PORT=COMPANY_PORT</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    ports</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      - </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">COMPANY_PORT=PORT</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    volumes</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      - </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">storage:/verdaccio/storage</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    networks</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      - </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">verdaccio_bridge</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">volumes</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">  storage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">networks</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">  verdaccio_bridge</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    driver</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">bridge</span></span></code></pre></figure>
<ul>
<li><code>COMPANY_PORT</code>를 통해 외부 접근을 제공</li>
<li><code>storage</code> 볼륨을 분리해 캐시 및 패키지 데이터를 지속성하게 저장</li>
<li><code>verdaccio_bridge</code>를 통해 다른 사내 서비스와 연동 가능하도록 네트워크 분리</li>
</ul>
<br/>
<h4 id="그럼-왜-별도의-네트워크verdaccio_bridge를-사용할까"><a class="anchor" href="#그럼-왜-별도의-네트워크verdaccio_bridge를-사용할까">그럼 왜 별도의 네트워크(<code>verdaccio_bridge</code>)를 사용할까?</a></h4>
<p>Docker Compose에서 <code>networks</code>를 명시적으로 분리한 이유는 <strong>사내 서비스 간 통신 경계를 제어하고 보안을 강화하기 위함</strong>이다.</p>
<ul>
<li><code>verdaccio</code>는 단일 독립 서비스가 아니라, CI/CD 서버, 릴리즈 빌더, 테스트 실행기 등 다양한 사내 컨테이너와 상호 작용하게 된다.</li>
<li><code>verdaccio_bridge</code>라는 브릿지 네트워크에 연결된 서비스들만 <code>http://verdaccio:PORT</code> 형태로 내부 통신이 가능하다.</li>
<li>외부에서의 접근은 오직 공개 포트(COMPANY_PORT)를 통해서만 가능하며, 내부 접근은 명시적으로 네트워크에 연결된 컨테이너로 제한된다.</li>
</ul>
<br/>
<p>이 구조는 다음과 같은 효과를 제공한다</p>
<table>
<thead>
<tr>
<th>목적</th>
<th>효과</th>
</tr>
</thead>
<tbody>
<tr>
<td>내부 통신 제한</td>
<td>의도된 컨테이너 간 연결만 허용 (예: CI만 verdaccio에 접근 가능)</td>
</tr>
<tr>
<td>보안 강화</td>
<td>외부로부터 직접 접근을 막고, 네트워크 구간을 논리적으로 분리</td>
</tr>
<tr>
<td>운영 유연성</td>
<td>환경별로 독립된 레지스트리 환경 구성 가능 (dev, staging, prod)</td>
</tr>
</tbody>
</table>
<p>결국 Verdaccio가 포함된 도커 네트워크를 분리함으로써, 단순한 패키지 저장소 이상의 <strong>통제 가능한 사내 배포 허브</strong>로 기능하게 된다.</p>
<hr>
<h3 id="설정-파일-구조-및-접근-제어"><a class="anchor" href="#설정-파일-구조-및-접근-제어">설정 파일 구조 및 접근 제어</a></h3>
<p>핵심 설정은 <code>config.yaml</code>로 관리된다. 주요 설정 포인트는 다음과 같다</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="yaml" data-theme="github-dark github-light"><code data-language="yaml" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">storage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">/verdaccio/storage</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">url_prefix</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">/npm/</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">auth</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">  htpasswd</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    file</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">/verdaccio/conf/htpasswd</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">packages</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">  "@company/*"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    access</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">$authenticated</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    publish</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">$authenticated</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">  "**"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    access</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">$all</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    publish</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">$authenticated</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    proxy</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">npmjs</span></span></code></pre></figure>
<h4 id="주요-포인트"><a class="anchor" href="#주요-포인트">주요 포인트</a></h4>
<ul>
<li><code>@company/*</code> 스코프 패키지에 대해서는 인증된 사용자만 publish/read 가능하도록 설정</li>
<li><code>proxy: npmjs</code> 설정으로, 사내 캐싱 기능과 fallback 지원</li>
<li><code>url_prefix</code> 를 <code>/npm/</code>으로 설정해 nginx 리버스 프록시 또는 CI 내부에서 명확한 경로 관리 가능</li>
<li>인증은 <code>htpasswd</code> 기반으로 구성하며, 사용자 수 제한 및 bcrypt 암호화도 설정 가능</li>
</ul>
<br/>
<h4 id="인증htpasswd은-어떻게-동작할까"><a class="anchor" href="#인증htpasswd은-어떻게-동작할까">인증(htpasswd)은 어떻게 동작할까?</a></h4>
<p>auth.htpasswd 설정은 기본적인 HTTP 인증 기반으로 작동하며, htpasswd 파일은 <code>사용자명:해시된 패스워드</code> 형태로 저장된다.
이때 사용되는 해시는 bcrypt이며, 패스워드 보안성과 저장 구조가 단순한 텍스트 인증보다 훌씬 안전하다.</p>
<ul>
<li>사용자 추가는 CLI 또는 직접 생성 가능</li>
<li>publish, install 시 HTTP Authorization 헤더로 인증이 수행됨</li>
<li>CI 환경에서는 npm login 또는 NPM_TOKEN으로 자동 처리 가능</li>
<li>사용자 수가 많아질 경우 LDAP, OAuth 등의 외부 인증 연동 플러그인도 존재함</li>
</ul>
<br/>
<h4 id="프록시proxy는-언제-동작하는가"><a class="anchor" href="#프록시proxy는-언제-동작하는가">프록시(proxy)는 언제 동작하는가?</a></h4>
<p>Verdaccio의 proxy 설정은 해당 패키지가 로컬(storage)에 존재하지 않을 때만 외부 registry로 요청을 전달한다.
이 기능을 통해 사내에서 외부 패키지를 설치하더라도, 최초 요청 이후에는 로컬 캐시에 저장되므로 성능이 향상된다.</p>
<ul>
<li><code>proxy: npmjs</code>는 uplink로 <a href="https://registry.npmjs.org%EB%A5%BC">https://registry.npmjs.org를</a> 의미</li>
<li>최초 요청 시 메타정보와 .tgz 파일이 /storage 디렉토리에 캐싱됨</li>
<li>동일 요청은 외부 네트워크 없이 캐시에서 처리됨</li>
</ul>
<hr>
<h3 id="보안을-고려한-운영-전략"><a class="anchor" href="#보안을-고려한-운영-전략">보안을 고려한 운영 전략</a></h3>
<p>프라이빗 레지스트리는 종종 <strong>dependency confusion</strong>과 같은 공격에 노출될 수 있다. 이를 방지하기 위해 다음과 같은 정책을 적용했다</p>
<table>
<thead>
<tr>
<th>설정 항목</th>
<th>목적</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>proxy</code> 제거 (특정 스코프에 대해)</td>
<td>외부 npmjs로 fallback 막기</td>
</tr>
<tr>
<td><code>publish</code> 제한</td>
<td>인증된 사용자만 업로드 가능</td>
</tr>
<tr>
<td><code>unpublish</code> 제한</td>
<td>과거 버전 삭제 불가하도록 설정</td>
</tr>
<tr>
<td><code>middlewares.audit.enabled: true</code></td>
<td>감사 로그 기록 활성화</td>
</tr>
<tr>
<td>Docker 내부 포트 비공개</td>
<td>외부 접근은 특정 게이트웨이에서만 허용</td>
</tr>
</tbody>
</table>
<p>@company/* 같이 사내 전용 스코프에는 프록시를 명시적으로 비활성화하는 것이 권장되고 있다.</p>
<p>또한 Verdaccio와 proxy 조합에서는 스코프 전략과 외부 fallback 경로를 명확하게 구분하는 것이 중요하다.</p>
<br/>
<h3 id="퍼블리시-및-연동-흐름"><a class="anchor" href="#퍼블리시-및-연동-흐름">퍼블리시 및 연동 흐름</a></h3>
<p>패키지 publish는 다음과 같은 명령어 흐름으로 구성된다:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="bash" data-theme="github-dark github-light"><code data-language="bash" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">pnpm</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> build</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">npm</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> set</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> registry</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> http://verdaccio.company.net:COMPANY_PORT/npm/</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">npm</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> login</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">lerna</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> publish</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> patch</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> --yes</span></span></code></pre></figure>
<ul>
<li><code>lerna publish</code>는 각 변경된 패키지에 대해 독립적으로 버전 증가 및 배포</li>
<li><code>registry</code> 설정을 통해 verdaccio에 publish가 가능하도록 한다</li>
<li>CI/CD에서는 <code>NPM_TOKEN</code>을 활용해 자동 로그인 처리</li>
</ul>
<hr>
<p>Verdaccio를 통해 빠르고 간편하게 프라이빗 레지스트리를 구축할 수 있었다. 추가로 보안 설정과 proxy 전략을 적절히 조합하여, dependency confusion을 효과적으로 방지할 수 있었다.</p>
<p>향후에는 OAuth 기반 인증, Web UI 비활성화, nginx 리버스 프록시 연동 등으로 보안 강화를 고민해봐도 좋을것 같다.</p>
<hr>
<h2 id="semantic-release-도입-배경"><a class="anchor" href="#semantic-release-도입-배경">semantic-release 도입 배경</a></h2>
<p>앞선 챕터에서는 <code>Lerna</code> 기반 모노레포와 <code>Verdaccio</code> 프라이빗 레지스트리 인프라 구성을 다루었다. 이제 이 구조가 실질적으로 작동하기 위해서는 <strong>버전 관리 및 배포 흐름의 자동화</strong>가 필요하다.</p>
<p>특히 사내 라이브러리의 특성상 배포가 자주 발생하고, 사람이 직접 <code>lerna publish</code>에 버전 인자를 입력해 버전 번호를 관리하는 것은 실수의 여지가 크다. 또한 변경 없는 패키지까지 함께 publish 되는 경우 발생하기도 하고, 버전 증가 정책이 일관되지 않음으로써 버전 관리가 어려운 상황이 발생하기도한다.</p>
<p>그래서 semantic-release(플러그인 기반의 릴리즈 파이프라인)를 활용해 커밋 메시지를 기반으로 자동으로 버전이 관리되도록 구성했다.</p>
<p>semantic-release는 각 단계가 plugin으로 구성되며, release process의 각 역할(버전 계산, changelog 생성, git 태그, publish 등)을 분리된 모듈로 처리하는 특징을 가진다.</p>
<p>기본 실행 흐름은 다음과 같다</p>
<p><strong>commit-analyzer</strong> => <strong>release-notes-generator</strong> => <strong>npm</strong> => <strong>git</strong> => github, slack, etc… (Optional)</p>
<hr>
<h3 id="ci에서-semantic-release-자동-실행"><a class="anchor" href="#ci에서-semantic-release-자동-실행">CI에서 semantic-release 자동 실행</a></h3>
<p>semantic-release는 보통 GitHub Action 또는 GitLab CI와 함께 사용되지만, 우리는 사내 인프라를 위해 Bitbucket Pipelines를 기반으로 구성하였다.</p>
<p>아래 예시와 같이 pipeline을 구성했다</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="yaml" data-theme="github-dark github-light"><code data-language="yaml" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">pipelines</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">  branches</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">    main</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      - </span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">step</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">          name</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">release</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">          caches</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            - </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">node</span></span>
<span data-line=""><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">          script</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">:</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            - </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">pnpm install</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            - </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">pnpm build</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            - </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">pnpm semantic-release</span></span></code></pre></figure>
<ul>
<li>main 브랜치에 머지되면 자동으로 <code>semantic-release</code>가 실행되고</li>
<li>커밋 메시지에 따라 버전이 증가하며, git tag가 생성되고</li>
<li>지정된 레지스트리 (Verdaccio)로 패키지가 publish된다</li>
</ul>
<hr>
<h3 id="lerna와의-통합"><a class="anchor" href="#lerna와의-통합">Lerna와의 통합</a></h3>
<p>Lerna 8 버전 이후에는 자체적인 conventional commits 기반 릴리즈 지원이 강화되었으나, 우리 구성에서는 <code>semantic-release</code> 단독 사용이 아닌, 다음과 같은 역할 분담 구조를 가졌다</p>
<ul>
<li>Lerna → 모노레포 패키지 실행 관리, build 스크립트 일괄 실행</li>
<li>semantic-release → 버전 계산 및 git tag, changelog 생성</li>
<li>pnpm → workspace 및 의존성 관리</li>
</ul>
<p>이 구조로 인해 각자의 역할이 명확하게 나뉘며, 중복 설정 없이 유지보수가 용이해졌다.</p>
<hr>
<p>semantic-release를 통해 다음과 같은 효과를 얻을 수 있었다</p>
<ul>
<li>사람이 직접 버전 관리를 하지 않아도 되어 실수 방지</li>
<li>릴리즈 로그, git tag, changelog 자동 생성으로 배포 이력 관리 용이</li>
<li>CI 환경에서 자동 publish 흐름 구성 → 완전한 GitOps 실현</li>
</ul>
<p>이는 특히 사내 공통 라이브러리처럼 빠르게 변화하고, 여러 프로젝트에서 재사용되는 컴포넌트를 관리할 때 큰 장점을 가진다.</p>
<hr>
<h2 id="scoped-패키지란"><a class="anchor" href="#scoped-패키지란">Scoped 패키지란?</a></h2>
<p>Scoped 패키지는 <code>@scope/name</code> 형태의 네이밍을 가진 NPM 패키지다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="bash" data-theme="github-dark github-light"><code data-language="bash" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">@company/company-common-library</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">@company/company-napi</span></span></code></pre></figure>
<p>이를 통해 다음과 같은 효과를 얻을 수 있다:</p>
<ul>
<li><strong>패키지 네이밍의 네임스페이스화</strong> → 사내 도메인 명확화</li>
<li><strong>프라이빗 레지스트리 내 권한 제어</strong> (config.yaml에서 <code>@company/*</code> 스코프 제한)</li>
<li><strong>모듈 간 관계 파악 용이</strong> → 범용 vs 도메인 특화 모듈 구분</li>
</ul>
<p>Verdaccio의 설정에서도 <code>@company/*</code> 에 대해 별도의 접근 및 publish 정책을 구성할 수 있어 보안/정책 관리에 적합하다.</p>
<hr>
<h3 id="서비스-프로젝트에서-설치-및-사용"><a class="anchor" href="#서비스-프로젝트에서-설치-및-사용">서비스 프로젝트에서 설치 및 사용</a></h3>
<p>패키지를 사용하려는 서비스 프로젝트에서는 다음과 같이 설정한다</p>
<h4 id="npmrc-구성"><a class="anchor" href="#npmrc-구성"><code>.npmrc</code> 구성</a></h4>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="bash" data-theme="github-dark github-light"><code data-language="bash" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">@company:registry</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">=http://verdaccio.company.net:COMPANY_PORT/npm/</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">//verdaccio.company.net:COMPANY_PORT/npm/:_authToken</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">=xxxxx</span></span></code></pre></figure>
<ul>
<li>특정 스코프(<code>@company</code>)만 내부 레지스트리로 리다이렉트</li>
<li>나머지는 public npm registry를 그대로 사용</li>
</ul>
<p>이를 통해 내부 패키지는 Verdaccio에서, 외부 라이브러리는 기존 npm에서 가져오도록 이원화된 설정이 가능하다.</p>
<br/>
<h4 id="설치"><a class="anchor" href="#설치">설치</a></h4>
<p>라이브러리는 비공개이기떄문에, 설치 예시만 보여주겠다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="bash" data-theme="github-dark github-light"><code data-language="bash" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">pnpm</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> install</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> @company/company-common-library</span></span></code></pre></figure>
<hr>
<h3 id="import-방식"><a class="anchor" href="#import-방식">import 방식</a></h3>
<p>scoped 패키지로 배포된 유틸리티는 다음과 같이 프로젝트 내에서 import 가능하다:</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="ts" data-theme="github-dark github-light"><code data-language="ts" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  formatDate,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  safeParse,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "@company/company-common-library/utils/date"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { buildQueryString } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "@company/company-common-library/utils/url"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span></code></pre></figure>
<ul>
<li>경량 ESM 번들로 export되기 때문에 트리 쉐이킹에 최적화</li>
<li>타입 정의도 함께 제공되므로 TS 프로젝트에서 타입 추론이 즉시 가능</li>
<li>IDE 자동완성 및 문서화에서 큰 이점</li>
</ul>
<hr>
<h3 id="호환성과-배포-주기-전략"><a class="anchor" href="#호환성과-배포-주기-전략">호환성과 배포 주기 전략</a></h3>
<p>공통 유틸리티의 경우 <strong>주기적인 배포보다는 필요 기반의 배포 전략</strong>을 채택했다.<br>
semantic-release가 자동으로 버전을 관리해주기 때문에, 사용처에서는 아래 전략을 따라간다</p>
<ul>
<li>patch 업데이트는 바로 반영</li>
<li>minor 이상 업데이트는 PR에 release note 링크 포함 후 수동 반영</li>
<li>major 업데이트는 코드베이스 검토 및 팀 공유 후 채택</li>
</ul>
<p>또한 scoped 패키지는 <strong>사내 Monorepo가 아닌 외부 레포</strong>에서도 문제 없이 사용할 수 있다는 점이 큰 장점이다.</p>
<hr>
<h2 id="결론"><a class="anchor" href="#결론">결론</a></h2>
<p>이번 유틸리티 라이브러리 구축 프로젝트는 단순히 패키지를 나누고 배포하는 수준을 넘어, 사내 개발 생산성, 코드 품질, 협업 효율성을 구조적으로 끌어올리기 위한 인프라 작업이었다.</p>
<hr>
<p><img src="/content/250404/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<hr>
<p>✅ 도입을 통해 얻게 된 주요 장점</p>
<ul>
<li>semantic-release를 통해 사람이 직접 버전 숫자를 관리하지 않아도 됨</li>
<li>Lerna + pnpm을 통해 패키지를 독립적으로 유지보수 가능</li>
<li>Verdaccio + scoped registry를 통해 외부 노출 없이 안전하게 운영</li>
<li>단일 install로 사용 가능하며 타입 정의 및 import 구조까지 통일</li>
<li>publish → install → 반영까지 모든 흐름이 자동화되어 안정적</li>
</ul>
<p>가장 큰 장점은 <strong>유지보수가 쉬운 아키텍처가 되었다</strong>는 점이다.</p>
<p>사람에 의존한 규칙이 아니라, 구조와 도구 자체가 품질과 일관성을 유지해주기 때문이다.</p>
<p><strong>이러한 인프라가 갖춰졌을 때, 개발자는 더 이상 공통 로직을 복사하거나 컨벤션을 따로 전달하지 않아도 된다. 이제는 그냥 install 하면 되고, 자동으로 type도 따라온다.</strong></p>
<hr>
<h2 id="앞으로의-개선-포인트"><a class="anchor" href="#앞으로의-개선-포인트">앞으로의 개선 포인트</a></h2>
<ul>
<li>
<p>전환 고려 여러 패키지에 영향을 주는 경우에는 Changesets의 PR 단위 버전 제안이 더 유리할 수도 있다고 생각해, semantic-release → Changesets 전환도 고려해볼 것 같다.</p>
</li>
<li>
<p>Verdaccio 인증 방식 개선 : 현재는 htpasswd 기반이지만, 사내 SSO 또는 OAuth2 연동 등 중앙 인증과 통합 가능성 검토..</p>
</li>
<li>
<p>컴포넌트 단위 모듈 분리 : 현재는 유틸리티 중심이지만, 디자인 시스템이나 React 컴포넌트도 동일한 방식으로 확장 가능</p>
</li>
<li>
<p>릴리즈 노트 시각화 자동화 : Slack 연동 및 Github Actions 기반 release summary 자동 게시 등 운영 효율화를 추가 검토 중.. (공수가 너무 크다)</p>
</li>
</ul>
<hr>
<p>인프라는 결국 <strong>개발 문화와 협업 체계의 중심</strong>을 구성하는 것이다.</p>
<p>처음엔 번거롭고 복잡해 보이지만, 한 번 체계를 만들어두면 이후의 유지 비용은 급격히 줄어든다.</p>
<p>힘든 일 뒤에 보람이 온다고 했던가. 지금 와서 돌아보면, 꽤 괜찮은 경험이었다.</p>
<hr>
<p><img src="/content/250404/1.jpg" alt="1.jpg" loading="lazy" decoding="async"></p>
<hr>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>인프라</category>
        </item>
        <item>
            <title><![CDATA[Biome이 ESLint와 Prettier를 대체할 수 있을까?]]></title>
            <link>https://hooninedev.com/241201</link>
            <guid isPermaLink="false">https://hooninedev.com/241201</guid>
            <pubDate>Sun, 01 Dec 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[이번 포스팅에서는 Biome이라는 도구에 대한 이야기를 해보려고 한다. 필자가 속한 팀에서는 서로 다른 IDE(WebStorm, VSCode 등)를 사용하는 환경에서 일관된 코드 스타일을 유지하는 데 꽤 어려움을 겪고 있었다. IDE별로 설정 파일을 따로 관리해야 하는 번거로움도 있었고, 포매팅 차이로 인해 코드 리뷰에서 로직과 무관한 지적이 오가는 일도 ...]]></description>
            <content:encoded><![CDATA[<p>이번 포스팅에서는 Biome이라는 도구에 대한 이야기를 해보려고 한다.</p>
<p>필자가 속한 팀에서는 서로 다른 IDE(WebStorm, VSCode 등)를 사용하는 환경에서 일관된 코드 스타일을 유지하는 데 꽤 어려움을 겪고 있었다. IDE별로 설정 파일을 따로 관리해야 하는 번거로움도 있었고, 포매팅 차이로 인해 코드 리뷰에서 로직과 무관한 지적이 오가는 일도 잦았다.</p>
<p>이런 상황에서 ESLint의 포매팅 관련 규칙들이 Deprecated되면서 새로운 대안을 찾아야 했다. <strong>Prettier + ESLint</strong> 조합은 도구 간 충돌을 방지하기 위한 추가 설정이 필요했고, <strong>@stylistic/eslint-plugin-ts</strong>는 아직 커뮤니티 초기 단계라 안정성 검증이 부족했다. 그러던 중 Biome이라는 도구에 관심을 가지게 되었다.</p>
<p>그렇다면 Biome은 정확히 어떤 도구이고, 정말로 ESLint와 Prettier를 대체할 수 있는 걸까?</p>
<hr>
<h2 id="biome이-뭔데"><a class="anchor" href="#biome이-뭔데">Biome이 뭔데?</a></h2>
<p>Biome은 웹 프로젝트를 위한 올인원(All-in-One) 툴체인이다. JavaScript, TypeScript, JSX, CSS, JSON, GraphQL 등의 코드 포매팅과 린팅을 하나의 도구에서 통합적으로 제공한다. ESLint와 Prettier가 각각 담당하던 역할을 단일 바이너리로 해결하겠다는 것이 핵심 철학이다.</p>
<p>Biome의 전신은 <a href="https://github.com/rome/tools">Rome</a>이다. <strong>Rome Tools Inc.</strong> 에서 2021년 $4.5M 벤처 투자를 유치하며 야심차게 출발했지만, 2023년 중반 전 직원이 해고되고 레포지토리가 아카이브되었다. 이후 핵심 컨트리뷰터들이 프로젝트를 포크하여 2023년 8월 Biome으로 새 출발을 했다. Rome 시절의 "과대 약속, 저달성" 이미지에서 벗어나, 실용적이고 꾸준한 릴리스로 신뢰를 쌓아가고 있다.</p>
<p>가장 큰 특징은 Rust로 작성되었다는 점이다. 이것이 성능에서 어떤 차이를 만들어내는지는 뒤에서 자세히 다루겠다.</p>
<hr>
<h2 id="왜-biome을-사용해야-할까"><a class="anchor" href="#왜-biome을-사용해야-할까">왜 Biome을 사용해야 할까?</a></h2>
<p>Biome을 선택하는 이유는 크게 세 가지로 정리할 수 있다.</p>
<p><strong>하나의 도구로 포매팅과 린팅을 모두 처리할 수 있다.</strong> ESLint + Prettier 조합에서는 두 도구 간의 규칙 충돌을 방지하기 위해 <code>eslint-config-prettier</code> 같은 추가 설정이 필요했다. Biome은 이 복잡성을 근본적으로 제거한다.</p>
<p><strong>압도적인 성능이다.</strong> 공식 벤치마크에 따르면 Prettier보다 약 25배, ESLint보다 약 15배 빠르다. 이 수치가 실제로 어느 정도인지는 뒤에서 직접 비교해 보겠다.</p>
<p><img src="/content/241201/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<p><strong>기존 도구와의 호환성이다.</strong> Prettier와 97% 수준의 포매팅 호환성을 제공하며, ESLint의 주요 규칙들을 빌트인으로 포함하고 있다. <code>eslint-plugin-react-hooks</code>, <code>eslint-plugin-jsx-a11y</code> 등 자주 사용되는 플러그인의 규칙들도 내장되어 있어, 마이그레이션 부담이 상대적으로 적다.</p>
<hr>
<h2 id="어떻게-사용할까"><a class="anchor" href="#어떻게-사용할까">어떻게 사용할까?</a></h2>
<p>Biome 설정은 상당히 간단하다. <a href="https://biomejs.dev/guides/getting-started/">공식 문서</a>에 친절하게 나와 있으니 참고해 보자.</p>
<p>먼저 Biome을 설치한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="bash" data-theme="github-dark github-light"><code data-language="bash" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">npm</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> install</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> --save-dev</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> --save-exact</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> @biomejs/biome</span></span></code></pre></figure>
<p>이후 설정 파일을 생성한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="bash" data-theme="github-dark github-light"><code data-language="bash" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">npx</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> @biomejs/biome</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> init</span></span></code></pre></figure>
<p>그러면 <code>biome.json</code> 파일이 생성된다. 여기에 팀의 포매팅과 린팅 규칙을 정의하면 된다.</p>
<p>IDE 확장 프로그램도 설치해야 한다. VSCode를 사용한다면 <a href="https://marketplace.visualstudio.com/items?itemName=biomejs.biome">VSCode Biome</a>, WebStorm을 사용한다면 <a href="https://plugins.jetbrains.com/plugin/22761-biome">WebStorm Biome</a> 플러그인을 설치하자.</p>
<p>마지막으로 VSCode의 <code>settings.json</code>에 아래 설정을 추가하면 저장 시 자동으로 포매팅과 린팅이 적용된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "editor.defaultFormatter"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"biomejs.biome"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "editor.codeActionsOnSave"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "quickfix.biome"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"explicit"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "source.organizeImports.biome"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"explicit"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<hr>
<h2 id="직접-비교해-보자"><a class="anchor" href="#직접-비교해-보자">직접 비교해 보자</a></h2>
<p>말로만 빠르다고 하면 와닿지 않으니, 동일한 프로젝트에서 Biome과 ESLint + Prettier를 직접 비교해 보았다. 왼쪽이 Biome, 오른쪽이 ESLint + Prettier이다.</p>
<h3 id="vite-프로젝트-로컬-실행-시간"><a class="anchor" href="#vite-프로젝트-로컬-실행-시간">Vite 프로젝트 로컬 실행 시간</a></h3>
<p><img src="/content/241201/biome1.png" alt="biome1.png" loading="lazy" decoding="async">  <img src="/content/241201/lint1.png" alt="lint1.png" loading="lazy" decoding="async"></p>
<p>Biome: <strong>506ms</strong> / ESLint + Prettier: <strong>630ms</strong>로 약 20% 더 빠른 실행 시간을 보여주었다.</p>
<hr>
<h3 id="vite-프로젝트-빌드-시간"><a class="anchor" href="#vite-프로젝트-빌드-시간">Vite 프로젝트 빌드 시간</a></h3>
<p><img src="/content/241201/biome2.png" alt="biome2.png" loading="lazy" decoding="async"> <img src="/content/241201/lint2.png" alt="lint2.png" loading="lazy" decoding="async"></p>
<p>Biome는 <strong>117.13s</strong> / ESLint + Prettier: <strong>131.48s</strong> 로 약 10% 더 빠른 빌드 시간을 보여주었다.</p>
<hr>
<h3 id="lint-작업"><a class="anchor" href="#lint-작업">Lint 작업</a></h3>
<p><img src="/content/241201/biome3.png" alt="biome3.png" loading="lazy" decoding="async"> <img src="/content/241201/lint3.png" alt="lint3.png" loading="lazy" decoding="async"></p>
<p>가장 큰 차이는 Lint 작업에서 나타났다. Biome: <strong>0.79s</strong> (CPU 0.470s), ESLint: <strong>16.32s</strong> (CPU 8.600s)로, <strong>Biome이 약 20배 더 빠른 성능</strong>을 보여주었다. CPU 사용량도 훨씬 효율적이었다.</p>
<p>개발 환경에서의 체감 차이도 크지만, CI/CD 파이프라인에서 수백 개의 파일을 검사할 때 이 차이는 더 극적으로 벌어진다. Biome은 npm 설치 없이 바이너리를 직접 실행할 수 있어, CI 콜드 스타트 시간까지 절약할 수 있다.</p>
<hr>
<p><img src="/content/241201/3.jpeg" alt="3.jpeg" loading="lazy" decoding="async"></p>
<p>흐..음.. (이쯤 되면 안 쓸 이유를 찾는 게 더 어렵다.)</p>
<hr>
<h2 id="왜-이렇게-빠른-걸까"><a class="anchor" href="#왜-이렇게-빠른-걸까">왜 이렇게 빠른 걸까?</a></h2>
<p>"Rust로 만들었으니까 빠르다"는 맞는 말이지만, 그것만으로는 설명이 부족하다. Biome의 성능 우위를 만들어내는 구체적인 기술적 요인을 살펴보자.</p>
<hr>
<h3 id="rust의-저수준-성능"><a class="anchor" href="#rust의-저수준-성능">Rust의 저수준 성능</a></h3>
<table>
<thead>
<tr>
<th><img src="/content/241201/5.webp" alt="5.webp" loading="lazy" decoding="async"></th>
<th><img src="/content/241201/6.webp" alt="6.webp" loading="lazy" decoding="async"></th>
</tr>
</thead>
</table>
<p>Biome은 시스템 프로그래밍 언어인 Rust로 작성되었다. Rust는 제로 비용 추상화(Zero-cost Abstraction)를 지향하는 언어로, 고수준의 추상화를 사용하더라도 수동으로 최적화한 저수준 코드와 동일한 성능을 낸다. 또한 가비지 컬렉터(GC) 없이 소유권(Ownership) 시스템을 통해 메모리를 관리하기 때문에, GC로 인한 런타임 오버헤드가 발생하지 않는다.</p>
<p>반면 ESLint와 Prettier는 JavaScript로 작성되어 Node.js 런타임 위에서 실행된다. V8 엔진의 JIT(Just-In-Time) 컴파일이 JavaScript를 최적화해 주지만, 인터프리터 언어의 근본적인 한계와 가비지 컬렉션 비용을 완전히 피할 수는 없다.</p>
<hr>
<h3 id="단일-파싱-아키텍처"><a class="anchor" href="#단일-파싱-아키텍처">단일 파싱 아키텍처</a></h3>
<p>Biome은 하나의 파서(Parser)로 코드를 한 번만 파싱하여 AST(Abstract Syntax Tree, 추상 구문 트리)를 생성한다. 이 AST를 포매팅과 린팅 모두에 재사용한다.</p>
<p>ESLint + Prettier 조합을 사용하면 어떻게 될까? ESLint가 코드를 파싱하여 AST를 만들고 린팅을 수행한 뒤, Prettier가 같은 코드를 다시 파싱하여 별도의 AST를 만들고 포매팅을 수행한다. 동일한 파일에 대해 파싱이 두 번 발생하는 것이다. Biome의 단일 파싱 아키텍처는 이 중복을 원천적으로 제거한다.</p>
<hr>
<h3 id="네이티브-병렬-처리"><a class="anchor" href="#네이티브-병렬-처리">네이티브 병렬 처리</a></h3>
<p><img src="/content/241201/7.png" alt="7.png" loading="lazy" decoding="async"></p>
<p>Rust의 동시성 모델을 활용하여 Biome은 파일 처리를 여러 스레드에서 병렬로 수행한다. 작업을 작은 단위로 분할하고, 작업 훔치기(Work-stealing) 스케줄러를 통해 스레드 간 부하를 효율적으로 분산한다. Rust의 소유권 시스템이 컴파일 타임에 데이터 경쟁(Data Race)을 원천 차단하기 때문에, 런타임에서의 동기화 비용도 최소화된다.</p>
<p>Node.js는 기본적으로 이벤트 루프 기반의 싱글 스레드 모델이다. Worker Threads를 사용하면 병렬 처리가 가능하지만, 스레드 생성과 메시지 패싱에 따른 추가 오버헤드가 발생한다. Biome은 OS 수준의 네이티브 스레드를 직접 활용하기 때문에 이런 오버헤드 없이 CPU 코어를 최대한 활용할 수 있다.</p>
<hr>
<h3 id="메모리-효율적인-ast-처리"><a class="anchor" href="#메모리-효율적인-ast-처리">메모리 효율적인 AST 처리</a></h3>
<p><img src="/content/241201/4.svg" alt="4.svg" loading="lazy" decoding="async"></p>
<p>Biome은 CST(Concrete Syntax Tree, 구체 구문 트리)를 사용한다. Biome의 공식 아키텍처 문서에 따르면, 이 CST는 rowan 라이브러리의 내부 포크를 기반으로 구현된 Green/Red Tree 패턴으로, 주석과 공백 등 원본 코드의 모든 정보를 보존한다. rowan의 아레나(Arena) 스타일 메모리 할당은 노드를 연속된 메모리 영역에 배치하여 CPU 캐시 지역성(Cache Locality)을 높이고, 불필요한 객체 할당을 최소화한다.</p>
<p>JavaScript의 객체 기반 AST 처리 방식은 각 노드가 독립적인 힙 객체로 존재하기 때문에, 메모리가 분산되고 GC 압력이 높아진다. Biome의 접근 방식은 더 적은 메모리를 사용하면서도 더 빠른 트리 순회가 가능한 것이다.</p>
<hr>
<h2 id="그래서-biome을-도입해야-할까"><a class="anchor" href="#그래서-biome을-도입해야-할까">그래서, Biome을 도입해야 할까?</a></h2>
<p>Biome의 성능과 편의성은 분명 매력적이다. 하지만 모든 프로젝트에 무조건 도입하는 것이 정답은 아니라고 생각한다. 몇 가지 현실적인 고려사항을 짚어보자.</p>
<hr>
<h3 id="biome이-적합한-경우"><a class="anchor" href="#biome이-적합한-경우">Biome이 적합한 경우</a></h3>
<ul>
<li><strong>대규모 코드베이스</strong>를 운영하고 있어 빌드/린트 성능이 중요한 경우</li>
<li>CI/CD 파이프라인에서 코드 검사 시간을 줄이고 싶은 경우</li>
<li>ESLint + Prettier 설정의 복잡성에 지친 경우</li>
<li>새 프로젝트를 시작하면서 간결한 도구 설정을 원하는 경우</li>
</ul>
<p>필자의 팀도 대규모 프로젝트를 운영하면서 CI 파이프라인에서 린팅에 많은 시간이 소요되고 있었고, 개발자들이 느린 린팅 속도에 불편을 느끼고 있어 Biome 도입을 결정했다.</p>
<hr>
<h3 id="주의해야-할-점"><a class="anchor" href="#주의해야-할-점">주의해야 할 점</a></h3>
<p><strong>플러그인 생태계의 한계가 가장 크다.</strong> ESLint에는 수천 개의 커뮤니티 플러그인이 존재하지만, Biome은 빌트인 규칙 중심으로 운영된다. <code>eslint-plugin-react</code>, <code>eslint-plugin-react-hooks</code>, <code>eslint-plugin-jsx-a11y</code>, <code>eslint-plugin-unicorn</code>, <code>typescript-eslint</code> 등 주요 플러그인의 규칙 상당수가 내장되어 있지만, 각 플러그인의 모든 규칙이 포팅된 것은 아니다. Biome v2에서 GritQL 기반의 플러그인 시스템 도입이 예고되어 있으나, 아직 실험적 단계이다. <code>@next/eslint-plugin-next</code>, <code>eslint-plugin-angular</code> 같은 프레임워크 특화 규칙이 필수적인 프로젝트라면 마이그레이션에 신중할 필요가 있다.</p>
<p><strong>언어 지원 범위도 확인이 필요하다.</strong> JavaScript, TypeScript, JSX, CSS, JSON, GraphQL은 안정적으로 지원하지만, Vue나 Svelte의 SFC(Single File Component) 파일은 <code>&#x3C;script></code> 블록만 부분적으로 지원한다. HTML, YAML, Markdown은 아직 지원하지 않는다.</p>
<p><strong>ESLint도 진화하고 있다는 점을 잊으면 안 된다.</strong> ESLint v9(2024년 4월)에서 도입된 Flat Config(<code>eslint.config.js</code>)는 기존 <code>.eslintrc</code> 방식의 복잡성을 대폭 간소화했다. 거기에 <code>@eslint/json</code>(2024년 10월)과 <code>@eslint/css</code>(2025년 2월)를 출시하며 JavaScript 외 언어까지 린팅 영역을 확장하고 있다. ESLint Stylistic(<code>@stylistic/eslint-plugin</code>) 프로젝트는 Prettier 없이도 ESLint만으로 포매팅을 처리할 수 있는 옵션을 제공한다. Biome의 "올인원" 장점이 ESLint 생태계의 진화로 다소 희석되고 있는 구도인 것이다.</p>
<p>또한 Rome에서 Biome으로 전환된 역사도 기억해 둘 필요가 있다. Rome이 아카이빙되면서 기존 사용자들이 겪은 불편함은 도구 선택에서 프로젝트의 지속 가능성이 얼마나 중요한지를 보여주는 사례이다. 다행히 Biome은 OpenCollective와 GitHub Sponsors를 통한 펀딩으로 운영되며, 꾸준한 릴리스 주기를 유지하고 있다.</p>
<p><img src="/content/241201/8.png" alt="8.png" loading="lazy" decoding="async"></p>
<p>npm trends를 보면 ESLint(주간 약 1억 2,000만)와 Prettier(주간 약 8,200만) 대비 Biome(주간 약 690만)의 다운로드 수는 아직 격차가 크다. 하지만 Biome의 성장 속도는 주목할 만하다. 불과 1년여 만에 주간 다운로드가 3~4배 이상 증가했으며, 특히 신규 프로젝트에서의 채택률이 눈에 띄게 올라가고 있다.</p>
<hr>
<h2 id="마치며"><a class="anchor" href="#마치며">마치며</a></h2>
<p>Biome이 ESLint와 Prettier를 완전히 대체할 수 있느냐는 질문에 대한 필자의 답은 <strong>"아직은 아니지만, 충분히 유력한 대안"</strong> 이다.</p>
<p>성능은 압도적이고, 설정은 간결하며, 개발 속도도 빠르다. 다만 플러그인 생태계의 미성숙함과 일부 언어 지원의 한계는 프로젝트에 따라 걸림돌이 될 수 있다. 프로젝트의 기술 스택과 팀의 요구사항을 면밀히 검토한 뒤 도입 여부를 판단하는 것이 바람직하다.</p>
<p>한 가지 확실한 것은, 프론트엔드 도구 생태계가 "더 빠르고, 더 간결하며, 더 통합된" 방향으로 나아가고 있다는 점이다. Biome이 그 흐름의 선두에 서 있는 것은 부정할 수 없다. 앞으로의 성장이 기대되는 도구임은 분명하다.</p>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[Zustand 불변성 관리와 immer 미들웨어 동작 원리(valtio를 곁들인)]]></title>
            <link>https://hooninedev.com/241116</link>
            <guid isPermaLink="false">https://hooninedev.com/241116</guid>
            <pubDate>Sat, 16 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[이번 포스팅에서는 Zustand의 immer 미들웨어가 내부적으로 어떻게 동작하는지, 그리고 불변성이라는 개념이 왜 프론트엔드 상태 관리에서 그토록 중요한지에 대한 이야기를 해보려고 한다. 필자는 팀에서 Zustand를 주력 상태 관리 라이브러리로 사용하고 있다. 어느 날 깊게 중첩된 객체 상태를 업데이트해야 하는 상황이 생겼는데, 스프레드 연산자를 세 겹...]]></description>
            <content:encoded><![CDATA[<p>이번 포스팅에서는 Zustand의 immer 미들웨어가 내부적으로 어떻게 동작하는지, 그리고 불변성이라는 개념이 왜 프론트엔드 상태 관리에서 그토록 중요한지에 대한 이야기를 해보려고 한다.</p>
<p>필자는 팀에서 Zustand를 주력 상태 관리 라이브러리로 사용하고 있다. 어느 날 깊게 중첩된 객체 상태를 업데이트해야 하는 상황이 생겼는데, 스프레드 연산자를 세 겹, 네 겹 중첩하다 보니 코드가 마치 피라미드처럼 쌓여가는 것이었다.</p>
<p>그래서 immer 미들웨어를 도입했고, 자연스럽게 "이 녀석은 대체 내부에서 어떻게 불변성을 보장하는 걸까?"라는 궁금증이 생겼다. 더 나아가 Zustand의 메인테이너인 Daishi Kato에게 직접 질문을 던지기까지 했는데, 그 과정에서 valtio라는 라이브러리까지 알게 되었다.</p>
<p>이 글에서는 불변성의 기본 개념부터 시작해서, immer의 Proxy 기반 변경 추적 메커니즘, Zustand immer 미들웨어의 소스 코드 분석, 그리고 valtio의 접근 방식까지 깊이 있게 다뤄볼 것이다.</p>
<hr>
<h1 id="불변성이란-무엇이고-왜-필요한가"><a class="anchor" href="#불변성이란-무엇이고-왜-필요한가">불변성이란 무엇이고, 왜 필요한가</a></h1>
<p>불변성(Immutability)은 한번 생성된 데이터의 상태가 이후에 변경되지 않는다는 개념이다. 직접 수정을 금지하고, 변경이 필요할 때마다 새로운 객체를 생성하여 원본 데이터의 무결성을 유지하는 것이다.</p>
<p><strong>"그냥 값을 바꾸면 되는데, 왜 굳이 새로운 객체를 만들어야 하는가?"</strong> 라고 생각할 수 있다. 이 질문에 답하려면 React의 렌더링 메커니즘을 이해해야 한다.</p>
<p>React는 상태가 변경되었는지를 <strong>참조 비교(Reference Equality)</strong> 로 판단한다. 즉, 이전 상태와 현재 상태가 같은 메모리 주소를 가리키고 있는지를 확인하는 것이다. 객체 내부의 값이 바뀌었더라도 참조가 동일하면 React는 "아무것도 안 변했네"라고 판단하고 리렌더링을 하지 않는다.</p>
<p>반대로, 불변성을 지켜서 새로운 객체를 생성하면 참조가 달라지므로 React가 변경을 정확하게 감지할 수 있다. 이것이 불변성이 React 생태계에서 필수적인 이유인 것이다.</p>
<hr>
<h2 id="참조-비교는-어떻게-동작하는가"><a class="anchor" href="#참조-비교는-어떻게-동작하는가">참조 비교는 어떻게 동작하는가</a></h2>
<p>참조 비교는 메모리 관점에서 두 값이 동일한 메모리 주소를 가리키고 있는지를 확인하는 것을 의미한다. JavaScript에서 원시값(Primitive)은 값 자체를 비교하고, 참조값(Reference)은 메모리 주소를 비교한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 원시값: 값 자체를 비교</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> a</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 42</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> b</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 42</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(a </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> b); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 같은 값(true)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 참조값: 메모리 주소를 비교</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> obj1</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { name: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"John"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> };</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> obj2</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { name: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"John"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(obj1 </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> obj2); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 내용은 같지만 다른 메모리 주소(false)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> obj3</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> obj1;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(obj1 </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> obj3); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 같은 메모리 주소를 가리킴(true)</span></span></code></pre></figure>
<p>여기서 한 가지 더 알아야 할 것이 있다. 얕은 비교(Shallow Comparison)와 깊은 비교(Deep Comparison)의 차이다.</p>
<p>얕은 비교는 객체의 최상위 레벨에서만 참조를 비교한다. React의 <code>React.memo</code>나 <code>useMemo</code>, <code>useCallback</code> 등이 기본적으로 사용하는 방식이다. 깊은 비교는 객체의 모든 중첩 레벨에서 값을 재귀적으로 비교하는데, 성능 비용이 크기 때문에 일반적으로 권장되지 않는다.</p>
<p>이 때문에 React에서는 상태를 업데이트할 때 최상위 참조를 변경해주는 것이 중요하다. 불변 업데이트가 필요한 이유가 바로 여기에 있는 것이다.</p>
<hr>
<h2 id="불변성을-지키지-않으면-생기는-일"><a class="anchor" href="#불변성을-지키지-않으면-생기는-일">불변성을 지키지 않으면 생기는 일</a></h2>
<p>비유를 하나 들어보겠다. 불변성을 지키지 않는 상태 관리는 마치 공유 구글 문서에서 실행 취소(Undo)가 작동하지 않는 상황과 같다. 여러 사람이 동시에 같은 문서를 수정하는데, 누가 무엇을 바꿨는지 추적이 안 되고, 이전 상태로 돌아갈 수도 없는 것이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 불변성을 지키지 않는 예시 - 직접 수정</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> state</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { user: { name: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"John"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, address: { city: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Seoul"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } } };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">state.user.address.city </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "Busan"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 원본을 직접 수정</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// React는 state 참조가 바뀌지 않았으므로 변경을 감지하지 못한다</span></span></code></pre></figure>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 불변성을 지키는 예시 - 새 객체 생성</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> newState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  ...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">state,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  user: {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    ...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">state.user,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    address: {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      ...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">state.user.address,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      city: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Busan"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 새로운 참조가 생성되어 React가 변경을 감지할 수 있다</span></span></code></pre></figure>
<p>보이는가? 불변성을 지키는 코드가 얼마나 장황한지. 중첩이 깊어질수록 이 스프레드 연산자의 향연은 더욱 심해진다. (필자는 이것을 "스프레드 지옥"이라 부르고 있다.)</p>
<p>그렇다면 이 문제를 우아하게 해결할 방법은 없을까?</p>
<hr>
<h1 id="zustand에서-immer-미들웨어를-사용하는-이유"><a class="anchor" href="#zustand에서-immer-미들웨어를-사용하는-이유">Zustand에서 immer 미들웨어를 사용하는 이유</a></h1>
<p><img src="/content/241116/3.png" alt="3.png" loading="lazy" decoding="async"></p>
<p>깊은 객체 구조에서 수동으로 불변성을 관리하는 것은 복잡하고 에러가 발생하기 쉽다. 특히 스프레드 연산자나 <code>Object.assign()</code> 같은 방식은 깊은 중첩 구조에서 코드가 매우 복잡해질 수 있다.</p>
<p>immer는 이 문제를 draft 객체라는 개념으로 해결한다. 마치 직접 객체를 수정하는 것처럼 코드를 작성하지만, 실제로는 불변 업데이트가 이루어지는 것이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> create </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "zustand"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { immer } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "zustand/middleware/immer"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> useStore</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> create</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  immer</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    users: [],</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    addUser</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">user</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">        // 마치 직접 push하는 것처럼 보이지만,</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">        // 내부적으로는 새로운 배열이 생성된다</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        state.users.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">push</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(user);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      }),</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    updateUserCity</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">userId</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">city</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">        // 깊은 중첩 구조도 직관적으로 수정 가능</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> user</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> state.users.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">find</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">u</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> u.id </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> userId);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (user) user.address.city </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> city;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      }),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span></code></pre></figure>
<p>스프레드 지옥에서 벗어나 마치 뮤터블(mutable)하게 코드를 작성할 수 있다. 하지만 결과는 이뮤터블(immutable)한 새로운 상태가 생성되는 것이다. 이것이 immer의 마법인 것이다.</p>
<p>그렇다면 이 마법은 어떻게 구현되어 있을까? 그 비밀은 JavaScript의 Proxy에 있다.</p>
<hr>
<h2 id="proxy"><a class="anchor" href="#proxy">Proxy</a></h2>
<p>본격적으로 immer의 내부를 들여다보기 전에, 그 핵심 메커니즘인 Proxy에 대해 먼저 짚고 넘어가자.</p>
<p>Proxy는 <strong>객체에 대한 기본 동작(속성 접근, 할당, 함수 호출 등)을 가로채고 재정의할 수 있는 JavaScript의 내장 기능이다.</strong> 원본 객체를 감싸는 일종의 "감시자" 역할을 한다고 생각하면 된다. (보안 카메라 같은 존재다. 객체에 누가 접근하고, 무엇을 바꾸는지 모두 기록한다.)</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> target</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { name: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"John"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, age: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">30</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> handler</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 속성을 읽을 때 가로챈다 (get trap)</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">target</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">prop</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">`"${</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">String</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">(</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">prop</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">)</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}" 속성을 읽었다`</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Reflect.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(target, prop);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 속성을 쓸 때 가로챈다 (set trap)</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">target</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">prop</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">`"${</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">String</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">(</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">prop</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">)</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}" 속성을 ${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">value</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}로 변경했다`</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Reflect.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(target, prop, value);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> proxy</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Proxy</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(target, handler);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">proxy.name;        </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 콘솔: "name" 속성을 읽었다</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">proxy.age </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 31</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;    </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 콘솔: "age" 속성을 31로 변경했다</span></span></code></pre></figure>
<p>Proxy의 handler에는 <code>get</code>, <code>set</code>, <code>deleteProperty</code>, <code>has</code> 등 다양한 <strong>트랩(trap)</strong> 을 정의할 수 있다. immer는 바로 이 트랩들을 활용해서 draft 객체에 대한 모든 변경 사항을 추적하는 것이다.</p>
<hr>
<h2 id="draft-객체"><a class="anchor" href="#draft-객체">draft 객체</a></h2>
<p>draft 객체는 불변성을 유지하면서도 마치 직접 객체를 수정하는 것처럼 코드를 작성할 수 있게 해주는 Proxy 기반의 임시 객체이다.</p>
<p>immer의 <code>produce</code> 함수가 실행되면 다음과 같은 과정이 일어난다.</p>
<ol>
<li><strong>Proxy 생성</strong>: 원본 상태(base state)를 감싸는 Proxy 객체, 즉 draft를 생성한다</li>
<li><strong>recipe 실행</strong>: 사용자가 전달한 함수(recipe)에 draft를 넘기고, 사용자는 이 draft를 자유롭게 "수정"한다</li>
<li><strong>변경 추적</strong>: Proxy의 <code>set</code> 트랩이 모든 변경 사항을 내부적으로 기록한다</li>
<li><strong>새 상태 생성</strong>: recipe 실행이 끝나면, 기록된 변경 사항을 바탕으로 새로운 불변 객체를 생성한다</li>
</ol>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { produce } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "immer"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> baseState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  name: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"John"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  age: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">30</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  address: { city: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Seoul"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, zip: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"12345"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> nextState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> produce</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(baseState, (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">draft</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  draft.age </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;                  </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// set 트랩이 age 변경을 기록</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  draft.address.city </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "Busan"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;    </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 중첩 객체의 변경도 추적</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// baseState는 전혀 변경되지 않았다</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(baseState.age);           </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 30</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(baseState.address.city);  </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// "Seoul"</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// nextState는 변경이 적용된 새로운 객체이다</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(nextState.age);           </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 31</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(nextState.address.city);  </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// "Busan"</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 구조적 공유: 변경되지 않은 부분은 같은 참조를 유지한다</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(baseState.address </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> nextState.address); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// false (city가 변경됨)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(baseState </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> nextState);                  </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// false</span></span></code></pre></figure>
<p>여기서 주목할 점은 <strong>구조적 공유(Structural Sharing)</strong> 이다. immer는 변경된 부분만 새로운 객체를 생성하고, 변경되지 않은 부분은 원본의 참조를 그대로 유지한다. 이 덕분에 메모리 효율성과 불변성을 동시에 확보할 수 있는 것이다.</p>
<hr>
<h1 id="immer-내부-동작"><a class="anchor" href="#immer-내부-동작">immer 내부 동작</a></h1>
<p>이제 본격적으로 소스 코드를 들여다볼 차례이다. 먼저 immer의 핵심인 <code>produce</code> 함수부터 살펴보자.</p>
<h2 id="produce-함수"><a class="anchor" href="#produce-함수">produce 함수</a></h2>
<p>immer의 <code>Immer</code> 클래스 내부에 정의된 <code>produce</code> 메서드는 세 가지 주요 단계를 거친다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">produce</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(base, recipe, patchListener) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 1. 커링 지원: base가 함수이면 커링된 producer를 반환</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> base </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "function"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> &#x26;&#x26;</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> recipe </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "function"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> defaultBase</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> recipe;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    recipe </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> base;</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 커링된 함수를 반환</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">base</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> defaultBase, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">args</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">produce</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(base, (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">draft</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> recipe.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">call</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, draft, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">args));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 2. 스코프 진입: 변경 추적을 위한 스코프를 생성</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> scope</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> enterScope</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 3. Proxy 생성: base 객체를 감싸는 draft proxy를 생성</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> proxy</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createProxy</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(scope, base, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">undefined</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 4. recipe 실행: 사용자 코드에서 draft를 "수정"</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> result</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> recipe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(proxy);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 5. 마무리: 변경사항을 적용한 새로운 불변 객체 반환</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> processResult</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(result, scope);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>여기서 <code>recipe</code>라는 이름이 재미있다. (필자도 처음에는 "왜 레시피지?"라고 생각했는데, 요리 레시피처럼 "상태를 이렇게 저렇게 변형하라"는 지시서라는 의미인 것 같다.) 실제로 immer의 GitHub 소스에서 이 매개변수 이름이 <code>recipe</code>로 되어 있다.</p>
<p>커링(Currying) 지원은 Zustand의 immer 미들웨어에서 핵심적인 역할을 한다. <code>produce</code>에 함수 하나만 전달하면, 나중에 base state를 받아서 실행하는 새로운 함수를 반환하는 것이다. 이 점을 기억해두자. 곧 immerImpl을 분석할 때 다시 등장한다.</p>
<hr>
<h2 id="proxy는-어떻게-변경을-추적하는가"><a class="anchor" href="#proxy는-어떻게-변경을-추적하는가">Proxy는 어떻게 변경을 추적하는가</a></h2>
<p>immer의 <code>createProxyProxy</code> 함수는 draft를 생성할 때 내부적으로 상태 추적 객체를 함께 만든다. 이 객체에는 다음과 같은 핵심 필드가 있다.</p>
<ul>
<li><code>modified_</code> : 변경이 발생했는지 여부를 나타내는 boolean 플래그</li>
<li><code>assigned_</code> : 어떤 속성이 변경(set)되었거나 삭제(delete)되었는지를 추적하는 Map</li>
<li><code>copy_</code> : 변경이 발생했을 때 생성되는 얕은 복사본</li>
</ul>
<p>Proxy의 각 트랩은 다음과 같이 동작한다.</p>
<p><strong>get 트랩</strong>: 속성을 읽을 때 호출된다. 핵심은 **지연 생성(Lazy Drafting)**이다. 중첩된 객체에 접근할 때 그 시점에서 비로소 해당 객체의 draft proxy를 생성한다. 모든 중첩 객체를 미리 프록시로 감싸는 것이 아니라, 실제로 접근하는 순간에만 프록시를 만드는 것이다. 이 전략 덕분에 깊은 중첩 구조에서도 성능이 유지된다.</p>
<p><strong>set 트랩</strong>: 속성에 값을 할당할 때 호출된다. 새로운 값이 현재 값과 다른지 비교하고, 다르면 <code>prepareCopy()</code>를 호출하여 얕은 복사본을 생성한 뒤 <code>markChanged()</code>로 변경 플래그를 설정한다. <code>assigned_</code> Map에도 해당 속성을 <code>true</code>로 기록한다.</p>
<p><strong>deleteProperty 트랩</strong>: 속성을 삭제할 때 호출된다. <code>assigned_</code> Map에 해당 속성을 <code>false</code>로 기록하고, 마찬가지로 변경 상태를 전파한다.</p>
<pre><code>[사용자 코드]
draft.user.name = "Jane"
     │
     ▼
[get 트랩] draft.user에 접근 → user 객체의 draft proxy를 지연 생성
     │
     ▼
[set 트랩] name = "Jane" 할당
     ├── prepareCopy(): 얕은 복사본 생성
     ├── markChanged(): modified_ = true (부모까지 재귀적으로 전파)
     └── assigned_.set("name", true): 변경 기록
</code></pre>
<p><code>markChanged()</code> 함수는 특히 중요하다. 변경이 발생한 노드에서 부모 방향으로 재귀적으로 올라가며 <code>modified_</code> 플래그를 전파한다. 이를 통해 최상위 produce 함수가 어느 부분에서 변경이 발생했는지를 알 수 있는 것이다.</p>
<hr>
<h2 id="finalization"><a class="anchor" href="#finalization">Finalization</a></h2>
<p>recipe 실행이 완료되면 <code>processResult</code>를 거쳐 finalization 단계가 시작된다. 이 과정에서 immer는 변경 추적 데이터를 바탕으로 최종 불변 상태를 생성한다.</p>
<ol>
<li><code>modified_</code>가 <code>false</code>인 노드는 원본 참조를 그대로 반환한다 (구조적 공유)</li>
<li><code>modified_</code>가 <code>true</code>인 노드는 <code>copy_</code>를 기반으로 새 객체를 생성한다</li>
<li>자식 노드들도 재귀적으로 같은 과정을 거친다</li>
<li><code>Proxy.revocable()</code>로 생성된 프록시를 revoke하여 더 이상 draft에 접근할 수 없게 한다</li>
</ol>
<p>여기서 <code>Proxy.revocable()</code>의 사용이 눈에 띈다. 일반 Proxy와 달리, revocable proxy는 나중에 무효화(revoke)할 수 있다. recipe 실행이 끝난 후 draft에 접근하려 하면 에러가 발생하도록 하여, 사용자가 실수로 draft를 recipe 바깥에서 수정하는 것을 방지하는 것이다. (안전벨트 같은 존재라고 할 수 있다.)</p>
<hr>
<h1 id="zustand-immerimpl-미들웨어의-정체"><a class="anchor" href="#zustand-immerimpl-미들웨어의-정체">Zustand immerImpl: 미들웨어의 정체</a></h1>
<p>자, 이제 immer의 내부 동작을 이해했으니, <a href="https://github.com/pmndrs/zustand/blob/main/src/middleware/immer.ts">Zustand의 immer 미들웨어</a> 가 어떻게 이것을 활용하는지 살펴보자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> immerImpl</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ImmerImpl</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">initializer</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">store</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ReturnType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> initializer>;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">updater</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">replace</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">a</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> nextState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> updater </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "function"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> ?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> produce</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(updater </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209"> updater</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    ) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">s</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Partial</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(nextState, replace </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">a);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> initializer</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(store.setState, get, store);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre></figure>
<p>코드가 짧다고 놀랄 수 있다. 필자도 처음 봤을 때 "이게 전부인가?"라고 생각했다. (Zustand의 미니멀리즘이 여기서도 빛을 발한다.)</p>
<p>하지만 이 짧은 코드 안에 핵심적인 패턴이 녹아있다. 하나씩 뜯어보자.</p>
<hr>
<h2 id="고차-함수-패턴"><a class="anchor" href="#고차-함수-패턴">고차 함수 패턴</a></h2>
<p><code>immerImpl</code>은 세 겹의 화살표 함수로 구성된 <strong>고차 함수(Higher-Order Function)</strong> 이다.</p>
<pre><code>immerImpl(initializer)          // 1단계: store creator 함수를 받는다
  → (set, get, store) => { }   // 2단계: Zustand의 store API를 받는다
    → initializer(...)          // 3단계: 수정된 setState로 initializer를 실행한다
</code></pre>
<p>Zustand에서 미들웨어는 store creator를 감싸서 새로운 store creator를 반환하는 패턴을 따른다. <code>set</code>(상태 업데이트 함수), <code>get</code>(현재 상태를 가져오는 함수), <code>store</code>(스토어 객체 자체에 대한 참조)가 Zustand 내부에서 주입되는 것이다.</p>
<hr>
<h2 id="setstate-가로채기"><a class="anchor" href="#setstate-가로채기">setState 가로채기</a></h2>
<p>핵심은 <code>store.setState</code>를 재정의하는 부분이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">updater</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">replace</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">a</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> nextState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> updater </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "function"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> ?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> produce</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(updater </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209"> updater</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  ) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">s</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Partial</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(nextState, replace </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">a);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre></figure>
<p>원래 Zustand의 <code>setState</code>는 새로운 상태 객체나 업데이터 함수를 받는다. immer 미들웨어는 이것을 가로채서 다음과 같이 처리한다.</p>
<p><strong>updater가 함수인 경우</strong>: <code>produce(updater)</code>를 호출한다. 앞서 언급한 produce의 커링 기능이 여기서 빛을 발한다. <code>produce</code>에 함수 하나만 전달하면, 해당 함수를 recipe로 사용하는 새로운 producer 함수를 반환한다. 이 반환된 함수가 나중에 <code>set</code>을 통해 현재 상태를 받아 실행되는 것이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// produce(updater)의 커링</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// updater = (state) => { state.count += 1; }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// produce가 반환하는 것:</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// (currentState) => produce(currentState, (draft) => { draft.count += 1; })</span></span></code></pre></figure>
<p><strong>updater가 객체인 경우</strong>: Zustand의 기본 <code>set</code>처럼 부분 상태(Partial State)를 직접 전달한다. 이 경우 immer를 거치지 않는다.</p>
<p>마지막으로 <code>initializer(store.setState, get, store)</code>를 호출하여, 사용자가 정의한 store creator에 수정된 <code>setState</code>를 전달한다. 이로써 store 내부에서 <code>set</code>을 호출할 때마다 자동으로 immer의 <code>produce</code>가 적용되는 것이다.</p>
<hr>
<h2 id="타입-안전성"><a class="anchor" href="#타입-안전성">타입 안전성</a></h2>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">s</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Partial</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span></code></pre></figure>
<p>이 타입 단언(Type Assertion)이 처음에는 이해하기 어려울 수 있다. 이것은 <code>nextState</code>가 될 수 있는 세 가지 형태를 명시하는 것이다.</p>
<ul>
<li><code>(s: T) => T</code>: 현재 상태를 받아 새 상태를 반환하는 함수 (produce로 감싸진 커링된 함수)</li>
<li><code>T</code>: 전체 상태 객체 그 자체</li>
<li><code>Partial&#x3C;T></code>: 상태의 일부분만 담은 객체</li>
</ul>
<p>Zustand의 <code>set</code> 함수가 이 세 가지 형태를 모두 지원하기 때문에, immer 미들웨어도 이를 맞춰주는 것이다.</p>
<hr>
<h1 id="immer--완전한-불변성"><a class="anchor" href="#immer--완전한-불변성">Immer = 완전한 불변성?</a></h1>
<p>여기까지 읽으면 자연스럽게 떠오르는 질문이 하나 있다. "immer를 쓰면 불변성 걱정은 끝인가?"</p>
<p>결론부터 말하면, <strong>아니다</strong>. Zustand의 immer 미들웨어는 <code>produce</code> 함수가 실행되는 동안에만 불변성을 보장한다. 그 바깥에서는 여전히 JavaScript의 본질적인 한계가 존재한다.</p>
<hr>
<h2 id="javascript"><a class="anchor" href="#javascript">JavaScript</a></h2>
<p>JavaScript라는 언어 자체가 불변성을 완벽하게 보장하도록 설계되지 않았다. 몇 가지 근본적인 문제를 살펴보자.</p>
<p><strong>1. 객체는 기본적으로 참조 전달이다</strong></p>
<p>얕은 복사만으로는 깊은 중첩 객체의 불변성을 보장할 수 없다. <code>const</code>도 참조의 재할당만 막을 뿐, 내부 속성 변경은 막지 못한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> obj</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { inner: { value: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">obj.inner.value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// const인데도 변경 가능!</span></span></code></pre></figure>
<p><strong>2. Object.freeze()는 얕은 동결만 제공한다</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> frozen</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Object.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">freeze</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ inner: { value: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">frozen.inner.value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// strict mode가 아니면 에러도 안 난다!</span></span></code></pre></figure>
<p><strong>3. 배열 메서드의 함정</strong></p>
<p><code>push</code>, <code>pop</code>, <code>splice</code>, <code>sort</code>, <code>reverse</code> 등 많은 배열 메서드가 원본을 직접 수정한다. (사실 이건 나쁜 습관이기도 한데, 생각보다 많은 개발자가 어떤 배열 메서드가 원본을 변경하는지 정확히 인지하지 못하고 있다.)</p>
<p><strong>4. 프로토타입 체인을 통한 우회</strong></p>
<p>프로토타입을 통한 우회적 접근이 가능하고, 상속된 속성은 예상치 못한 부수 효과를 초래할 수 있다.</p>
<hr>
<h2 id="produce-바깥에서의-위험"><a class="anchor" href="#produce-바깥에서의-위험">produce 바깥에서의 위험</a></h2>
<p>immer의 produce 내부에서는 Proxy가 모든 변경을 추적하고 안전하게 관리한다. 하지만 produce 바깥에서는 아무런 보호 장치가 없다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> useStore</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> create</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  immer</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    data: { nested: { value: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } },</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // produce 안에서는 안전</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    safeUpdate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { state.data.nested.value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; }),</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 하지만 이런 실수는 막을 수 없다</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    getDataRef</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">().data,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 외부에서 참조를 가져와 직접 수정하면?</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> dataRef</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> useStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">().data;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">dataRef.nested.value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 999</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// immer가 관여하지 않는 직접 수정!</span></span></code></pre></figure>
<p>이런 상황에서 어떻게 대응해야 할까? 필자는 Zustand Discord에 직접 이 질문을 던져보았다.</p>
<p><img src="/content/241116/image.png" alt="이미지1" loading="lazy" decoding="async"></p>
<p>Discord 커뮤니티에서는 특별한 문제가 없다면 <code>Object.freeze()</code>를 사용하라는 제안을 받았다. 성능상에 큰 문제가 없는지 재질문을 했더니, 성능에 많은 영향을 주지 않는다는 답변도 받았다.</p>
<p>그런데 이야기는 여기서 끝나지 않았다. 이틀 뒤, Zustand의 메인테이너인 <strong>Daishi Kato</strong>가 직접 답변을 달았다. immer로는 완전한 불변성 보장이 힘들기에 <strong>valtio</strong>를 만들었다는 것이다.</p>
<p><img src="/content/241116/image2.png" alt="이미지2" loading="lazy" decoding="async"></p>
<p>Zustand를 만든 사람이 "불변성을 제대로 보장하려면 내가 만든 다른 라이브러리를 보라"고 한 것이다.</p>
<p>그래서 필자도 valtio를 알아보러 갔다.</p>
<hr>
<h1 id="valtio"><a class="anchor" href="#valtio">valtio</a></h1>
<p>Zustand의 immer를 파헤치다가 여기까지 왔다. 조금만 더 힘내보자.</p>
<p>valtio와 Zustand + immer 조합의 가장 큰 차이점은 <strong>Proxy의 수명(lifetime)</strong> 에 있다. 이것이 두 라이브러리의 철학을 근본적으로 갈라놓는 지점이다.</p>
<hr>
<h2 id="immer의-proxy-와-valtio의-proxy"><a class="anchor" href="#immer의-proxy-와-valtio의-proxy">immer의 Proxy 와 valtio의 Proxy</a></h2>
<p>immer에서 Proxy는 <code>produce</code> 함수가 실행되는 동안에만 존재하는 임시 객체이다. recipe 함수가 끝나면 Proxy는 <code>revoke</code>되어 사라진다. 변경사항을 기록했다가 한번에 새로운 불변 객체를 생성하는, 말하자면 <strong>"일회용 감시 카메라"</strong> 같은 존재인 것이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">produce</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(state, (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">draft</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 이 블록 안에서만 Proxy가 존재</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  draft.count </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 여기서는 Proxy가 이미 사라졌다</span></span></code></pre></figure>
<p>반면 valtio에서 Proxy는 상태 객체가 생성되는 순간부터 영구적으로 유지된다. 상태 자체가 곧 Proxy인 것이다. 모든 읽기와 쓰기가 항상 Proxy를 통해 이루어지므로, 상태 변경을 실시간으로 감지하고 추적할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { proxy } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "valtio"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> state</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> proxy</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ count: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 이 순간부터 영구적인 Proxy</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">state.count </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// Proxy가 자동으로 변경을 감지하고 구독자에게 통지</span></span></code></pre></figure>
<p>비유하자면 immer의 Proxy는 "필요할 때만 켜는 블랙박스"이고, valtio의 Proxy는 "항상 켜져 있는 CCTV"인 것이다. 후자가 실시간 감시에는 더 유리하지만, 그만큼 항상 동작하고 있다는 의미이기도 하다.</p>
<hr>
<h2 id="valtio의-proxy"><a class="anchor" href="#valtio의-proxy">valtio의 proxy</a></h2>
<p>valtio의 <code>proxy</code> 함수는 다음과 같이 동작한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> proxy</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> object</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">baseObject</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> listeners</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Listener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> proxyObject</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> newProxy</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(baseObject, handler);</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // proxyStateMap에 상태 메타데이터를 저장</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  proxyStateMap.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(proxyObject, [baseObject, ensureVersion, addListener]);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> proxyObject;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>각 프록시는 <code>proxyStateMap</code>이라는 WeakMap에 자신의 상태 메타데이터(원본 객체, 버전 추적 함수, 리스너 등록 함수)를 저장한다. 이를 통해 프록시 객체로부터 언제든지 내부 상태에 접근할 수 있는 것이다.</p>
<hr>
<h2 id="handler-trap"><a class="anchor" href="#handler-trap">handler trap</a></h2>
<p>valtio의 Proxy 핸들러에서 가장 중요한 것은 <code>set</code> 트랩이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(target: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, prop: string </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> symbol, value: any, receiver: object) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> hasPrevValue</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> !</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">isInitializing</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Reflect.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">has</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(target, prop);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> prevValue</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Reflect.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(target, prop, receiver);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 이전 값과 새 값이 같으면 불필요한 업데이트를 방지한다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    hasPrevValue </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    (</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">objectIs</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(prevValue, value) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">||</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      (proxyCache.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">has</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> objectIs</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(prevValue, proxyCache.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value))))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  ) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  removePropListener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(prop);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">isObject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value)) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getUntracked</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">||</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 핵심: 중첩된 객체도 자동으로 프록시로 감싼다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> nextValue</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    !</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">proxyStateMap.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">has</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> canProxy</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> proxy</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  addPropListener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(prop, nextValue);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  Reflect.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(target, prop, nextValue, receiver);</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  notifyUpdate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"set"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, [prop], value, prevValue]);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이 코드에서 핵심적인 부분을 짚어보겠다.</p>
<p><strong>1. Object.is() 기반 비교로 불필요한 업데이트 방지</strong></p>
<p><code>objectIs(prevValue, value)</code>로 이전 값과 새 값을 비교한다. 값이 실제로 변경되지 않았다면 아무런 업데이트도 발생하지 않는다. 이것은 성능 최적화의 첫 번째 방어선이다.</p>
<p><strong>2. 자동 프록시 래핑 (Auto-Proxying)</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> nextValue</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  !</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">proxyStateMap.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">has</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> canProxy</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> proxy</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span></code></pre></figure>
<p>이것이 immer와의 가장 큰 차이점이다. 새로 할당되는 값이 객체이고 아직 프록시가 아니라면, 자동으로 <code>proxy()</code>로 감싼다. 즉, 중첩된 모든 객체가 항상 프록시 상태를 유지하는 것이다. immer처럼 <code>produce</code> 블록 안에서만 추적하는 것이 아니라, 상태 트리 전체가 항상 감시 상태에 있는 것이다.</p>
<p><strong>3. 구독자 알림 (notifyUpdate)</strong></p>
<p>변경이 발생하면 <code>notifyUpdate</code>를 호출하여 <code>["set", [prop], value, prevValue]</code> 형태의 연산(Operation) 정보를 구독자에게 전달한다. <code>deleteProperty</code> 트랩에서도 마찬가지로 <code>["delete", [prop], prevValue]</code> 형태로 알림을 보낸다.</p>
<hr>
<h2 id="proxy-compare-렌더링-최적화의-비밀"><a class="anchor" href="#proxy-compare-렌더링-최적화의-비밀">proxy-compare: 렌더링 최적화의 비밀</a></h2>
<p>그렇다면 valtio는 어떻게 컴포넌트가 사용하는 속성만 추적해서 최적화된 리렌더링을 수행할까? 그 비밀은 <strong>proxy-compare</strong>라는 별도의 라이브러리에 있다.</p>
<blockquote>
<p><a href="https://github.com/dai-shi/proxy-compare">dai-shi/proxy-compare</a></p>
</blockquote>
<p>proxy-compare는 "어떤 속성에 접근했는지"를 추적하는 라이브러리이다. valtio의 <code>useSnapshot</code> 훅이 내부적으로 이것을 사용한다.</p>
<p>동작 원리는 다음과 같다.</p>
<ol>
<li><code>useSnapshot</code>이 상태의 스냅샷을 반환할 때, proxy-compare의 <code>createProxy</code>로 한 번 더 감싼다</li>
<li>컴포넌트의 렌더링 과정에서 접근한 속성들이 <code>affected</code> WeakMap에 기록된다</li>
<li>상태가 변경되면, <code>isChanged</code> 함수가 <code>affected</code>에 기록된 속성만 비교한다</li>
<li>접근하지 않은 속성이 변경되었다면 해당 컴포넌트는 리렌더링하지 않는다</li>
</ol>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { proxy, useSnapshot } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "valtio"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> state</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> proxy</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ name: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"John"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, age: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">30</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, email: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"john@example.com"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> NameComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> snap</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSnapshot</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(state);</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // snap.name만 접근 → name이 변경될 때만 리렌더링</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>{snap.name}&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// state.age = 31; → NameComponent는 리렌더링되지 않는다</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// state.name = "Jane"; → NameComponent가 리렌더링된다</span></span></code></pre></figure>
<p>이것은 매우 세밀한 수준의 렌더링 최적화이다. Zustand에서는 selector를 작성하여 필요한 상태만 구독하는 방식으로 비슷한 효과를 얻을 수 있지만, valtio는 이것을 자동으로 해주는 것이다.</p>
<hr>
<h2 id="스냅샷과-버전-관리"><a class="anchor" href="#스냅샷과-버전-관리">스냅샷과 버전 관리</a></h2>
<p>valtio의 <code>snapshot</code> 함수는 현재 프록시 상태의 읽기 전용 복사본을 생성한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> snapshot</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> object</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">proxyObject</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Snapshot</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">target</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">ensureVersion</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> proxyState </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ProxyState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createSnapshot</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(target, </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ensureVersion</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Snapshot</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>여기서 **버전 번호(version)**가 핵심이다. 상태가 변경될 때마다 버전이 증가하고, 스냅샷은 버전별로 캐싱된다. 같은 버전에 대해 <code>snapshot</code>을 여러 번 호출해도 동일한 캐시된 객체를 반환하므로 불필요한 객체 생성이 발생하지 않는다.</p>
<p>또한 구독자 알림은 기본적으로 <code>Promise.resolve()</code>를 통해 비동기적으로 배치(batch) 처리된다. 여러 속성을 연속으로 변경해도 하나의 알림으로 묶이는 것이다. 이는 React의 배치 업데이트 메커니즘과도 잘 어우러진다.</p>
<hr>
<h2 id="valtio의-장점-정리"><a class="anchor" href="#valtio의-장점-정리">valtio의 장점 정리</a></h2>
<p>valtio가 가진 구조적 장점을 정리하면 다음과 같다.</p>
<p><strong>메모리 효율성</strong>: 프록시를 지속적으로 유지하면서 실제 변경된 부분만 업데이트한다. <code>targetCache</code>와 <code>snapCache</code>를 활용하여 불필요한 객체 재생성을 방지한다.</p>
<p><strong>자동화된 렌더링 최적화</strong>: proxy-compare를 통해 컴포넌트가 접근한 속성만 추적하므로, selector를 수동으로 작성하지 않아도 최적화된 리렌더링이 이루어진다.</p>
<p><strong>직관적인 API</strong>: 상태를 직접 수정하는 것처럼 코드를 작성하면 된다. <code>produce</code>로 감싸거나 <code>set</code> 함수를 호출할 필요가 없다.</p>
<p><strong>실시간 변경 추적</strong>: 상태 변경이 항상 프록시를 통해 이루어지므로, 외부에서 실수로 직접 수정하는 문제를 원천적으로 차단한다.</p>
<hr>
<h2 id="그렇다면-valtio를-당장-도입해야-할까"><a class="anchor" href="#그렇다면-valtio를-당장-도입해야-할까">그렇다면 valtio를 당장 도입해야 할까?</a></h2>
<p>필자는 valtio가 기술적으로 우수한 라이브러리라고 생각한다. 하지만 몇 가지 고려해야 할 점이 있다.</p>
<p>첫째, TypeScript 타입 추론에서 Proxy 기반 라이브러리 특유의 제약이 존재할 수 있다. Proxy로 감싸진 객체의 타입이 원본과 미묘하게 달라지는 경우가 있기 때문이다.</p>
<p>둘째, 생태계와 커뮤니티 규모에서 Zustand에 비해 아직 작은 편이다. 문제가 발생했을 때 참고할 수 있는 자료가 상대적으로 적을 수 있다.</p>
<p>셋째, 기존 Zustand 기반 코드베이스에서의 마이그레이션 비용도 무시할 수 없다.</p>
<p>물론 실시간 변경을 주로 다루거나, 깊은 중첩 구조의 상태를 빈번하게 업데이트하는 상황이라면 valtio가 더 적합할 수 있다. 중요한 것은 프로젝트의 요구사항에 맞는 도구를 선택하는 것이다.</p>
<hr>
<h1 id="마무리"><a class="anchor" href="#마무리">마무리</a></h1>
<p>이번 글에서는 불변성의 기본 개념부터 시작하여, immer가 Proxy를 통해 어떻게 변경을 추적하고 새로운 불변 객체를 생성하는지, Zustand의 immer 미들웨어가 <code>setState</code>를 가로채서 <code>produce</code>의 커링 기능을 어떻게 활용하는지, 그리고 valtio가 영구적인 Proxy와 proxy-compare를 통해 어떻게 더 근본적인 수준에서 불변성과 렌더링 최적화를 달성하는지를 살펴보았다.</p>
<p>불변성은 React 개발에서 피할 수 없는 주제이다. 어떤 도구를 사용하든 그 도구가 불변성을 어떤 범위에서, 어떤 메커니즘으로 보장하는지를 이해하는 것이 중요하다. 도구의 한계를 알아야 그 한계를 보완할 수 있기 때문이다.</p>
<p>이 글을 읽는 독자분들도 각자의 프로젝트 상황에 맞는 최적의 선택을 찾아보기를 바란다. 정답은 없다. 다만 더 나은 선택이 있을 뿐이다.</p>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[어디까지 알고 있으세요. package.json]]></title>
            <link>https://hooninedev.com/241106</link>
            <guid isPermaLink="false">https://hooninedev.com/241106</guid>
            <pubDate>Wed, 06 Nov 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[오늘은 프론트엔드 개발자로서 매일 마주하지만 깊이있게 들여다보지 않았던 package.json에 대해 이야기해볼 예정이다. 프로젝트를 다룰때마다 항상 핸들링하지만 그 이상으로 깊게 공부하지 않았기에 이번기회에 알아보려한다. package.json은 단순한 의존성 관리를 위한 파일을 넘어서 프로젝트의 전반적인 메타데이터와 설정을 관리하는 핵심 파일이다. 지금...]]></description>
            <content:encoded><![CDATA[<p><img src="/content/241106/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<br>
<p>오늘은 프론트엔드 개발자로서 매일 마주하지만 깊이있게 들여다보지 않았던 package.json에 대해 이야기해볼 예정이다.</p>
<p>프로젝트를 다룰때마다 항상 핸들링하지만 그 이상으로 깊게 공부하지 않았기에 이번기회에 알아보려한다.</p>
<p>package.json은 단순한 의존성 관리를 위한 파일을 넘어서 프로젝트의 전반적인 메타데이터와 설정을 관리하는 핵심 파일이다.</p>
<p>지금부터 package.json에 대해 깊이있게 알아보자</p>
<br>
<h1 id="packagejson의-생성-과정"><a class="anchor" href="#packagejson의-생성-과정">package.json의 생성 과정</a></h1>
<p>package.json은 <code>npm init</code>을 사용해서 생성하는 방법과 수동으로 프로젝트 루트 디렉토리에 직접 JSON 형식으로 생성하는 방법이 있다.</p>
<p>JSON 문법에 에러 발생 가능성이 있기에 직접 생성보다는 패키지 라이브러리 init 명령어를 통해 생성하는 것을 추천한다.</p>
<p>init을 이용해 package.json이 생성되는 과정을 살펴보자. (본론에서는 기본으로 많이 사용되는 npm을 예시로 설명하겠다.)</p>
<blockquote>
<p>운영체제, 컴퓨터 시스템 등등 많은 조건에 따라 package.json이 생성되는 과정이 다르고 복잡하다. 이 글을 읽는 사람들이 이해할 수 있는 선에서 설명해보겠다!</p>
</blockquote>
<br>
<h2 id="프로세스-초기화-단계"><a class="anchor" href="#프로세스-초기화-단계">프로세스 초기화 단계</a></h2>
<p>프로세스 초기화 단계는 npm init 명령어 실행 --> 프로세스 생성 --> 환경 변수 로드 --> npm 설정 파일 읽기 --> 기본값 초기화 로 이루어진다.</p>
<p>npm init 명령어를 실행하게 되면 Node.js 런타임이 새로운 프로세스를 생성하게된다. 그렇게하면 process ID(PID)를 할당하게 되고 메모리 공간을 할당한다.</p>
<p>이 때 힙은 동적 메모리를 할당하고, 스택은 함수 호출 및 지역 변수를 할당하게 된다. data segment는 전역변수를 할당한다.</p>
<p>위 작업이 종료되면 환경 설정을 로드한다. (npm을 예시로 했으니) npm 관련 환경 변수(process.env.npm<em>config</em>*), 사용자 홈 디렉토리(process.env.HOME), 시스템 경로(process.env.PATH) 등이 포함된다.</p>
<p>프로세스 초기화 단계의 마지막 과정에는 설정된 파일을 탐색하는 과정을 거친다.</p>
<p>설정 파일 탐색은 .npmrc 파일 탐색을 의미하는데, 프로젝트/사용자/전역/내장 4개의 공간에 위치한 .npmrc 파일을 탐색한다.</p>
<br>
<h2 id="interactive-cli대화식-인터페이스"><a class="anchor" href="#interactive-cli대화식-인터페이스">Interactive CLI(대화식 인터페이스)</a></h2>
<p>Interactive CLI는 package.json의 데이터를 수집하기전에 빈 템플릿을 준비하는 단계다.</p>
<p>이 단계에서 필수 필드와 기본값이 있는 필드로 나누어 빈 객체를 아래 예시코드처럼 초기화를 진행한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PackageJsonTemplate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  constructor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.template </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      name: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 필수</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      version: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 필수</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      description: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 선택</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      main: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 선택 (기본값: index.js)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      scripts: {}, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 선택</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      keywords: [], </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 선택</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      author: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 선택</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      license: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 선택 (기본값: ISC)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 필수 필드</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.requiredFields </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"name"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"version"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">];</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 기본값</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.defaults </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      version: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"1.0.0"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      main: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"index.js"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      license: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"ISC"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      scripts: {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        test: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'echo "Error: no test specified" &#x26;&#x26; exit 1'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>그 다음 프롬프트를 이용해 사용자 입력을 수집한다. 수신 받은 데이터를 검증하게 되고, 이 과정이 완료되면 다음 단계인 파일 시스템 조작단계로 넘어간다.</p>
<p>여기서 조심해야할 부분은 입력받은 데이터를 그대로 저장하는 것이 아닌 임시 데이터로 저장하게된다.</p>
<br>
<h2 id="파일-시스템-조작"><a class="anchor" href="#파일-시스템-조작">파일 시스템 조작</a></h2>
<p>수집된 package.json 데이터를 실제 파일로 저장하는 과정이다.</p>
<p>파일 시스템을 초기화하게 되고, 접근 가능한 권한인지 확인한다. 2단계에서 임시로 저장해둔 데이터를 가지고 파일에 적용하고 임시 파일을 실제 파일로 이동하는 작업을 진행한다.</p>
<p>이 작업은 npm에서 write-file-atomic 패키지의 <code>PackageJsonWriter</code> 클래스의 <code>writePackageJson</code> 함수를 활용해 간단하고 안전하는 기능을 구현하고있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> writeFileAtomic</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> require</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"write-file-atomic"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PackageJsonWriter</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  async</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> writePackageJson</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">filename</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> contents</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> JSON</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">stringify</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(data, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">\n</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      await</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> writeFileAtomic</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(filename, contents);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (error) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      throw</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">`Failed to write package.json: ${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">error</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">.</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">message</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}`</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>파일 시스템 조작 단게에서 파일 변경이 감지 되면 <code>handleFileChange</code> 함수를 이용해 변경을 감지해 이벤트를 처리한다.</p>
<p>오류가 발생하게 되면 백업파일의 유무를 파악해 복구하거나 초기 상태로 초기화를 진행한다.</p>
<p>그리고 마지막으로 Lock 파일을 생성해 동시 작업을 유연하게 만드는 작업을 진행한다.</p>
<br>
<h2 id="json-생성-및-검증"><a class="anchor" href="#json-생성-및-검증">JSON 생성 및 검증</a></h2>
<p>JSON 파일을 생성해 기본값과 사용자 입력 값을 병합한다. 그리고 npm 패키지 이름 규칙에 맞도록 정규화 작업을 진행한다.</p>
<p>그 다음 필수 필드를 비롯한 각 필드의 유효성 검사를 진행한다. 이 과정에는 이름 규칙 검증, 버전 형식 검증, 의존성 형식 검증, Schema 검증 등이 있다.</p>
<p>이후 최종 검증된 데이터를 반환한다.</p>
<br>
<h2 id="오류-처리-및-복구"><a class="anchor" href="#오류-처리-및-복구">오류 처리 및 복구</a></h2>
<p>package.json 을 생성하는 과정에서 다양한 오류과 마주할 수 있다.</p>
<p>npm cli github에서는 다양한 오류 클래스들이 존재하지만 지금까지 제시한 단계에 밀접한 연관을 가진 오류들만 확인해보면 아래와 같다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PackageJsonError</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  constructor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">message</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">code</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    super</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(message);</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.name </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "PackageJsonError"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.code </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> code;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ValidationError</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PackageJsonError</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  constructor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">message</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    super</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(message, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"VALIDATION_ERROR"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> FileSystemError</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PackageJsonError</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  constructor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">message</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    super</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(message, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"FS_ERROR"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> LockError</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> PackageJsonError</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  constructor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">message</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    super</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(message, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"LOCK_ERROR"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이 오류들을 오류 처리 매니저를 통해, 오류들이 복구 시도, 백업 생성, 오류 처리, clean up(정리) 등 다양하게 처리된다.</p>
<p>이 과정에서 로깅 및 모니터링 작업이 일어나 package.json 생성에 따른 전반적 오류와 정보를 확인해볼 수 있다.</p>
<br>
<h2 id="메모리-관리--프로세스-종료"><a class="anchor" href="#메모리-관리--프로세스-종료">메모리 관리 &#x26; 프로세스 종료</a></h2>
<p>package.json은 메모리를 효율적으로 관리하기 위해 dependencies, devDependencies, peerDependencies, bundledDependencies 필드를 생성한다.</p>
<p>이후 모든 프로세스가 종료된다.</p>
<p>이 과정에서도 안전한 종료를 위해 프로세스 종료 신호를 분석한다.</p>
<ul>
<li>SIGTERM: 정상 종료 요청</li>
<li>SIGINT: Ctrl+C로 인한 종료</li>
<li>SIGKILL: 강제 종료 (처리 불가)</li>
<li>uncaughtException: 처리되지 않은 예외</li>
</ul>
<p>위 4가지 신호에의 결과값인 0(정상 종료)/1(에러로 인한 종료)에 따라 프로세스가 마무리된다.</p>
<p>메모리 관리와 프로세스 종료는 시스템 환경에서 중요한 개념이지만, package.json을 생성하는 단계에서는 자동으로 처리해주기에 이번 세션에서는 가볍게 이야기하고 넘어가보겠다</p>
<br>
<h1 id="packagejson의-본질"><a class="anchor" href="#packagejson의-본질">package.json의 본질</a></h1>
<p>나는 package.json를 프로젝트 의존성을 관리하고, 프로젝트의 정보를 문서화는 특성 때문에 <strong>Node.js 프로젝트의 명세서</strong>라고 생각한다.</p>
<p>프로젝트를 진행할 때 제일 먼저 살펴보는 package.json의 본질에는 어떤 것들이 있는지 살펴보자</p>
<br>
<h2 id="프로젝트-메타데이터-관리-project-metadata-management"><a class="anchor" href="#프로젝트-메타데이터-관리-project-metadata-management">프로젝트 메타데이터 관리 (Project Metadata Management)</a></h2>
<p>프로젝트 메타데이터는 프로젝트를 설명하고 식별하는 데 필요한 모든 정보를 포함한다.</p>
<p>자주 보았겠지만, 메타데이터 필드에는 아래와 같은 정보들이 있다</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "name"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"존잘지훈짱짱맨"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "version"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"1.0.0"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "description"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"지훈블로그 킹왕짱"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "author"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Jihoon Lee &#x3C;jihoon7705@gmail.com>"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "license"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"MIT"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "keywords"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"web"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"frontend"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"react"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">],</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "repository"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"git"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "url"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"https://github.com/jiji-hoon96"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "blogs"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "url"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"https://hooninedev.com/"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "homepage"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"https://hooninedev.com/"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<br>
<h3 id="그럼-메타데이터가-왜-중요할까"><a class="anchor" href="#그럼-메타데이터가-왜-중요할까">그럼 메타데이터가 왜 중요할까?</a></h3>
<ul>
<li>
<p>npm 레지스트리에서 고유한 식별자 역할을 통해 버전 관리 및 업데이트 추적을 가능하게한다.</p>
</li>
<li>
<p>npm 검색에서 프로젝트 발견 가능성 향상시킨다.</p>
</li>
<li>
<p><code>keywords</code>를 통한 관련 프로젝트 그룹화할 수 있다. (원하는 것을 찾기 수월)</p>
</li>
<li>
<p>오픈소스 라이선스 명시를 통해 사용 제한 사항 명확화할 수 있다.</p>
</li>
</ul>
<br>
<h2 id="의존성-관리-dependency-management"><a class="anchor" href="#의존성-관리-dependency-management">의존성 관리 (Dependency Management)</a></h2>
<p>의존성 관리에는 의존성 유형, 의존성 버전 관리, 의존성 관리 전략이 있다.</p>
<br>
<h3 id="의존성-유형"><a class="anchor" href="#의존성-유형">의존성 유형</a></h3>
<p>의존성 유형에는 dependencies, devDependencies, peerDependencies 등이 있다.</p>
<blockquote>
<p>자세한 내용은 <a href="https://hooninedev.com/231002/">Dependencies vs DevDependencies vs PeerDependencies</a>을 확인해보면 좋다.</p>
</blockquote>
<br>
<h3 id="의존성-버전-관리"><a class="anchor" href="#의존성-버전-관리">의존성 버전 관리</a></h3>
<h4 id="semantic-versioning"><a class="anchor" href="#semantic-versioning">Semantic Versioning</a></h4>
<p>소프트웨어 버전 번호를 체계적으로 관리하기 위한 규칙으로 아래와 같은 구조를 가진다.</p>
<pre><code>MAJOR.MINOR.PATCH[-PRE_RELEASE][+BUILD]

</code></pre>
<ul>
<li>메이저 버전 : 이전 버전과 호환되지 않는 API 변경</li>
<li>마이너 버전 : 이전 버전과 호환되는 기능 추가</li>
<li>패치 버전 : 이전 버전과 호환되는 버그 수정, 작은 변경</li>
<li>프리릴리즈 식별자 : 정식 버전 전 테스트</li>
<li>빌드 메타데이터 : 빌드 관련 메타 데이터</li>
</ul>
<br>
<h3 id="의존성-관리-전략"><a class="anchor" href="#의존성-관리-전략">의존성 관리 전략</a></h3>
<ul>
<li>
<p>직접 의존성을 최소화 할 수 있다(필요한 패키지만 설치하거나, 중복 기능 패키지를 제거하는 등)</p>
</li>
<li>
<p>의존성 감사를 진행할 수 있다. (audit, outdated 등을 활용해서)</p>
</li>
<li>
<p>Lock 파일 활용해서 정확한 의존성 보장할 수 있고, 팀 간 일관된 환경을 유지할 수 있다.</p>
</li>
</ul>
<br>
<h2 id="프로젝트-실행-관리"><a class="anchor" href="#프로젝트-실행-관리">프로젝트 실행 관리</a></h2>
<p>개발 워크플로우와 빌드 프로세스를 자동화한다. 상세한 역할은 아래와 같다.</p>
<ul>
<li>
<p>스크립트를 통한 프로젝트 실행, 빌드, 테스트 등의 작업 자동화 설정</p>
</li>
<li>
<p>프로젝트 진입점 설정할 수 있다</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "main"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"dist/index.js"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// CommonJS 진입점</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "module"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"dist/index.esm.js"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// ES Module 진입점</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "browser"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"dist/index.umd.js"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 브라우저 진입점</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "types"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"dist/index.d.ts"</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D"> // TypeScript 타입 정의</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
</li>
</ul>
<p>npm scripts는 다양한 스크립트 패턴들을 사용해 개발 워크플로우를 구축한다.</p>
<p>아래 제공하는 3가지의 방법을 잘 활용해서 다양한 환경에 대응하는 스크립트를 구축하면 효율성이 높아진다.</p>
<br>
<h3 id="prepost-훅"><a class="anchor" href="#prepost-훅">Pre/Post 훅</a></h3>
<p>특정 스크립트 실행 전(Pre)/후(Post)에 자동으로 실행되는 스크립트를 정의를 의미한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">  "scripts"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    "prebuild"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"rm -rf dist/"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    "build"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"webpack"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    "postbuild"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"cp -r public/* dist/"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    "pretest"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"npm run lint"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    "test"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"jest"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    "posttest"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"npm run coverage"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이렇게 정의하고 <code>npm run build</code> 를 실행하면 prebuild => build => postbuild 순서대로 진행된다.</p>
<p>이런 순서는 이전 단계가 성공적으로 완료되어야 다음 단계가 실행된다.</p>
<br>
<h3 id="병렬-실행"><a class="anchor" href="#병렬-실행">병렬 실행</a></h3>
<p><code>npm run all</code>, &#x26; 연산자, concurrently 패키지를 사용해 복잡한 병렬 실행을 처리한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    "dev"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"npm-run-all --parallel watch:*"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// npm run all</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    "dev"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"webpack --watch &#x26; sass --watch src:dist &#x26; nodemon server.js"</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D"> // &#x26;연산자 사용</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    "start"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"concurrently -k ~~~"</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D"> // concurrently 사용</span></span></code></pre></figure>
<br>
<h3 id="조건부-스크립트"><a class="anchor" href="#조건부-스크립트">조건부 스크립트</a></h3>
<p>크로스 플랫폼 환경변수 설정을 처리하기 위한 패턴으로 <code>cross-env</code>를 사용한다.</p>
<p>그리고 환경에 따른 조건부 실행을 지원하기 위해 <code>if-env</code>를 사용한다.</p>
<br>
<h2 id="공부-소감"><a class="anchor" href="#공부-소감">공부 소감</a></h2>
<p>상당히 어렵다. 쉽게 공부하려하면 너무 쉽고 다룰 부분이 없고, 깊게 공부하니 컴퓨터 지식에 밀접해진다.</p>
<p>일단 동작원리와 package.json의 본질에 대해서 살펴보았다.</p>
<p>이번 공부를 기반으로 점차 깊이 공부해봐야겠다고 생각했다. 특히 package.json 버전관리 문법에 대해 잘 다뤄보지 못했다.</p>
<p>이 부분은 Semantic Versioning이라는 세션을 따로 만들어 다뤄보려한다.</p>
<p>모노레포의 경우 package.json이 더 중요해진다 생각하기때문이다.</p>
<br>
<h2 id="참고자료"><a class="anchor" href="#참고자료">참고자료</a></h2>
<ul>
<li><a href="https://heynode.com/tutorial/what-packagejson/">hey(node)의 What Is package.json?</a></li>
<li><a href="https://docs.npmjs.com/cli/v10/configuring-npm/package-json">npm Docs - package.json</a></li>
<li><a href="https://nodesource.com/blog/the-basics-of-package-json/">NODESOURCE-The Basic of Package.json</a></li>
<li><a href="https://hoya-kim.github.io/2021/09/14/package-json/">알고 쓰자 package.json - hoya's dev blog</a></li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>소박한궁금증</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[useSyncExternalStore Deep Dive]]></title>
            <link>https://hooninedev.com/241031</link>
            <guid isPermaLink="false">https://hooninedev.com/241031</guid>
            <pubDate>Thu, 31 Oct 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[이번 포스팅에서는 React 18에서 도입된 useSyncExternalStore 훅에 대한 이야기를 해보려고 한다. 필자가 이 훅을 처음 접하게 된 건 Redux와 Zustand의 내부 구현을 뜯어보던 중이었다. 평소에 외부 상태 관리 라이브러리를 당연하게 사용하면서도, 이것들이 React의 동시성 렌더링과 어떻게 안전하게 공존하는지에 대해서는 깊이 생각...]]></description>
            <content:encoded><![CDATA[<p>이번 포스팅에서는 React 18에서 도입된 <code>useSyncExternalStore</code> 훅에 대한 이야기를 해보려고 한다.</p>
<p>필자가 이 훅을 처음 접하게 된 건 Redux와 Zustand의 내부 구현을 뜯어보던 중이었다. 평소에 외부 상태 관리 라이브러리를 당연하게 사용하면서도, 이것들이 React의 동시성 렌더링과 어떻게 안전하게 공존하는지에 대해서는 깊이 생각해 본 적이 없었다. 그런데 코드를 따라가다 보니 <code>useSyncExternalStore</code>라는 다소 긴 이름의 훅이 핵심적인 역할을 하고 있었고, 파면 팔수록 React 팀의 치밀한 설계가 느껴져서 이 글을 쓰게 되었다.</p>
<p>(이름이 길어서 타이핑할 때마다 오타가 난다. useSyncExternalStore. 한 번에 치면 천재다.)</p>
<br/>
<h2 id="tearing이-뭐길래-이렇게-난리인가"><a class="anchor" href="#tearing이-뭐길래-이렇게-난리인가">Tearing이 뭐길래 이렇게 난리인가</a></h2>
<p>본격적으로 <code>useSyncExternalStore</code>를 살펴보기 전에, 이 훅이 해결하려는 문제인 <strong>Tearing 현상</strong>부터 이해해야 한다.</p>
<p>Tearing은 직역하면 "찢어짐"이다. UI가 찢어진다니, 무슨 소리일까. 쉽게 비유하자면 이런 상황이다. 영화관에서 같은 영화를 보고 있는데, 왼쪽 절반은 10분 전 장면을 보여주고 오른쪽 절반은 현재 장면을 보여주는 것이다. 같은 스크린인데 서로 다른 시점의 화면이 동시에 보이는 셈이다.</p>
<p>React에서 Tearing은 <strong>동일한 데이터를 구독하는 여러 컴포넌트가 서로 다른 값을 렌더링하는 현상</strong>을 말한다. 금융 서비스에서 잔액을 표시하는 컴포넌트 두 개가 같은 화면에 있는데 하나는 10만원, 하나는 15만원을 보여준다면? 사용자 입장에서는 공포 그 자체일 것이다.</p>
<p>(내 돈 5만원 어디 갔어...)</p>
<br/>
<h3 id="그렇다면-tearing은-왜-발생하는가"><a class="anchor" href="#그렇다면-tearing은-왜-발생하는가">그렇다면 Tearing은 왜 발생하는가</a></h3>
<p>React 18 이전에는 렌더링이 항상 동기적으로 수행되었다. 한번 시작하면 끝까지 쭉 진행되었기 때문에 렌더링 도중에 외부 상태가 바뀔 틈이 없었다. 그런데 React 18에서 **Concurrent Rendering(동시성 렌더링)**이 도입되면서 상황이 달라졌다.</p>
<p>Concurrent 모드에서 React는 렌더링 작업을 여러 청크로 나누어 처리한다. 중간에 더 우선순위가 높은 작업이 들어오면 현재 렌더링을 **중단(yield)**하고, 나중에 다시 이어서 처리한다. 바로 이 "중단"이 문제의 핵심이다.</p>
<p>구체적인 시나리오를 살펴보자.</p>
<ol>
<li>컴포넌트 A가 외부 스토어에서 값 <code>42</code>를 읽고 렌더링을 시작한다.</li>
<li>React가 렌더링을 중단하고 브라우저에게 제어권을 넘긴다. (사용자 입력 처리 등 더 급한 일이 생겼다.)</li>
<li>이 틈에 외부 스토어의 값이 <code>42</code>에서 <code>100</code>으로 변경된다.</li>
<li>React가 렌더링을 재개하고, 컴포넌트 B가 같은 스토어에서 값을 읽는다. 이때 B는 <code>100</code>을 읽는다.</li>
<li>결과적으로 A는 <code>42</code>, B는 <code>100</code>을 보여주게 된다. <strong>찢어졌다.</strong></li>
</ol>
<p>React 내부 상태(<code>useState</code>, <code>useReducer</code>)는 이런 문제가 발생하지 않는다. React가 상태 업데이트를 큐에 넣고 직접 관리하기 때문에 렌더링 도중 값이 변경되는 일이 구조적으로 불가능하다. 문제는 React <strong>바깥</strong>에 존재하는 상태, 즉 Redux, Zustand, MobX 같은 외부 스토어나 브라우저 API(<code>window.innerWidth</code> 등)를 구독할 때 발생한다.</p>
<br/>
<h3 id="전통적인-방식의-한계"><a class="anchor" href="#전통적인-방식의-한계">전통적인 방식의 한계</a></h3>
<p>기존에 외부 스토어를 구독하는 전형적인 패턴을 살펴보면 문제가 명확해진다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> store</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  count: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  listeners: </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(),</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">listener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.listeners.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">add</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(listener);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.listeners.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">delete</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(listener);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  getValue</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.count;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  setValue</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">newValue</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.count </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> newValue;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.listeners.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">forEach</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">listener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> listener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Counter1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">count</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getValue</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getValue</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, []);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>Counter1: {count}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">div</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Counter2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">count</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getValue</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getValue</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, []);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>Counter2: {count}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">div</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이 코드의 문제점은 두 가지다.</p>
<p>첫째, <code>useEffect</code>를 통한 구독 설정은 <strong>비동기적</strong>이다. 렌더링이 완료되고 브라우저가 페인트를 마친 후에야 구독이 시작된다. 그 사이에 스토어 값이 변경되면 초기 렌더링과 실제 스토어 값 사이에 불일치가 발생한다.</p>
<p>둘째, Concurrent 모드에서 렌더링이 중단되었다가 재개되는 사이에 스토어 값이 변경되면, 위에서 설명한 Tearing이 발생한다. <code>useEffect</code> 기반 구독은 이를 감지하거나 방지할 방법이 없다.</p>
<p>데이터 일관성이 중요한 도메인에서는 치명적인 오류를 야기할 수 있는 것이다.</p>
<br/>
<h2 id="usesyncexternalstore가-어떻게-해결하는가"><a class="anchor" href="#usesyncexternalstore가-어떻게-해결하는가">useSyncExternalStore가 어떻게 해결하는가</a></h2>
<p><code>useSyncExternalStore</code>는 이 문제를 우아하게 해결한다. 핵심 전략은 단순하면서도 효과적이다. <strong>렌더링 도중 외부 스토어 값이 변경되었음을 감지하면, 기존 렌더링을 폐기하고 처음부터 다시 시작하는 것이다.</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Counter1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> count</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSyncExternalStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    store.subscribe, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 구독 함수</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    store.getValue,  </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 현재 스냅샷을 반환하는 함수</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>Counter1: {count}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">div</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Counter2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> count</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSyncExternalStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(store.subscribe, store.getValue);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>Counter2: {count}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">div</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>코드가 훨씬 간결해졌을 뿐만 아니라, Tearing 문제가 원천적으로 차단된다. React가 렌더링 도중 스토어 값 변경을 감지하면 현재 진행 중인 렌더링을 버리고 새로운 값으로 <strong>동기적으로</strong> 다시 렌더링하기 때문에, 모든 컴포넌트가 항상 동일한 시점의 데이터를 보여주게 된다.</p>
<p>여기서 "Sync"라는 이름이 의미하는 바가 드러난다. 외부 스토어의 업데이트를 React의 렌더링 사이클과 **동기화(Synchronize)**한다는 뜻인 것이다. <code>startTransition</code>으로 감싸더라도 외부 스토어 업데이트는 항상 동기적으로 처리된다. 이것은 의도된 트레이드오프다. 시간 분할(time-slicing)의 이점을 일부 포기하더라도 데이터 일관성을 보장하는 것이 더 중요하다는 React 팀의 판단이 담겨 있다.</p>
<br/>
<h2 id="api를-자세히-살펴보자"><a class="anchor" href="#api를-자세히-살펴보자">API를 자세히 살펴보자</a></h2>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> snapshot</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSyncExternalStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  subscribe: (</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">onStoreChange</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  getSnapshot: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  getServerSnapshot</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> T</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span></code></pre></figure>
<p>출처: <a href="https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShim.js">React GitHub - useSyncExternalStoreShim.js</a></p>
<p>세 개의 파라미터를 하나씩 뜯어보겠다.</p>
<br/>
<h3 id="subscribe"><a class="anchor" href="#subscribe">subscribe</a></h3>
<p>스토어의 변경을 구독하는 함수다. 콜백 함수 <code>onStoreChange</code>를 인자로 받아서, 스토어가 변경될 때마다 이 콜백을 호출하도록 설정한다. 그리고 <strong>구독 해제 함수를 반환</strong>해야 한다.</p>
<p>여기서 중요한 점이 하나 있다. <code>subscribe</code> 함수의 참조가 렌더링마다 바뀌면 React가 매번 재구독을 시도하기 때문에 성능 문제가 발생할 수 있다. 컴포넌트 외부에 정의하거나, <code>useCallback</code>으로 감싸서 안정적인 참조를 유지해야 한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 좋은 예: 컴포넌트 외부에 정의</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> subscribe</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addEventListener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'change'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, callback);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">removeEventListener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'change'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, callback);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 나쁜 예: 렌더링마다 새로운 함수 생성</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Component</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSyncExternalStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 매 렌더링마다 새 참조 → 무한 재구독</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addEventListener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'change'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, callback);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">removeEventListener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'change'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, callback);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    store.getSnapshot,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>(무한 재구독은 무한 루프의 친척뻘 되는 녀석이다. 만나면 반갑지 않다.)</p>
<br/>
<h3 id="getsnapshot"><a class="anchor" href="#getsnapshot">getSnapshot</a></h3>
<p>스토어의 현재 상태를 반환하는 함수다. 단순해 보이지만 몇 가지 중요한 규칙이 있다.</p>
<p><strong>첫째, 스토어가 변경되지 않았다면 동일한 값을 반환해야 한다.</strong> React는 내부적으로 <code>Object.is</code>를 사용해 이전 스냅샷과 현재 스냅샷을 비교한다. 스토어가 바뀌지 않았는데 매번 새로운 객체를 반환하면 불필요한 리렌더링이 발생한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 나쁜 예: 매번 새 객체 생성 → 무한 리렌더링</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getSnapshot</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({ count: store.count }); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 매번 새 참조!</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 좋은 예: 불변 데이터 반환</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getSnapshot</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 변경 시에만 새 참조</span></span></code></pre></figure>
<p><strong>둘째, 반환값은 불변(immutable)이어야 한다.</strong> <code>getSnapshot</code>에서 매번 동일한 mutable 객체의 참조를 반환하면 React는 변경을 감지할 수 없다. 실제 데이터가 바뀌었더라도 참조가 같으면 리렌더링이 발생하지 않는다. 이것이 Redux나 Zustand가 상태를 업데이트할 때 항상 새로운 객체를 생성하는 이유이기도 하다.</p>
<br/>
<h3 id="getserversnapshot-optional"><a class="anchor" href="#getserversnapshot-optional">getServerSnapshot (optional)</a></h3>
<p>SSR(Server-Side Rendering) 환경에서 사용될 초기 스냅샷을 반환하는 함수다. 이 파라미터가 왜 필요한지 이해하려면 하이드레이션(Hydration) 과정을 떠올려야 한다.</p>
<p>서버에서 HTML을 렌더링할 때는 브라우저 API나 외부 스토어에 접근할 수 없는 경우가 많다. 예를 들어 <code>window.navigator.onLine</code>을 구독하는 커스텀 훅이 있다고 하자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useOnlineStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSyncExternalStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    subscribe,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> navigator.onLine,        </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 클라이언트에서 사용</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,                      </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 서버에서 사용 (항상 online 가정)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p><code>getServerSnapshot</code>을 제공하지 않으면 서버 렌더링 시 에러가 발생한다. React는 서버 환경에서 <code>getServerSnapshot</code>이 없으면 명시적으로 에러를 던지도록 설계되어 있다. 이는 개발자가 하이드레이션 불일치(hydration mismatch)를 의식적으로 처리하도록 강제하는 것이다.</p>
<p>다만, React 18 이전 버전을 위한 shim 구현체(<code>use-sync-external-store/shim</code>)에서는 <code>getServerSnapshot</code>을 사용하지 않는다. pre-18 버전에서는 하이드레이션 여부를 확인할 방법이 없기 때문이다.</p>
<br/>
<h2 id="소스-코드를-해부해보자"><a class="anchor" href="#소스-코드를-해부해보자">소스 코드를 해부해보자</a></h2>
<p>여기서부터가 진짜 재미있는 부분이다. React 팀이 <code>useSyncExternalStore</code>를 어떻게 구현했는지, 소스 코드를 직접 들여다보겠다.</p>
<p>출처: <a href="https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js">useSyncExternalStoreShimClient.js</a></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSyncExternalStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">getSnapshot</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">getServerSnapshot</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getSnapshot</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [{ </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">inst</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">forceUpdate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    inst: { value, getSnapshot },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useLayoutEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    inst.value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    inst.getSnapshot </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> getSnapshot;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">checkIfSnapshotChanged</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(inst)) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      forceUpdate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ inst });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, [subscribe, value, getSnapshot]);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">checkIfSnapshotChanged</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(inst)) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      forceUpdate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ inst });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> handleStoreChange</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">checkIfSnapshotChanged</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(inst)) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        forceUpdate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ inst });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(handleStoreChange);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, [subscribe]);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>코드가 짧다고 얕보면 안 된다. 이 안에는 React 팀의 깊은 고민이 담겨 있다.</p>
<br/>
<h3 id="1단계-초기-설정"><a class="anchor" href="#1단계-초기-설정">1단계: 초기 설정</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getSnapshot</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 현재 스냅샷을 가져온다</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [{ </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">inst</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">forceUpdate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  inst: {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    value,        </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 현재 스냅샷 값</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    getSnapshot,  </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 스냅샷을 가져오는 함수</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span></code></pre></figure>
<p>여기서 눈여겨볼 부분은 <code>inst</code> 객체의 역할이다. 이것은 렌더링 간에 지속적으로 참조해야 하는 값들을 저장하는 일종의 <strong>인스턴스 변수</strong>다. <code>useRef</code>와 비슷한 역할이지만, <code>useState</code> 안에 넣은 이유가 있다. <code>forceUpdate</code>를 통해 새로운 <code>{ inst }</code> 객체를 전달하면 React의 동등성 검사(<code>Object.is</code>)를 통과하지 못해 리렌더링이 트리거되기 때문이다.</p>
<p>(리렌더링을 강제하려고 <code>useState</code>를 이렇게 쓰다니, 해킹 같으면서도 우아하다.)</p>
<br/>
<h3 id="2단계-uselayouteffect에서의-동기화--핵심-중의-핵심"><a class="anchor" href="#2단계-uselayouteffect에서의-동기화--핵심-중의-핵심">2단계: useLayoutEffect에서의 동기화 — 핵심 중의 핵심</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useLayoutEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  inst.value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  inst.getSnapshot </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> getSnapshot;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">checkIfSnapshotChanged</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(inst)) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    forceUpdate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ inst });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, [subscribe, value, getSnapshot]);</span></span></code></pre></figure>
<p>이 부분이 Tearing 방지의 핵심 메커니즘이다. 그런데 왜 <code>useEffect</code>가 아니라 <code>useLayoutEffect</code>를 사용했을까?</p>
<p><strong><code>useLayoutEffect</code>는 DOM 업데이트 직후, 브라우저가 화면을 그리기 전에 동기적으로 실행된다.</strong> 반면 <code>useEffect</code>는 브라우저가 화면을 그린 후 비동기적으로 실행된다. 이 차이가 결정적이다.</p>
<p>만약 여기서 <code>useEffect</code>를 사용했다면 이런 시나리오가 가능하다.</p>
<ol>
<li>컴포넌트가 스냅샷 <code>42</code>로 렌더링된다.</li>
<li>브라우저가 <code>42</code>를 화면에 그린다.</li>
<li>그 사이에 스토어 값이 <code>100</code>으로 변경된다.</li>
<li><code>useEffect</code>가 실행되어 변경을 감지하고 리렌더링을 트리거한다.</li>
<li>사용자는 <code>42</code> → <code>100</code>으로 <strong>깜빡이는 화면</strong>을 보게 된다.</li>
</ol>
<p><code>useLayoutEffect</code>를 사용하면 3단계에서 즉시 변경을 감지하고, <strong>브라우저가 화면을 그리기 전에</strong> 리렌더링을 트리거한다. 사용자는 깜빡임 없이 항상 최신 값을 보게 되는 것이다.</p>
<p>이것은 React 소스 코드를 볼 때 자주 발견되는 패턴이다. 시각적 일관성이 중요한 곳에서는 반드시 <code>useLayoutEffect</code>를 사용해 <strong>페인트 전에 동기적으로 처리</strong>하는 것이다.</p>
<br/>
<h3 id="3단계-useeffect에서의-구독-설정"><a class="anchor" href="#3단계-useeffect에서의-구독-설정">3단계: useEffect에서의 구독 설정</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">checkIfSnapshotChanged</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(inst)) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    forceUpdate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ inst });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> handleStoreChange</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">checkIfSnapshotChanged</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(inst)) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      forceUpdate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ inst });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(handleStoreChange);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, [subscribe]);</span></span></code></pre></figure>
<p>여기서 자연스럽게 떠오르는 질문이 하나 있다. "구독도 <code>useLayoutEffect</code>에서 하면 되지 않나?"</p>
<p>구독을 <code>useEffect</code>에서 하는 데는 이유가 있다. <code>useLayoutEffect</code>에서 구독을 설정하면 <strong>서버 사이드 렌더링 시 경고가 발생</strong>한다. <code>useLayoutEffect</code>는 서버에서 실행되지 않기 때문이다. 또한 구독 설정 자체는 DOM과 관련이 없으므로 <code>useEffect</code>에서 처리하는 것이 의미적으로도 올바르다.</p>
<p>그런데 <code>useEffect</code> 시작 부분에서 다시 한번 <code>checkIfSnapshotChanged</code>를 호출하는 것이 보이는가? 이것은 <code>useLayoutEffect</code> 실행 이후, <code>useEffect</code> 실행 사이의 간극에 스토어가 변경될 수 있기 때문이다. 이 미세한 틈까지 메워주는 것이다.</p>
<p>(빈틈을 용서하지 않는 React 팀의 꼼꼼함에 감탄한다.)</p>
<p>그리고 <code>handleStoreChange</code>는 스토어가 변경될 때마다 호출되는 콜백이다. 변경이 감지되면 <code>forceUpdate</code>로 리렌더링을 트리거하고, <code>subscribe</code>의 반환값인 구독 해제 함수가 클린업으로 자동 실행된다.</p>
<br/>
<h3 id="4단계-스냅샷-변경-확인-로직"><a class="anchor" href="#4단계-스냅샷-변경-확인-로직">4단계: 스냅샷 변경 확인 로직</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> checkIfSnapshotChanged</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">inst</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> latestGetSnapshot</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> inst.getSnapshot;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> prevValue</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> inst.value;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> nextValue</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> latestGetSnapshot</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> !</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Object.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">is</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(prevValue, nextValue);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (error) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 에러 발생 시 변경된 것으로 간주</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p><code>Object.is</code>를 사용해 이전 값과 현재 값을 정확하게 비교한다. <code>===</code> 대신 <code>Object.is</code>를 쓴 이유는 <code>NaN === NaN</code>이 <code>false</code>를 반환하는 등 엣지 케이스를 올바르게 처리하기 위함이다.</p>
<p><code>try-catch</code>로 감싼 것도 중요하다. <code>getSnapshot</code> 실행 중 에러가 발생하면 "변경된 것으로 간주"하여 리렌더링을 트리거한다. 방어적 프로그래밍의 좋은 예시인 것이다.</p>
<br/>
<h2 id="usesyncexternalstorewithselector는-무엇인가"><a class="anchor" href="#usesyncexternalstorewithselector는-무엇인가">useSyncExternalStoreWithSelector는 무엇인가</a></h2>
<p>여기까지 읽으면 자연스럽게 떠오르는 질문이 있다. "스토어 전체가 아니라 특정 부분만 구독하고 싶으면 어떻게 하나?"</p>
<p><code>useSyncExternalStore</code>는 <code>getSnapshot</code>이 반환하는 전체 값을 기준으로 변경을 감지한다. 스토어의 일부분만 필요한 컴포넌트가 있다면, 관련 없는 상태 변경에도 불필요하게 리렌더링될 수 있다. 이 문제를 해결하는 것이 <code>useSyncExternalStoreWithSelector</code>다.</p>
<p>출처: <a href="https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js">useSyncExternalStoreWithSelector.js</a></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { useSyncExternalStoreWithSelector } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'use-sync-external-store/with-selector'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> UserName</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> userName</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSyncExternalStoreWithSelector</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    store.subscribe,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    store.getSnapshot,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    store.getServerSnapshot,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> state.user.name,      </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// selector: 필요한 부분만 추출</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">a</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">b</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> a </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> b,                </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// equalityFn: 커스텀 비교 함수 (optional)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>{userName}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">div</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이 훅은 다섯 개의 파라미터를 받는다. 처음 세 개는 <code>useSyncExternalStore</code>와 동일하고, 네 번째가 <strong>selector</strong>, 다섯 번째가 <strong>equalityFn</strong>이다.</p>
<p>내부적으로는 클로저 기반의 메모이제이션을 사용한다. 이전 스냅샷과 현재 스냅샷이 참조적으로 동일하면 selector를 다시 실행하지 않고 이전 선택 결과를 재사용한다. 스냅샷이 바뀌었더라도 selector의 결과를 <code>equalityFn</code>으로 비교하여, 실제로 관심 있는 값이 변경되지 않았다면 리렌더링을 건너뛴다.</p>
<p>이것이 바로 Redux와 Zustand가 채택한 전략이다.</p>
<br/>
<h3 id="redux는-어떻게-사용하고-있는가"><a class="anchor" href="#redux는-어떻게-사용하고-있는가">Redux는 어떻게 사용하고 있는가</a></h3>
<p>React-Redux v8에서는 <code>useSelector</code>의 내부 구현을 <code>useSyncExternalStore</code> 기반으로 전면 교체했다. <a href="https://github.com/reduxjs/react-redux/releases/tag/v8.0.0">릴리스 노트</a>에서 확인할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// React-Redux의 useSelector 내부 (간략화)</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSelector</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">selector</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">equalityFn</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> refEquality) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSyncExternalStoreWithSelector</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    store.subscribe,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    store.getState,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    store.getState, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// SSR 시에도 동일한 스토어 사용</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    selector,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    equalityFn,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>Zustand 역시 내부적으로 <code>useSyncExternalStoreWithSelector</code>를 사용한다. 외부 상태 관리 라이브러리들이 Concurrent Mode에서 안전하게 동작할 수 있는 것은 모두 이 훅 덕분인 것이다.</p>
<br/>
<h2 id="usemutablesource에서-usesyncexternalstore로"><a class="anchor" href="#usemutablesource에서-usesyncexternalstore로">useMutableSource에서 useSyncExternalStore로</a></h2>
<p>사실 React 팀이 처음부터 <code>useSyncExternalStore</code>를 설계한 것은 아니다. 이전에 <code>useMutableSource</code>라는 API가 있었다. 이 역사를 알면 <code>useSyncExternalStore</code>의 설계 철학을 더 깊이 이해할 수 있다.</p>
<p>출처: <a href="https://github.com/reactwg/react-18/discussions/86">React 18 WG Discussion #86</a></p>
<p><code>useMutableSource</code>는 외부 상태를 동시성 렌더링과 통합하려는 첫 번째 시도였지만, 세 가지 치명적인 문제가 있었다.</p>
<p><strong>첫째, selector 메모이제이션 부담.</strong> <code>useMutableSource</code>를 사용하는 라이브러리는 사용자에게 selector 메모이제이션을 강제해야 했다. inline selector가 변경될 때마다 재구독이 발생했기 때문이다.</p>
<p><strong>둘째, 예측 불가능한 폴백(fallback).</strong> <code>startTransition</code> 내부에서도 예상치 못한 시점에 기존 UI가 로딩 상태로 교체되는 현상이 발생했다. 부분적으로 동시성을 지원하려다 보니 deoptimization이 일관되지 않게 발생한 것이다.</p>
<p><strong>셋째, 복잡한 API 표면.</strong> <code>createMutableSource</code>와 <code>source</code> 인자가 필요해서 라이브러리 메인테이너들의 채택 부담이 컸다.</p>
<p>React 팀은 결국 "동시성 최적화를 일부 포기하더라도 예측 가능성과 단순성을 택하자"는 결론에 도달했다. 그래서 외부 스토어 업데이트는 <strong>항상 동기적으로</strong> 처리하는 <code>useSyncExternalStore</code>가 탄생한 것이다.</p>
<br/>
<h2 id="성능-고려사항과-흔한-실수"><a class="anchor" href="#성능-고려사항과-흔한-실수">성능 고려사항과 흔한 실수</a></h2>
<p><code>useSyncExternalStore</code>를 실제로 사용할 때 주의해야 할 점들을 정리해보겠다.</p>
<br/>
<h3 id="getsnapshot에서-매번-새-객체를-생성하지-말-것"><a class="anchor" href="#getsnapshot에서-매번-새-객체를-생성하지-말-것">getSnapshot에서 매번 새 객체를 생성하지 말 것</a></h3>
<p>가장 흔한 실수다. <code>getSnapshot</code>이 호출될 때마다 새로운 객체를 반환하면 <code>Object.is</code> 비교가 항상 <code>false</code>를 반환하여 무한 리렌더링에 빠진다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 이러면 안 된다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> snapshot</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSyncExternalStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(subscribe, () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { count: store.count }; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 매번 새 객체!</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 이렇게 해야 한다</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> snapshot</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSyncExternalStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(subscribe, () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// store.getState()는 상태가 변경될 때만 새 참조를 반환</span></span></code></pre></figure>
<br/>
<h3 id="subscribe-함수의-참조-안정성을-유지할-것"><a class="anchor" href="#subscribe-함수의-참조-안정성을-유지할-것">subscribe 함수의 참조 안정성을 유지할 것</a></h3>
<p>앞서 언급했듯이 <code>subscribe</code> 참조가 바뀌면 재구독이 발생한다. 이는 <code>useEffect</code>의 의존성 배열에 <code>subscribe</code>가 포함되어 있기 때문이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 컴포넌트 외부에서 정의하거나</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> subscribe</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(callback);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// useCallback으로 안정적인 참조 유지</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> subscribe</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useCallback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> store.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(callback);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, []);</span></span></code></pre></figure>
<br/>
<h3 id="필요하다면-selector를-활용할-것"><a class="anchor" href="#필요하다면-selector를-활용할-것">필요하다면 selector를 활용할 것</a></h3>
<p>스토어 전체가 아니라 일부만 필요하다면 <code>useSyncExternalStoreWithSelector</code>를 사용하여 불필요한 리렌더링을 방지하는 것이 좋다. 특히 객체 형태의 상태를 다룰 때, 얕은 비교(shallow equality)를 <code>equalityFn</code>으로 전달하면 효과적이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { useSyncExternalStoreWithSelector } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'use-sync-external-store/with-selector'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { shallowEqual } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'some-utility'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> UserProfile</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> profile</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSyncExternalStoreWithSelector</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    store.subscribe,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    store.getSnapshot,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({ name: state.name, avatar: state.avatar }),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    shallowEqual, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 얕은 비교로 불필요한 리렌더링 방지</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>{profile.name}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">div</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<br/>
<h3 id="동기적-특성을-인지할-것"><a class="anchor" href="#동기적-특성을-인지할-것">동기적 특성을 인지할 것</a></h3>
<p><code>useSyncExternalStore</code>는 외부 스토어 업데이트를 동기적으로 처리하기 때문에, <code>startTransition</code>으로 감싸도 시간 분할의 이점을 받지 못한다. 이것은 버그가 아니라 의도된 동작이다. 하지만 외부 스토어의 업데이트가 매우 빈번한 경우(예: 마우스 위치 추적), 성능 문제가 발생할 수 있으므로 throttle이나 debounce를 고려해야 한다.</p>
<br/>
<h2 id="react-18-이전-버전은-어떻게-하나"><a class="anchor" href="#react-18-이전-버전은-어떻게-하나">React 18 이전 버전은 어떻게 하나</a></h2>
<p>React 18 이전 버전(16.8+)을 사용하고 있다면 shim 패키지를 사용할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// React 18 미만에서 사용</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { useSyncExternalStore } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'use-sync-external-store/shim'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// selector 버전</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { useSyncExternalStoreWithSelector } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'use-sync-external-store/shim/with-selector'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span></code></pre></figure>
<p>shim은 React 18 이상에서는 네이티브 구현체를 사용하고, 이전 버전에서는 <code>useState</code> + <code>useLayoutEffect</code> + <code>useEffect</code> 조합으로 동일한 동작을 폴리필한다. 다만, 앞서 살펴보았듯이 shim에서는 <code>getServerSnapshot</code>을 사용하지 않는다. pre-18 버전에서는 하이드레이션 여부를 확인할 방법이 없기 때문이다.</p>
<br/>
<h2 id="마치며"><a class="anchor" href="#마치며">마치며</a></h2>
<p><code>useSyncExternalStore</code>는 겉보기에 단순한 API이지만, 그 안에는 React의 동시성 렌더링 모델과 외부 세계를 안전하게 연결하기 위한 깊은 고민이 담겨 있다.</p>
<p>정리하면 이렇다. React 18의 Concurrent Mode는 렌더링 중단과 재개를 가능하게 했지만, 이로 인해 외부 스토어와의 데이터 일관성 문제(Tearing)가 발생했다. <code>useSyncExternalStore</code>는 렌더링 도중 외부 값 변경을 감지하면 동기적으로 다시 렌더링함으로써 이 문제를 해결한다. 내부적으로는 <code>useLayoutEffect</code>로 페인트 전 동기화를 보장하고, <code>useEffect</code>로 구독을 관리하는 이중 구조를 사용한다.</p>
<p>필자가 가장 인상 깊었던 것은 React 팀의 트레이드오프 결정이다. <code>useMutableSource</code>에서 <code>useSyncExternalStore</code>로 전환하면서, 동시성 최적화를 일부 포기하더라도 <strong>예측 가능성과 데이터 일관성</strong>을 선택한 것이다. 때로는 더 느리더라도 확실한 것이 낫다는 엔지니어링 철학이 느껴진다.</p>
<p>이 글을 읽는 독자분들도 외부 상태 관리 라이브러리의 내부를 한번쯤 들여다보시기를 권한다. <code>useSyncExternalStore</code>를 이해하고 나면, Redux, Zustand, Jotai 같은 라이브러리들이 어떻게 Concurrent Mode와 공존하는지가 한눈에 보이기 시작할 것이다. 정답은 없지만, 그 과정에서 React의 설계 철학을 이해하는 것만으로도 충분히 가치 있는 경험이 될 것이다.</p>
<br/>
<h2 id="출처"><a class="anchor" href="#출처">출처</a></h2>
<ul>
<li><a href="https://react.dev/blog/2022/03/29/react-v18#usesyncexternalstore">React v18 - useSyncExternalStore</a></li>
<li><a href="https://ko.react.dev/reference/react/useSyncExternalStore">useSyncExternalStore 공식 문서</a></li>
<li><a href="https://github.com/reactwg/react-18/discussions/86">useMutableSource → useSyncExternalStore 논의</a></li>
<li><a href="https://github.com/reactwg/react-18/discussions/70">What is tearing? - React 18 WG</a></li>
<li><a href="https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js">useSyncExternalStoreShimClient.js 소스 코드</a></li>
<li><a href="https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js">useSyncExternalStoreWithSelector.js 소스 코드</a></li>
<li><a href="https://github.com/reduxjs/react-redux/releases/tag/v8.0.0">React-Redux v8 릴리스 노트</a></li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[React 상태가 변화는 과정에 대해서!]]></title>
            <link>https://hooninedev.com/240909</link>
            <guid isPermaLink="false">https://hooninedev.com/240909</guid>
            <pubDate>Mon, 09 Sep 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[React 상태가 변화하는 과정은 크게 5단계로 나눌 수 있다. 초기 트리거 단계: useState를 통한 상태 생성과 업데이트 객체가 만들어지는 단계 스케줄링 단계: 여러 상태 업데이트를 우선순위에 따라 배치 처리하는 단계 재조정(Reconciliation) 단계: Virtual DOM을 비교하여 실제로 변경이 필요한 부분을 파악하는 단계 커밋(Commi...]]></description>
            <content:encoded><![CDATA[<p>React 상태가 변화하는 과정은 크게 5단계로 나눌 수 있다.</p>
<ul>
<li><strong>초기 트리거 단계</strong>: useState를 통한 상태 생성과 업데이트 객체가 만들어지는 단계</li>
<li><strong>스케줄링 단계</strong>: 여러 상태 업데이트를 우선순위에 따라 배치 처리하는 단계</li>
<li><strong>재조정(Reconciliation) 단계</strong>: Virtual DOM을 비교하여 실제로 변경이 필요한 부분을 파악하는 단계</li>
<li><strong>커밋(Commit) 단계</strong>: 변경사항을 실제 DOM에 적용하고 부수효과를 실행하는 단계</li>
<li><strong>완료 단계</strong>: 클린업을 수행하고 다음 업데이트를 준비하는 단계</li>
</ul>
<p>이 각 단계들은 React가 상태 변화를 효율적이고 일관되게 처리하여 성능 최적화와 안정적인 렌더링을 보장하기 위해 설계되었다.</p>
<br/>
<p><strong>배치 처리 (Batching)</strong></p>
<p><img src="/content/240909/1.jpg" alt="1.jpg" loading="lazy" decoding="async"></p>
<ul>
<li>
<p>배치 처리는 여러 상태 업데이트를 하나의 리렌더링으로 그룹화하는 프로세스다.</p>
</li>
<li>
<p>배치 처리를 통해 불필요한 렌더링을 최소화하고, 메모리 할당을 줄이는 등의 장점을 누릴 수 있다.</p>
</li>
<li>
<p>React 18에서는 모든 업데이트가 자동으로 배치 처리된다.</p>
</li>
</ul>
<br/>
<p><strong>트랜잭션 처리</strong></p>
<ul>
<li>
<p>트랜잭션은 여러 업데이트를 하나의 원자적 단위로 처리하여 일관성을 보장하는 것을 의미한다.</p>
</li>
<li>
<p>최대 장점은 원자성, 일관성, 격리성이 있다.</p>
</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TransactionExample</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">user</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setUser</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">posts</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setPosts</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([]);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> updateUserData</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> async</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      startTransaction</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 트랜잭션 시작</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setUser</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(newUser);</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setPosts</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(newPosts);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      commitTransaction</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 트랜잭션 커밋</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (error) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      rollbackTransaction</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 오류 발생하면 롤백</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이제 각 단계별로 자세한 내용을 살펴보며 React의 상태 관리 메커니즘에 대해 알아보자</p>
<br/>
<h2 id="초기-트리거-단계"><a class="anchor" href="#초기-트리거-단계">초기 트리거 단계</a></h2>
<p><img src="/content/240909/3.webp" alt="3.webp" loading="lazy" decoding="async"></p>
<p>초기 트리거 단계는 React에서 상태 변화가 시작되는 첫 단계다.</p>
<p>초기 상태를 생성하고, 업데이트를 위한 객체를 생성하고, 업데이트를 큐로 관리해 배치 처리한다. 이러한 단계를 예시코드를 통해 자세하게 알아보자.</p>
<br/>
<h3 id="초기-상태-생성"><a class="anchor" href="#초기-상태-생성">초기 상태 생성</a></h3>
<p>우선 useState, useReducer, useContext, 외부 상태 관리 라이브러리를 이용해 초기 상태가 생성되고, set 같은 상태 업데이트 함수가 호출될 때 트리거된다.</p>
<p>트리거(Trigger)는 상태(state)가 변경될 때 컴포넌트(component)가 다시 렌더링(re-rendering)되도록 하는 것을 의미한다.</p>
<br/>
<h3 id="업데이트-객체-생성"><a class="anchor" href="#업데이트-객체-생성">업데이트 객체 생성</a></h3>
<p>상태 변경이 발생하면 React는 Update 객체를 생성한다.</p>
<p>이 객체는 새로운 상태값, 우선순위, 다음 업데이트에 대한 참조 등의 정보를 포함한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">State</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  lane</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Lane</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  tag</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> UpdateTag</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  payload</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  next</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">State</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  eventTime</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p><strong>lane는</strong> React의 우선순위 시스템을 구현하는 필드다. 동기/비동기 업데이트 구분, 사용자 상호작용, 데이터 페칭 등 업데이트 유형에 따른 우선순위 할당등의 역할을 한다.</p>
<p><strong>tag는</strong> UpdateState, ReplaceState, ForceUpdate, CaptureUpdate 4가지 종류가 있다.</p>
<p><strong>payload는</strong> 상태 계산에 필요한 정보를 가지고 있다.</p>
<p><strong>next는</strong> 업데이트 큐에서 연결 리스트 구현(업데이트 순차 처리)에 사용한다.</p>
<p><strong>eventTime은</strong> 우선순위 계산, 타임아웃 처리, 성능 측정, 디버깅에 활용한다.</p>
<p>실제 생성은 아래처럼 된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> update</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  lane: SyncLane,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  tag: UpdateState,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  payload: newState,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  next: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  eventTime: </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getCurrentTime</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre></figure>
<br/>
<h3 id="업데이트-큐에-추가"><a class="anchor" href="#업데이트-큐에-추가">업데이트 큐에 추가</a></h3>
<p><img src="/content/240909/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<p>생성된 업데이트 객체는 업데이트 큐에 추가된다.</p>
<p>여러 업데이트가 발생하면 큐에서 연결 리스트 형태로 관리되는데, 구조는 아래와같다</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> UpdateQueue</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">State</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  baseState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> State</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  firstBaseUpdate</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">State</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  lastBaseUpdate</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">State</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  shared</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">    pending</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">State</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  effects</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Array</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Effect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p><strong>baseState</strong> 로 기본 상태를 저장하고, <strong>firstBaseUpdate/lastBaseUpdate</strong> 를 활용해 업데이트 연결 리스트의 첫 번째와 마지막 업데이트를 저장한다.</p>
<p>아직 처리 대기중인 업데이트는 <strong>pending</strong> 에 저장하고 사이드 이팩트는 <strong>effect</strong> 에 할당된다.</p>
<p>업데이트 큐에 추가되면 큐 처리는 초기화를 우선하고 업데이트를 연결한다.</p>
<p>아래 코드를 살펴보면, 업데이트 연결을 할 때 첫 업데이트/기존 업데이트 상황에 따라 연결 리스트의 구조가 다르다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> enqueueUpdate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">fiber</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">queue</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> pending</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> queue.shared.pending;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (pending </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    update.next </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> update;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    update.next </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> pending.next;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    pending.next </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> update;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  queue.shared.pending </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> update;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>enqueueUpdate 함수는 컴포넌트의 fiber 노드, 업데이트를 지정하는 큐, 새로 추가할 업데이트 객체를 매개변수로 받는다.</p>
<p>그리고 원형 연결 리스트 형태로 구현되어있는 큐의 마지막 업데이트를 확인하여, 큐가 비어있으면 자기 자신을 가리키도록, 큐가 이미 업데이트에 있으면 기존 리스트에 연결하게된다.</p>
<p>최종적으로 새 업데이트를 마지막 업데이트로 설정하게된다.</p>
<br/>
<h2 id="스케줄링-단계"><a class="anchor" href="#스케줄링-단계">스케줄링 단계</a></h2>
<p>스케줄링 단계에서는 React가 상태 업데이트를 효율적으로 처리하기 위한 단계로 사용자 경험을 해치지 않는 것을 중점으로 처리한다.</p>
<br/>
<h3 id="우선순위-지정-lanes-시스템"><a class="anchor" href="#우선순위-지정-lanes-시스템">우선순위 지정 (Lanes 시스템)</a></h3>
<p><img src="/content/240909/4.png" alt="4.png" loading="lazy" decoding="async"></p>
<p>업데이트 객체를 생성할 때 Lane을 이용해 우선순위를 표현한다.</p>
<p>여기서 Lane은 비트 마스크를 사용하여 우선순위를 표현하는 방식으로, 각 비트는 특정 우선순위 레벨을 나타낸다.</p>
<br/>
<p><strong>SyncLane(동기업데이트)</strong> => 우선 순위 제일 높음</p>
<ul>
<li>즉시 처리가 필요한 동기 업데이트 (긴급한 상태 업데이트, 직접적인 DOM 조작)</li>
<li>다른 작업을 중단하고 즉시 실행한다.</li>
</ul>
<br/>
<p><strong>InputContinuousLane(사용자 입력과 관련)</strong></p>
<ul>
<li>사용자 입력과 관련된 업데이트 (키보드 입력, 마우스 이벤트)</li>
<li>반응성을 위해 높은 우선순위 부여</li>
</ul>
<br/>
<p><strong>DefaultLane(일반적인 상태 업데이트)</strong></p>
<ul>
<li>일반적인 상태 업데이트 (API 응답에 따른 상태 변경)</li>
<li>보통 수준의 우선순위로 처리</li>
</ul>
<br/>
<p><strong>TransitionLane(UI 전환 효과, 지연효과)</strong></p>
<ul>
<li>UI 전환과 관련된 업데이트 (페이지 전환, 애니메이션)</li>
<li>useTransition, startTransition 과 같은 지연 가능한 업데이트 처리</li>
</ul>
<br/>
<p><strong>IdleLane(백그라운드 작업)</strong> => 우선 순위 제일 낮음</p>
<ul>
<li>백그라운드 작업 (데이터 프리페칭, 로깅)</li>
<li>브라우저 유휴 시간에 처리</li>
</ul>
<br/>
<h3 id="업데이트-배치-처리-batch-processing"><a class="anchor" href="#업데이트-배치-처리-batch-processing">업데이트 배치 처리 (Batch Processing)</a></h3>
<p>React는 여러 상태 업데이트를 효율적으로 처리하기 위해 <strong>배치 처리</strong>를 사용한다. 여러 상태 업데이트를 단일 배치로 그룹화하여 처리함으로써 <strong>성능을 최적화하고 불필요한 리렌더링을 방지한다.</strong></p>
<p><strong>배치 처리 메커니즘은</strong> 업데이트를 그룹화하고, 각 업데이트에 우선순위를 할당한 후, 이를 업데이트 큐에 순차적으로 추가한다.</p>
<p>이 과정에서 <strong>React는 동기/비동기 컨텍스트를 구분</strong>하고, 트랜잭션 단위로 상태를 관리하여 일관성을 유지한다.</p>
<p>React의 스케줄러는 우선순위를 기반으로 업데이트 큐를 관리한다. 각 업데이트는 우선순위와 만료 시간을 가지며, 스케줄러는 이를 기반으로 실행 시점을 결정한다.</p>
<p><img src="/content/240909/5.gif" alt="5.gif" loading="lazy" decoding="async"></p>
<p>이러한 스케줄링은 <strong>브라우저의 이벤트 루프와 긴밀하게 통합</strong>되어 있어, 프레임 레이트(디스플레이가 화면의 이미지를 얼마나 자주 업데이트하는지를 나타내는 측정 단위)를 최적화하고 리소스 사용을 효율적으로 관리할 수 있다.</p>
<p><strong>React 18부터는 자동 배치(Automatic Batching)가</strong> 기본적으로 적용되어, 더욱 효율적인 상태 업데이트가 가능해졌다.</p>
<p>이를 통해 개발자는 별도의 설정 없이도 최적화된 상태 관리의 이점을 누릴 수 있게 되었다. 이러한 배치 처리 시스템은 React 애플리케이션의 성능과 사용자 경험을 크게 향상시키는 핵심 기능이다.</p>
<br/>
<h3 id="불필요한-리렌더링-방지"><a class="anchor" href="#불필요한-리렌더링-방지">불필요한 리렌더링 방지</a></h3>
<p>메모리제이션 활용(useMemo, useCallback)과 선택적 렌더링(필요한 props만 전달해 불필요한 객체 생성 방지)을 기반으로 불필요한 리렌더링을 방지한다.</p>
<br/>
<h2 id="재조정reconciliation-단계"><a class="anchor" href="#재조정reconciliation-단계">재조정(Reconciliation) 단계</a></h2>
<p><img src="/content/240909/6.jpeg" alt="6.jpeg" loading="lazy" decoding="async"></p>
<p>React의 재조정은 상태 변화를 실제 UI 변화로 변환하는 핵심 과정이다.</p>
<p>이 과정은 효율적인 UI 업데이트를 위해 변경된 부분만을 식별하고 수정하는 것에 중점을 두고있다.</p>
<br/>
<h3 id="새로운-상태-계산"><a class="anchor" href="#새로운-상태-계산">새로운 상태 계산</a></h3>
<p>React는 상태 업데이트를 처리할 때 두 가지 핵심 작업을 수행한다.</p>
<ul>
<li>현재 상태와 컴포넌트의 마지막 렌더링 결과를 확인해 이전 상태를 평가한다.</li>
<li>들어온 업데이트들을 순차적으로 처리하여 최종 상태를 계산한다.</li>
</ul>
<br/>
<h3 id="virtual-dom-비교"><a class="anchor" href="#virtual-dom-비교">Virtual DOM 비교</a></h3>
<p>React는 효율적인 UI 업데이트를 위해 Virtual DOM을 활용한다.</p>
<p>현재 Virtual DOM은 실제 DOM에 반영된 현재 UI 상태이고, 새로운 Virtual DOM은 새로운 상태를 기반으로 생성된 UI 트리다.</p>
<p><strong><a href="https://devocean.sk.com/blog/techBoardDetail.do?ID=165611">Diff 알고리즘</a>은</strong> 두 Virtual DOM을 비교하여 차이점을 찾는다. 특히 key prop은 리스트 요소의 식별과 효율적인 재사용을 위해 중요하다.</p>
<br/>
<h3 id="필요한-업데이트-결정"><a class="anchor" href="#필요한-업데이트-결정">필요한 업데이트 결정</a></h3>
<p>React는 텍스트/속성/구조 변경 업데이트를 처리한다.</p>
<p><strong>텍스트 업데이트</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>Hello {name}&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 텍스트 내용만 변경</span></span></code></pre></figure>
<p><strong>속성 업데이트</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> className</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{isActive </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'active'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> :</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> ''</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}>  // 클래스만 변경</span></span></code></pre></figure>
<p><strong>구조적 변경</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  condition </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">ComponentA</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> /> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">ComponentB</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> />;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 컴포넌트 전체 교체</span></span></code></pre></figure>
<br/>
<h3 id="fiber-트리-구성"><a class="anchor" href="#fiber-트리-구성">Fiber 트리 구성</a></h3>
<p>Fiber 트리에 대한 내용은 다룰 것이 너무 많다. 그래서 별도로 다루려한다.</p>
<p>내용이 궁금하면 <strong><a href="https://github.com/acdlite/react-fiber-architecture">여기를 참조해보면 좋다</a></strong></p>
<br/>
<h2 id="커밋commit-단계"><a class="anchor" href="#커밋commit-단계">커밋(Commit) 단계</a></h2>
<p><img src="/content/240909/7.png" alt="7.png" loading="lazy" decoding="async"></p>
<p>커밋 단계는 React의 변경사항을 실제 DOM에 안전하게 적용하고, 부수 효과를 관리하며, 메모리를 효율적으로 관리하는 중요한 단계다.</p>
<p>동기적 실행, 부수 효과 관리, 성능 최적화를 중점으로 다루어 성능과 안정성에 직접적인 영향을 미친다.</p>
<br/>
<h3 id="실제-dom-업데이트"><a class="anchor" href="#실제-dom-업데이트">실제 DOM 업데이트</a></h3>
<p>삭제될 노드를 처리 => 새로운 노드를 마운트 => 기존 노드 업데이트 순서대로 DOM 업데이트를 진행한다.</p>
<p>그리고 DOM 조작 최적화를 위해 아래 3가지의 작업을 진행한다.</p>
<ul>
<li>변경사항을 하나의 배치로 처리</li>
<li>레이아웃 트리거 최소화</li>
<li>브라우저 리페인트 최적화</li>
</ul>
<br/>
<h3 id="부수-효과effects-실행"><a class="anchor" href="#부수-효과effects-실행">부수 효과(Effects) 실행</a></h3>
<p><img src="/content/240909/9.png" alt="9.png" loading="lazy" decoding="async"></p>
<p>부수효과에는 useLayoutEffect(레이아웃 효과) 와 useEffect(패시브 효과)가 있다.</p>
<p><strong>useLayoutEffect (레이아웃 효과)</strong></p>
<ul>
<li>
<p>DOM 변경 직후, 브라우저가 화면을 그리기 전에 동기적으로 실행</p>
</li>
<li>
<p>React가 DOM을 업데이트한 직후, 브라우저 페인팅 전에 발생</p>
</li>
<li>
<p>실행 순서: DOM 업데이트 → useLayoutEffect → 브라우저 페인팅</p>
</li>
</ul>
<p><strong>useEffect (패시브 효과)</strong></p>
<ul>
<li>
<p>브라우저가 화면을 그린 후 비동기적으로 실행</p>
</li>
<li>
<p>React가 DOM을 업데이트하고 브라우저가 화면을 그린 후 발생</p>
</li>
<li>
<p>실행 순서: DOM 업데이트 → 브라우저 페인팅 → useEffect</p>
</li>
</ul>
<br/>
<h3 id="참조-정리"><a class="anchor" href="#참조-정리">참조 정리</a></h3>
<p>참조 정리를 위해 ref 업데이트를 처리하고, 메모리 관리를 진행한다.</p>
<p>메모리 관리에는 가비지 컬렉션 준비(불필요한 참조 제거, 순환 참조 방지, 메모리 누수 방지)와 리소스 정리(이벤트 리스너 제거, 타이머 정리 ,외부 구독 취소)가 포함되어있다.</p>
<br/>
<h2 id="완료-단계"><a class="anchor" href="#완료-단계">완료 단계</a></h2>
<p>React 애플리케이션의 안정성과 성능을 보장하는 중요한 단계다.</p>
<p>적절한 클린업, 메모리 관리, 그리고 다음 업데이트 준비를 통해 리소스 누수 방지, 안정적인 성능 유지, 일관된 사용자 경험 제공 등의 요소들을 보장한다.</p>
<br/>
<h3 id="클린업-함수-실행"><a class="anchor" href="#클린업-함수-실행">클린업 함수 실행</a></h3>
<p>클린업의 목적은 이전 렌더링의 부수 효과 정리, 리소스 누수 방지, 상태 일관성 유지 등이 있다.</p>
<p>클린업 실행 순서는 아래 3가지 단계를 거친다.</p>
<ol>
<li>이펙트 클린업</li>
</ol>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 구독 설정</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> subscription</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> dataSource.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 클린업 함수</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 다음 이펙트 실행 전 또는 언마운트 시 호출</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    subscription.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">unsubscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, [dataSource]);</span></span></code></pre></figure>
<ol start="2">
<li>
<p>등록된 이벤트 리스너 제거, 타이머/인터벌 정리, 웹소켓 연결 종료 등을 통해 이벤트를 정리한다.</p>
</li>
<li>
<p>이후에는 외부 연결인 데이터 구독 해제, 외부 API 연결 종료, 캐시 정리등을 진행한다.</p>
</li>
</ol>
<br/>
<h3 id="메모리-정리"><a class="anchor" href="#메모리-정리">메모리 정리</a></h3>
<p>참조 정리, 캐시 관리, 가비지 컬렉션 준비를 통해 메모리를 정리한다.</p>
<p>자세하게 설명해보면 cleanupFiber 함수를 사용해 Fiber 노드를 정리해준다 (하위 fiber 옵션을 null로 초기화해주는 개념?)</p>
<p>참조 정리에는 DOM 참조 해제(함수형 ref, 객체 ref)와 컴포넌트 인스턴스 정리를 의미한다.</p>
<br/>
<h3 id="다음-업데이트-준비"><a class="anchor" href="#다음-업데이트-준비">다음 업데이트 준비</a></h3>
<p>상태를 초기화하고 최적화 준비를 끝낸다.</p>
<p>다음 업데이트를 준비하기 위해, 업데이트 큐 초기화를 진행한다. 그 다음 우선순위 시스템을 새로 준비하게 되는데 그 내용들은 아래 5개를 의미한다.</p>
<ul>
<li>
<p>위에서 설명했던 레인(Lane) 할당하고 작업 우선순위 설정</p>
</li>
<li>
<p>작업 스케줄링 준비 (작업 큐 초기화, 스케줄링 상태 리셋)</p>
</li>
<li>
<p>성능 최적화 준비 (메모리 모니터링, 성능 메트릭 초기화)</p>
</li>
<li>
<p>실행 컨텍스트 준비 (렌더 컨텍스트 초기화)</p>
</li>
<li>
<p>동시성 모드 준비 (시간 분할 설정, 인터럽트 처리 준비)</p>
</li>
</ul>
<br/>
<h2 id="참고자료"><a class="anchor" href="#참고자료">참고자료</a></h2>
<ul>
<li><a href="https://github.com/facebook/react/tree/main/packages/react-reconciler">React-reconciler</a></li>
<li><a href="https://github.com/facebook/react/tree/main/packages/scheduler">React-scheduler</a></li>
<li><a href="https://github.com/facebook/react/tree/main/packages/react-dom">React-DOM</a></li>
<li><a href="https://github.com/acdlite/react-fiber-architecture">React Fiber architecture</a></li>
<li><a href="https://pomb.us/build-your-own-react/">React 내부구현 해보기</a></li>
<li><a href="https://philippspiess.com/scheduling-in-react/">Scheduling in React</a></li>
<li><a href="https://yozm.wishket.com/magazine/detail/2493/">리액트 배칭의 모든 것</a></li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[Concurrent Mode]]></title>
            <link>https://hooninedev.com/240824</link>
            <guid isPermaLink="false">https://hooninedev.com/240824</guid>
            <pubDate>Sat, 24 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[zustand의 공식문서를 살펴보다, "Zustand는 React의 useSyncExternalStore 훅을 사용하여 React의 상태 관리 시스템과 원활하게 통합되어 Concurrent Mode와 호환된다" 는 내용을 보았다. Concurrent Mode라는 낯선 단어를 찾아보게 되었고, 생각보다 면접 질문으로도 쏠쏠하게 등장하기에 정리해보려한다! Re...]]></description>
            <content:encoded><![CDATA[<p>zustand의 공식문서를 살펴보다, <strong>"Zustand는 React의 useSyncExternalStore 훅을 사용하여 React의 상태 관리 시스템과 원활하게 통합되어 Concurrent Mode와 호환된다"</strong> 는 내용을 보았다.</p>
<p><strong>Concurrent Mode라는</strong> 낯선 단어를 찾아보게 되었고, 생각보다 면접 질문으로도 쏠쏠하게 등장하기에 정리해보려한다!</p>
<p>React 18에서 도입된 Concurrent Mode는 React 애플리케이션의 성능과 사용자 경험을 향상 시키기 위해 생긴 기능이다. 앞으로 Concurrent Mode가 무엇인지, 어떻게 작동하는지, 그리고 개발자들이 이를 어떻게 활용할 수 있는지 자세히보자.</p>
<br>
<h2 id="concurrent-mode란"><a class="anchor" href="#concurrent-mode란">Concurrent Mode란</a></h2>
<p><img src="/content/240824/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<p>Concurrent Mode는 React가 렌더링 작업을 중단, 재개, 그리고 even 폐기할 수 있게 해주는 새로운 기능이다. React가 브라우저의 메인 스레드를 차단하지 않고 백그라운드에서 컴포넌트 트리의 여러 버전을 준비할 수 있게 해준다.</p>
<p>주요 특징으로는 <strong>렌더링 작업의 우선순위 지정, 렌더링 중단 및 재개를 가능하게 하고, 불필요한 렌더링을 방지</strong>한다.</p>
<br>
<h3 id="작동-원리와-장점"><a class="anchor" href="#작동-원리와-장점">작동 원리와 장점</a></h3>
<p>Concurrent Mode는 React의 내부 작동 방식을 근본적으로 변경해서 기존의 동기식 렌더링 대신 비동기적으로 렌더링을 수행할 수 있다.</p>
<ul>
<li>React는 여러 업데이트의 우선순위를 지정하고 중요한 업데이트를 먼저 처리한다.</li>
<li><strong>더블 버퍼링:</strong> 화면에 표시되는 내용을 변경하기 전에 메모리에서 트리 전체를 준비</li>
<li>높은 우선순위의 업데이트가 들어오면, 진행 중인 낮은 우선순위의 렌더링을 중단할 수 있다. <strong>(인터럽트가 가능하다)</strong></li>
</ul>
<p><img src="/content/240824/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<br>
<p>Concurrent Mode를 사용하게 되면 <strong>중요한 업데이트를 우선적으로 처리해</strong> 응답성을 높일 수 있다.</p>
<p>그리고 <strong>불필요한 렌더링을 방지</strong>하고, Suspense와 결합하여 더 <strong>자연스러운 로깅 상태</strong>를 제공할 수 있다</p>
<br>
<h2 id="그럼-사용해보자"><a class="anchor" href="#그럼-사용해보자">그럼 사용해보자</a></h2>
<p>사용해보기 전에 몇가지 주의 사항을 확인해보자!</p>
<ul>
<li>Concurrent Mode는 기존의 일부 패턴과 호환되지 않을 수 있다.</li>
<li>비동기 렌더링으로 인해 디버깅이 더 복잡해질 수 있다.</li>
<li>side effect 관리에 신경써야한다.</li>
<li>사용하려면 React 18 이상의 버전이 필요하다.</li>
</ul>
<p>주의사항을 확인했으면 아래처럼 Concurrent Mode를 활성화해보자</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> React </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "react"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ReactDOM </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "react-dom/client"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> App </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "./App"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> root</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ReactDOM.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">createRoot</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(document.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getElementById</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"root"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">root.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">render</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">App</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> />);</span></span></code></pre></figure>
<p>이제 <code>ReactDOM.render()</code> 대신 <code>ReactDOM.createRoot()</code>를 사용하여 애플리케이션을 렌더링한다.</p>
<br>
<h3 id="usetransition"><a class="anchor" href="#usetransition">useTransition</a></h3>
<p><code>useTransition</code> 훅은 우선순위가 낮은 상태 업데이트를 표시할 수 있게 해준다. 그리고 무거운 계산을 수행하면서도 UI의 응답성을 유지할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { useTransition } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "react"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> App</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">isPending</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">startTransition</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useTransition</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">count</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> handleClick</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    startTransition</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">c</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> c </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      {isPending </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x26;&#x26;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Spinner</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> />}</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{handleClick}>{count}&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<br>
<h3 id="usedeferredvalue"><a class="anchor" href="#usedeferredvalue">useDeferredValue</a></h3>
<p><code>useDeferredValue</code>는 덜 중요한 UI 부분의 업데이트를 지연시킬 수 있게 해준다. 그리고 대규모 리스트의 업데이트를 최적화할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { useDeferredValue } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "react"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> SearchResults</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">query</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> deferredQuery</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useDeferredValue</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(query);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 이 컴포넌트의 리렌더링은 다른 중요한 업데이트 후로 지연될 수 있다.</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">SlowList</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> query</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{deferredQuery} />;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<br>
<h3 id="suspense를-사용"><a class="anchor" href="#suspense를-사용">Suspense를 사용</a></h3>
<p>Suspense를 사용하면 컴포넌트의 렌더링을 특정 조건이 충족될 때까지 <strong>"중단"</strong> 할 수 있다. 또한 더 자연스러운 로딩 상태를 제공한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { Suspense } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "react"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> App</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Suspense</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> fallback</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Spinner</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> />}></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">SlowComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> /></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;/</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Suspense</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<br>
<h2 id="react-19에서는"><a class="anchor" href="#react-19에서는">React 19에서는?</a></h2>
<p>24년 8월 24일 기준 React 19에 대한 이야기 많이 들려오고있다. Concurrent Mode의 개념이 22년 3월 React 문서에서 보이기 시작했는데, 2년 반 시간이 지난 지금 관점이 어떻게 변했을까?</p>
<p>아직까지 React 팀은 Concurrent Mode의 개념을 지향하고 있지만, 그 구현 방식과 "Concurrent Mode"라는 용어 대신 "Concurrent Rendering"이라는 용어를 사용하며 변화를 주고 있다.</p>
<p>몇개의 자료들을 살펴보며 어떤 이유때문에 용어의 변화가 생겼는지 알아보자.</p>
<br>
<h3 id="react-github-discussions"><a class="anchor" href="#react-github-discussions">React Github Discussions</a></h3>
<p><a href="https://github.com/reactwg/react-18/discussions/64">React 18 워킹 그룹 GitHub 토론</a>의 Discussions을 살펴보자.</p>
<p><strong>Rename 'Concurrent Mode' to 'Concurrent Rendering'을</strong> 확인해보면 <strong>Dan Abramov</strong>가 아래처럼 서술하고 있다.</p>
<br>
<blockquote>
<p>우리는 'Concurrent Mode'라는 용어를 더 이상 사용하지 않고 대신 'Concurrent Rendering'이라는 용어를 사용할 것입니다. 이는 동시성이 모드가 아니라 렌더링 동작의 변화임을 강조하기 위함입니다.</p>
<p>We're moving away from the term "Concurrent Mode" and will be using "Concurrent Rendering" instead. This is to emphasize that concurrency is not a "mode" but a behind-the-scenes change in the rendering behavior.</p>
</blockquote>
<br>
<h3 id="react-18-docs"><a class="anchor" href="#react-18-docs">React 18 Docs</a></h3>
<p><a href="https://react.dev/blog/2022/03/29/react-v18#what-is-concurrent-react">React 18 공식 문서</a>에서 React 팀은 아래처럼 내용을 다루고 있다.</p>
<br>
<blockquote>
<p>동시성은 React의 내부 구현 세부사항입니다. 최종 사용자로서, React가 동시적이라는 사실을 알 필요는 없습니다. React를 사용하는 방식에 근본적인 변화는 없으며, 단지 React가 제공하는 새로운 기능을 사용하면 됩니다.</p>
<p>Concurrency is a new behind-the-scenes mechanism in React. As an end user, you don't need to think about it most of the time. React automatically opts into concurrent features depending on which ones you're using.</p>
</blockquote>
<br>
<p>위 글을 아래 2가지 이유로 요약할 수 있다.</p>
<ul>
<li><strong>점진적 채택 가능성:</strong> "Mode"라는 단어는 전체 애플리케이션에 대한 on/off 스위치와 같은 느낌을 주지만, "Rendering"은 더 유연하고 점진적인 도입을 암시한다.</li>
<li><strong>혼란 감소:</strong> 많은 개발자들이 Concurrent Mode를 하나의 기능으로 오해했다. 실제로는 여러 기능의 집합이며, 이를 "Concurrent Rendering"으로 표현하는 것이 더 정확하다.</li>
</ul>
<br>
<h2 id="결론"><a class="anchor" href="#결론">결론</a></h2>
<p>Concurrent Mode는 React 애플리케이션의 성능과 사용자 경험을 크게 향상시킬 수 있는 강력한 도구인 것 같다.</p>
<p>이를 효과적으로 활용하기 위해서는 새로운 개념과 API에 대한 이해도가 필요해보여서, 공부를 더 해봐야겠다!</p>
<p><img src="/content/240824/4.jpeg" alt="4.jpeg" loading="lazy" decoding="async"></p>
<p>React 팀은 계속해서 Concurrent Mode, Concurrent Rendering을 강조하고 있기때문에, 앞으로 더 많은 기능과 최적화가 이루어질 것으로 예상된다!</p>
<br>
<h3 id="참고자료"><a class="anchor" href="#참고자료">참고자료</a></h3>
<ul>
<li><a href="https://react.dev/blog/2022/03/29/react-v18">React v18.0</a></li>
<li><a href="https://w55ng.com/entry/React-18%EC%9D%98-%EC%83%88%EB%A1%9C%EC%9A%B4-%EA%B8%B0%EB%8A%A5-%EB%8F%99%EC%8B%9C%EC%84%B1-%EB%AA%A8%EB%93%9CConcurrent-Mode">동시성 모드에 대해서</a></li>
<li><a href="https://medium.com/swlh/what-is-react-concurrent-mode-46989b5f15da">What is React Concurrent Mode?</a></li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>React</category>
        </item>
        <item>
            <title><![CDATA[Zustand, 너 뭔데 ProviderLess 인 거야?]]></title>
            <link>https://hooninedev.com/240818</link>
            <guid isPermaLink="false">https://hooninedev.com/240818</guid>
            <pubDate>Sun, 18 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[이번 포스팅에서는 Zustand가 어떻게 Provider 없이 상태관리를 해내는지에 대한 이야기를 해보려고 한다. 필자는 Zustand를 사용하면서 늘 당연하게 Provider 없이 상태를 관리해왔다. 그러다 문득 이런 생각이 들었다. React 생태계의 대부분의 라이브러리는 Provider로 앱을 감싸는 것이 거의 의식처럼 굳어져 있다. TanStack ...]]></description>
            <content:encoded><![CDATA[<p>이번 포스팅에서는 Zustand가 어떻게 Provider 없이 상태관리를 해내는지에 대한 이야기를 해보려고 한다.</p>
<p>필자는 Zustand를 사용하면서 늘 당연하게 Provider 없이 상태를 관리해왔다. 그러다 문득 이런 생각이 들었다. React 생태계의 대부분의 라이브러리는 Provider로 앱을 감싸는 것이 거의 의식처럼 굳어져 있다. TanStack React Query는 <code>QueryClientProvider</code>로 감싸야 <code>useQuery</code>를 쓸 수 있고, toss의 overlay-kit도 <code>OverlayProvider</code> 없이는 <code>overlay.open()</code>을 호출할 수 없다. React의 Context API 역시 반드시 Provider로 컴포넌트 트리를 감싸야 한다. 그런데 Zustand는 대체 어떤 마법을 부리기에 그런 과정이 필요 없는 걸까?</p>
<p>궁금해서 Zustand의 소스 코드를 직접 뜯어보았고, 생각보다 흥미로운 구조가 숨어있었다. 그 과정에서 알게 된 내용을 정리해보려 한다.</p>
<hr>
<h2 id="react에서-상태는-어떻게-흘러가는가"><a class="anchor" href="#react에서-상태는-어떻게-흘러가는가">React에서 상태는 어떻게 흘러가는가</a></h2>
<p>일반적인 React 애플리케이션에서 상태는 아래 그림처럼 동작한다.</p>
<p><img src="/content/240818/3.png" alt="3.png" loading="lazy" decoding="async"></p>
<p>컴포넌트 내부 상태는 React가 제공하는 상태 관리 훅(<code>useState</code>, <code>useReducer</code>)을 사용하여 관리한다. 그리고 하위 컴포넌트로의 상태 전달은 props를 통해 이루어진다. 여기까지는 단순한 이야기이다.</p>
<p>문제는 멀리 떨어진 컴포넌트 간에 상태를 공유해야 할 때 발생한다. 이때 React가 제공하는 공식적인 해법이 바로 Context API인데, 이 녀석은 반드시 Provider 컴포넌트로 하위 트리를 감싸야 한다.</p>
<hr>
<h3 id="왜-context-api는-provider가-필요할까"><a class="anchor" href="#왜-context-api는-provider가-필요할까">왜 Context API는 Provider가 필요할까?</a></h3>
<p>이 질문에 답하려면 React의 내부 동작을 조금 들여다봐야 한다.</p>
<p>React는 컴포넌트 트리를 Fiber라는 내부 자료구조로 관리한다. 각 Fiber 노드는 부모-자식 관계로 연결되어 있고, Context의 값이 변경되면 React는 이 Fiber 트리를 위에서 아래로 순회하면서 해당 Context를 구독하는 컴포넌트를 찾아 리렌더링을 트리거한다.</p>
<p>핵심은 이것이다. <strong>Context의 값 전파는 Fiber 트리의 구조에 의존한다.</strong> Provider가 트리의 어느 지점에 위치하느냐에 따라 값이 전달되는 범위가 결정되고, <code>useContext</code>를 호출한 컴포넌트는 자신의 상위 Fiber 트리를 거슬러 올라가며 가장 가까운 Provider를 찾는다. Provider가 없으면? <code>createContext</code>에 전달한 기본값이 사용될 뿐이다.</p>
<p>즉, Context API는 React의 렌더링 시스템과 긴밀하게 결합되어 있다. 상태의 저장, 전파, 구독 모두가 React의 컴포넌트 트리 내부에서 일어나는 것이다.</p>
<p>그렇다면 Zustand는 이 구조를 어떻게 우회하는 걸까?</p>
<hr>
<h2 id="zustand는-react-바깥에-산다"><a class="anchor" href="#zustand는-react-바깥에-산다">Zustand는 React 바깥에 산다</a></h2>
<p><img src="/content/240818/4.png" alt="4.png" loading="lazy" decoding="async"></p>
<p>Zustand는 Flux 패턴을 기반으로 동작한다. 클로저 내부의 <code>state</code>가 Store 역할을, 사용자 정의 함수들이 Action 역할을, <code>set</code> 함수가 Dispatcher 역할을, React 컴포넌트가 View 역할을 수행한다. 여기서 결정적인 차이가 있다.</p>
<p><strong>Zustand의 스토어는 React 컴포넌트 트리 외부, JavaScript 모듈의 스코프 내에 존재한다.</strong></p>
<p>컴포넌트 트리 외부라는 것의 의미는 React 내부의 상태관리와 달리, Zustand에서 자주 언급되는 "컴포넌트 트리 외부"라는 표현은 상태가 React의 Fiber 트리와 무관하게 독립적으로 존재한다는 뜻이다. 어떤 컴포넌트든 <code>import</code>만 하면 스토어에 접근할 수 있고, Provider로 앱을 감쌀 필요가 없다. (마치 전역 변수처럼 어디서든 접근 가능하되, 클로저로 잘 보호되어 있는 셈이다.)</p>
<p>어떻게 이렇게 가능할까? 아래 코드를 살펴보자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { create } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'zustand'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> useStore</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> create</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  count: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  increment</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({ count: state.count </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> })),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}));</span></span></code></pre></figure>
<p>이 코드에서 <code>create</code>가 호출되는 시점은 모듈이 로드될 때이다. 즉, React가 렌더링을 시작하기도 전에 스토어는 이미 메모리에 존재하게 된다. 이것이 <strong>모듈 레벨 싱글톤(Module-level Singleton) 패턴</strong>이다.</p>
<hr>
<h3 id="모듈-레벨-싱글톤이란"><a class="anchor" href="#모듈-레벨-싱글톤이란">모듈 레벨 싱글톤이란?</a></h3>
<p>JavaScript의 ES 모듈 시스템은 <strong>모듈을 최초 한 번만 평가(evaluate)하고, 그 결과를 캐싱</strong>한다. 이후 어디서든 같은 모듈을 <code>import</code>하면 새로 실행하는 것이 아니라 캐싱된 동일한 객체를 반환한다. 즉, <code>import { useStore } from './store'</code>를 컴포넌트 A에서 하든 컴포넌트 B에서 하든, 둘 다 <strong>정확히 같은 스토어 인스턴스</strong>를 참조하게 된다.</p>
<p>별도의 싱글톤 클래스를 구현하거나, 전역 변수(<code>window.store</code>)에 매달 필요가 없다. 모듈 시스템 자체가 "한 번만 생성되고 어디서든 같은 인스턴스에 접근한다"는 싱글톤의 조건을 자연스럽게 충족해주는 것이다. Zustand는 이 언어 레벨의 보장을 그대로 활용하여, 별도의 Provider 없이도 모든 컴포넌트가 하나의 스토어를 공유할 수 있게 설계한 것이다.</p>
<p>여기까지 읽으면 자연스럽게 떠오르는 질문이 하나 있다. 그래서 Zustand의 내부는 구체적으로 어떻게 생겼을까?</p>
<hr>
<h2 id="zustand-내부-구조"><a class="anchor" href="#zustand-내부-구조">Zustand 내부 구조</a></h2>
<p><a href="https://github.com/pmndrs/zustand/tree/main/src">Zustand의 GitHub 저장소</a>를 들여다보면, 핵심 로직은 놀라울 정도로 간결하다. 크게 두 개의 파일이 핵심인데, <code>vanilla.ts</code>가 스토어의 본체를, <code>react.ts</code>가 React와의 연결 고리를 담당한다.</p>
<hr>
<h3 id="vanillats"><a class="anchor" href="#vanillats">vanilla.ts</a></h3>
<p><a href="https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts">vanilla.ts</a>는 Zustand의 심장부이다. 스토어가 어떻게 생성되고, 상태가 어떻게 관리되는지 이 파일 하나에 모두 담겨있다. 더 쉽게 말해, 클로저에 갇힌 상태와 그 상태를 조작하는 함수들이 이 파일에 정의되어 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createStoreImpl</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> CreateStoreImpl</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">createState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ReturnType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> createState></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Listener</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">prevState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> state</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TState</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> listeners</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">Listener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> setState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> StoreApi</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>[</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'setState'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">partial</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">replace</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> nextState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> partial </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'function'</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        ?</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (partial </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)(state)</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        :</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> partial</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Object.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">is</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(nextState, state)) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> previousState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> state</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        (replace </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">??</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> nextState </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'object'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> ||</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> nextState </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">))</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">          ?</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (nextState </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">          :</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Object.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">assign</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({}, state, nextState)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      listeners.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">forEach</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">listener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> listener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(state, previousState))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> StoreApi</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>[</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'getState'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> state</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getInitialState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> StoreApi</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>[</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'getInitialState'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    initialState</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> subscribe</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> StoreApi</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>[</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'subscribe'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">listener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    listeners.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">add</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(listener)</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> listeners.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">delete</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(listener)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> api</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { setState, getState, getInitialState, subscribe }</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> initialState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(setState, getState, api))</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> api </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>이 코드를 한 줄 한 줄 뜯어보면 Zustand의 핵심 메커니즘이 드러난다.</p>
<ul>
<li>
<p><strong>클로저를 통한 상태 캡슐화</strong></p>
<ul>
<li>
<p><code>let state: TState</code>라는 변수가 <code>createStoreImpl</code> 함수의 지역 변수로 선언되어 있다. 이 변수는 함수 실행이 끝난 후에도 <code>setState</code>, <code>getState</code> 등의 내부 함수가 참조하고 있기 때문에 가비지 컬렉션되지 않는다. 이것이 클로저의 본질이다.</p>
</li>
<li>
<p>외부에서는 <code>state</code> 변수에 직접 접근할 방법이 없다. 오로지 <code>getState()</code>로 읽고, <code>setState()</code>로 쓸 수 있을 뿐이다. (객체지향에서 말하는 private 필드를 클로저로 구현한 셈이다.)</p>
</li>
</ul>
</li>
<li>
<p><strong><code>Object.is</code>를 활용한 변경 감지</strong></p>
<ul>
<li>
<p><code>setState</code>는 새로운 상태를 계산한 뒤, <code>Object.is(nextState, state)</code>로 기존 상태와 비교한다. 참조가 동일하면 아무 일도 일어나지 않는다. 이것이 불필요한 리렌더링을 방지하는 첫 번째 방어선이다.</p>
</li>
<li>
<p>그런데 이 <code>Object.is</code> 비교는 <strong>엄격한 참조 동등성(strict reference equality)</strong> 검사이기 때문에, 사용하는 쪽에서 주의해야 할 지점이 있다. 원시값(숫자, 문자열 등)을 하나만 꺼내 쓸 때는 문제가 없다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> count</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> state.count);</span></span></code></pre></figure>
<p>하지만 selector가 <strong>새로운 객체를 반환</strong>하면 이야기가 달라진다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">count</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">name</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  count: state.count,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  name: state.name,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}));</span></span></code></pre></figure>
<p><code>{ count, name }</code> 객체는 값이 동일하더라도 호출할 때마다 새로운 참조가 만들어진다. <code>Object.is</code>는 내부 프로퍼티를 비교하지 않고 참조만 비교하므로, Zustand 입장에서는 "상태가 바뀌었다"고 판단하여 매번 리렌더링을 트리거하게 된다.</p>
<p>이 문제를 해결하기 위해 Zustand는 <strong><code>useShallow</code></strong> 훅을 제공한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { useShallow } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'zustand/react/shallow'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">count</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">name</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useShallow</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({ count: state.count, name: state.name }))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span></code></pre></figure>
<p><code>useShallow</code>는 반환된 객체의 <strong>최상위 프로퍼티들을 하나씩 비교</strong>하여, 실제로 값이 변한 경우에만 리렌더링을 발생시킨다. Redux의 <code>useSelector</code>가 기본적으로 참조 비교를 사용하되 <code>shallowEqual</code>을 두 번째 인자로 넘길 수 있는 것과 비슷한 맥락이다. (다만 <code>useShallow</code>는 이름 그대로 "얕은" 비교이므로, 중첩된 객체의 내부까지는 추적하지 않는다는 점을 기억해두자.)</p>
</li>
</ul>
</li>
<li>
<p><strong>Pub/Sub 패턴의 리스너 시스템</strong></p>
<ul>
<li><code>const listeners: Set&#x3C;Listener> = new Set()</code>라는 한 줄이 Zustand의 구독 시스템 전체이다. 상태가 변경되면 <code>listeners.forEach</code>로 모든 구독자에게 알림을 보낸다.</li>
<li><code>subscribe</code>를 호출하면 리스너가 <code>Set</code>에 추가되고, 반환된 함수를 호출하면 <code>Set</code>에서 제거된다.</li>
<li>이 패턴이 중요한 이유는, <strong>React의 Fiber 트리와 완전히 독립적인 알림 시스템</strong>이기 때문이다. Provider가 트리를 순회하며 구독자를 찾는 방식이 아니라, 스토어가 직접 구독자 목록을 관리하는 방식인 것이다.</li>
</ul>
</li>
<li>
<p><strong>초기 상태 생성</strong></p>
<ul>
<li>
<p>초기 상태를 핸들링하는 마지막 줄 코드를 살펴보자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> initialState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(setState, getState, api))</span></span></code></pre></figure>
<p>한 줄에 많은 것이 압축되어 있다. JavaScript에서 할당 연산자(<code>=</code>)는 <strong>할당된 값 자체를 반환</strong>하는 표현식(expression)이다. 즉, 괄호 안의 <code>state = createState(...)</code> 가 먼저 실행되어 <code>state</code>에 초기 상태가 할당되고, 그 반환값이 다시 <code>const initialState</code>에 할당된다. 결과적으로 <code>state</code>와 <code>initialState</code>가 <strong>동일한 객체를 참조</strong>하게 되는 것이다.</p>
<p>그런데 왜 같은 값을 굳이 두 변수에 나눠 담는 걸까? 핵심은 두 변수의 역할이 다르다는 점이다.</p>
<ul>
<li><strong><code>state</code></strong> 는 <code>let</code>으로 선언된 변수이다. <code>setState</code>가 호출될 때마다 새로운 값으로 교체된다. 즉 <strong>현재 시점의 살아있는 상태</strong>를 나타낸다.</li>
<li><strong><code>initialState</code></strong> 는 <code>const</code>로 선언된 변수이다. 스토어가 생성된 시점의 상태가 영구히 보존된다. 이후 어떤 <code>setState</code>가 호출되더라도 이 값은 변하지 않는다. <strong>스토어의 최초 스냅샷</strong>인 셈이다.</li>
</ul>
<p>이 <code>initialState</code>는 <code>getInitialState()</code> 메서드를 통해 외부에 노출되고, <code>react.ts</code>에서 <code>useSyncExternalStore</code>의 <strong>세 번째 인자(서버 스냅샷)</strong> 로 전달된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> slice</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> React.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useSyncExternalStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  api.subscribe,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> selector</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(api.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()),       </span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> selector</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(api.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getInitialState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()), </span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span></code></pre></figure>
<p>서버 사이드 렌더링(SSR) 환경에서는 브라우저 API가 없고, 사용자 인터랙션도 없으므로 <code>setState</code>가 호출될 일이 없다. 따라서 서버에서는 항상 <code>initialState</code>(= 최초 상태)가 스냅샷으로 사용된다. 클라이언트에서 hydration이 시작될 때, React는 서버에서 렌더링한 HTML과 클라이언트의 초기 렌더링 결과를 비교하는데, 양쪽 모두 동일한 <code>initialState</code>를 기준으로 렌더링했기 때문에 <strong>hydration 불일치를 방지</strong>할 수 있는 것이다.</p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="reactts"><a class="anchor" href="#reactts">react.ts</a></h3>
<p><a href="https://github.com/pmndrs/zustand/blob/main/src/react.ts">react.ts</a>는 위에서 만든 순수 JavaScript 스토어를 React의 렌더링 시스템에 연결하는 역할을 한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">StateSlice</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  api</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ReadonlyStoreApi</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>,</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  selector</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> StateSlice</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> identity </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> slice</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> React.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useSyncExternalStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    api.subscribe,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    React.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useCallback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> selector</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(api.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()), [api, selector]),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    React.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useCallback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> selector</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(api.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getInitialState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()), [api, selector]),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  )</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  React.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useDebugValue</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(slice)</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> slice</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>여기서 핵심은 <code>useSyncExternalStore</code>이다. 이 훅은 React 18에서 도입된 것으로, <strong>React 외부에 존재하는 상태 저장소를 React의 렌더링 사이클에 안전하게 통합</strong>하기 위해 설계되었다.</p>
<p><code>useSyncExternalStore</code>가 받는 세 가지 인자를 살펴보면 구조가 명확해진다. (앞서 vanilla.ts 에서 다룬 내용과 거의 비슷하다)</p>
<ul>
<li><strong><code>api.subscribe</code></strong>: 스토어의 변경을 구독하는 함수이다. React는 이 함수를 통해 "상태가 바뀌면 알려달라"고 요청한다.</li>
<li><strong><code>() => selector(api.getState())</code></strong>: 현재 상태의 스냅샷을 반환한다. React는 렌더링할 때마다 이 함수를 호출하여 최신 상태를 가져온다.</li>
<li><strong><code>() => selector(api.getInitialState())</code></strong>: 서버 사이드 렌더링 시 사용할 초기 스냅샷이다. hydration 과정에서 서버와 클라이언트의 상태 불일치를 방지한다.</li>
</ul>
<p>특히 <code>useSyncExternalStore</code>는 React의 동시성 모드(Concurrent Mode)에서 발생할 수 있는 <strong>tearing 문제</strong>를 해결한다. Tearing이란 같은 렌더 패스 안에서 서로 다른 컴포넌트가 <strong>동일한 데이터 소스의 서로 다른 스냅샷</strong>을 보여주는 현상이다.</p>
<p>구체적인 시나리오를 보면 이해가 쉽다. 컴포넌트 A가 <code>store.value</code>(= 10)를 읽고 렌더링을 시작한다. 이때 React가 동시성 모드에서 렌더링을 <strong>일시 중단(yield)</strong> 하고 브라우저에게 제어권을 넘긴다. 그 틈에 WebSocket 메시지가 도착하여 <code>store.value</code>가 11로 변경된다. React가 렌더링을 재개하면서 컴포넌트 B가 <code>store.value</code>(= 11)를 읽는다. 결과적으로 같은 프레임에 A는 10을, B는 11을 보여주는 <strong>찢어진(teared) UI</strong>가 만들어지는 것이다. React 18 이전에는 렌더링이 항상 동기적이었기 때문에 이 문제가 발생하지 않았다.</p>
<p><code>useSyncExternalStore</code>는 렌더링 시작 시점의 스냅샷(<code>getSnapshot</code>)을 기록해 두고, 렌더링 도중 외부 스토어가 변경되어 스냅샷이 달라지면 이를 감지하여 <strong>렌더링을 처음부터 다시 시작</strong>한다. 이를 통해 모든 컴포넌트가 동일한 스냅샷을 기반으로 렌더링되는 것을 보장하는 것이다.</p>
<p>그리고 <code>createImpl</code> 함수가 이 모든 것을 하나로 묶는다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createImpl</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">createState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> StateCreator</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, [], []>) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> api</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(createState)</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useBoundStore</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">selector</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(api, selector)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  Object.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">assign</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(useBoundStore, api)</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> useBoundStore</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p><code>createStore</code>로 vanilla 스토어를 생성하고, <code>useBoundStore</code>라는 커스텀 훅으로 감싼 뒤, <code>Object.assign</code>으로 스토어 API의 메서드들(<code>setState</code>, <code>getState</code>, <code>subscribe</code> 등)을 훅 함수 자체에 붙여버린다. 그 결과 반환되는 <code>useBoundStore</code>는 <strong>React 훅이면서 동시에 스토어 API</strong>라는 이중적인 성격을 가지게 된다. (함수인데 메서드도 있는, 꽤나 JavaScript스러운 패턴이다.)</p>
<hr>
<h2 id="다른-상태관리-라이브러리는-어떨까"><a class="anchor" href="#다른-상태관리-라이브러리는-어떨까">다른 상태관리 라이브러리는 어떨까?</a></h2>
<p>여기까지 이해했다면 자연스럽게 다른 라이브러리들과 비교해보고 싶어질 것이다.</p>
<p>Jotai, Recoil, MobX, Xstate, Redux 등 다양한 상태관리 라이브러리가 존재하겠지만, 필자가 직접 사용해본 라이브러리 위주로 비교해보려고 한다.</p>
<blockquote>
<p>참고로, Jotai와 자주 비교되던 <strong>Recoil</strong>(Meta)은 2025년 1월 저장소가 아카이브되면서 사실상 개발이 중단되었다. React 19 지원도 이루어지지 않은 상태이다. 원자적 상태 모델을 원한다면 현시점에서는 Jotai가 유일한 현실적 선택지라고 할 수 있다.</p>
</blockquote>
<hr>
<h3 id="redux"><a class="anchor" href="#redux">Redux</a></h3>
<p>Redux도 내부적으로는 모듈 레벨의 스토어를 사용한다. 그런데 왜 Provider가 필요할까?</p>
<p>Redux의 <code>&#x3C;Provider store={store}></code>는 React Context를 통해 스토어 인스턴스를 컴포넌트 트리에 <strong>주입(inject)</strong> 한다. <code>useSelector</code>나 <code>useDispatch</code>는 내부적으로 <code>useContext</code>를 호출하여 Provider가 제공하는 스토어에 접근하는 구조이다. 여기서 중요한 점은, Redux가 Context를 <strong>상태 전파 채널이 아니라 의존성 주입(Dependency Injection) 수단</strong>으로 사용한다는 것이다. Context를 통해 전달되는 것은 상태 값 자체가 아니라 상태를 관리하는 <strong>스토어 객체의 참조</strong>이다. 실제 상태 구독과 업데이트는 스토어 내부의 Pub/Sub으로 처리된다.</p>
<p>이 설계가 가져다주는 이점은 명확하다. 테스트 시 다른 스토어 인스턴스를 Provider로 감싸면 완벽한 격리가 되고, 하나의 앱에서 <code>context</code> prop을 통해 여러 독립적인 스토어 트리를 구성할 수도 있다. Mark Erikson(Redux 메인테이너)이 강조하듯, "Context는 전송 메커니즘(transport mechanism)이지 상태 관리 도구가 아니다."</p>
<hr>
<h3 id="jotai"><a class="anchor" href="#jotai">Jotai</a></h3>
<p>Jotai는 Redux나 Zustand와 근본적으로 다른 <strong>원자적(atomic) 상태 모델</strong>을 채택한다. 하나의 큰 스토어 객체에 상태를 모아두는 것이 아니라, <strong>각각의 상태 조각을 독립적인 atom으로 분리</strong>하는 접근이다. (Jotai 공식 문서에서도 "Zustand가 Redux와 유사하다면, Jotai는 Recoil과 유사하다"고 설명한다.)</p>
<p>이 구조의 핵심 차이는 <strong>렌더링 최적화 방식</strong>에 있다. Zustand는 하나의 스토어에서 selector를 통해 필요한 부분만 추출하는 <strong>하향식(top-down)</strong> 접근이다. 개발자가 <code>useStore((state) => state.count)</code>처럼 selector를 직접 작성해야 하고, 참조 동일성(referential equality)을 유지하기 위해 때로는 메모이제이션이 필요하다. 반면 Jotai는 atom 간의 <strong>의존성 그래프(dependency graph)</strong> 를 자동으로 구축하여, 특정 atom이 변경되면 그 atom에 의존하는 컴포넌트만 정확히 리렌더링하는 <strong>상향식(bottom-up)</strong> 전파를 수행한다. 스프레드시트나 캔버스 에디터처럼 수십 개의 상태가 서로 얽혀 있는 경우에 이 자동 의존성 추적이 큰 힘을 발휘한다.</p>
<p>Provider 측면에서 Jotai는 흥미로운 중간 지점에 위치한다. 기본적으로 전역 스토어를 사용하여 Provider 없이 동작하지만, 필요하다면 <code>&#x3C;Provider></code>로 감싸서 격리된 스토어 스코프를 만들 수 있다. Jotai 공식 문서의 표현을 빌리면, Jotai는 <strong>"context first, module second"</strong> 이고, Zustand는 <strong>"module first, context second"</strong> 인 것이다.</p>
<hr>
<h3 id="zustand의-선택"><a class="anchor" href="#zustand의-선택">Zustand의 선택</a></h3>
<p>Zustand는 가장 급진적인 선택을 했다. 기본적으로 모듈 레벨 싱글톤이며, Provider가 아예 없다. 이 선택이 가져다주는 것은 <strong>극도로 단순한 API</strong>이다. <code>create</code>로 스토어를 만들고, 컴포넌트에서 훅을 호출하면 끝이다.</p>
<p>다만 "Provider가 아예 없다"는 말은 정확히는 <strong>기본 설계</strong>에 대한 이야기이다. v4부터는 <code>createStore</code>(vanilla 스토어)와 React의 <code>createContext</code>를 조합하여 <strong>스코프드 스토어(Scoped Store)</strong> 패턴을 구현할 수 있다.</p>
<p><a href="https://tkdodo.eu/blog/zustand-and-react-context">TkDodo(React Query 메인테이너)의 블로그</a>에서 이 패턴을 깊이 있게 다루고 있는데, 그가 제시하는 핵심 논지는 이렇다. 전역 싱글톤 스토어에는 세 가지 한계가 있다.</p>
<ul>
<li><strong>Props로 초기화할 수 없다</strong> : 모듈 로드 시점에 스토어가 생성되므로, 서버에서 내려온 데이터나 부모 컴포넌트의 props를 초기값으로 넣을 방법이 없다.</li>
<li><strong>테스트 격리가 어렵다</strong> : 테스트마다 스토어를 수동으로 리셋해야 한다.</li>
<li><strong>재사용이 불가능하다</strong> : 같은 구조의 스토어가 필요한 컴포넌트를 페이지에 두 개 렌더링하면, 둘이 상태를 공유해버린다.</li>
</ul>
<p>이 세 가지를 모두 해결하는 것이 스코프드 스토어 패턴이다. 핵심 아이디어는 <strong>Context로 상태 값을 전달하는 것이 아니라, 스토어 인스턴스의 참조를 전달</strong>하는 것이다. (Redux의 Provider가 하는 일과 정확히 같은 구조이다.)</p>
<p>구체적인 구현을 보면 이렇다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { createStore, useStore } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'zustand'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { createContext, useContext, useState } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'react'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 1. 스토어 팩토리 함수 — props를 받아 스토어를 생성</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createSelectionStore</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">initialItems</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[]) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  createStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">SelectionState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    items: initialItems,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    selected: </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(),</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    toggle</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">id</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> next</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(state.selected);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        next.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">has</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(id) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> next.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">delete</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(id) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> next.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">add</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(id);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { selected: next };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      }),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 2. Context 생성</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> SelectionStore</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> ReturnType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> createSelectionStore>;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> SelectionContext</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createContext</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">SelectionStore</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 3. Provider — useState로 스토어를 한 번만 생성</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> SelectionProvider</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  children,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  initialItems,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  children</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> React</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ReactNode</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  initialItems</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">store</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createSelectionStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(initialItems));</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    &#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">SelectionContext.Provider value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{store}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      {</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">children</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    &#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">SelectionContext.Provider</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 4. 커스텀 훅 — Context에서 스토어를 꺼내 useStore로 구독</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useSelectionStore</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,>(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">selector</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> SelectionState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> store</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useContext</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(SelectionContext);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">store) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">throw</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'SelectionProvider가 필요합니다'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useStore</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(store, selector);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre></figure>
<p>이제 같은 페이지에 독립적인 멀티셀렉트 컴포넌트를 원하는 만큼 렌더링할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 각 SelectionProvider가 자신만의 스토어 인스턴스를 가진다</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">SelectionProvider</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> initialItems</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{[</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'A'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'B'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'C'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]}></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">MultiSelect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> /></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">SelectionProvider</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">SelectionProvider</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> initialItems</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{[</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'X'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'Y'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'Z'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]}></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">MultiSelect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> />  {</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/* 위 컴포넌트와 상태가 완전히 독립 */</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">SelectionProvider</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span></code></pre></figure>
<p>여기서 주목할 점은, Context를 통해 전달되는 것이 <strong>상태 값이 아니라 스토어 객체</strong>라는 것이다. 상태 값이 변경되어도 Context의 <code>value</code>(= 스토어 참조)는 바뀌지 않으므로, <strong>Context의 값 변경으로 인한 불필요한 리렌더링이 발생하지 않는다.</strong> 실제 리렌더링은 <code>useStore</code> 내부의 <code>useSyncExternalStore</code>가 selector 기반으로 처리한다. Context의 전송 역할과 Zustand의 구독 역할이 깔끔하게 분리되는 것이다.</p>
<p>TkDodo는 디자인 시스템의 멀티셀렉트 컴포넌트에서 이 패턴을 실제로 적용한 사례를 소개했다. 기존에 <code>useState</code> + Context로 내부 상태를 관리하던 구조가 50개 이상의 항목에서 성능 저하를 보였고, Zustand의 selector 기반 구독으로 전환하여 해결했다고 한다.</p>
<p>이 패턴은 v3에서 <code>zustand/context</code>로 제공되던 <code>createContext</code> 헬퍼가 v4에서 제거된 이후, <strong>React의 네이티브 <code>createContext</code> + Zustand의 <code>createStore</code>/<code>useStore</code>를 직접 조합하는 방식</strong>으로 정착했다. v5에서도 이 API는 그대로 유지되고 있으며, <a href="https://github.com/pmndrs/zustand/blob/main/docs/previous-versions/zustand-v3-create-context.md">Zustand 공식 문서</a>에서도 v4+ 마이그레이션 가이드로 이 패턴을 안내하고 있다.</p>
<hr>
<h2 id="providerless의-그림자"><a class="anchor" href="#providerless의-그림자">ProviderLess의 그림자</a></h2>
<p>물론 Provider가 없다는 것이 장점만 있는 것은 아니다. 필자가 생각하는 주의해야 할 지점들을 정리해보겠다.</p>
<hr>
<h3 id="ssr에서의-상태-공유-문제"><a class="anchor" href="#ssr에서의-상태-공유-문제">SSR에서의 상태 공유 문제</a></h3>
<p>모듈 레벨 싱글톤은 서버 환경에서 위험할 수 있다. Node.js 서버는 여러 요청을 하나의 프로세스에서 처리하는데, 모듈은 프로세스 내에서 한 번만 로드된다. 이는 서로 다른 사용자의 요청이 <strong>같은 스토어 인스턴스를 공유</strong>할 수 있다는 뜻이다.</p>
<p>Zustand가 <code>getInitialState</code>를 제공하고 <code>useSyncExternalStore</code>의 세 번째 인자로 서버 스냅샷을 넘기는 이유가 여기에 있다. 하지만 이것만으로는 요청 간 상태 격리가 완벽하지 않을 수 있어, SSR 환경에서는 앞서 언급한 스코프드 스토어 패턴(<code>createStore</code> + React Context)을 활용하여 요청마다 새로운 스토어를 생성하는 것이 권장된다.</p>
<hr>
<h3 id="테스트-격리의-어려움"><a class="anchor" href="#테스트-격리의-어려움">테스트 격리의 어려움</a></h3>
<p>Provider 기반 라이브러리는 테스트마다 다른 Provider로 감싸면 스토어가 자연스럽게 격리된다. 반면 Zustand의 모듈 레벨 싱글톤은 테스트 간에 상태가 누수될 수 있다. 각 테스트의 <code>beforeEach</code>에서 스토어를 명시적으로 리셋해야 하는 것이다. (필자도 이 문제로 한 번 고생한 적이 있다.)</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 테스트 파일에서의 스토어 리셋 예시</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">beforeEach</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  useStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(useStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getInitialState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span></code></pre></figure>
<p>여기서도 스코프드 스토어 패턴이 해결책이 된다. Provider로 감싸는 방식이라면 각 테스트에서 새로운 스토어를 생성하여 주입하면 되므로, 리셋 로직 없이 완벽한 격리가 가능하다.</p>
<hr>
<h3 id="다중-인스턴스의-부재"><a class="anchor" href="#다중-인스턴스의-부재">다중 인스턴스의 부재</a></h3>
<p>하나의 애플리케이션에서 같은 구조의 독립적인 스토어 두 개가 필요한 경우, Provider 패턴이라면 각각 다른 Provider로 감싸면 된다. 하지만 모듈 레벨 싱글톤에서는 스토어 생성 함수를 별도로 호출하여 서로 다른 스토어 인스턴스를 만들어야 한다. 예를 들어, 같은 페이지에 독립적인 탭 패널 두 개가 있고 각각의 선택 상태를 별도로 관리해야 한다면, 전역 싱글톤으로는 자연스럽게 표현하기 어렵다.</p>
<p>이런 경우에도 <code>createStore</code> + Context 패턴이 정답이다. 각 탭 패널 컴포넌트가 자신만의 Provider를 렌더링하면, 동일한 스토어 구조를 가진 완전히 독립적인 인스턴스가 만들어진다. Zustand 공식 문서에서도 "재사용 가능한 컴포넌트에 스토어가 필요한 경우"에 이 패턴을 권장하고 있다.</p>
<h2 id="결론"><a class="anchor" href="#결론">결론</a></h2>
<p>지금까지 살펴본 내용을 정리하면, Zustand의 ProviderLess 설계는 다음 네 가지 메커니즘의 조합으로 가능해진다.</p>
<ul>
<li><strong>모듈 레벨 싱글톤</strong>: 스토어가 React 컴포넌트 트리 외부, JavaScript 모듈의 스코프 내에 생성된다.</li>
<li><strong>클로저를 통한 상태 캡슐화</strong>: <code>vanilla.ts</code>의 <code>createStoreImpl</code>에서 <code>state</code> 변수와 <code>listeners</code> Set이 클로저에 갇혀 외부 접근이 차단된다.</li>
<li><strong>자체 Pub/Sub 시스템</strong>: Fiber 트리 순회 대신 <code>Set&#x3C;Listener></code>를 직접 관리하여 상태 변경을 구독자에게 알린다.</li>
<li><strong><code>useSyncExternalStore</code>를 통한 React 통합</strong>: 외부 스토어의 상태 변경을 React의 렌더링 사이클에 안전하게 동기화한다.</li>
</ul>
<p>결국 Zustand가 던지는 질문은 이것이다. "상태가 꼭 React 안에 살아야 하는가?" Zustand의 답은 명확하다. 상태는 React 밖에 두고, 필요할 때 다리만 놓으면 된다는 것이다. 그 다리가 바로 <code>useSyncExternalStore</code>이다.</p>
<p>물론 이 접근 방식이 모든 상황에서 최선인 것은 아니다. SSR, 테스트 격리, 다중 인스턴스 같은 상황에서는 Provider 기반의 설계가 더 적합할 수 있다. 정답은 없지만, 각 라이브러리가 어떤 설계적 트레이드오프를 선택했는지 이해하고 있다면 상황에 맞는 도구를 고를 수 있을 것이다.</p>
<p>이 글을 읽는 분들도 한 번쯤 사용하고 있는 라이브러리의 소스 코드를 직접 열어보기를 권한다. 공식 문서에는 없는 깊이를 발견할 수 있을 것이다.</p>
<hr>
<p><img src="/content/240818/7.jpeg" alt="7.jpeg" loading="lazy" decoding="async"></p>
<h3 id="아-그리고-새로운-소식"><a class="anchor" href="#아-그리고-새로운-소식">아 그리고 새로운 소식</a></h3>
<p>위 내용을 찾아보다 알게 된 사실인데, <strong>Zustand v5.0.0이 2024년 10월에 정식 릴리스</strong>되었다.</p>
<p>흥미로운 점은 v5에 새로운 기능이 거의 없다는 것이다. v4.x에서 이미 새로운 기능들을 추가하면서 기존 API를 deprecated 처리해왔고, v5는 그 <strong>정리(cleanup) 릴리스</strong>의 성격이 강하다. 주요 변경사항은 아래와 같다. (자세한 내용은 <strong><a href="https://github.com/pmndrs/zustand/releases">릴리즈 페이지</a></strong> 와 <strong><a href="https://zustand.docs.pmnd.rs/reference/migrations/migrating-to-v5">마이그레이션 가이드</a></strong> 를 참고하기 바란다.)</p>
<ul>
<li><strong>React 18, TypeScript 4.5 이상</strong>으로 최소 요구사항이 상향되었다.</li>
<li><strong><code>getServerState</code>가 삭제</strong>되었다. (<code>useSyncExternalStore</code>의 세 번째 인자로 대체)</li>
<li><strong>ES5 지원이 중단</strong>되었다.</li>
<li><code>create</code> 함수에서 <strong>커스텀 equality 함수 지정이 제거</strong>되었다.</li>
<li>반복 가능한 객체를 지원하도록 <strong><code>shallow</code> 함수가 개선</strong>되었다.</li>
</ul>
<p>v4에서 v5로 마이그레이션할 때는 먼저 v4 최신 버전으로 업데이트하는 것이 권장된다. v4 최신 버전에서 deprecation 경고가 표시되므로, 이를 먼저 해결한 뒤 v5로 올리면 무리 없이 전환할 수 있다.</p>
<hr>
<h3 id="참고자료"><a class="anchor" href="#참고자료">참고자료</a></h3>
<ul>
<li><a href="https://github.com/pmndrs/zustand/tree/main/src">Zustand GitHub</a></li>
<li><a href="https://react.dev/reference/react/useSyncExternalStore">React useSyncExternalStore 문서</a></li>
<li><a href="https://tkdodo.eu/blog/zustand-and-react-context">Zustand and React Context - TkDodo</a></li>
<li><a href="https://jotai.org/docs/basics/comparison">Jotai Comparison 문서</a></li>
<li><a href="https://interbolt.org/blog/react-ui-tearing/">Concurrent React, External Stores, and Tearing - InterBolt</a></li>
</ul>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>React</category>
        </item>
        <item>
            <title><![CDATA[2024 인프콘 후기 with SIPE]]></title>
            <link>https://hooninedev.com/240805</link>
            <guid isPermaLink="false">https://hooninedev.com/240805</guid>
            <pubDate>Mon, 05 Aug 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[8월 2일 삼성역 코엑스에서 인프랩이 주관하는 인프콘이 열렸다. 당연히 나는 광탈했다.. 하지만 인프랩에서 SIPE 부스 홍보의 기회와 네트워킹, 스피커들의 세션에 참여할 수 있는 기회까지 제공해 주셨다. Thank you 인프랩.. 그럼 인프콘은 어떤 행사이고, SIPE는 어떤 동아리이고, 인프콘을 위해서 SIPE는 어떤 것을 준비했는지와 더 나아가 인프...]]></description>
            <content:encoded><![CDATA[<p><img src="/content/240805/26.png" alt="26.png" loading="lazy" decoding="async"></p>
<br>
<p>8월 2일 삼성역 코엑스에서 인프랩이 주관하는 <strong>인프콘</strong>이 열렸다. <del>당연히 나는 광탈했다..</del></p>
<p>하지만 인프랩에서 SIPE 부스 홍보의 기회와 네트워킹, 스피커들의 세션에 참여할 수 있는 기회까지 제공해 주셨다. <strong>Thank you 인프랩.. 🥰</strong></p>
<br>
<p><img src="/content/240805/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<br>
<p>그럼 인프콘은 어떤 행사이고, SIPE는 어떤 동아리이고, 인프콘을 위해서 SIPE는 어떤 것을 준비했는지와 더 나아가 인프콘에서 듣게 된 세션들을 소개해보겠다!!</p>
<br>
<h2 id="sipe의-우당탕-인프콘-참여기"><a class="anchor" href="#sipe의-우당탕-인프콘-참여기">SIPE의 우당탕 인프콘 참여기</a></h2>
<p>동아리 활동이 막바지로 가고 있는데, 인프랩에서 연락이 왔다! 다름 아니라 SIPE를 인프콘 커뮤니티 부스에서 홍보할 수 있도록 자리를 만들어준다는 것이었다!!!!!</p>
<p><img src="/content/240805/2.jpg" alt="2.jpg" loading="lazy" decoding="async"></p>
<blockquote>
<p>아니 그저 빛.. 우리 같은 아기 동아리에게 이런 행운이..</p>
</blockquote>
<br>
<p>급히 동아리 운영진들과 참여에 관해 회의를 진행했다. 2기 활동 마무리를 위해 바쁜 일정이었지만, 다음 기수인 3기 홍보를 위해도 좋다고 판단하여 참여하기로 했다. 그렇게 참여가 결정이 되고, 행사에 관한 내용을 전달받기 위해 인프랩에서 주관해준 커뮤니티 오프라인 행사에 참여했다.</p>
<p><img src="/content/240805/3.jpeg" alt="3.jpeg" loading="lazy" decoding="async"></p>
<blockquote>
<p>사진 출처 : <a href="https://www.linkedin.com/posts/yeoneui-hong_suauiktgo2024-suauikrvc-ittecrnoqwityy-activity-7203743971724988416-bFWM?utm_source=share&#x26;utm_medium=member_desktop">홍연의님 링크드인</a></p>
</blockquote>
<br>
<p>커뮤니티 부스에는 <strong><a href="https://www.depromeet.com/">DEPROMEET</a>, <a href="https://linktr.ee/ssafynity">SSAFYnity</a>, <a href="https://www.sopt.org/">SOPT</a>, <a href="https://www.yapp.co.kr/">YAPP</a></strong> 이 함께 참여하게 되었다. <del>너무 좋은 동아리들과 함께해서 영광!!</del></p>
<p>우리는 인프랩이 준비해준 맛있는 치킨,피자를 먹으며 다른 동아리 운영진들과 네트워킹을 하며, 인프콘에서 맡게 될 우리의 역할에 대해 전달받았다.</p>
<br>
<h3 id="인프콘에-참여한-sipe"><a class="anchor" href="#인프콘에-참여한-sipe">인프콘에 참여한 SIPE</a></h3>
<p>동아리 부스에 방문해주신 분들을 위해 굿즈와 작은 선물과 이벤트를 준비하는 동안 8월 2일이 되었다.</p>
<p>인프콘은 삼성역 코엑스에서 진행되었고, SIPE의 부스는 2층 라운지 4번째 공간을 제공받았다. 공간도 넓고, 접근성이 좋아서 많은 사람이 몰리지 않고 오실 수 있다는 생각을 했다. (아주 좋은 위치를 뽑은 SIPE 부회장님 굿 보이!)</p>
<p><img src="/content/240805/6.png" alt="6.png" loading="lazy" decoding="async"></p>
<table>
<thead>
<tr>
<th><img src="/content/240805/4.png" alt="4.png" loading="lazy" decoding="async"></th>
<th><img src="/content/240805/5.png" alt="5.png" loading="lazy" decoding="async"></th>
</tr>
</thead>
</table>
<br>
<p>우리는 귀여운 스티커들과 팔로우 이벤트를 통한 커피 이벤트를 준비했다. 많은 분이 참여해주시고 좋아해주셨다!! <del>동아리 인스타 팔로워가 2배이상 늘게었다.. 화력🔥🔥🔥</del></p>
<br>
<table>
<thead>
<tr>
<th><img src="/content/240805/7.JPG" alt="7.JPG" loading="lazy" decoding="async"></th>
<th><img src="/content/240805/8.JPG" alt="8.JPG" loading="lazy" decoding="async"></th>
</tr>
</thead>
</table>
<br>
<p>운영진 몇 분이 개인 사정 때문에 오시지 못해 일손이 부족했는데, 미진님이랑 동생분이 인프콘에 참여해서 시간 날 때마다 부스를 열심히 도와주셔서 쉽게 마무리할 수 있었다. 고마워요..! 😊🙏</p>
<br>
<h3 id="sipe-3기에-대한-qa"><a class="anchor" href="#sipe-3기에-대한-qa">SIPE 3기에 대한 Q/A</a></h3>
<p>동아리 부스에 오셔서 많은 분께서 질문을 해주셨는데, 많은 분이 또 궁금해하실 것 같아서 간단히 적어보겠다!</p>
<br>
<p><img src="/content/240805/9.png" alt="9.png" loading="lazy" decoding="async"></p>
<br>
<h4 id="q1-sipe는-어떤-동아리인가요"><a class="anchor" href="#q1-sipe는-어떤-동아리인가요">Q1. SIPE는 어떤 동아리인가요?</a></h4>
<p>다수의 IT 커뮤니티들은 주어진 기간 동안 프로젝트를 완성하고 배포하는 목표가 있다. 단기간에 팀원들과 서비스를 만들어가는 경험은 굉장히 매력적이지만, 각자가 생각하는 프로젝트의 규모, 방향성, 목표가 다를 경우 모두가 만족스러운 결과물을 얻기에는 다소 어려움이 발생하곤 한다.</p>
<p>시간이 지날수록 사이드 프로젝트의 한계를 느낌에 따라 서비스를 고도화하기 보다는 함께 하는 사람들과의 네트워킹에 더 집중하는 현상을 발견했고, 많은 사람이 프로젝트를 완성해야 한다는 부담감을 느끼는 모습을 볼 수 있었다.</p>
<p>그래서 <strong>사이드 프로젝트에 집중하는 것이 아닌 네트워킹을 중심으로 함께 성장할 수 있는 자유로운 분위기를 만들어보자는 생각 속에서, SIPE가 생겨났다.</strong></p>
<p>SIPE는 구성원들이 본인만의 성장 방식을 찾고 나아갈 수 있도록 도와주는 역할을 한다. 이를 통해 열정적으로 인사이트를 공유하고, 모두의 성장을 위해 긍정적인 영향력을 공유할 수 있는 집단을 만들게 된다.</p>
<br>
<h4 id="q2-동아리-진행-방식이-어떻게-되나요"><a class="anchor" href="#q2-동아리-진행-방식이-어떻게-되나요">Q2. 동아리 진행 방식이 어떻게 되나요?</a></h4>
<p>4달 동안 2번의 미션을 진행하며 직무과 관련된 내용을 학습하고 공유하고 있다.</p>
<p>그리고 <strong>사이프챗, 내친소(내 친구를 소개합니다)를</strong> 통해 같은 업계 종사자들을 만나 대화를 하며 인사이트를 넓히고있다. 또한 <strong>사담콘(컨퍼런스), 사이프톤(사이프 해커톤)을</strong> 통해 본인이 습득한 지식 또는 경험을 나눌 수 있다.</p>
<p>기수와 관계없이 모두가 참여할 수 있는 비공식 활동(러닝/클라이밍/풋살과 같은 운동, 주차별 회고, 매주 아티클 읽기 등) 또한 활발하게 진행되고 있다.</p>
<br>
<h4 id="q3-동아리에-가입하기-위해선-어떻게-해야-하고-어떤-사람을-원하나요"><a class="anchor" href="#q3-동아리에-가입하기-위해선-어떻게-해야-하고-어떤-사람을-원하나요">Q3. 동아리에 가입하기 위해선 어떻게 해야 하고, 어떤 사람을 원하나요?</a></h4>
<p><img src="/content/240805/10.jpg" alt="10.jpg" loading="lazy" decoding="async"></p>
<br>
<p>SIPE는 <strong>구성원들의 성장에 긍정적인 영향을 끼칠 수 있는 사람을 선호한다.</strong></p>
<p>각자 원하는 방식의 성장을 임팩트 있게 이루기 위해서는 성장 과정을 교류하는 사람이 무척 중요하다고 생각한다. 그러므로 어떤 면이든 배울 점이 있는 사람을 우선하여 선발하고 있다.</p>
<p>우리는 다양하거나 깊이 있는 경험을 쌓은 사람(동아리/커뮤니티/스터디/교육 참여 또는 운영, 사이드 프로젝트, 기술 관련 발표, 커리어 패스 등) 또는 성실하고 책임감 있는 사람 (깃허브, 블로그, 사이드 프로젝트/대외활동 기간 및 횟수)이 우리와 함께하길 원한다!!</p>
<br>
<h4 id="q4-sipe-활동중-가장-인상적인-활동"><a class="anchor" href="#q4-sipe-활동중-가장-인상적인-활동">Q4. SIPE 활동중 가장 인상적인 활동?</a></h4>
<p><strong>사담콘, 내친소</strong>와 같이 다양한 사람들과 함께하는 활동들이 인상적이었
다는 말을 많이 들었다. 아마 발표를 해보고 싶지만 기술적이고, 깊은 주제에 어려움을 느끼셔서, 자신의 경험을 시작으로 발표할 수 있는 용기를 얻게 된 것과 친구들을 데리고 와 동아리를 소개하고, 다른 친구들을 소개받는 활동이기 때문이라 생각한다.</p>
<p>그리고 <strong>각 행사의 TF를</strong> 해보며 동아리 운영을 직접 해볼 수 있다는 점도 인상적이라고 이야기를 많이 들었다!!</p>
<br>
<h4 id="q5-3기는-언제-어떻게-진행될-예정이에요"><a class="anchor" href="#q5-3기는-언제-어떻게-진행될-예정이에요">Q5. 3기는 언제, 어떻게 진행될 예정이에요?</a></h4>
<p>3기에는 사이프라는 동아리가 줬던 가치 중에서도 <strong>"기술적 영감, 네트워킹"</strong> 에 좀 더 초점을 맞추고 활동을 할 예정이라 한다. 또한 기존 동아리에서도 겪을만한 <strong>"자신의 활동 그룹(조) 외의 사람들과 네트워킹을 하기가 어려움"</strong> 이라는 문제를 해결하기 위한 프로그램 구성할 예정이니, 동일한 고민을 하고 있으면 지원해보자!</p>
<p><img src="/content/240805/11.png" alt="11.png" loading="lazy" decoding="async"></p>
<br>
<p>활동은 <strong>10월 초 ~ 중에</strong> 시작 예상하고, 일정은 2주 1회 오프라인 모임에, 8회차로, 기존 사이프 행사들을 멋지게 가다듬어 진행할 예정이니 많은 관심 부탁한다~!!</p>
<br>
<h2 id="인프콘-세션-활동"><a class="anchor" href="#인프콘-세션-활동">인프콘 세션 활동</a></h2>
<table>
<thead>
<tr>
<th><img src="/content/240805/12.jpg" alt="12.jpg" loading="lazy" decoding="async"></th>
<th><img src="/content/240805/13.jpg" alt="13.jpg" loading="lazy" decoding="async"></th>
</tr>
</thead>
</table>
<br>
<p>우리에게 스피커의 발표에 참석할 기회가 주어졌다. 그래서 나는 3가지 세션과 네트워킹 모임에 참여했다.</p>
<p>모든 세션이 경험, 기술에 관해 밸런스가 잘 맞춰져 있었고, 좋은 스피커 분들이 많이 계셔 인상적인 발표를 들을 수 있었다. 모든 세션을 듣고 싶었지만.. Vite 플러그인.. 아쉽다.... 동영상이 올라오면 좋겠다!)</p>
<br>
<table>
<thead>
<tr>
<th><img src="/content/240805/18.jpg" alt="18.jpg" loading="lazy" decoding="async"></th>
<th><img src="/content/240805/20.jpg" alt="20.jpg" loading="lazy" decoding="async"></th>
</tr>
</thead>
</table>
<br>
<h3 id="7년-동안-하나-만들었습니다-프론트엔드-개발의-결정적-순간들---이찬희님ab180"><a class="anchor" href="#7년-동안-하나-만들었습니다-프론트엔드-개발의-결정적-순간들---이찬희님ab180">7년 동안 하나 만들었습니다: 프론트엔드 개발의 결정적 순간들 - 이찬희님(AB180)</a></h3>
<p><img src="/content/240805/21.jpg" alt="21.jpg" loading="lazy" decoding="async"></p>
<p>찬희님 발표에서는 단순한 작업으로 시작한 플랫폼이 큰 고객이 들어오면서 발생한 문제들에 소개해주셨다. 그리고 문제 해결을 위한 선택들을 통해 얻은 교훈들을 이야기해주셨다.</p>
<p>서비스가 커지면서 API가 느려지고, 서버가 다운되는 현상이 발견되었다. 이런 상황일 때 백앤드 입장에서는 페이지네이션을 고려하면 되었지만 프론트는 어떻게 처리할지 난감하셨다. 결국 메타데이터가 일부만 존재하게 되니 불완전한 UI가 되어버렸다.</p>
<p>데이터 양과 변화량이 UI에 큰 영향을 끼치기때문에 데이터의 참조를 쉽게 해야 된다. 자주 쓰는 값을 기준으로 묶어서 정규화(Normalization)로 만들거나, 양도 많고, 참조도 빈번하면 Map을 사용하자. (빠른 성능과 가독성이 높아진다)</p>
<p>기술에는 편향이 있기에 신뢰할 수 없다. 어떤 기술 도입과정에서 벤치마크와 측정자료가 잘못된 것은 아니다. 하지만 마음이 급하면 편향이 된다.. 유리한 것만 POC하는 것은 안 좋다. 이럴 때 문제를 원초적으로 해결하는 방법은 문제를 뒤집어서 원천적 이유를 찾으면 다양한 해결법이 존재한다.(리팩토링의 결론은 레거시들을 가지고 유산을 만들기)</p>
<p>찬희님 발표에서 인상적인 부분은 마지막에 전달해주신 나 자신에게 하는 질문인 <strong>나만의 결정적 순간은 꿈꿔보고 생각해보자!</strong> 이었다. 올해의 2/3가 지나간 만큼 생각해보면 좋을 것 같다!</p>
<br>
<h3 id="nextjs-블로그-모범-사례-탐구-vercel-리더십-블로그-아키텍처-파헤치기---하조은님당근"><a class="anchor" href="#nextjs-블로그-모범-사례-탐구-vercel-리더십-블로그-아키텍처-파헤치기---하조은님당근">Next.js 블로그 모범 사례 탐구: Vercel 리더십 블로그 아키텍처 파헤치기 - 하조은님(당근)</a></h3>
<p><img src="/content/240805/15.jpg" alt="15.jpg" loading="lazy" decoding="async"></p>
<table>
<thead>
<tr>
<th><img src="/content/240805/16.jpg" alt="16.jpg" loading="lazy" decoding="async"></th>
<th><img src="/content/240805/17.jpg" alt="17.jpg" loading="lazy" decoding="async"></th>
</tr>
</thead>
</table>
<p>두 번째 조은님의 발표는 Next.js를 만든 Vercel의 Guillermo Rauch와 Lee Robinson의 블로그를 비교하며 Next.js의 다양한 기능을 탐구하는 세션이었다.</p>
<p>View Count 구현 방식(Guillermo: Redis, Lee: PostgreSQL), MDX를 통한 콘텐츠 데이터 관리, 그리고 ISR과 PPR 렌더링 방식에 대한 것과 Open Graph 이미지와 JSON-LD를 활용한 SEO 기법도 다루었다.</p>
<p>블로그를 처음 만들 때 Vercel의 블로그에 대해 찾아본 적 있었는데, 이런 접근을 가지고 깊이 탐구하는 자세를 배워야겠다는 다짐을 하였다</p>
<br>
<h3 id="처음으로-기술-리더가-된-개발자를-위한-안내서---박서진님토스"><a class="anchor" href="#처음으로-기술-리더가-된-개발자를-위한-안내서---박서진님토스">처음으로 기술 리더가 된 개발자를 위한 안내서 - 박서진님(토스)</a></h3>
<p><img src="/content/240805/14.jpg" alt="14.jpg" loading="lazy" decoding="async"></p>
<p>토스팀에서 리더의 역할을 하면서 리더들이 겪는 문제들을 살펴보고, "신뢰의 7가지 요소"라고 하는 키워드 중심으로, 어떻게 단계적으로 조직에서 역할에 익숙할 수 있을지 알아보는 시간을 가졌다.</p>
<p>내가 생각한 리더의 역량에는 강한 기술력과 의견제시를 통한 팀원 포용력을 생각했다. 하지만 생각보다 고려해야 할 요소가 많다는 것을 알게 되었다.</p>
<p>팀을 위해 본인이 갖춰야 될 자세와 관점에 대해, 팀원과 리더의 밸런스 등 다양한 요소들을 고려해야 한다는 것을 새롭게 알게 되었다. 지금은 주니어지만 언젠가 리더가 될 수 있기에 항상 준비하고, 배우고 의견을 제시하는 자세를 갖춰야겠다!</p>
<br>
<h3 id="인프콘의-네트워킹-파티와-라이트닝-토크"><a class="anchor" href="#인프콘의-네트워킹-파티와-라이트닝-토크">인프콘의 네트워킹 파티와 라이트닝 토크</a></h3>
<p><img src="/content/240805/22.jpg" alt="22.jpg" loading="lazy" decoding="async"></p>
<p>인프콘의 세션 중간에 라이트닝 토크가 2층 행사장에서 열렸다. 라이트닝 토크에 미리 발제를 해주신 분들을 찾아 회사에 관한 이야기, 요즘 취미와 관심사, 개발에 대한 네트워킹을 했다.</p>
<p>다른 컨퍼런스에서는 세션(강의)이 많기에 네트워킹을 할 시간이 부족하거나 없었다. 하지만 인프콘에서는 시간과 공간을 마련해주어, 다양한 사람들과 다양한 주제를 가지고 의견을 공유할 수 있다는 점이 인상적이었다.</p>
<p>네트워킹 파티에서는 직군마다 가지고 있는 고민에 대해 비슷한 관심사끼리 모여 이야기를 나눌 수 있었다. 짧은 시간이 아닌 긴 시간 동안 다양한 이야기를 할 수 있다는 점이 좋았다.</p>
<br>
<h2 id="커뮤니티-부스"><a class="anchor" href="#커뮤니티-부스">커뮤니티 부스</a></h2>
<p><img src="/content/240805/19.jpg" alt="19.jpg" loading="lazy" decoding="async"></p>
<p>요즘 IT 직군의 많은 사람이 동아리와 컨퍼런스에 참여하기를 원한다. 하지만 높은 경쟁률로 참여하는 것은 둘째로, 동아리에 대해 직접적인 질문을 하거나, 구성원과 대화해볼 수 없다는 것이 아쉬웠다.</p>
<p>인프랩에서 IT 직군의 커뮤니티 활성화를 통해 위와 같은 간극을 줄이기 위해 커뮤니티 부스를 두었다고 생각한다.</p>
<p>이를 통해 예비지원자분에게 질문을 받고 홍보를 할 수 있는 시간을 가질 수 있었다!! 내 블로그에 방문하시는 예비지원자분을 위해 SIPE를 비롯한 인프콘 커뮤니티 부스에 참여했던 동아리들을 소개해보려 한다!</p>
<blockquote>
<p>SIPE는 위에 상세하게 적어두었기에 참조하면 좋을 것 같다!</p>
</blockquote>
<br>
<h3 id="depromeet"><a class="anchor" href="#depromeet">Depromeet</a></h3>
<table>
<thead>
<tr>
<th><img src="/content/240805/27.png" alt="27.png" loading="lazy" decoding="async"></th>
<th><img src="/content/240805/28.png" alt="28.png" loading="lazy" decoding="async"></th>
</tr>
</thead>
</table>
<p><strong>디프만은 디자이너와 개발자가 만나 서비스 런칭부터 개선까지 경험하며 동반 성장하는 IT 연합 동아리다.</strong> 8년 이상 운영 기간 동안 1,000명 이상의 누적 활동 인원과, 60개 이상의 론칭 서비스를 자랑한다.</p>
<p>좋은 아이디어를 기반으로 한 서비스가 매번 출시되고, 구성원들(기획, 디자이너, 개발자)과 좋은 교류 활동으로 활동을 이어나가고 있다.</p>
<p>다른 동아리와 대외 활동을 하면서 디프만 수료하신 분들을 많이 만났는데, 하나같이 실력도 좋고 성격도 좋으셔서 나도 관심을 많이 가지고 있다!!</p>
<br>
<h3 id="sopt"><a class="anchor" href="#sopt">SOPT</a></h3>
<table>
<thead>
<tr>
<th><img src="/content/240805/29.jpeg" alt="29.jpeg" loading="lazy" decoding="async"></th>
<th><img src="/content/240805/30.jpeg" alt="30.jpeg" loading="lazy" decoding="async"></th>
</tr>
</thead>
</table>
<p><strong>SOPT(솝트)는 국내 최대 규모의 대학생연합 IT벤처창업 동아리로</strong>, IT창업 및 기획/디자인/개발에 관심있는 대학생들이 함께 모여 활동하고 있다.</p>
<p>2008년부터 지난 17년 간 3,300명이 넘는 회원이 300개가 넘는 서비스를 만들정도로 탄탄한 동아리다.</p>
<p>세미나, 스터디, 멘토링, 컨퍼런스, 해커톤, 네트워킹 등 모든 행사를 직접 기획해 진행하며 대학생들의 열정을 함께 외치며 도전하는 커뮤니티니 이런 니즈가 있으신 분들 지원해보면 좋을 것 같다!</p>
<p>간혹 SOPT 분들과 같이 운동하고, 대외활동에서 만나고 있는데 정말 열정 가득하시다!! 대학생이면 꼭 해보는 것을 추천한다!! <del>나도 하고싶다.. 진짜 하고싶다.. 몇년만 어렸어도 흑흑.. 😢</del></p>
<br>
<h3 id="ssafynity"><a class="anchor" href="#ssafynity">SSAFYnity</a></h3>
<table>
<thead>
<tr>
<th><img src="/content/240805/31.png" alt="31.png" loading="lazy" decoding="async"></th>
<th><img src="/content/240805/32.png" alt="32.png" loading="lazy" decoding="async"></th>
</tr>
</thead>
</table>
<p><strong>SSAFYnity는 삼성청년SW아카데미(SSAFY) 동문회다.</strong></p>
<p>SSAFYnity는 분기별로 싸피인의밤, SSAFYnale 기술컨퍼런스, 가을 체육대회, SSAFYnity 세미나 등 다양한 행사를 진행하고 있다. 그뿐만 아니라 다양한 동아리 활동, MT 등 커뮤니티 활동도 진행하고 있다.</p>
<p>저번 유닛에서 주관하는 해커톤에 SSAFY를 진행하고 있는 동생과 해커톤을 즐겁게 한 기억이 있다.</p>
<br>
<h3 id="yapp"><a class="anchor" href="#yapp">YAPP</a></h3>
<table>
<thead>
<tr>
<th><img src="/content/240805/33.jpeg" alt="33.jpeg" loading="lazy" decoding="async"></th>
<th><img src="/content/240805/34.jpeg" alt="34.jpeg" loading="lazy" decoding="async"></th>
<th><img src="/content/240805/35.jpeg" alt="35.jpeg" loading="lazy" decoding="async"></th>
</tr>
</thead>
</table>
<p>YAPP은 14년간의 역사를 자랑하는 400명 이상의 IT 인재 커뮤니티로, <strong>"작은 아이디어로 세상에 변화를 이끈다"는</strong> 목표 아래 도전의 기회와 성장의 환경을 제공하며, 프로덕트 기획부터 창업 지원까지 다양한 활동을 통해 차세대 IT 리더와 혁신가를 양성하는 커뮤니티다.</p>
<p>매번 멋진 서비스를 만들어내는 YAPP도 관심이 많다!! 유니톤에서 인연을 맺은 준수님께서 YAPP에서 음악 관련 서비스를 만들고 있던데 정말 멋지다!! YAPP에서 만든 여비도 사용해보았는데 정말 잘만들었으니 꼭꼭 사용해보길!!</p>
<p><img src="/content/240805/36.png" alt="36.png" loading="lazy" decoding="async"></p>
<p>~~계획한 일 잘 정리하고 지원해볼 예정이다! ~~</p>
<br>
<h2 id="마무리"><a class="anchor" href="#마무리">마무리</a></h2>
<table>
<thead>
<tr>
<th><img src="/content/240805/37.jpg" alt="37.jpg" loading="lazy" decoding="async"></th>
<th><img src="/content/240805/25.jpg" alt="25.jpg" loading="lazy" decoding="async"></th>
</tr>
</thead>
</table>
<p>이렇게 인프콘이 마무리되었다! 아주 힘든 시간이었다! 감사한 분들 덕분에, 좋은 경험을 할 수 있었다.</p>
<p>먼저 인프콘에 참가할 기회를 주신 인프랩 다시 한번 감사드립니다. SIPE 2기의 인프콘 참여를 위해 후원해주신 분께도 너무 감사드립니다.</p>
<p>이렇게 SIPE 2기도 마무리되었다. 행사에 참여하지 못했지만, 진행을 위해 고생 많이 한 <strong>성민님, 민준님 정말 감사합니다!!!</strong> 그리고 당일에 힘 많이 쓴 <strong>진욱님, 신원님, 지원님 고생 많으셨고 감사합니다!!</strong> 놀러 와서 일하게 된 <strong>미진님, 미진님 동생분도 정말 감사드립니다!!!!</strong></p>
<p>행사를 위해 많은 조언과 지원을 해주신 <strong>유닛 감사합니다!</strong></p>
<p><img src="/content/240805/23.gif" alt="23.gif" loading="lazy" decoding="async"></p>
<br>
<p>좋은 분들 덕분에 많은 경험을 하고, 좋은 세션과 네트워킹 때문에 많은 것을 배운 하루였다. 동아리에서 작은 행사만 해보아서, 어느 정도로 인프랩 직원분들과 봉사하시는 분들께서 힘들셨을 지 감이 오지 않지만, 다시 한번 고생 많으셨고 감사하단 말씀 드리고 싶다.</p>
<p>인프랩의 오픈 세션에서 <strong>이형주님, 이동욱님, 홍연의님</strong>의 발표에서 말씀하신 것처럼 매년 성장을 위해 노력하시는 것이 정말 잘 보이는 행사였다. 인프랩의 앞으로가 정말 기대된다.</p>
<br>
<p><img src="/content/240805/24.jpg" alt="24.jpg" loading="lazy" decoding="async"></p>
<br>
<p>지금까지 인프콘에서 800개의 부채를 나눠준 부채왕이었다! Thank you~!!</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>대외활동</category>
        </item>
        <item>
            <title><![CDATA[프론트엔드 개발자의 역할과 미래]]></title>
            <link>https://hooninedev.com/240731</link>
            <guid isPermaLink="false">https://hooninedev.com/240731</guid>
            <pubDate>Wed, 31 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[이번 포스팅에서는 프론트엔드 개발자의 역할과 미래에 대한 이야기를 해보려고 한다. 개발을 하다 보면 어느 순간 익숙한 것들에 편안해지는 자신을 발견하게 된다. 요구사항이 들어오면 늘 하던 대로 컴포넌트를 쪼개고, 상태를 관리하고, API를 연결한다. 그러다 문득 이런 생각이 든다. "프론트엔드 개발자로서 내가 하고 있는, 그리고 할 수 있는 일의 범위는 어...]]></description>
            <content:encoded><![CDATA[<p>이번 포스팅에서는 프론트엔드 개발자의 역할과 미래에 대한 이야기를 해보려고 한다.</p>
<p>개발을 하다 보면 어느 순간 익숙한 것들에 편안해지는 자신을 발견하게 된다. 요구사항이 들어오면 늘 하던 대로 컴포넌트를 쪼개고, 상태를 관리하고, API를 연결한다. 그러다 문득 이런 생각이 든다. "프론트엔드 개발자로서 내가 하고 있는, 그리고 할 수 있는 일의 범위는 어디까지일까?"</p>
<p>경력이 쌓일수록 고민은 사라지지 않고 오히려 더 깊어진다. 몇 년 전에 그려놓은 목표의 방향대로 잘 가고 있는 건지, AI가 코드를 대신 작성해주는 시대에 프론트엔드 개발자는 어디에 서 있어야 하는 건지. 코드를 잘 짜는 것은 당연히 중요하고 기본이다. 하지만 그것만이 전부라고 말할 수 있을까?</p>
<p>이번 글에서는 이런 고민들을 정리하면서, 프론트엔드 개발자에게 필요한 역량은 무엇이고 앞으로의 변화 속에서 어디에 집중해야 하는지에 대해 필자의 생각을 풀어보려 한다.</p>
<br>
<h2 id="프론트엔드-개발자-그래서-뭘-하는-사람인가"><a class="anchor" href="#프론트엔드-개발자-그래서-뭘-하는-사람인가">프론트엔드 개발자, 그래서 뭘 하는 사람인가?</a></h2>
<p>흔히 프론트엔드 개발자를 "화면을 만드는 사람"이라고 말한다. 틀린 말은 아니지만, 이것은 마치 건축가를 "벽돌 쌓는 사람"이라고 부르는 것과 비슷하다. 물론 벽돌도 쌓지만, 그게 본질은 아닌 것이다.</p>
<p>프론트엔드 개발자는 디지털 경험의 최전선에 서 있는 사람이다. 사용자가 서비스를 처음 마주하는 순간부터 떠나는 순간까지, 그 모든 접점을 설계하고 구현하는 역할을 맡고 있다. 버튼 하나의 위치, 로딩 스피너의 타이밍, 에러 메시지의 톤까지 — 이 모든 것이 사용자가 서비스를 어떻게 인식하는지를 결정한다.</p>
<p>다만 "최전선"이라는 비유가 프론트엔드 개발자만 중요하다는 뜻은 아니다. 백엔드, 인프라, 디자인, 기획 등 모든 직군의 노력이 결국 프론트엔드라는 창구를 통해 사용자에게 전달된다는 의미에 가깝다. (사실 최전선이라기보단 최종 배달부에 더 가까울 수도 있겠다.)</p>
<p>그렇다면 이 "최전선"에서 프론트엔드 개발자는 구체적으로 어떤 역량을 갖춰야 할까?</p>
<br>
<h2 id="기술적-역량-코드를-잘-짜는-것만으로-충분할까"><a class="anchor" href="#기술적-역량-코드를-잘-짜는-것만으로-충분할까">기술적 역량: 코드를 잘 짜는 것만으로 충분할까?</a></h2>
<p>당연하게도 기술적 역량은 프론트엔드 개발자의 기반이다. HTML, CSS, JavaScript라는 웹의 삼위일체를 깊이 이해하고, React나 Vue 같은 프레임워크를 능숙하게 다루는 것은 기본 중의 기본이다.</p>
<p>하지만 여기서 멈추면 안 된다. 프론트엔드 개발자가 신경 써야 할 기술적 영역은 생각보다 넓다.</p>
<h3 id="성능-최적화"><a class="anchor" href="#성능-최적화">성능 최적화</a></h3>
<p>사용자는 느린 서비스를 참지 않는다. Google의 Core Web Vitals(LCP, INP, CLS)는 이제 검색 순위에도 영향을 미친다. LCP(Largest Contentful Paint)는 가장 큰 콘텐츠가 화면에 그려지는 시간, INP(Interaction to Next Paint)는 사용자 인터랙션에 대한 응답 속도, CLS(Cumulative Layout Shift)는 레이아웃이 얼마나 안정적인지를 측정하는 지표이다.</p>
<p>코드 스플리팅을 통해 초기 번들 크기를 줄이고, 레이지 로딩으로 뷰포트 밖의 리소스 로딩을 지연시키며, 이미지 최적화를 통해 로딩 속도를 개선하는 것. 이런 작업들이 프론트엔드 개발자가 사용자 경험을 직접적으로 개선할 수 있는 영역이다.</p>
<h3 id="접근성과-seo"><a class="anchor" href="#접근성과-seo">접근성과 SEO</a></h3>
<p>접근성(Accessibility, a11y)은 종종 "시간이 남으면 하는 것"으로 취급되곤 한다. 하지만 스크린 리더 사용자, 키보드만으로 네비게이션하는 사용자, 색각 이상이 있는 사용자 — 이들도 우리 서비스의 사용자이다. 한국에서는 「장애인차별금지법」 제21조에 의거하여 웹 접근성 준수가 법적 의무이며, 이를 위한 기준으로 WCAG(Web Content Accessibility Guidelines)를 기반으로 만들어진 KWCAG(한국형 웹 콘텐츠 접근성 지침)가 사용된다. 법적 의무를 떠나서도, 접근성은 더 넓은 사용자층에게 서비스를 열어두는 비즈니스 가치이기도 하다.</p>
<p>SEO(Search Engine Optimization) 역시 마찬가지이다. SSR(Server Side Rendering)이나 SSG(Static Site Generation) 같은 렌더링 전략의 선택, 시맨틱 HTML의 사용, 메타데이터 관리 — 이 모든 것이 검색 엔진에서 서비스가 얼마나 잘 노출되는지를 결정한다. 프론트엔드 아키텍처의 결정이 곧 비즈니스 성과에 직결되는 것이다.</p>
<h3 id="추상화-잘못된-추상화보다-중복이-낫다"><a class="anchor" href="#추상화-잘못된-추상화보다-중복이-낫다">추상화: 잘못된 추상화보다 중복이 낫다</a></h3>
<p>프론트엔드 코드베이스가 커지면 자연스럽게 "이 로직을 공통으로 뽑아야 하지 않을까?"라는 유혹이 찾아온다. 추상화 자체는 좋은 것이다. 반복을 줄이고, 변경 지점을 한 곳에 모으며, 코드의 의도를 더 명확하게 드러낼 수 있다.</p>
<p>하지만 Sandi Metz는 *"The Wrong Abstraction"*이라는 글에서 이렇게 경고했다. <strong>"잘못된 추상화보다 중복이 훨씬 싸다(duplication is far cheaper than the wrong abstraction)."</strong> 그녀가 묘사한 패턴은 이렇다. 개발자 A가 중복 코드를 발견하고 공통 함수로 추출한다. 시간이 지나 개발자 B가 "거의 비슷하지만 약간 다른" 요구사항을 만나고, 기존 추상화를 수정하여 파라미터와 조건 분기를 추가한다. 이 과정이 반복되면 원래의 깔끔한 추상화는 온갖 조건문으로 뒤덮인 괴물이 된다.</p>
<p>프론트엔드에서 이 문제가 특히 빈번하게 나타나는 곳이 <strong>공통 컴포넌트</strong>이다. <code>&#x3C;Button></code> 하나에 <code>variant</code>, <code>size</code>, <code>icon</code>, <code>loading</code>, <code>disabled</code>, <code>as</code> 같은 props가 20개씩 달려 있다면, 그것은 추상화가 아니라 모든 경우의 수를 떠안은 조건문 덩어리이다.</p>
<p>좋은 추상화를 위한 실용적인 기준은 이런 것들이다.</p>
<ul>
<li><strong>세 번째 중복</strong>이 나타나기 전까지는 추출을 참는다. 두 곳의 코드가 비슷해 보여도, 실제로는 서로 다른 방향으로 진화할 수 있다.</li>
<li>추상화에 <strong>조건 분기가 늘어나기 시작하면</strong> 의심한다. 파라미터가 세 개를 넘기면, 그 추상화는 하나의 개념이 아니라 여러 개념을 억지로 합쳐놓은 것일 수 있다.</li>
<li>잘못된 추상화를 발견하면 <strong>되돌릴 용기</strong>를 갖는다. 공통 코드를 다시 각 호출처로 인라인하고, 불필요한 분기를 제거한 뒤, 진짜 공통점이 드러날 때 다시 추상화한다.</li>
</ul>
<h3 id="관심사의-분리와-소프트웨어-아키텍처"><a class="anchor" href="#관심사의-분리와-소프트웨어-아키텍처">관심사의 분리와 소프트웨어 아키텍처</a></h3>
<p>관심사의 분리(Separation of Concerns)는 소프트웨어 공학의 근본 원칙 중 하나이다. 시스템을 서로 겹치지 않는 영역으로 나누어, 각 영역이 하나의 관심사만 책임지도록 하는 것이다.</p>
<p>프론트엔드에서 이 원칙이 구체적으로 적용되는 방식을 살펴보자.</p>
<p><strong>레이어 기반 분리</strong> — 애플리케이션을 수평적인 계층으로 나눈다. 프레젠테이션 레이어(UI 컴포넌트), 비즈니스 로직 레이어(도메인 규칙, 유효성 검증), 데이터 액세스 레이어(API 호출, 캐싱)로 분리하면 각 계층을 독립적으로 개발하고 테스트할 수 있다. 예를 들어 사용자 목록을 보여주는 컴포넌트가 직접 <code>fetch</code>를 호출하고 있다면, 데이터를 가져오는 책임과 보여주는 책임이 한 곳에 뒤섞여 있는 것이다. 데이터 페칭을 커스텀 훅이나 서비스 레이어로 분리하면, 컴포넌트는 "무엇을 보여줄 것인가"에만 집중할 수 있다.</p>
<p><strong>기능 기반 분리</strong> — 계층이 아닌 기능(feature) 단위로 코드를 나눈다. 최근 주목받고 있는 <strong>Feature-Sliced Design(FSD)</strong> 이 이 접근의 대표적인 예시이다. <code>features/auth</code>, <code>features/payment</code>, <code>features/profile</code>처럼 비즈니스 도메인별로 코드를 응집시키면, 관련 없는 기능의 변경이 서로 영향을 미치지 않는다.</p>
<p><strong>합성(Composition) 우선 설계</strong> — React 공식 문서에서도 "수천 개의 컴포넌트에서 상속 계층 구조를 추천할 만한 사용 사례를 찾지 못했다"고 밝힌 바 있다. 프론트엔드에서는 상속 대신 <strong>합성</strong>이 관심사 분리의 핵심 수단이다. 작고 단일한 책임을 가진 컴포넌트를 조합하여 복잡한 UI를 구성하면, 각 컴포넌트는 자신의 관심사에만 집중할 수 있다. 거대한 <code>if-else</code> 분기로 가득한 하나의 만능 컴포넌트 대신, 역할이 명확한 작은 컴포넌트들의 조합이 결국 더 유지보수하기 쉬운 코드가 된다.</p>
<h3 id="선언적-코드-vs-명령적-코드"><a class="anchor" href="#선언적-코드-vs-명령적-코드">선언적 코드 vs 명령적 코드</a></h3>
<p>프론트엔드 개발에서 선언적(Declarative)이냐 명령적(Imperative)이냐의 구분은 단순한 스타일 차이가 아니라, <strong>코드의 유지보수성과 확장성을 좌우하는 설계 철학</strong>이다.</p>
<p>명령적 코드는 <strong>"어떻게(How)"</strong> 에 집중한다. DOM을 직접 조작하고, 상태 변화에 따라 어떤 엘리먼트를 추가하고 제거할지를 단계별로 지시한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 명령적: 토글 버튼을 직접 구현</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> btn</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> document.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">querySelector</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'.toggle'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">btn.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addEventListener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'click'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (btn.classList.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">contains</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'active'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    btn.classList.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">remove</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'active'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    btn.textContent </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> '비활성'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    btn.classList.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">add</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'active'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    btn.textContent </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> '활성'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span></code></pre></figure>
<p>선언적 코드는 <strong>"무엇을(What)"</strong> 에 집중한다. 현재 상태가 주어졌을 때 UI가 어떤 모습이어야 하는지를 기술한다. "어떻게 그 상태에 도달하는가"는 프레임워크에 위임한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 선언적: 상태에 따른 UI를 선언</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">isActive</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setIsActive</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">false</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    className</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{isActive </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'active'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> :</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> ''</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> setIsActive</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">prev</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> !</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">prev)}</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  ></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    {isActive </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> '활성'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> :</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> '비활성'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span></code></pre></figure>
<p>React가 선언적 패러다임을 채택한 이유는 명확하다. 명령적 코드에서는 상태가 변할 때마다 개발자가 <strong>UI의 어느 부분을 어떻게 업데이트해야 하는지를 직접 추적</strong>해야 한다. 상태가 5개이고 각 상태의 조합에 따라 UI가 달라진다면, 관리해야 할 전환(transition) 경로가 기하급수적으로 늘어난다. 반면 선언적 코드에서는 "이 상태일 때 UI는 이렇게 생겼다"만 정의하면 된다. 상태 → UI의 매핑이 명시적이므로, 코드를 읽는 사람이 현재 상태만 보면 화면의 모습을 예측할 수 있다.</p>
<p>이 철학은 컴포넌트 수준을 넘어 아키텍처 전반으로 확장된다. <strong>선언적 라우팅</strong>(React Router의 <code>&#x3C;Route path="/users" element={&#x3C;Users />} /></code>), <strong>선언적 데이터 페칭</strong>(TanStack Query의 <code>useQuery({ queryKey: ['users'], queryFn: fetchUsers })</code>), <strong>선언적 애니메이션</strong>(Framer Motion의 <code>&#x3C;motion.div animate={{ opacity: 1 }} /></code>) 등. 이들의 공통점은 "원하는 결과를 선언하면, 도달 과정은 라이브러리가 알아서 처리한다"는 것이다.</p>
<p>물론 선언적 코드가 항상 정답은 아니다. <code>useRef</code>를 통한 DOM 직접 접근, <code>useImperativeHandle</code>을 통한 명령적 API 노출, Canvas/WebGL 렌더링처럼 명령적 접근이 불가피한 영역도 있다. 중요한 것은 <strong>기본적으로 선언적으로 사고하되, 명령적 접근이 필요한 경계를 인식하는 것</strong>이다.</p>
<h3 id="테스트-전략"><a class="anchor" href="#테스트-전략">테스트 전략</a></h3>
<p>"잘 돌아가는 것 같으니까 괜찮겠지"는 개인 프로젝트에서나 통하는 말이다. (물론 개인 프로젝트에서도 통하면 안 된다.) 단위 테스트로 개별 함수와 컴포넌트의 동작을 보장하고, 통합 테스트로 모듈 간의 상호작용을 검증하며, E2E(End to End) 테스트로 사용자 시나리오 전체를 확인하는 것. 이러한 테스트 전략을 수립하고 실행하는 것도 프론트엔드 개발자의 중요한 역할이다.</p>
<p>앞서 이야기한 관심사의 분리와 선언적 코드는 테스트 용이성과도 직결된다. 비즈니스 로직이 UI 컴포넌트에 뒤섞여 있으면 테스트하기 어렵지만, 커스텀 훅이나 순수 함수로 분리되어 있으면 단위 테스트가 간결해진다. 선언적 코드는 "이 입력이 주어지면 이 출력이 나온다"는 예측 가능한 구조이므로, 테스트 케이스를 작성하기도 훨씬 수월하다.</p>
<p>기술적 역량은 분명 중요하다. 그런데 흥미로운 이야기가 하나 있다. Go Make Things의 Chris Ferdinandi는 "좋은 프론트엔드 개발자에게 기술 스킬은 가장 덜 중요한 것(The least important skills for a front-end developer to have are technical ones)"이라고 말했다. 그가 기술보다 더 중요하다고 꼽은 것은 다섯 가지였다. 문제 해결 능력, 검색(Googling) 능력, 비판적 사고(Critical Thinking), 공감 능력(Empathy), 그리고 커뮤니케이션이다.</p>
<p>그렇다면 기술을 넘어서, 프론트엔드 개발자에게 필요한 서비스적 역량이란 무엇일까?</p>
<br>
<h2 id="서비스적-역량-사용자-경험을-책임지는-사람"><a class="anchor" href="#서비스적-역량-사용자-경험을-책임지는-사람">서비스적 역량: 사용자 경험을 책임지는 사람</a></h2>
<p>토스의 프론트엔드 챕터는 "좋은 제품을 만들기 위해 필요한 역량"의 첫 번째로 "사용자 경험을 중요시하는 마인드셋"을 꼽았다. 개발자 관점이 아닌 사용자 시각에서 서비스를 바라보는 능력 말이다.</p>
<h3 id="제품-오너십"><a class="anchor" href="#제품-오너십">제품 오너십</a></h3>
<p>프론트엔드 개발자가 단순히 기획서에 적힌 스펙을 구현하는 사람이라면, 그건 사실 누구나 할 수 있는 일이다. (조금 극단적으로 말하면 그렇다는 것이다.) 진짜 가치는 제품의 맥락을 이해하고, 스스로 문제를 정의하며, 때로는 기획에 없는 개선점을 제안하는 데서 나온다.</p>
<p>"이 버튼을 여기에 배치하면 사용자가 다음 액션을 찾기 어려울 것 같다", "이 로딩 상태에서 스켈레톤 UI를 넣으면 체감 속도가 개선될 것이다" — 이런 판단을 내릴 수 있는 것이 제품 오너십이다.</p>
<h3 id="인터랙션의-디테일"><a class="anchor" href="#인터랙션의-디테일">인터랙션의 디테일</a></h3>
<p>서비스가 "살아있다"고 느끼게 만드는 것은 거창한 기능이 아니라 사소한 디테일에서 나온다. 버튼을 눌렀을 때의 미세한 피드백, 페이지 전환 시의 부드러운 애니메이션, 에러가 발생했을 때의 친절한 안내 메시지. 이런 디테일 하나하나가 사용자의 신뢰를 쌓는다.</p>
<p>프론트엔드 개발자는 이 디테일을 구현할 수 있는 유일한 위치에 있다. 디자이너가 의도한 경험을 코드로 정확하게 번역하고, 때로는 디자인 시안에 담기지 않은 예외 상황(로딩, 에러, 빈 상태 등)까지 고려하는 것. 이것이 프론트엔드 개발자만이 할 수 있는 서비스적 기여이다.</p>
<p>여기까지 읽으면 자연스럽게 떠오르는 질문이 하나 있다. 프론트엔드 개발자가 이 모든 것을 혼자 해낼 수 있을까?</p>
<br>
<h2 id="ai-발전에-따라-프론트엔드-개발자-역할의-변화"><a class="anchor" href="#ai-발전에-따라-프론트엔드-개발자-역할의-변화">AI 발전에 따라 프론트엔드 개발자 역할의 변화</a></h2>
<p>AI 도구의 발전은 프론트엔드 개발자의 역할에 근본적인 변화를 가져오고 있다. 단순한 코드 자동완성을 넘어, 다단계 작업을 자율적으로 수행하는 <strong>에이전트형 AI(Agentic AI)</strong> 가 등장하면서 개발 워크플로우 자체가 달라지고 있다.</p>
<h3 id="코드-작성자에서-오케스트레이터로"><a class="anchor" href="#코드-작성자에서-오케스트레이터로">코드 작성자에서 오케스트레이터로</a></h3>
<p>Anthropic이 2026년 3월에 발표한 *"2026 Agentic Coding Trends Report"*에 따르면, 개발자들은 업무의 약 <strong>60%에서 AI를 활용</strong>하고 있다고 응답했다. Claude Code의 2026년 1분기 세션 중 <strong>78%가 다중 파일 편집</strong>을 포함하고 있으며, 이는 2025년 1분기의 34%에서 두 배 이상 증가한 수치이다. 평균 세션 시간도 4분에서 23분으로 늘어났다.</p>
<p>하지만 동시에, <strong>완전히 AI에게 위임할 수 있는 작업은 전체의 0~20%에 불과</strong>하다는 조사 결과도 함께 나왔다. 이것이 시사하는 바는 분명하다. AI가 코드 작성의 상당 부분을 대신할 수 있게 되었지만, <strong>인간의 판단과 감독은 여전히 핵심</strong>이라는 것이다.</p>
<p>모든 코드를 직접 손으로 작성하던 시대에서, AI가 생성한 코드를 검증하고 최적화하며 전체 시스템을 조율하는 <strong>"오케스트레이터"</strong> 역할로 비중이 옮겨가고 있는 것이다. 보고서에서도 엔지니어링 역할이 <strong>에이전트 감독(agent supervision), 시스템 설계(system design), 결과 검증(output review)</strong> 쪽으로 이동하고 있다고 분석했다.</p>
<h3 id="그렇다면-프론트엔드-개발자는-어디에-집중해야-할까"><a class="anchor" href="#그렇다면-프론트엔드-개발자는-어디에-집중해야-할까">그렇다면 프론트엔드 개발자는 어디에 집중해야 할까?</a></h3>
<p>AI가 가장 잘하는 것은 <strong>패턴이 명확하고 반복적인 작업</strong>이다. 보일러플레이트 코드 작성, CRUD UI 생성, 타입 정의, 간단한 리팩토링 같은 작업은 AI가 빠르고 정확하게 처리한다.</p>
<p>반면 AI가 아직 어려워하는 것은 <strong>맥락에 대한 판단</strong>이다.</p>
<ul>
<li>이 컴포넌트의 추상화 수준이 적절한지 (앞서 다룬 "잘못된 추상화" 문제)</li>
<li>이 인터랙션이 사용자에게 자연스럽게 느껴지는지 (서비스적 역량)</li>
<li>이 아키텍처 결정이 6개월 후의 팀에게 어떤 영향을 미치는지 (소프트웨어 설계)</li>
<li>이 기획이 기술적으로 실현 가능한지, 더 나은 대안은 없는지 (협업 역량)</li>
</ul>
<p>Code With Seb은 *"Frontend Developer 2026"*이라는 글에서 이를 명확히 정리했다. <strong>"시스템 설계, 상태 관리, 접근성, 테스트라는 기본기는 여전히 토대이지만, 이제 최고의 개발자를 구분 짓는 것은 그 기본기를 AI 코파일럿, 에이전트 워크플로우와 어떻게 결합하느냐이다."</strong></p>
<h3 id="ai-시대에-더-중요해지는-것들"><a class="anchor" href="#ai-시대에-더-중요해지는-것들">AI 시대에 더 중요해지는 것들</a></h3>
<p>역설적이지만, AI가 코드 작성의 비중을 줄여줄수록 이 글에서 다룬 역량들의 중요성이 더 커진다.</p>
<ul>
<li><strong>추상화 판단력</strong> — AI가 생성한 코드가 올바른 추상화 수준을 가지고 있는지 평가하는 능력. AI는 요청받은 코드를 작성하지만, 그 코드가 "세 번째 중복을 뽑아야 할 시점인지"를 판단하지는 못한다.</li>
<li><strong>아키텍처 설계</strong> — 기능 단위의 코드는 AI가 작성할 수 있어도, 전체 시스템의 관심사를 어떻게 분리하고 레이어를 어떻게 구성할지는 여전히 인간의 영역이다.</li>
<li><strong>사용자 경험 감각</strong> — "이 로딩 상태에서 스켈레톤 UI를 넣을지, 스피너를 넣을지, 아무것도 보여주지 않을지"는 기술적 구현의 문제가 아니라 서비스적 판단의 문제이다.</li>
<li><strong>제품 맥락 이해</strong> — Anthropic의 보고서에서도 AI가 만들어낸 작업의 약 <strong>27%가 "원래라면 하지 않았을 작업"</strong> 이라고 밝혔다. AI가 생산성을 높여주는 것은 분명하지만, "이 작업이 정말 필요한 것인가"를 판단하는 것은 제품 맥락을 이해하는 인간의 몫이다.</li>
</ul>
<p>필자는 이렇게 생각한다. AI가 "어떻게(How)"의 상당 부분을 대신해줄수록, 프론트엔드 개발자의 가치는 <strong>"무엇을(What)"과 "왜(Why)"</strong> 를 판단하는 데서 나올 것이다. 어떤 문제를 풀어야 하는지, 왜 이 방식이 사용자에게 더 나은지, 왜 이 아키텍처가 팀의 미래에 적합한지. 이것이야말로 기계가 대체하기 어려운, 프론트엔드 개발자 고유의 가치가 아닐까.</p>
<br>
<h2 id="마치며"><a class="anchor" href="#마치며">마치며</a></h2>
<p>CSS-Tricks에서 Philip Walton이 쓴 글에 이런 문장이 있다. "좋은 개발자를 정말 좋은 개발자로 구분 짓는 것은 아는 것이 아니라 생각하는 방식이다(What separates the good people from the really good people isn't what they know; it's how they think)." 필자도 이 말에 깊이 공감한다.</p>
<p>프론트엔드 개발자의 역할은 코드를 잘 짜는 것에서 출발하지만, 거기서 끝나지 않는다. 사용자의 눈으로 서비스를 바라보고, 다양한 직군의 교차점에서 소통하며, 기술적 결정이 비즈니스에 미치는 영향을 이해하는 것. 이 모든 것이 프론트엔드 개발자의 역할이다.</p>
<p>정답은 없다. 회사마다, 팀마다, 그리고 개인마다 프론트엔드 개발자에게 기대하는 역할은 다를 수 있다. 하지만 "화면을 만드는 사람"이라는 틀에 스스로를 가두지 않고, 역할의 범위를 조금씩 넓혀가는 것. 그 과정에서 이 글이 작은 실마리가 되었기를 바란다.</p>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
        </item>
        <item>
            <title><![CDATA[SIPE 2기 운영진 회고]]></title>
            <link>https://hooninedev.com/240717</link>
            <guid isPermaLink="false">https://hooninedev.com/240717</guid>
            <pubDate>Wed, 17 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[불과 1, 2년 전까지만 해도 상상하지 못했던 일들이 나에게 많이 일어났다. 개발자로 취업하게 되고, 이직을 하고, IT 동아리에 참여해 사람들과 교류하고, 심지어 IT 동아리 운영진까지 해보았다. 누군가에게는 별일 아닐 수 있겠지만, 얼마 전까지 카페에서 일하고, 경영학과로 취업하기 위해 독서실에서 자격증 공부를 하고 있던 나에게는 큰 변화였다. 이번 활...]]></description>
            <content:encoded><![CDATA[<p>불과 1, 2년 전까지만 해도 상상하지 못했던 일들이 나에게 많이 일어났다. 개발자로 취업하게 되고, 이직을 하고, IT 동아리에 참여해 사람들과 교류하고, 심지어 IT 동아리 운영진까지 해보았다.</p>
<br>
<p><img src="/content/240717/23.png" alt="23.png" loading="lazy" decoding="async"></p>
<br>
<p>누군가에게는 별일 아닐 수 있겠지만, 얼마 전까지 카페에서 일하고, 경영학과로 취업하기 위해 독서실에서 자격증 공부를 하고 있던 나에게는 큰 변화였다.</p>
<p>이번 활동으로 좋은 사람들을 많이 만나고, 운영진으로 활동하며 훌륭한 동료들과 함께 많은 것을 배웠다. 이런 경험이 SIPE 3기 신청을 원하는 분들에게 도움이 될 것 같아, SIPE 2기 활동은 어떻게 했고 어떤 행사를 했는지 회고 형식으로 풀어보려고 한다.</p>
<br>
<h3 id="sipe-2기-준비"><a class="anchor" href="#sipe-2기-준비">SIPE 2기 준비</a></h3>
<br>
<p><img src="/content/240717/16.jpeg" alt="16.jpeg" loading="lazy" decoding="async"></p>
<br>
<p>SIPE 2기를 위해 6명의 운영진들과 함께 우당탕탕 준비를 시작했다.</p>
<p>일정 계획을 잡고, 장소를 대관하고, 행사에 필요한 기획안을 작성했다. 그리고 어떤 분들과 2기를 함께하고 싶은지 의견을 모으고, 모집 폼과 홈페이지를 만들어 게시했다.</p>
<p>준비를 마치고 우리들끼리 깊은 이야기도 나눠볼 겸, 서류 모집 마지막 날에 주문진으로 달려가 맛있는 것도 먹고 대화를 나누었다. 이를 통해 모두가 SIPE를 위해 많은 준비를 했고, 진심을 다하고 있다는 것을 느낄 수 있었다.</p>
<br>
<table>
<thead>
<tr>
<th><img src="/content/240717/17.jpeg" alt="17.jpeg" loading="lazy" decoding="async"></th>
<th><img src="/content/240717/18.jpeg" alt="18.jpeg" loading="lazy" decoding="async"></th>
</tr>
</thead>
</table>
<br>
<p><img src="/content/240717/19.jpeg" alt="19.jpeg" loading="lazy" decoding="async"></p>
<p>아직 이름이 널리 알려지지 않았음에도 불구하고, 정말 많은 분들이 동아리에 함께하기 위해 지원해주셔서 감동했다... 부족한 점도 있었지만, 여러 과정을 거쳐 함께 4개월 동안 활동할 인원들을 선정했다. 인원 제한이 있어 더 많은 분들과 함께하지 못한 것이 너무 아쉬울 뿐이다.</p>
<p>SIPE는 총 8회차로 세션이 진행된다. 격주로 이루어져 있어 16주(4개월)의 시간 동안 2개의 미션을 진행해야 했는데, 어떤 행사들을 했는지 천천히 살펴보자!</p>
<br>
<h3 id="orientation"><a class="anchor" href="#orientation">Orientation</a></h3>
<p><img src="/content/240717/2.jpg" alt="2.jpg" loading="lazy" decoding="async"></p>
<br>
<p>SIPE 2기의 시작을 알리는 OT(오리엔테이션)를 진행했다. 이 자리에서 서로를 소개하고 이야기를 나누며, 앞으로 SIPE 활동이 어떻게 이루어질지 설명하고 친해지는 시간을 가졌다.</p>
<p>OT 전에 서로에 대한 소개를 GitHub에 미리 올렸는데, 덕분에 어색함이 좀 줄어든 것 같다! <del>번거롭지만 아주 좋은 아이디어 👍</del> 이날 기존 기수들의 적극적인 리드로 정말 많이 웃고 재미있는 시간을 보냈다. 밤늦게까지 뒤풀이가 이어졌는데, 참가자들의 열정이 가장 뜨거웠던 날이었다! 🔥</p>
<br>
<h3 id="사이프챗"><a class="anchor" href="#사이프챗">사이프챗</a></h3>
<p><img src="/content/240717/3.JPG" alt="3.JPG" loading="lazy" decoding="async"></p>
<br>
<p>사이프챗을 진행하기 전에 슬랙을 이용해 1차 미션 주제에 대한 투표를 받았다. SIPE 1기 못지않게 이색적이고 재미있는 주제가 많이 나왔다. 이번에는 AI 관련한 미션도 있고, Rust와 같은 언어들도 있고 흥미로웠다.</p>
<p>여러 주제 중에서 무엇을 선택할지 고민했는데, 1기에서 진행한 미션을 더 발전시키고 싶어 <code>React hook + Closure</code>라는 주제를 선택했다.</p>
<br>
<p><img src="/content/240717/4.JPG" alt="4.JPG" loading="lazy" decoding="async"></p>
<br>
<p>사이프챗에서는 선택한 주제를 가지고 팀원들과 함께 4~5주 동안 어떻게 진행할지 이야기를 나누었다. 토론 끝에 'You Don't Know JS Yet'라는 책을 선택해서 공부하고, 관련 아티클을 읽어 인사이트를 공유하기로 계획했다.</p>
<br>
<h3 id="전반기-사이데이션"><a class="anchor" href="#전반기-사이데이션">전반기 사이데이션</a></h3>
<p><img src="/content/240717/6.jpg" alt="6.jpg" loading="lazy" decoding="async"></p>
<br>
<p>회사를 다니면서 팀원들과 온라인으로 미션을 진행했다. 하지만 미션만 할 수는 없기에 아이데이션도 하고 팀원들과 색다른 활동을 하며 대화를 나누기 위해 '사이데이션'을 기획했다.</p>
<p>우리 팀은 사이데이션에서 어떤 활동을 할까 고민했고, 결국 을지로에서 나만의 테라피 오일 만들기와 치킨 먹기를 하기로 했다! 테라피 오일을 만들면서 서로의 고민을 공유하고, 미션에 대한 이야기도 나누었다. 그러다 을지로의 한 치킨집에서 무려 6시간 동안이나 수다를 떨며 재미있는 이야기를 나누었다!</p>
<br>
<h3 id="사담콘"><a class="anchor" href="#사담콘">사담콘</a></h3>
<p><img src="/content/240717/7.JPG" alt="7.JPG" loading="lazy" decoding="async"></p>
<br>
<p>SIPE 1기에서 가장 인기가 많았던 행사인 사담콘을 이번에는 광화문 시민회관에서 진행했다. 사이퍼들뿐만 아니라 외부 인원까지 신청을 받았는데, 예상보다 모집 인원이 빠르게 차서 감동받았다.</p>
<p>사담콘과 같은 큰 행사는 사이퍼 내부에서 TF팀을 뽑아 함께 주관했다. 멋진 분들이 다양한 주제로 발표를 해주셨고, 중간에 재미있는 게임도 진행해 주셔서 긴 시간 동안 지루하지 않게 행사에 참여할 수 있었다.</p>
<br>
<p><img src="/content/240717/8.JPG" alt="8.JPG" loading="lazy" decoding="async"></p>
<br>
<p>아직 개발자로서 발표를 해본 적이 없는데, 3기에 참여하게 된다면 나의 첫 동아리에서 첫 발표를 해보고 싶다.. 이건 올해의 버킷리스트로 남기고 꼭꼭 실천할 것이다!! 👏</p>
<br>
<h3 id="1차-미션-발표"><a class="anchor" href="#1차-미션-발표">1차 미션 발표</a></h3>
<p><img src="/content/240717/9.JPG" alt="9.JPG" loading="lazy" decoding="async"></p>
<br>
<p>짧으면 짧고 길면 긴 5주의 4주의 시간이 지나서 그 동안 공부한 결과물을 다른 사이퍼에게 공유하기 위해 다같이 모였다.</p>
<p>자세한 글은 이미 작성한 적이 있어서 <strong><a href="https://hooninedev.com/240509/">여기를</a></strong> 확인해주면 좋을 것 같다!</p>
<p>다른 분들의 발표를 들으며 다양한 주제에 재미있게 접근하고, 새로운 것에 도전하는 모습을 보았다. 이런 모습들이 정말 멋지다고 생각한다.. 🙌</p>
<br>
<h3 id="사이프톤"><a class="anchor" href="#사이프톤">사이프톤</a></h3>
<p><img src="/content/240717/10.JPG" alt="10.JPG" loading="lazy" decoding="async"></p>
<br>
<p>사이프톤은 내가 SIPE 운영진을 하면서 기획한 행사이다. 동아리 인원들이 자기의 주제를 발제하고 팀을 꾸려 짧은 시간동안 작은 플랫폼을 만들어내며 대화를 했으면 하는 마음에 기획하였다.</p>
<p>직장인들에게 너무 가혹한 해커톤을 조금이라도 재미있게 해결하고자, 맛있는 간식과 재미있는 이벤트를 통한 상품을 준비했다. <del>짧은 진행 계획도..</del></p>
<br>
<p><img src="/content/240717/11.JPG" alt="11.JPG" loading="lazy" decoding="async"></p>
<br>
<p>처음에는 아주 걱정이 많았다. 하지만 멋진 TF와 운영진들 덕분에 재미있게 행사를 진행할 수 있었고, 사이퍼들과 외부 디자이너들의 열정에 깊은 감동을 받고 <strong>나도 열심히 살아야겠다</strong>는 생각을 불태웠다!! 🔥🔥</p>
<br>
<h3 id="후반기-사이데이션"><a class="anchor" href="#후반기-사이데이션">후반기 사이데이션</a></h3>
<p><img src="/content/240717/12.jpg" alt="12.jpg" loading="lazy" decoding="async"></p>
<br>
<p>후반기 사이데이션은 2차 미션을 함께할 팀원들과, 어떤 미션을 어떻게 진행할지 오프라인 환경에서 이야기하기위해 기획했다.</p>
<br>
<p><img src="/content/240717/13.jpg" alt="13.jpg" loading="lazy" decoding="async"></p>
<br>
<p>프론트엔드 개발자로 근무하며 모듈, 배포환경 등 다양한 것들을 핸들링하다보니 공부가 불가피하다고 생각이 느껴져 2차 미션에는 사람들과 DevOps에 관해서 공부했다. 도커, CI/CD 환경 등 다양한 관점에 대해 질의응답하고 미션에 관련된 내용을 매주 공유했다.</p>
<br>
<h3 id="내친소"><a class="anchor" href="#내친소">내친소</a></h3>
<p><img src="/content/240717/14.jpg" alt="14.jpg" loading="lazy" decoding="async"></p>
<br>
<p>SIPE 행사의 최고의 꽃 내친소다. 대부분의 사람들이 제일 기대하고 TF 모집도 가장 적극적으로 이루어졌다. 열정있고 관심있으신 분들이 많이 행사에 참여해주시고 기여해주셔서 다양한 사람들과 인사이트를 공유할 수 있는 행사였다.</p>
<p>SIPE는 개발자들만 있다. 그렇기에 마케터, PM, 디자이너 등 다양한 직군의 사람들과 교류할시간이 없는데, 내친소로 보완하고있다. 사람들과 진로, 삶, 대인관계, 취미 등 다양한 관점으로 이야기를 맛있는 간식과 함께 나눌 수 있는 행사다.</p>
<p>Maybe.. SIPE 3기에도 진행 될 예정이니 꼭꼭!! 참여해보시는 것을 추천드린다!</p>
<br>
<h3 id="2차-미션-발표"><a class="anchor" href="#2차-미션-발표">2차 미션 발표</a></h3>
<p><img src="/content/240717/15.JPG" alt="15.JPG" loading="lazy" decoding="async"></p>
<br>
<p>SIPE의 2차 미션에 대해 발표하고, 장장 4개월동안 진행된 행사에 대해 사람들과 이야기해보는 시간을 가졌다.</p>
<p>하나의 주제를 4개월동안 진행한 분들도 계시고, 2개의 미션에 다른팀에 들어가서 서브 미션까지 진행하시는 분들도 있으셨다. 회사다니고 미션을 하며 사이드를 하시는 분들, 매일 운동을 하시고 바프를 찍으려하시는 분들 등등.. 멋진 분들과 함께한 좋은 시간이었다. 🥰</p>
<br>
<h3 id="the-end"><a class="anchor" href="#the-end">The End...</a></h3>
<p>이렇게 SIPE 2기 활동이 끝났다. 아직 운영진 여행, 사이프 2기 MT, 인프콘, 3기 인수인계 등등... 많은 것들이 남아있지만, 앞으로 일정이 더 바빠질 것을 염두해 미리 작성한다.</p>
<br>
<p><img src="/content/240717/20.jpeg" alt="20.jpeg" loading="lazy" decoding="async"></p>
<br>
<p>먼저 SIPE 2기에 함께해준 분들에게 부족하지만 적극적으로 참여해주셔서 다시한번 감사하다는 말씀을 드리고싶다. 그리고 동아리를 운영하며 도움을 주셧던 분들에게도 다시한번 감사하다는 말씀을 전하고싶다.</p>
<p>사회생활 속에서 동아리 운영은 처음이라 많이 부족한 것을 알고있다. 하지만 좋은 사람들 덕분에 혼자서는 못할 것을 함께여서 진행할 수 있었고, 이렇게 잘 마무리할 수 있었다.</p>
<p>SIPE를 하면서 많은 것이 변했다. 만나는 사람들, 나의 관점, 기술적인 지식 등등..!! 개발자라는 직업을 선택하고 나서 결정한 선택중 가장 뿌듯하고 잘했다고 생각한다.</p>
<p>좋은 분들에게 질좋은 이야기를 듣고, 같이 외국 여행도 다녀오고, 이직을 위해 도움을 주시고, 힘들 때 함께 할 수 <strong>"사람"</strong> 을 얻었다. SIPE는 앞으로 지속될 것인데, 나와 같은 니즈가 있는 분들이 많이 신청해서 좋은 영향을 많이 받아가셨으면 좋겠다.</p>
<br>
<p><img src="/content/240717/22.png" alt="22.png" loading="lazy" decoding="async"></p>
<br>
<p><strong>SIPE는 인프콘에 참여한다!</strong> 인프콘에서 SIPE와 저에게 궁금한 것에 대해 여쭤보셔도 좋다!! 물론, 댓글로도 다양한 이야기를 해주면 좋겠다!!</p>
<br>
<p><img src="/content/240717/21.jpeg" alt="21.jpeg" loading="lazy" decoding="async"></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>일상</category>
            <category>대외활동</category>
        </item>
        <item>
            <title><![CDATA[Import (정적) vs Dynamic Import (동적)]]></title>
            <link>https://hooninedev.com/240711</link>
            <guid isPermaLink="false">https://hooninedev.com/240711</guid>
            <pubDate>Thu, 11 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[React와 JavaScript를 사용하는 개발자에게 있어서 import와 dynamic import는 각각 고유한 용도와 특징을 가지고 있다. 그럼 이 두가지는 어떤 차이점이 있을까? Import import는 JavaScript ES6에서 도입된 정적(Static) 모듈 가져오기 방식이다. 코드 작성 시, 모듈을 상단에 명시적으로 선언하고, 모듈을 로드...]]></description>
            <content:encoded><![CDATA[<p><img src="/content/240711/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<br>
<p>React와 JavaScript를 사용하는 개발자에게 있어서 import와 dynamic import는 각각 고유한 용도와 특징을 가지고 있다. 그럼 이 두가지는 어떤 차이점이 있을까?</p>
<br>
<h3 id="import"><a class="anchor" href="#import">Import</a></h3>
<p>import는 JavaScript ES6에서 도입된 정적(Static) 모듈 가져오기 방식이다. 코드 작성 시, 모듈을 상단에 명시적으로 선언하고, 모듈을 로드한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> React </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "react"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> MyComponent </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "./MyComponent"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span></code></pre></figure>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> React, { useState } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "react"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> MyComponent </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "./MyComponent"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> App</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">MyComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> /></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> default</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> App;</span></span></code></pre></figure>
<p>Import는 아래 3가지의 특징을 가진다.</p>
<ul>
<li><strong>정적 로드</strong>: 컴파일 시점에 모든 의존성이 결정되고 로드되어, 빌드 도구가 전체 모듈 그래프를 미리 알 수 있게 해준다.</li>
<li><strong>호이스팅(Hoisting)</strong>: import 문은 파일의 최상단으로 끌어올려진다.</li>
<li><strong>트리 쉐이킹(Tree Shaking)</strong>: 불필요한 코드(사용되지 않는 모듈)를 제거하여 최종 번들 크기를 줄일 수 있다.</li>
</ul>
<br>
<h3 id="dynamic-import"><a class="anchor" href="#dynamic-import">Dynamic Import</a></h3>
<p>dynamic import는 ES2020에서 도입된 동적(Dynamic) 모듈 가져오기 방식이다. 이는 함수처럼 호출하여 필요할 때 모듈을 가져오는 방식이다</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> loadMyComponent</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> async</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">default</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">MyComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"./MyComponent"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre></figure>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> React, { useState, useEffect } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "react"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> App</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">MyComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setMyComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> loadComponent</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> async</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">default</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">loadedComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"./MyComponent"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setMyComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> loadedComponent);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    loadComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, []);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>{MyComponent </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">MyComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> /> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>Loading...&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>}&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> default</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> App;</span></span></code></pre></figure>
<p>Dynamic Import는 아래 3가지의 특징을 가진다.</p>
<ul>
<li><strong>동적 로드</strong>: 모듈이 필요할 때 런타임에 로드되어, 특정 상황(ex : 사용자 인터랙션)에 따라 모듈을 로드할 수 있다.</li>
<li><strong>비동기 처리</strong>: import()는 프로미스를 반환하므로 async/await와 함께 사용하거나 .then을 사용하여 처리할 수 있다.</li>
<li><strong>코드 스플리팅(Code Splitting)</strong>: 초기 로딩 시간을 줄이기 위해 필요한 모듈을 나중에 로드하는 방식으로, 애플리케이션의 성능을 향상시킬 수 있다.</li>
</ul>
<br>
<h3 id="결론"><a class="anchor" href="#결론">결론</a></h3>
<p><strong>정적 import는</strong> 빌드 시 모든 의존성을 미리 로드하고, 파일 크기를 최적화하는 특징을 가지고 있고, <strong>동적 import는</strong> 런타임에 모듈을 비동기로 로드하여 초기 로딩 시간을 줄이고, 필요할 때만 로드할 수 있다.</p>
<p>이 두 가지 방법을 적절히 사용하면 애플리케이션의 성능과 유지보수성을 향상시킬 수 있다!</p>
<p>사내에서는 Lazy Loading을 위해서 Dynamic Import를 적극 활용하고있다. 이로써 렌더링 시 불필요한 모듈을 로드하지 않기때문에 초기 로딩 시간이 감소한다. 또한 애플리케이션의 코드를 여러 번들로 나누어 필요한 시점에 로드하기 때문에 불필요한 리소스 로드 방지도 되었다.<strong>(코드 스플리팅)</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>소박한궁금증</category>
        </item>
        <item>
            <title><![CDATA[VITE의 HMR에 대해서]]></title>
            <link>https://hooninedev.com/240710</link>
            <guid isPermaLink="false">https://hooninedev.com/240710</guid>
            <pubDate>Wed, 10 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[HMR(Hot Module Replacement)이란? Hot Module Replacement(HMR)는 개발 환경에서 코드 변경 시 브라우저 전체를 새로고침하지 않고도 변경된 모듈만 업데이트하는 기능을 의미한다. 이는 특히 React와 같은 현대 프론트엔드 프레임워크에서 매우 유용하게 사용되고있다. HMR은 Webpack과 같은 모듈 번들러에서 지원하며...]]></description>
            <content:encoded><![CDATA[<p><img src="/content/240710/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<h2 id="hmrhot-module-replacement이란"><a class="anchor" href="#hmrhot-module-replacement이란">HMR(Hot Module Replacement)이란?</a></h2>
<p><strong>Hot Module Replacement(HMR)는</strong> 개발 환경에서 코드 변경 시 브라우저 전체를 새로고침하지 않고도 변경된 모듈만 업데이트하는 기능을 의미한다. 이는 특히 React와 같은 현대 프론트엔드 프레임워크에서 매우 유용하게 사용되고있다. HMR은 Webpack과 같은 모듈 번들러에서 지원하며, 개발 과정에서 생산성을 크게 향상시킨다. 수정사항을 웹에서 바로 확인할 수 있어, 불필요한 시간 소비를 방지할 수 있다.</p>
<br>
<h3 id="그럼-hmr은-어떻게-동작할까"><a class="anchor" href="#그럼-hmr은-어떻게-동작할까">그럼 HMR은 어떻게 동작할까?</a></h3>
<p>HMR이 편하게 해주는 것은 알겠다! 거의 2년여동안 HMR을 자연스럽게 사용하고 있는데, 궁금한적 없어 이참에 알아보자!</p>
<p><img src="/content/240710/2.jpeg" alt="2.jpeg" loading="lazy" decoding="async"></p>
<p>우선 개발자가 코드를 수정하고 저장하면, <strong>Webpack Dev Server는</strong> 변경 사항을 감지한다. <strong>Webpack Dev Server는</strong> 변경된 모듈만을 포함하는 업데이트를 생성하고 WebSocket을 토해 브라우저에 업데이트 정보를 전송한다. 브라우저는 해당 모듈을 교체하고 애플리케이션 상태를 유지한 채로 변경된 내용을 반영한다.</p>
<br>
<p><img src="/content/240710/3.jpeg" alt="3.jpeg" loading="lazy" decoding="async"></p>
<br>
<p>놀랍게도 끝이다. 엄청 간단하다!! 결국 Webpack과 같은 모듈 번들러를 사용하는 것이다. 공식 문서를 조금 더 살펴보니 <strong>devServer.hot</strong>의 값을 가지고 HMR을 핸들링 할 수 있다.</p>
<br>
<p><img src="/content/240710/4.png" alt="4.png" loading="lazy" decoding="async"></p>
<br>
<blockquote>
<p>Webpack-dev-server v4부터 HMR이 기본적으로 활성화되어 있습니다.</p>
<p>HMR 활성화에 필요한 webpack.HotModuleReplacementPlugin을 자동으로 적용합니다.</p>
<p>따라서 설정에서 hot이 true로 설정되거나 CLI 옵션 --hot을 통해 이 플러그인을 webpack.config.js에 추가할 필요가 없습니다. 자세한 내용은 HMR 개념 페이지를 참고하세요.</p>
</blockquote>
<br>
<h3 id="그럼-사용하면-뭐가-좋은데"><a class="anchor" href="#그럼-사용하면-뭐가-좋은데">그럼 사용하면 뭐가 좋은데?</a></h3>
<p>HMR의 장점은 애플리케이션의 상태(state)를 유지하는 것이다. 개발자가 애플리케이션을 새로고침하지 않고도 변경 사항을 즉시 확인할 수 있다. 예를 들어, 사용자가 입력한 폼 데이터나 페이지 내의 특정 위치가 그대로 유지가 된다.</p>
<p>피드백이 상당히 빠르다. 그래서 개발자는 수정한 내용이 어떻게 동작하는지 빠르게 확인할 수 있다. 이는 특히 UI를 다루는 작업에서 유용하다.</p>
<p>생산성 향상되어, 불필요한 대기 시간을 준다. 예를 들어, 검색 로직을 수정하는 경우, 전체 페이지를 새로고침하고 다시 검색어를 입력할 필요 없이, 변경된 필터 조건이 즉시 반영된다.</p>
<p>일반적인 경우, 검색 로직을 변경하고 그 결과를 검증하기 위해 다음 단계를 거쳐야한다.</p>
<p><strong>코드 수정 => 저장 => 브라우저 새로고침 => 검색어 입력 => 검색 버튼 클릭 => API 호출 => 데이터 표시</strong></p>
<p>하지만 HMR을 사용하면 4개의 단계가 사라진다.</p>
<p><strong>코드 수정 => 저장 => 데이터 표시</strong></p>
<br>
<p><img src="/content/240710/5.webp" alt="5.webp" loading="lazy" decoding="async"></p>
<br>
<h2 id="추가적으로-궁금한-것"><a class="anchor" href="#추가적으로-궁금한-것">추가적으로 궁금한 것</a></h2>
<p>흠.. 생각해보면 CRA에서는 Webpack을 사용하기 때문에 HMR을 사용하는데, VITE에서는 HMR을 어떻게 쓰고 차이가 있을까?? 한번 알아보자!!</p>
<br>
<p><img src="/content/240710/6.jpeg" alt="6.jpeg" loading="lazy" decoding="async"></p>
<br>
<h3 id="vite에서의-hmr"><a class="anchor" href="#vite에서의-hmr">VITE에서의 HMR</a></h3>
<p>Vite는 WebSocket 연결을 통해 브라우저와 지속적으로 통신하는데, 이 연결은 개발 서버가 변경 사항을 브라우저에 실시간으로 전달하는 데 사용된다.</p>
<p>그 이후 Vite는 <strong>fs.watch (파일 시스템 감시)</strong> 기능을 사용하여 프로젝트 파일의 변경을 실시간으로 탐지한다. 파일이 변경되면 Vite는 해당 파일의 종속성을 분석하여 어느 모듈이 영향을 받는지 파악한다.</p>
<p>변경된 파일에 대한 분석이 완료되면, Vite는 변경된 부분만을 포함하는 HMR 업데이트를 생성한다. 이 업데이트는 변경된 모듈의 새로운 버전을 포함한다.</p>
<p>WebSocket을 통해 브라우저에 HMR 업데이트 메시지를 전송한다. 이 메시지에는 변경된 모듈과 이를 어떻게 교체할지에 대한 정보가 포함되어있다.</p>
<p>브라우저는 Vite 개발 서버로부터 HMR 메시지를 수신하고, 해당 모듈을 교체한다. React와 같은 프레임워크는 HMR을 통해 상태를 유지하면서도 변경된 컴포넌트를 다시 렌더링할 수 있게 도와준다.</p>
<p>Webpack과 가장 다른 점은 네이티브 ES 모듈을 활용한 것이다.</p>
<br>
<h2 id="참고자료"><a class="anchor" href="#참고자료">참고자료</a></h2>
<ul>
<li><a href="!https://velog.io/@woohm402/vite-react-hmr">Vite 프로젝트에서 리엑트 컴포넌트는 어떻게 HMR될까?</a></li>
<li><a href="https://webpack.kr/guides/hot-module-replacement/#enabling-hmr">Webpack-HMR</a></li>
<li><a href="https://ko.vitejs.dev/guide/api-hmr.html#hmr-api">Vite-HMR</a></li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>소박한궁금증</category>
            <category>프론트엔드</category>
        </item>
        <item>
            <title><![CDATA[zip, gzip, zstd, bzip2, xz — 압축 알고리즘에 대해 알아보자]]></title>
            <link>https://hooninedev.com/240706</link>
            <guid isPermaLink="false">https://hooninedev.com/240706</guid>
            <pubDate>Sat, 06 Jul 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[이번 포스팅에서는 소프트웨어의 압축 알고리즘에 대한 이야기를 해보려고 한다. 사내 프로젝트의 배포 프로세스를 개선하는 업무를 맡게 되었다. 높은 용량의 빌드 결과물을 S3에 업로드해야 하는 구조였는데, 빌드 폴더의 용량이 곧 업로드 시간과 스토리지 비용에 직결된다는 것을 체감하게 되었다. 자연스럽게 "어떻게 하면 효율적으로 압축해서 올릴 수 있을까?"라는 ...]]></description>
            <content:encoded><![CDATA[<p>이번 포스팅에서는 소프트웨어의 압축 알고리즘에 대한 이야기를 해보려고 한다.</p>
<p>사내 프로젝트의 배포 프로세스를 개선하는 업무를 맡게 되었다. 높은 용량의 빌드 결과물을 S3에 업로드해야 하는 구조였는데, 빌드 폴더의 용량이 곧 업로드 시간과 스토리지 비용에 직결된다는 것을 체감하게 되었다. 자연스럽게 "어떻게 하면 효율적으로 압축해서 올릴 수 있을까?"라는 고민이 시작되었다.</p>
<p>그런데 막상 압축에 대해 찾아보니, zip, gzip, zstd, bzip2, xz 등 종류가 생각보다 많았다. 이름은 비슷비슷한데 각각 뭐가 다른 건지, 어떤 상황에서 어떤 걸 써야 하는지 정리된 자료를 찾기가 쉽지 않았다. (압축이라고 하면 다 같은 거 아니었나 싶었는데, 세상은 넓고 압축 방법은 많았다.)</p>
<p>그래서 이번 기회에 각 압축 형식의 원리와 특성을 비교하고, 필자가 왜 특정 방식을 선택했는지 정리해 보려고 한다.</p>
<hr>
<h2 id="무손실-압축이란"><a class="anchor" href="#무손실-압축이란">무손실 압축이란?</a></h2>
<p>무손실 압축(Lossless Compression)은 원본 데이터를 완벽하게 복원할 수 있는 압축 방식이다. 이미지나 오디오에서 사용하는 손실 압축(Lossy Compression)과 달리, 압축을 풀었을 때 원본과 1비트도 다르지 않다. 소스 코드나 빌드 결과물처럼 데이터 무결성이 중요한 경우에 반드시 무손실 압축을 사용해야 한다.</p>
<p>무손실 압축의 핵심 아이디어는 <strong>데이터에 존재하는 통계적 중복성을 활용하는 것</strong>이다. 반복되는 패턴을 짧은 표현으로 대체하면 전체 크기가 줄어드는 원리인 것이다.</p>
<p>이 중에서 <strong>사전 기반(Dictionary-Based) 방식</strong>이 무손실 압축에서 가장 널리 사용되는 알고리즘 계열이다. (여기서 "사전"은 국어사전의 그 사전이 아니라, 이전에 등장한 데이터 조각들을 짧은 코드와 매핑해 저장해 둔 참조 테이블을 뜻한다.) 1977년 Abraham Lempel과 Jacob Ziv가 발표한 논문 <em>"A Universal Algorithm for Sequential Data Compression"</em>(IEEE Transactions on Information Theory)에서 처음 제안된 <strong>LZ77</strong>, 그리고 이듬해인 1978년에 발표된 <strong>LZ78</strong>이 이 계열의 시조이다. 두 사람의 이름에서 알파벳 한 글자씩 따와 "LZ"가 된 것인데, 이 두 알고리즘은 이후 등장한 거의 모든 사전 기반 압축 알고리즘(DEFLATE, LZMA, LZ4, Zstd 등)의 뿌리가 되었다. (압축 알고리즘 족보의 대부분이 이 두 사람으로 수렴한다고 봐도 과언이 아니다.)</p>
<p>쉽게 설명하면 이런 것이다. "Linux"라는 단어가 텍스트에서 100번 반복된다면, 처음 등장할 때 사전에 등록해 두고 이후에는 "사전의 1번 항목"이라는 짧은 참조(포인터)로 대체하는 방식이다. "Linux"는 5바이트지만, 포인터는 이보다 훨씬 적은 바이트로 표현할 수 있으므로 전체 크기가 줄어든다.</p>
<p>그렇다면 LZ77과 LZ78은 구체적으로 어떻게 다를까?</p>
<hr>
<h3 id="lz77-슬라이딩-윈도우sliding-window-방식"><a class="anchor" href="#lz77-슬라이딩-윈도우sliding-window-방식">LZ77: 슬라이딩 윈도우(Sliding Window) 방식</a></h3>
<p>LZ77은 <strong>명시적인 사전을 만들지 않고</strong>, 입력 스트림의 일정 구간을 그 자체로 사전처럼 사용한다. 이 구간을 <strong>슬라이딩 윈도우</strong>라고 부르는데, 데이터를 처리하는 동안 윈도우가 한 칸씩 앞으로 이동한다는 뜻에서 붙은 이름이다. (알고리즘 문제 풀 때 많이보았다)</p>
<p>윈도우는 두 영역으로 나뉜다.</p>
<ul>
<li><strong>검색 버퍼(Search Buffer)</strong> : 이미 처리된, 직전까지의 데이터. 사전 역할을 한다.</li>
<li><strong>룩어헤드 버퍼(Look-ahead Buffer)</strong> : 아직 처리되지 않은, 앞으로 압축할 데이터.</li>
</ul>
<p>알고리즘은 룩어헤드 버퍼의 첫 부분이 검색 버퍼 어딘가에 등장한 적이 있는지를 찾는다. 같은 패턴이 발견되면, 그 매치를 <strong>(거리, 길이, 다음 문자)</strong> 형태의 튜플로 인코딩한다. "거리"는 검색 버퍼 내에서 매치가 시작되는 위치까지 몇 글자 뒤로 가야 하는지를, "길이"는 매치가 몇 글자나 이어지는지를 의미한다.</p>
<p>예를 들어 <code>"banana_banana"</code> 라는 문자열을 LZ77로 압축한다고 해보자. 두 번째 <code>"banana"</code>를 만난 시점에서 알고리즘은 이렇게 말하는 셈이다. <em>"7글자 뒤로 가서 6글자를 그대로 복사해라."</em> 그러면 6바이트짜리 문자열이 단 두 개의 숫자로 표현된다.</p>
<p>이 방식의 핵심은 <strong>사전을 별도로 저장하거나 전송할 필요가 없다는 점</strong>이다. 디코더는 압축을 풀어 나가면서 자연스럽게 검색 버퍼를 재구성하기 때문에, 사전은 데이터 그 자체에 암묵적으로 내장되어 있다고 볼 수 있다. 다만 그 대가로, 압축 해제는 항상 데이터의 처음부터 순차적으로 진행해야 한다. 중간부터 임의로 풀어내는 것은 원리적으로 불가능하다.</p>
<p>윈도우 크기는 압축률과 직접적인 트레이드오프 관계에 있다. 윈도우가 크면 더 멀리 떨어진 패턴까지 참조할 수 있어 압축률이 높아지지만, 매치 검색에 드는 연산량과 메모리 사용량이 함께 늘어난다.</p>
<hr>
<h3 id="lz78-명시적-사전explicit-dictionary-방식"><a class="anchor" href="#lz78-명시적-사전explicit-dictionary-방식">LZ78: 명시적 사전(Explicit Dictionary) 방식</a></h3>
<p>LZ78은 LZ77과 달리, <strong>사전을 명시적으로 구축</strong>하면서 압축을 진행한다. 슬라이딩 윈도우 같은 것은 없다. 대신 이전에 본 패턴들을 인덱스가 붙은 사전 항목으로 저장해 두고, 이후에 같은 패턴이 등장하면 인덱스로 대체한다.</p>
<p>LZ78이 출력하는 단위는 <strong>(사전 인덱스, 다음 문자)</strong> 형태의 태그이다. 인코더는 사전에서 가장 길게 일치하는 항목을 찾은 뒤, 그 항목의 인덱스와 매치를 깨뜨린 다음 문자를 묶어서 출력한다. 그리고 *"방금 매치된 항목 + 새로운 문자"*를 다시 사전에 새 항목으로 추가한다. 이렇게 사전이 점진적으로 자라나는 구조이다.</p>
<p>LZ78의 가장 유명한 변종이 바로 <strong>LZW</strong>(Lempel-Ziv-Welch)이다. 1984년 Terry Welch가 LZ78을 개선하여 발표한 것으로, GIF 이미지 포맷과 유닉스의 <code>compress</code> 유틸리티(<code>.Z</code> 확장자)에서 사용되었다. (한때 LZW는 특허 분쟁의 중심에 서기도 했는데, 이 사건이 PNG 포맷이 탄생하는 계기 중 하나가 되었다.)</p>
<hr>
<h3 id="그래서-현대-압축-알고리즘은-어느-쪽-후손인가"><a class="anchor" href="#그래서-현대-압축-알고리즘은-어느-쪽-후손인가">그래서 현대 압축 알고리즘은 어느 쪽 후손인가?</a></h3>
<p>흥미롭게도, 오늘날 우리가 사용하는 거의 모든 주류 압축 알고리즘은 <strong>LZ77 계열의 후손</strong>이다.</p>
<p>1982년 Storer와 Szymanski가 발표한 <strong>LZSS</strong>는 LZ77을 개선한 변종으로, 1비트 플래그를 도입해 매 출력이 "리터럴(원본 문자)"인지 "길이-거리 쌍"인지를 구분했다. 매치가 너무 짧아서 오히려 손해일 때는 그냥 원본 문자를 그대로 출력하도록 한 것이다.</p>
<p>1993년 Phil Katz는 이 LZSS에 <strong>허프만 부호화</strong>(빈도가 높은 심볼에 짧은 비트를 할당하는 엔트로피 코딩)를 결합해 <strong>DEFLATE</strong>를 만들었다. ZIP, GZIP, PNG가 모두 이 DEFLATE를 사용한다. 즉, 우리가 매일같이 만지는 <code>.zip</code>, <code>.gz</code>, <code>.png</code> 파일은 모두 LZ77의 직계 후손인 셈이다.</p>
<p>이후에 등장한 <strong>LZMA</strong>(7-Zip, XZ), <strong>LZ4</strong>, <strong>Zstd</strong> 또한 모두 LZ77의 슬라이딩 윈도우 아이디어를 출발점으로 삼아, 매치 검색 자료구조와 엔트로피 코딩 방식을 진화시켜 온 것이다. 반면 LZ78 계열은 LZW를 끝으로 주류 무대에서 사실상 물러났다고 봐도 무방하다.</p>
<p>이론적으로 두 알고리즘은 <em>"전체 데이터를 압축 해제할 경우"</em> 동등한 능력을 가진다는 것이 증명되어 있다. 그럼에도 LZ77이 살아남은 이유는, <strong>사전을 데이터에 내장한다는 설계가 구현과 확장 양면에서 더 유연했기 때문</strong>이다. 슬라이딩 윈도우의 크기, 매치 검색 알고리즘, 뒤에 붙일 엔트로피 코더 등을 자유롭게 조합할 수 있어서, 시대의 요구에 맞춰 계속 진화할 여지가 있었던 것이다.</p>
<p>압축 성능을 평가할 때는 두 가지 축을 고려해야 한다. <strong>압축률</strong>(얼마나 작아지는가)과 <strong>압축 속도</strong>(얼마나 빨리 끝나는가)이다. 이론적으로 더 높은 압축률을 추구하면 더 많은 연산이 필요하기 때문에 압축 시간이 길어진다. 이 둘 사이의 절충점을 찾는 것이 현실적인 압축 전략의 핵심이다.</p>
<p>그럼 차근차근 여러 가지 압축 형식들을 비교해 보자.</p>
<hr>
<h2 id="zip"><a class="anchor" href="#zip">ZIP</a></h2>
<p>ZIP은 1989년 Phil Katz가 만든 파일 형식으로, 내부적으로 <strong>DEFLATE 알고리즘</strong>(LZ77 + Huffman coding의 조합)을 사용하여 압축한다. 여기서 중요한 점은, ZIP은 "압축 알고리즘"이 아니라 "파일 형식(format)"이라는 것이다. ZIP이라는 컨테이너 안에 DEFLATE라는 알고리즘이 들어가 있는 구조이다.</p>
<p>ZIP의 특징은 <strong>각 파일을 개별적으로 압축</strong>한다는 점이다. 이를 비솔리드 아카이브(Non-solid Archive)라고 부르는데, 덕분에 압축 파일 내의 특정 파일 하나만 꺼내는 것이 가능하다. 반면 파일 간의 중복 데이터를 활용하지 못하기 때문에, 뒤에서 설명할 tar.gz 방식에 비해 압축률이 낮을 수 있다.</p>
<p>Windows, macOS, Linux 등 대부분의 운영체제에서 별도의 소프트웨어 없이 기본 지원되므로, 크로스 플랫폼 호환성이 중요한 경우에 가장 무난한 선택이다.</p>
<hr>
<h2 id="gzip-gnu-zip"><a class="anchor" href="#gzip-gnu-zip">GZIP (GNU Zip)</a></h2>
<p>GZIP은 ZIP과 마찬가지로 내부적으로 <strong>DEFLATE 알고리즘</strong>을 사용한다. 같은 알고리즘인데 왜 별도의 형식이 존재할까? ZIP이 여러 파일을 하나의 아카이브에 담는 "컨테이너" 역할까지 수행하는 반면, GZIP은 <strong>단일 파일(또는 단일 스트림)의 압축에 특화</strong>된 형식이다.</p>
<p>그래서 여러 파일이나 디렉토리를 GZIP으로 압축하려면, 먼저 TAR로 하나의 아카이브로 묶은 뒤 GZIP으로 압축하는 2단계를 거쳐야 한다. 이것이 바로 <code>.tar.gz</code>(또는 <code>.tgz</code>) 파일이 탄생하는 과정이다.</p>
<p>GZIP의 파일 구조는 RFC 1952에 명세되어 있으며, 상당히 단순하다. <strong>10바이트 고정 헤더</strong> + 선택적 확장 헤더(원본 파일명, 코멘트 등) + DEFLATE 압축 데이터 + <strong>8바이트 트레일러</strong>(CRC-32 체크섬과 원본 크기)로 이루어진다. 여기서 CRC-32는 압축 해제 후 데이터가 원본과 동일한지를 검증하는 무결성 체크 장치이다. 즉 GZIP은 DEFLATE 압축 데이터를 감싸는 가벼운 래퍼(wrapper)인 셈이다.</p>
<p>DEFLATE 내부에서 사용하는 슬라이딩 윈도우의 크기는 <strong>최대 32KB</strong>이다. 이 크기가 GZIP 압축률의 상한을 결정하는 중요한 요소인데, 32KB 이상 떨어진 패턴은 참조할 수 없기 때문이다. 또한 GZIP은 압축 레벨을 1부터 9까지 지정할 수 있다. 레벨 1은 빠르지만 압축률이 낮고(약 60%), 레벨 9는 느리지만 최대 압축률(약 75%)을 달성한다. 기본값은 레벨 6으로, 속도와 압축률 사이의 균형점이다.</p>
<p>Unix/Linux 환경에서 소스 코드 배포, 로그 파일 압축, 소프트웨어 패키지 등에 표준처럼 사용되고 있다. 웹 서버의 HTTP 압축(<code>Content-Encoding: gzip</code>)에서도 여전히 기본값으로 널리 쓰이고 있지만, 이 영역에서는 점차 Brotli로 대체되는 추세이다.</p>
<hr>
<h2 id="zstd-zstandard"><a class="anchor" href="#zstd-zstandard">ZSTD (Zstandard)</a></h2>
<p>ZSTD는 Meta(구 Facebook)의 Yann Collet이 개발하여 2016년에 오픈소스로 공개한 압축 알고리즘이다. GZIP과 비슷한 수준의 압축률을 유지하면서도, <strong>압축/해제 속도가 압도적으로 빠르다</strong>는 것이 핵심 강점이다.</p>
<p>ZSTD의 내부 구조는 크게 세 단계로 나뉜다. 먼저 LZ77 계열의 <strong>매치 파인더(Match Finder)</strong> 가 입력 데이터에서 반복 패턴을 찾아낸다. 그 다음 찾아낸 매치 정보(리터럴, 매치 길이, 오프셋)를 <strong>시퀀스</strong>로 인코딩한다. 마지막으로 이 시퀀스를 <strong>엔트로피 코딩</strong>으로 압축하는데, 여기서 GZIP의 허프만 부호화 대신 <strong>FSE(Finite State Entropy)</strong> 라는 방식을 사용한다. FSE는 ANS(Asymmetric Numeral Systems) 이론에 기반한 엔트로피 코더로, 허프만 부호화와 산술 부호화(Arithmetic Coding)의 장점을 결합한 것이다. 허프만 부호화가 심볼당 정수 비트만 할당할 수 있는 반면, FSE는 분수 비트까지 표현할 수 있어 이론적 최적값에 더 가까운 압축이 가능하다. (이름이 거창하지만, 핵심은 "같은 데이터를 더 적은 비트로 표현하는 더 똑똑한 방법"이라고 이해하면 된다.)</p>
<p>매치 파인더도 압축 레벨에 따라 전략이 달라진다. 낮은 레벨(1~4)에서는 단순 해시 테이블로 빠르게 검색하고, 중간 레벨(5~12)에서는 여러 후보를 비교한 뒤 가장 좋은 매치를 선택하는 Lazy 방식을, 높은 레벨(13~22)에서는 이진 트리와 동적 프로그래밍을 활용해 최적에 가까운 매치를 찾아낸다. 이렇게 레벨을 1부터 22까지 세밀하게 조절할 수 있어, 실시간 전송에는 낮은 레벨을, 아카이빙에는 높은 레벨을 사용하는 식으로 유연하게 대응할 수 있다.</p>
<p>Silesia Corpus 벤치마크 기준으로, ZSTD 기본 레벨(3)의 압축 속도는 약 300MB/s, 해제 속도는 약 1,200MB/s이다. 반면 GZIP 기본 레벨(6)은 압축 약 34MB/s, 해제 약 380MB/s에 그친다. <strong>압축은 약 8배, 해제는 약 3배 빠르면서도 압축률은 ZSTD(3.17) : GZIP(3.09)로 오히려 앞선다.</strong> 이 숫자가 ZSTD의 트레이드오프 개선을 가장 직관적으로 보여준다.</p>
<p>현재 생태계에서의 채택도 빠르게 확대되고 있다. Linux 커널의 모듈 압축과 파일시스템 투명 압축에 사용되고 있으며, Arch Linux, Fedora, Debian/Ubuntu 등 주요 배포판에서 패키지 압축 기본 형식으로 채택했다. 2025년 2월 릴리스된 v1.5.7부터는 <strong>멀티스레드 압축이 기본 활성화</strong>(최대 4스레드)되어, 싱글스레드만 지원하는 GZIP과의 실질적인 속도 격차는 더욱 벌어졌다. AWS는 내부 서비스에서 gzip을 zstd로 전환하여 S3 스토리지를 약 30% 절감했다고 밝힌 바 있다.</p>
<hr>
<h2 id="bzip2"><a class="anchor" href="#bzip2">BZIP2</a></h2>
<p>BZIP2는 여러 단계의 변환을 거쳐 압축을 수행한다. 핵심 파이프라인은 다음과 같다.</p>
<ol>
<li><strong>RLE(Run-Length Encoding)</strong> : 초기 데이터의 연속 반복을 줄임</li>
<li><strong>BWT(Burrows-Wheeler Transform)</strong> : 데이터를 압축하기 쉬운 형태로 재배열</li>
<li><strong>MTF(Move-to-Front Transform)</strong> : BWT의 출력을 숫자 배열로 변환</li>
<li><strong>RLE</strong> : MTF 결과의 반복을 다시 줄임</li>
<li><strong>Huffman Coding</strong> : 최종적으로 빈도 기반 부호화로 압축</li>
</ol>
<p>GZIP보다 높은 압축률을 제공하지만, 압축 속도와 해제 속도 모두 느리다. 고압축률이 필요하면서 속도가 덜 중요한 아카이빙 용도로 사용되어 왔다.</p>
<p>다만 마지막 릴리스가 2019년(v1.0.8)이며, 활발한 개발은 이루어지지 않고 있다. 압축률과 속도 양면에서 ZSTD가 BZIP2를 능가하는 벤치마크 결과가 나오면서, 새로운 프로젝트에서는 ZSTD를 선택하는 경우가 늘고 있다.</p>
<hr>
<h2 id="xz"><a class="anchor" href="#xz">XZ</a></h2>
<p>XZ는 <strong>LZMA2</strong>를 사용하는 압축 형식이다. LZMA(Lempel-Ziv-Markov chain Algorithm)는 Igor Pavlov가 개발한 알고리즘으로, LZ77 기반의 사전 압축에 범위 부호화(Range Encoding)를 결합한 것이다. LZMA2는 LZMA의 "개선된 버전"이라기보다는, LZMA 스트림을 감싸는 <strong>컨테이너 형식</strong>에 가깝다. 멀티스레드 압축/해제 지원과 비압축 데이터의 효율적인 처리가 추가된 것이 핵심이다.</p>
<p>이 글에서 다루는 형식 중 <strong>가장 높은 압축률</strong>을 자랑한다. 그 대신 압축 속도가 매우 느리고, 메모리 사용량도 크다. 저장 공간 절약이 최우선인 아카이빙에 적합하다.</p>
<p>다만 2024년 3월, XZ의 핵심 라이브러리인 xz-utils에서 <strong>백도어가 발견되는 심각한 보안 사건(CVE-2024-3094)이 있었다.</strong> 2년간의 소셜 엔지니어링 공격으로 메인테이너 권한이 탈취되어, CVSS 10.0(최고 심각도)을 받았다. 주요 배포판에서 즉시 안전한 버전으로 롤백했지만, 이 사건은 오픈소스 공급망 보안에 대한 경각심을 일깨워 주었다. (XZ 자체의 기술적 가치는 여전하지만, 도구 선택 시 이런 맥락도 알아두면 좋다.)</p>
<hr>
<h2 id="tar"><a class="anchor" href="#tar">TAR</a></h2>
<p>TAR(Tape Archive)는 그 자체로 압축 알고리즘이 아니다. 여러 파일과 디렉토리를 <strong>하나의 아카이브 파일로 묶는</strong> 도구이다. 이름에서 알 수 있듯이 원래 자기 테이프(Tape) 백업을 위해 만들어진 포맷인데, 테이프라는 매체 특성상 데이터를 순차적으로 쭉 이어 붙이는 구조가 자연스러웠다.</p>
<p>TAR의 내부 구조는 의외로 단순하다. 모든 데이터는 <strong>512바이트 블록</strong> 단위로 처리된다. 각 파일 앞에 512바이트짜리 헤더 블록이 붙는데, 이 헤더에 파일명(최대 100바이트), 파일 모드, 소유자 UID/GID, 파일 크기, 수정 시간, 체크섬 등의 메타데이터가 들어간다. 파일 데이터는 헤더 뒤에 이어지며, 512바이트의 배수로 패딩된다. 아카이브의 끝은 512바이트짜리 제로 블록 두 개로 표시된다. 현대의 대부분의 TAR 구현체는 POSIX에서 정의한 <strong>UStar(Unix Standard TAR)</strong> 형식을 따르며, 이 형식은 더 긴 파일명(최대 256바이트)과 추가 메타데이터 필드를 지원한다.</p>
<p>핵심은 TAR가 파일의 권한, 소유권, 타임스탬프, 심볼릭 링크 같은 <strong>Unix 파일시스템 메타데이터를 그대로 보존</strong>한다는 점이다. ZIP은 이러한 Unix 고유 메타데이터를 완벽히 보존하지 못하는 경우가 있어, 서버 환경에서의 배포에는 TAR가 더 적합하다.</p>
<p>TAR 자체는 크기를 줄여주지 않는다. 오히려 헤더와 패딩 때문에 원본보다 약간 커진다. 실제 압축은 GZIP, BZIP2, XZ, ZSTD 등과 결합하여 수행한다. 이것이 <code>.tar.gz</code>, <code>.tar.bz2</code>, <code>.tar.xz</code>, <code>.tar.zst</code> 같은 확장자가 존재하는 이유이다. TAR가 "묶기"를, 압축 도구가 "줄이기"를 각각 담당하는 <strong>Unix 철학("한 가지 일을 잘 하라")</strong> 의 전형적인 사례이다.</p>
<p>Unix/Linux 환경에서 표준 아카이빙 도구로 사용되며, Windows에서는 별도의 소프트웨어(7-Zip 등)가 필요하다.</p>
<hr>
<h2 id="잠깐-brotli도-알아두자"><a class="anchor" href="#잠깐-brotli도-알아두자">잠깐, Brotli도 알아두자</a></h2>
<p>프론트엔드 개발자라면 <strong>Brotli</strong>도 짚고 넘어갈 필요가 있다. Brotli는 Google이 개발한 압축 알고리즘으로, 2015년에 HTTP 스트림 압축 사양(<code>Content-Encoding: br</code>)으로 표준화되었다.</p>
<p>모든 주요 브라우저에서 HTTPS 환경에서 지원되며(지원률 96% 이상), GZIP 대비 <strong>약 15~25% 더 높은 압축률</strong>을 보인다. 특히 JavaScript, CSS, HTML 같은 텍스트 기반 정적 파일의 압축에 효과적이다. Cloudflare 등 주요 CDN에서 기본 압축 방식으로 채택하고 있으며, 현대 웹 최적화의 모범 사례는 "Brotli 우선, GZIP 폴백"으로 정리된다.</p>
<p>빌드 결과물을 S3에 올려 CDN으로 서빙하는 구조라면, 정적 파일을 사전에 Brotli로 압축해 두는 것이 네트워크 전송 최적화에 큰 도움이 된다. (지금 당장 고민하기에는 자료가 많지 않아 도입하기 어렵지만 추후 고민의 여지가 될 수 있기에 개발자라면 알아두면 좋을 것 같다.)</p>
<hr>
<h2 id="targz가-zip보다-압축률이-높은-이유"><a class="anchor" href="#targz가-zip보다-압축률이-높은-이유">tar.gz가 zip보다 압축률이 높은 이유</a></h2>
<p>이것은 <strong>솔리드 아카이브(Solid Archive)</strong> 와 <strong>비솔리드 아카이브(Non-solid Archive)</strong> 의 차이에서 비롯된다.</p>
<p>tar.gz는 TAR로 모든 파일을 하나의 연속된 데이터 스트림으로 묶은 뒤, GZIP이 이 전체 스트림을 한 번에 압축한다. 이 과정에서 <strong>파일 간의 중복 데이터</strong>를 인식하고 활용할 수 있다. 이것이 솔리드 아카이브 방식이다. 예를 들어 빌드 폴더에 구조가 유사한 JavaScript 번들 파일이 수십 개 있다면, 파일 A에서 등장한 패턴이 파일 B에서도 반복될 때 이를 하나의 참조로 처리할 수 있다. 또한 파일마다 별도의 헤더, 체크섬, 목차(Table of Contents)를 기록할 필요가 없으므로 메타데이터 오버헤드도 줄어든다.</p>
<p>반면 ZIP은 각 파일을 독립적으로 압축하는 비솔리드 방식이므로, 파일 간 중복을 활용하지 못한다. 파일 A와 파일 B에 동일한 코드 블록이 있더라도, 각각의 DEFLATE 스트림은 상대방의 존재를 모른다. 일반적으로 tar.gz가 ZIP 대비 5~15% 더 높은 압축률을 보이는 이유가 바로 이것이다. 특히 유사한 구조의 파일이 대량으로 포함된 빌드 결과물일수록 이 격차는 더 벌어진다.</p>
<p>다만 솔리드 아카이브에도 뚜렷한 단점이 있다.</p>
<ul>
<li>압축 파일 내의 특정 파일 하나만 꺼내려면 <strong>그 파일 앞에 위치한 모든 데이터를 먼저 해제</strong>해야 한다. 모든 파일이 하나의 스트림으로 이어져 있기 때문에, 중간 지점으로 바로 건너뛸 수가 없는 것이다. ZIP은 개별 파일에 대한 랜덤 액세스가 가능하므로, 아카이브에서 특정 파일만 빈번하게 꺼내야 하는 상황이라면 ZIP이 더 적합할 수 있다.</li>
<li>아카이브의 일부가 손상되면 <strong>손상 지점 이후의 모든 데이터가 복구 불가능</strong>해질 수 있다. 비솔리드 방식에서는 손상된 파일 하나만 잃고 나머지는 살릴 수 있는 것과 대비된다.</li>
</ul>
<hr>
<p>&#x3C;2026년 추가></p>
<h2 id="2024년의-선택은-targz-지금은"><a class="anchor" href="#2024년의-선택은-targz-지금은">2024년의 선택은 tar.gz, 지금은?</a></h2>
<p>필자가 당시 tar.gz를 선택한 이유는 호환성과 안정성이었다. S3 업로드 이후 다양한 환경에서 해제해야 했기 때문에, 어디서든 지원되는 tar.gz가 안전한 선택이었다.</p>
<p>하지만 지금 같은 상황이 다시 온다면 <strong>tar.zst(TAR + ZSTD)</strong> 에 대해 고민해볼 것이다. 앞서 살펴본 벤치마크 수치를 다시 떠올려 보자.</p>
<p>GZIP 기본 레벨의 압축 속도가 34MB/s인데, ZSTD 기본 레벨은 300MB/s이다. 2GB 빌드 폴더 기준으로 단순 계산하면 GZIP은 약 60초, ZSTD는 약 7초 만에 압축이 끝난다. 여기에 ZSTD v1.5.7부터 기본 활성화된 멀티스레드(최대 4스레드)까지 고려하면, 실제 체감 시간은 더 짧아진다. CI/CD 파이프라인에서 이 차이는 배포 한 사이클마다 누적되는 것이니, 무시할 수 없는 수치이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="sh" data-theme="github-dark github-light"><code data-language="sh" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D"># tar.zst 생성 (멀티스레드 자동 활용)</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">tar</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> --zstd</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -cf</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> archive.tar.zst</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> directory/</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D"># 또는 압축 레벨 지정 (-T0은 사용 가능한 모든 코어 활용)</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">tar</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -cf</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> archive.tar.zst</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -I</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'zstd -3 -T0'</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> directory/</span></span></code></pre></figure>
<p>압축률 면에서도 ZSTD는 GZIP과 동등하거나 오히려 앞서기 때문에, "속도를 위해 압축률을 포기한다"는 트레이드오프가 사실상 존재하지 않는다. 속도도 빠르고, 결과물도 작다.</p>
<p>다만 수신 측 환경에서 zstd 해제가 가능한지는 반드시 확인해야 한다. 주요 Linux 배포판에는 이미 기본 설치되어 있고, macOS에서도 Homebrew(<code>brew install zstd</code>)로 간단히 설치할 수 있다. 하지만 레거시 시스템이나 최소 설치 환경에서는 별도 설치가 필요할 수 있으므로, 팀 내 모든 환경을 사전에 점검하는 것이 중요하다. 호환성이 최우선이라면 여전히 tar.gz가 가장 무난한 선택이다.</p>
<hr>
<h2 id="한눈에-비교하기"><a class="anchor" href="#한눈에-비교하기">한눈에 비교하기</a></h2>
<table>
<thead>
<tr>
<th>형식</th>
<th>알고리즘</th>
<th>압축률</th>
<th>속도</th>
<th>특징</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>ZIP</strong></td>
<td>DEFLATE</td>
<td>보통</td>
<td>빠름</td>
<td>크로스 플랫폼, 비솔리드</td>
</tr>
<tr>
<td><strong>GZIP</strong></td>
<td>DEFLATE</td>
<td>보통</td>
<td>빠름</td>
<td>단일 스트림, TAR와 결합</td>
</tr>
<tr>
<td><strong>ZSTD</strong></td>
<td>Zstandard</td>
<td>높음</td>
<td>매우 빠름</td>
<td>압축 레벨 조절, 최신 표준</td>
</tr>
<tr>
<td><strong>BZIP2</strong></td>
<td>BWT+MTF+Huffman</td>
<td>높음</td>
<td>느림</td>
<td>개발 정체</td>
</tr>
<tr>
<td><strong>XZ</strong></td>
<td>LZMA2</td>
<td>매우 높음</td>
<td>매우 느림</td>
<td>최고 압축률, 보안 사건 주의</td>
</tr>
<tr>
<td><strong>Brotli</strong></td>
<td>Brotli</td>
<td>높음</td>
<td>보통</td>
<td>웹 최적화 특화</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="마치며"><a class="anchor" href="#마치며">마치며</a></h2>
<p>압축이라는 주제를 파보기 전까지는, 솔직히 "그냥 zip으로 묶으면 되는 거 아닌가?"라고 생각했다. 하지만 실제로 2GB가 넘는 빌드 폴더를 다루다 보니, 어떤 알고리즘을 선택하느냐에 따라 업로드 시간과 비용이 유의미하게 달라진다는 것을 체감할 수 있었다.</p>
<p>각 압축 형식은 저마다의 설계 철학과 트레이드오프를 가지고 있다. ZIP의 호환성, GZIP의 범용성, ZSTD의 속도, XZ의 압축률까지 어느 것이 "최고"라고 단정할 수 없고, 프로젝트의 맥락에 따라 적합한 선택이 달라진다.</p>
<p>평소에 무심코 사용하던 도구의 원리를 파보는 것은, 다음에 비슷한 문제를 만났을 때 더 나은 판단을 할 수 있게 해준다. 이 글이 압축 알고리즘을 선택해야 하는 누군가에게 작은 참고가 되었기를 바란다.</p>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>소박한궁금증</category>
            <category>소프트웨어</category>
        </item>
        <item>
            <title><![CDATA[비전공 프론트엔드 2년차 개발자 멘티 일지: 8주차 - 진짜!! 너무 바쁘다~!]]></title>
            <link>https://hooninedev.com/240625</link>
            <guid isPermaLink="false">https://hooninedev.com/240625</guid>
            <pubDate>Tue, 25 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[동아리, 회사, 스터디 등등 많이 바쁘다.. 멘토링 끝나고 블로그에 회고 작성하려는 큰 포부를 가졌지만.. 요즈음 과제가 어려워서? 시간이 빠듯해서 잘 못했다ㅠㅠ 그렇기에 오랜만에 회고를 작성해보고 복습해보자! 타입스크립트는 어떻게 컴파일할까? 우선 타입스크립트는 TSC 컴파일러를 통해 바로 컴파일되는 것이 아닌 자바스크립트 코드로 변환된다. 이 과정에서 ...]]></description>
            <content:encoded><![CDATA[<br>
<p><img src="/content/240625/1.jpeg" alt="1.jpeg" loading="lazy" decoding="async"></p>
<p>동아리, 회사, 스터디 등등 많이 바쁘다.. 멘토링 끝나고 블로그에 회고 작성하려는 큰 포부를 가졌지만.. 요즈음 과제가 어려워서? 시간이 빠듯해서 잘 못했다ㅠㅠ</p>
<p>그렇기에 오랜만에 회고를 작성해보고 복습해보자!</p>
<br>
<h3 id="타입스크립트는-어떻게-컴파일할까"><a class="anchor" href="#타입스크립트는-어떻게-컴파일할까">타입스크립트는 어떻게 컴파일할까?</a></h3>
<p>우선 타입스크립트는 TSC 컴파일러를 통해 바로 컴파일되는 것이 아닌 자바스크립트 코드로 변환된다. 이 과정에서 고수준 언어인 타입스크립트가 저수준 언어인 자바스크립트로 변환되어 트랜스파일이라고 부르기도 한다. 상세한 과정은 아래와 같다.</p>
<ul>
<li>타입스크립트 소스코드를 타입스크립트 AST로 만들고, 타입 검사기가 AST를 확인하여 타입을 확인한다. (TSC)</li>
<li>타입스크립트 AST를 자바스크립트 소스로 변환한다. (TSC)</li>
<li>자바스크립트 소스코드를 자바스크립트 AST로 만든다. (런타임)</li>
<li>AST가 바이트 코드로 변환되고 런타임에 바이트 코드가 평가되어 프로그램이 실행된다. (런타임)</li>
</ul>
<br>
<h4 id="타입스크립트-컴파일러의-구성-요소와-동작-방식"><a class="anchor" href="#타입스크립트-컴파일러의-구성-요소와-동작-방식">타입스크립트 컴파일러의 구성 요소와 동작 방식</a></h4>
<p>Scanner => Parser => Binder => Checker => Emitter 단계를 거쳐 타입 검사와 JS 소스 변환을 진행한다.</p>
<p><strong>스캐너</strong>는 소스코드를 작은 단위로 나누어 의미 있는 토큰으로 변환하는 작업을 수행한다.</p>
<p>그리고 <strong>스캐너</strong>가 소스 파일을 토큰으로 나눠주면, <strong>파서</strong>는 그 토큰을 활용해 AST를 생성한다.</p>
<p><strong>체커</strong>(checker) 단계에서 타입 검사를 할 수 있도록 <strong>바인더</strong>(Binder)에서 기반을 마련하고, <strong>체커</strong> 단계에서 <strong>파서</strong>가 생성한 AST와 <strong>바인더</strong>가 생성한 심볼을 활용하여 타입 검사를 수행한다.</p>
<p>그리고 <strong>이미터</strong> 단계에서 TS 소스를 자바스크립트(js) 파일과 타입 선언 파일(d.ts)로 생성한다.</p>
<br>
<h3 id="타입스크립트의-타입-추론-과정과-원리"><a class="anchor" href="#타입스크립트의-타입-추론-과정과-원리">타입스크립트의 타입 추론 과정과 원리</a></h3>
<p>타입스크립트에서 타입 추론은 명시적인 타입 선언 없이도 변수나 표현식의 타입을 자동으로 결정하는 기능이다. 그럼 타입 추론이 어떻게 작동하는지 알아보자.</p>
<p><strong>1. 초기화 기반 추론</strong>: 변수가 초기화될 때, 초기화 값에 따라 변수의 타입이 결정된다. ex) <code>let x = 3;</code>에서 <code>x는 number</code>로 추론된다.</p>
<p><strong>2. 최적 공통 타입 (Best Common Type)</strong>: 여러 표현식에서 타입을 추론할 때 각 표현식의 타입을 고려하여 가장 적합한 공통 타입을 선택한다. ex) <code>let x = [0, 1, null];</code>에서 x의 타입은 <code>(number | null)[]</code>로 추론된다. 배열이나 객체에서 공통의 상위 타입이 명확하지 않은 경우에는 유니언 타입으로 추론될 수 있다.</p>
<p><strong>3. 문맥상 타이핑 (Contextual Typing)</strong>: 표현식의 위치에 따라 그 타입이 추론되는 경우다. ex) ₩window.onmousedown₩ 함수에서는 마우스 이벤트의 타입을 <code>MouseEvent</code>로 추론하고, 이를 통해 <code>mouseEvent</code> 객체 내 속성들의 접근을 제어한다.</p>
<p><strong>4. 함수와 메소드의 반환 타입 추론</strong>: 함수 내에서 반환되는 값의 타입을 기반으로 함수의 반환 타입을 추론하는 방법이다. ex) <code>function createZoo(): Animal[] { return [new Rhino(), new Elephant(), new Snake()]; }</code>에서 반환 타입은<code>Animal[]</code>로 추론된다.</p>
<br>
<h3 id="타입-추론-최대화를-위한-전략"><a class="anchor" href="#타입-추론-최대화를-위한-전략">타입 추론 최대화를 위한 전략</a></h3>
<p>props를 전달하거나, 복잡한 로직 등등에서 타입 추론을 사용하다 보면 예상과 다르게 추론이 되거나, 안되는 경우들이 있다. 이런 경우를 방지하기 위해서 어떻게 코드를 작성하면 타입 추론을 효율적으로 사용할 수 있을지도 알아보자.</p>
<h4 id="명시적-타입-정의"><a class="anchor" href="#명시적-타입-정의">명시적 타입 정의</a></h4>
<p>인터페이스나 타입을 사용하여 props나 함수의 반환 타입 등을 명시적으로 정의하는 방법으로 타입스크립트 컴파일러에게 알려주는 것이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> UserProps</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  name</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  age</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> greet</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">user</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> UserProps</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> `Hello, ${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">user</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">.</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">name</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}!`</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<h4 id="제네릭-사용"><a class="anchor" href="#제네릭-사용">제네릭 사용</a></h4>
<p>특히 컴포넌트나 함수가 다양한 타입을 처리해야 할 때, 제네릭을 사용하면 각 상황에 맞는 타입 추론을 할 수 있다. 또한 제네릭을 사용하면 사용 시점에 타입을 결정하기 때문에 유연성이 높고 타입 안전성을 보장할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> merge</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">U</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">a</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">b</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> &#x26;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> U</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">a, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">b };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> result</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> merge</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ name: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"John"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }, { age: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">30</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> });</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// result는 { name: string; age: number; } 타입으로 추론</span></span></code></pre></figure>
<h4 id="기본값과-구조-분해-사용"><a class="anchor" href="#기본값과-구조-분해-사용">기본값과 구조 분해 사용</a></h4>
<p>함수의 매개변수나 컴포넌트의 props에 기본값을 제공하면, 타입스크립트는 이 정보를 사용하여 타입을 추론할 수 있다. 구조 분해 할당을 사용할 때도 마찬가지로 타입 정보를 제공하면 도움이 된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Props</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  name</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  age</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> setup</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">name</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "Unknown"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">age</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Props</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 함수 내에서 name과 age의 타입이 string과 number로 추론</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<h4 id="조건부-타입-사용"><a class="anchor" href="#조건부-타입-사용">조건부 타입 사용</a></h4>
<p>복잡한 로직에 따라 타입이 결정되어야 하는 경우, 조건부 타입을 사용하여 타입 추론을 개선할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> LoadingState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> extends</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> undefined</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> ?</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "loading"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> :</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> fetchData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> LoadingState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> undefined</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> ?</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "loading"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<h4 id="타입-단언-사용"><a class="anchor" href="#타입-단언-사용">타입 단언 사용</a></h4>
<p>타입스크립트의 타입 추론이 기대한 대로 작동하지 않을 수 있는 경우 타입 단언(type assertion)을 사용하여 컴파일러에게 명확한 타입 정보를 제공할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> myCanvas</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> document.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getElementById</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"mainCanvas"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HTMLCanvasElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span></code></pre></figure>
<br>
<h3 id="타입스크립트-object타입과-덕타이핑"><a class="anchor" href="#타입스크립트-object타입과-덕타이핑">타입스크립트 object타입과 덕타이핑</a></h3>
<p>타입스크립트에서 <code>object</code> 타입과 덕 타이핑(duck typing)은 객체의 구조에 기반하여 타입 체크를 수행하는 방식으로 사용된다. 어떤 연관성을 가지는지 찾아보자!</p>
<h4 id="object-타입"><a class="anchor" href="#object-타입">Object 타입</a></h4>
<p>타입스크립트에서 <code>object</code>는 원시 타입(primitive type)이 아닌 값(함수, 배열, 객체 등)을 나타내는 타입이다. <code>object</code> 타입은 <code>Object</code> 자바스크립트 생성자 함수가 생성하는 인스턴스와 혼동하면 안된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> logObject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">obj</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> object</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(obj);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">logObject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ name: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"John"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 올바른 사용</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">logObject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Hello, world!"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 에러: "Hello, world!"는 원시 타입이기 때문에 object 타입과 호환되지 않음</span></span></code></pre></figure>
<h4 id="덕-타이핑-duck-typing"><a class="anchor" href="#덕-타이핑-duck-typing">덕 타이핑 (Duck Typing)</a></h4>
<p>덕 타이핑은 "만약 어떤 새가 오리처럼 걷고, 오리처럼 소리를 낸다면, 그 새를 오리라고 부를 수 있다"는 개념에서 유래했다.</p>
<p>타입스크립트는 이 개념을 타입 시스템에 적용하여, 객체가 특정 인터페이스에 선언된 모든 속성과 메서드를 갖추고 있으면, 그 객체를 해당 인터페이스의 인스턴스로 간주한다. 이를 통해 타입의 명시적인 선언 없이도 타입 호환성을 판단할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">interface</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Duck</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  walk</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  quack</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> makeItQuack</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">duck</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Duck</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  duck.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">quack</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> myBird</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  walk</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Walking like a duck"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">),</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  quack</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Quacking like a duck"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">),</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  swim</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Swimming like a duck"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">makeItQuack</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(myBird); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 정상 작동: myBird가 Duck 인터페이스를 충족</span></span></code></pre></figure>
<p>위 예시에서 <code>myBird</code> 객체는 <code>Duck</code> 인터페이스에 명시적으로 구현되지 않았지만, <code>Duck</code> 인터페이스가 요구하는 <code>walk</code>와 <code>quack</code> 메서드를 가지고 있기 때문에 <code>Duck</code> 타입의 매개변수로 전달될 수 있다.</p>
<h4 id="object-타입과-덕-타이핑의-연관성"><a class="anchor" href="#object-타입과-덕-타이핑의-연관성">Object 타입과 덕 타이핑의 연관성</a></h4>
<p>타입스크립트에서 <code>object</code> 타입을 사용할 때, 객체의 구체적인 구조를 지정하지 않고, 어떤 형태의 객체라도 받을 수 있다. 하지만 덕 타이핑을 사용하면, 객체가 특정 구조를 충족하는지를 기반으로 타입 호환성을 검사할 수 있다. 따라서 <code>object</code> 타입보다 더 세밀한 구조적 타이핑을 제공하려면 인터페이스나 타입 별칭을 사용하여 객체의 구조를 명확하게 정의하는 것이 좋다.</p>
<br>
<h3 id="6월-넷째-주-회고"><a class="anchor" href="#6월-넷째-주-회고">6월 넷째 주 회고</a></h3>
<p>선택과 집중을 명확히 하자!</p>
<br>
<h4 id="이번-주-좋은-것과-나쁜-것"><a class="anchor" href="#이번-주-좋은-것과-나쁜-것">이번 주 좋은 것과 나쁜 것</a></h4>
<ul>
<li>일이 많았습니다
<ul>
<li>회사일</li>
<li>토스 플랫폼 과제</li>
<li>동아리 큰 행사 준비</li>
<li>토스 자기소개서 작성</li>
<li>이력서 수정</li>
</ul>
</li>
<li>좋은 자료들을 찾아 react의 DOM 구조에 대해 공부할 수 있었습니다</li>
</ul>
<h4 id="이번-주-진행했던-학습개발-내용은"><a class="anchor" href="#이번-주-진행했던-학습개발-내용은">이번 주 진행했던 학습/개발 내용은??</a></h4>
<ul>
<li>과제를 단계별 계획을 세워 진행했습니다</li>
<li>사내에서 사용하는 utility 함수, constant, 자체 hooks 등을 verdaccio와 github에 자체적으로 버전관리 진행중입니다.</li>
<li>토스 행사를 위해 자기소개서 작성했습니다</li>
<li>이력서 수정했습니다.</li>
<li>devops와 infra에서 필요한 개념들을 공부했습니다</li>
</ul>
<h4 id="가장-고민했던-부분은-무엇이었나요"><a class="anchor" href="#가장-고민했던-부분은-무엇이었나요">가장 고민했던 부분은 무엇이었나요?</a></h4>
<ul>
<li>과제를 어떤 단계로 공부해야 할지</li>
<li>공용함수 버전관리</li>
</ul>
<h4 id="아쉬운-부분을-개선하는-데-필요한-것은-무엇인가요"><a class="anchor" href="#아쉬운-부분을-개선하는-데-필요한-것은-무엇인가요">아쉬운 부분을 개선하는 데 필요한 것은 무엇인가요?</a></h4>
<ul>
<li>이번주 바쁠거라 생각해서 잠을 줄이고 일정을 소화했는데, 컨디션이 좋지 못합니다.</li>
</ul>
<h4 id="다음-주는-어떻게-보낼-예정인가요"><a class="anchor" href="#다음-주는-어떻게-보낼-예정인가요">다음 주는 어떻게 보낼 예정인가요?</a></h4>
<ul>
<li>6/27 커피챗하러 선릉역으로 떠납니다.</li>
<li>이력서 최종 수정</li>
<li>멘토링 과제</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>멘토링</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[Elastic Beanstalk를 이용해 귀여운 Docker 자동화 배포 실습해보자]]></title>
            <link>https://hooninedev.com/240618</link>
            <guid isPermaLink="false">https://hooninedev.com/240618</guid>
            <pubDate>Tue, 18 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[우선 해당 공부는 docker-test 레포를 이용했습니다. 문서를 열심히 읽어보아도 한 번의 실습이 더 값지다고 생각한다! 그렇기에 간단히 중요한 개념을 복습하고, 자주 사용하는 CRA부터 Elastic Beanstalk + Github Action + S3를 이용해 코드를 구현해보자 중요한 개념을 복습해보자. Dockerfile: 기본 이미지, 종속성 ...]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>우선 해당 공부는 <a href="https://github.com/jiji-hoon96/docker-test">docker-test</a> 레포를 이용했습니다.</p>
</blockquote>
<p>문서를 열심히 읽어보아도 한 번의 실습이 더 값지다고 생각한다!</p>
<p>그렇기에 간단히 중요한 개념을 복습하고, 자주 사용하는 CRA부터 Elastic Beanstalk + Github Action + S3를 이용해 코드를 구현해보자</p>
<br>
<p><img src="/content/240618/1.jpeg" alt="1.jpeg" loading="lazy" decoding="async"></p>
<br>
<h3 id="중요한-개념을-복습해보자"><a class="anchor" href="#중요한-개념을-복습해보자">중요한 개념을 복습해보자.</a></h3>
<ol>
<li>
<p><strong>Dockerfile</strong>: 기본 이미지, 종속성 및 실행 명령을 지정하여 Docker 이미지를 빌드하는 지침을 포함</p>
</li>
<li>
<p><strong>Docker 이미지</strong>: 애플리케이션을 실행하는 데 필요한 모든 것(코드, 라이브러리 및 종속성)을 포함하는 경량의 독립 실행형 패키지로, Dockerfile에서 빌드되며 버전을 지정할 수 있다.</p>
</li>
<li>
<p><strong>Docker 컨테이너</strong>: Docker 이미지의 실행 중인 인스턴스로, 컨테이너는 서로 격리되어 있고 호스트 시스템으로부터 격리되어 앱을 실행하기 위한 안전하고 재현 가능한 환경을 제공</p>
</li>
<li>
<p><strong>Docker 레지스트리</strong>: Docker 이미지를 저장하고 배포하기 위한 중앙 집중식 리포지토리로, Docker Hub는 기본 공용 레지스트리이지만 개인 레지스트리를 설정할 수도 있다.</p>
</li>
<li>
<p><strong>Docker 볼륨</strong>: 컨테이너에서 생성된 데이터를 유지하는 방법으로, 볼륨은 컨테이너의 파일 시스템 외부에 있으며 여러 컨테이너 간에 공유할 수 있다.</p>
</li>
<li>
<p><strong>Docker Compose</strong>: 다중 컨테이너 Docker 애플리케이션을 정의하고 실행하기 위한 도구로, 전체 스택을 쉽게 관리할 수 있다.</p>
</li>
</ol>
<br>
<p><img src="/content/240618/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<br>
<h3 id="도커를-이용해-개발-단계에서-cra-실행해보기"><a class="anchor" href="#도커를-이용해-개발-단계에서-cra-실행해보기">도커를 이용해 개발 단계에서 CRA 실행해보기</a></h3>
<p>그 이후 개발 환경의 도커 파일(Dockerfile.dev), 운영 환경의 도커 파일(Dockerfile)을 만들어줘야한다.</p>
<p>그렇게 만들어진 도커 파일을 가지고 도커 이미지를 생성하고, 이미지를 통해 컨테이너를 만들고, 컨테이너에서 앱을 실행하게 된다.</p>
<p>Dockerfile은 어떤 내용을 넣어줘야될까?</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="markdown" data-theme="github-dark github-light"><code data-language="markdown" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">FROM => Docker 이미지를 빌드할 때 기본이 되는 베이스 이미지를 지정</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">WORKDIR => 모든 명령어가 실행될 작업 디렉토리를 설정</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">COPY => 로컬 파일 시스템에서 Docker 이미지의 파일 시스템으로 파일을 복사</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">RUN => Docker 이미지 빌드 시에 실행할 명령어를 지정합니다. 주로 패키지 설치나 소프트웨어 설정에 사용</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">COPY</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">CMD => 컨테이너가 시작될 때 실행할 명령어를 지정한다. 주로 실행 파일이나 스크립트를 지정하는 데 사용된다. CMD는 Dockerfile 당 하나만 사용할 수 있다.</span></span></code></pre></figure>
<p>위 방법으로 <code>docker build .</code> 명령어를 실행해주니까 <code>unable to evaluate symlink</code> 에러가 발생했는데, dockerfile을 찾지 못하고 dockerfile.dev만 찾기 때문에 <code>-f 옵션</code>을 이용해 개발환경에서 사용할 수 있도록 수정했다.</p>
<p>docker을 이용해 build를 진행하니 <code>node_modules</code>의 용량이 무거워 <code>dockerignore</code> 또는 삭제를 하는 쪽으로 변경하는 것을 권장한다.</p>
<br>
<h3 id="도커-이미지로-컨테이너-실행해-리액트-앱을-실행해보자"><a class="anchor" href="#도커-이미지로-컨테이너-실행해-리액트-앱을-실행해보자">도커 이미지로 컨테이너 실행해 리액트 앱을 실행해보자.</a></h3>
<p><code>docker run 이미지 이름</code>을 하게 되면 포트 매핑이 안되어있어서, 컨테이너 내부로 리엑트가 도달하지 못해 <strong>This site can't be reached</strong> 오류가 발생한다.</p>
<p>이것을 해결하기 위해서는 <code>docker run -it -p 3000:3000 이미지 이름</code>을 이용해 포트 매핑을 해줘야 한다.</p>
<blockquote>
<p>여기서 -i는 상호 입출력, -t는 tty를 활성화하여 bash shell을 사용하는 것이다.</p>
</blockquote>
<br>
<h3 id="볼륨을-이용해보자"><a class="anchor" href="#볼륨을-이용해보자">볼륨을 이용해보자.</a></h3>
<p>Volume을 설정하면 소스의 변경을 Live Server처럼 바로 반영되는 것을 확인할 수 있다. 그러면 명령어를 작성해보자.</p>
<p><code>docker run -p 3000:3000 -v /usr/src/app/node_modules -v ${pwd}:/usr/src/app 이미지 아이디</code> 로 작성하면된다.</p>
<p>어지러워서 천천히 확인해보면, node_modules이 호스트 디렉토리에 없어서 컨테이너에게 매핑을 하지 말라고 명령하는 <code>/usr/src/app/node_modules</code>이 있고, pwd 경로에 있는 디렉토리 파일을 <code>usr/src/app</code> 참조하도록 한다.</p>
<br>
<h3 id="도커-컴포즈를-이용하자"><a class="anchor" href="#도커-컴포즈를-이용하자">도커 컴포즈를 이용하자.</a></h3>
<p>앞에 명령어 너무 길어서 어떻게 줄일 수 있을까 고민을 했는데 <strong>Docker Compose</strong>를 이용하면 된다는 것을 알게 되었다.</p>
<p>docker-compose.yml을 작성해보자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="markdown" data-theme="github-dark github-light"><code data-language="markdown" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">version: "3" => 도커 컴포저의 버전</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">services:</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">react: => 컨테이너 이름</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">build:</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">context: . => 도커 이미지를 구성하기 위한 파일과 폴더들이 있는 위치</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">dockerfile: Dockerfile.dev => 어떤 도커 파일인지</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">ports: - "3000:3000" => 컨테이너 포트번호</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">volumes: - /usr/src/app/node_modules - ./:usr/src/app</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">stdin_open: true => 리앱트 앱을 끌때 필요한 코드</span></span></code></pre></figure>
<p>완성이 되었으면 docker-compose를 <code>docker-compose up</code> 명령어를 이용해 실행해보자</p>
<p>테스트 코드 배포도 동일하게 하면된다. 명령어만 조금 수정하면! <del>귀찮아서 패스하겠다</del></p>
<br>
<h3 id="운영환경의-도커파일-작성과-nginx를-활용해보자"><a class="anchor" href="#운영환경의-도커파일-작성과-nginx를-활용해보자">운영환경의 도커파일 작성과 Nginx를 활용해보자</a></h3>
<p>운영환경에서 배포를 하려면 Nginx가 필요하다. 이유는 서버 트래픽도 분산시키고, 데이터 전송에 보안을 강화하고, 리버스 프록시로도 동작할 수 있기 때문이다. 뭐 간단하게 생각하면 정적 파일을 빠르게 서버를 이용해 다룰 때 사용한다.</p>
<p>Nginx를 이용하기 위해 Dockerfile에는 2가지 옵션을 추가해줘야 한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="markdown" data-theme="github-dark github-light"><code data-language="markdown" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">From nginx</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">COPY --from=builder /usr/src/app/build /usr/share/nginx/html</span></span></code></pre></figure>
<p>위 내용은 <a href="https://hub.docker.com/_/nginx">Dockerhub 공식 Nginx</a>에 상세하게 기록되어있으니 확인해주세요~</p>
<p>참고로 운영서버에서 실행시키려면 포트번호를 Nginx 기본 사용 포트인 80번으로 해야한다.</p>
<br>
<h3 id="github-action--aws를-이용해-배포해보자"><a class="anchor" href="#github-action--aws를-이용해-배포해보자">Github Action + AWS를 이용해 배포해보자.</a></h3>
<p>Github Action을 설정하는 방법은 <a href="https://docs.github.com/ko/actions">여기를</a> 확인해보면 좋다.</p>
<p>자동화 배포를 하기 위해서는 AWS의 IAM ROLE, Elastic Beanstalk, S3등이 필요한데, 설정 방법은 자료가 많기 때문에, 각각을 왜 사용하고 어떤것인지 찾아보자</p>
<p>아 맞다.. Github Action을 활용하기 위해서는 아까 저장해둔 액세스키, 비밀 엑세스키, 도커 관련된 설정등도 값을 넣어줘야된다.</p>
<br>
<h2 id="iam-role"><a class="anchor" href="#iam-role">IAM Role</a></h2>
<p>IAM Roles(Identity and Access Management Roles)은 AWS 리소스에 접근 권한을 부여하는 데 사용된다.</p>
<p>실습을 하면서 아래 조건들에 대한 설정을 해주었다. 너무 어려워보여서 일단 체크만 하고 넘어갔는데, <strong>IAM Role</strong>에서 왜 선택해주었는지 찾아보자</p>
<br>
<h3 id="awselasticbeanstalkmulticontainerdocker"><a class="anchor" href="#awselasticbeanstalkmulticontainerdocker">AWSElasticBeanstalkMulticontainerDocker</a></h3>
<p>이 역할은 Elastic Beanstalk에서 다중 컨테이너 Docker 환경을 지원하는 데 사용된다. 또한 아래와 같은 역할을 포함하고 있다.</p>
<ul>
<li>Elastic Beanstalk 환경의 컨테이너 설정 및 관리 및 Docker 컨테이너를 시작하고 중지하는 역할</li>
<li>Elastic Load Balancing (ELB), Amazon EC2 인스턴스, Amazon S3 버킷 등의 AWS 리소스에 접근하여 컨테이너를 배포하고 관리하는 작업을 수행</li>
</ul>
<p>이 역할은 Docker Compose를 사용하여 여러 Docker 컨테이너를 조정하는 Elastic Beanstalk 환경에서 필수적이기때문에, 꼭 설정해줘야한다.</p>
<br>
<h3 id="awselasticbeanstalkwebtier"><a class="anchor" href="#awselasticbeanstalkwebtier">AWSElasticBeanstalkWebTier</a></h3>
<p>이 역할은 Elastic Beanstalk의 웹 애플리케이션 환경에서 사용된다. 주로 웹 서버 역할을 수행하는 애플리케이션에 필요한 권한을 제공하며 아래와 같은 작업을 수행할 수 있는 권한을 포함하고 있다.</p>
<ul>
<li>애플리케이션 로그를 Amazon S3 버킷에 업로드</li>
<li>Elastic Beanstalk 환경의 상태를 모니터링하고 CloudWatch 로그를 관리</li>
<li>Amazon EC2 인스턴스 및 기타 AWS 리소스와의 상호작용</li>
</ul>
<p>웹 티어는 사용자가 직접 상호작용하는 프론트엔드 웹 서버 애플리케이션에 적합한 역할이다.</p>
<br>
<h3 id="awselasticbeanstalkworkertier"><a class="anchor" href="#awselasticbeanstalkworkertier">AWSElasticBeanstalkWorkerTier</a></h3>
<p>이 역할은 Elastic Beanstalk의 워커 애플리케이션 환경에서 사용된다. 주로 백그라운드 작업을 처리하는 애플리케이션에 필요한 권한을 제공하며 아래와 같은 작업을 수행할 수 있는 권한을 포함하고 있다.</p>
<ul>
<li>SQS (Simple Queue Service) 메시지를 처리</li>
<li>애플리케이션 로그를 Amazon S3 버킷에 업로드</li>
<li>백그라운드 작업 및 비동기 작업을 수행하기 위해 Amazon EC2 인스턴스와 상호작용.</li>
</ul>
<p>워커 티어는 주로 백엔드에서 처리해야 하는 긴 작업이나 비동기 작업을 수행하는 데 적합한 역할이다. 주로 이메일 알림 전송, 이미지 처리, 데이터베이스 백업과 같은 작업에 사용된다.</p>
<br>
<h2 id="elastic-beanstalk"><a class="anchor" href="#elastic-beanstalk">Elastic Beanstalk</a></h2>
<p><strong>Elastic Beanstalk</strong> 이름이 너무 낯설다. 간단하게 설명해보면, 개발자가 애플리케이션을 쉽게 배포하고 확장할 수 있도록 해주는 <strong>Paas(Platform as a Service)</strong> 서비스이다.</p>
<p>개발자가 애플리케이션 코드를 업로드하면, Elastic Beanstalk은 배포, 프로비저닝, 로드 벨런싱, 확장, 모니터링 등을 자동으로 처리해준다.</p>
<blockquote>
<p><a href="https://hong-yp-ml-records.tistory.com/125">프로비저닝(provisioning)</a></p>
<blockquote>
<p>IT 인프라를 생성하고 설정하는 프로세스다. 서버, 애플리케이션, 네트워크, 스토리지 등을 배포하는 과정의 가장 초기 단계를 의미한다.
<br></p>
</blockquote>
</blockquote>
<blockquote>
<p><a href="https://aws.amazon.com/ko/what-is/load-balancing/">로드 벨런싱(Load Balancing)</a></p>
<blockquote>
<p>로드 밸런싱은 애플리케이션을 지원하는 리소스 풀 전체에 네트워크 트래픽을 균등하게 배포하는 방법이다.</p>
</blockquote>
<blockquote>
<p>많은 양의 트래픽을 처리하기 위해 대부분의 애플리케이션에는 데이터가 중복되는 리소스 서버가 많이 있는데, 로드 밸런서는 사용자와 서버 그룹 사이에 위치하며 보이지 않는 촉진자 역할을 하여 모든 리소스 서버가 동일하게 사용되도록 한다.</p>
</blockquote>
</blockquote>
<br>
<h3 id="오잉-ec2랑-뭐가-다름"><a class="anchor" href="#오잉-ec2랑-뭐가-다름">오잉? EC2랑 뭐가 다름?</a></h3>
<p>우리가 자주 사용하는 EC2도 <strong>Elastic Compute Cloud</strong>로 같은 Elastic이다. <del>ㅋㅋㅋㅋ</del></p>
<p>EC2는 가상의 서버를 제공하는 <strong>Iaas(Infrastructure as a Service)</strong> 서비스다. 가상 머신 생성, 시작, 정지, 삭제와 같은 모든 작업을 직정 수행할 수 있다.</p>
<p>두개의 가장 큰 차이는 Elastic Beanstalk는 개발자가 애플리케이션 코드를 업로드하면 AWS가 배포 및 관리를 자동화하는 PaaS 서비스서비스이며, EC2는 가상 서버를 직접 관리해야 하는 IaaS 서비스이다.</p>
<br>
<h3 id="ec2-대신-elastic-beanstalk을-사용했을까"><a class="anchor" href="#ec2-대신-elastic-beanstalk을-사용했을까">EC2 대신 Elastic Beanstalk을 사용했을까?</a></h3>
<p>EC2를 사용하면 작업을 수동으로 설정하고 관리해야한다. 하지만 Elastic Beanstalk은 애플리케이션 배포와 관리를 자동으로 처리해준다. 이를 통해 사용자는 인프라 구성, 서버 설정, 로드 밸런싱, 스케일링 등을 직접 관리할 필요가 없어지는 장점이 생긴다.</p>
<p>또한 통합 모니터링 및 로깅을 쉽게 파악할 수 있고, 다양한 언어 환경에서 쉽게 애플리케이션을 배포할 수 있다.</p>
<p><strong>자동화</strong> 를 위해 사용했다는 것을 복습해보니 알게되었다.</p>
<br>
<h3 id="s3-설정-변경-및-iam-유저-생성"><a class="anchor" href="#s3-설정-변경-및-iam-유저-생성">S3 설정 변경 및 IAM 유저 생성</a></h3>
<p>S3는 AWS의 객체 스토리지 서비스로, 데이터를 안전하고 확장 가능하게 저장하게 도와준다. 대용량의 데이터를 저장할 수 있어서 데이터 관리에 용이하다.</p>
<p>S3에 들어가서 객체 소유권을 편집해준다. ACL 활성화해주면 된다.</p>
<p>그 이후 IAM 유저를 생성해주고 액세스 키와 비밀 액세스 키를 저장해줘야한다. 나중에 깃헙에 넣어야되기 때문!</p>
<br>
<h2 id="소감"><a class="anchor" href="#소감">소감</a></h2>
<p><img src="/content/240618/3.webp" alt="3.webp" loading="lazy" decoding="async"></p>
<br>
<p>이러면 배포는 된다. 하지만.. 너무 넘어야 할 산이 많다. 아직 도커 컨테이너와 이미지를 다루기도 어렵고, yaml 파일을 어떻게 작성하는지도.. 코드를 좀 더 살펴보아야겠다!</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>DevOps</category>
            <category>대외활동</category>
        </item>
        <item>
            <title><![CDATA[비전공 프론트엔드 2년차 개발자 멘티 일지: 6주차 - 보기좋은 코드 만들기]]></title>
            <link>https://hooninedev.com/240612</link>
            <guid isPermaLink="false">https://hooninedev.com/240612</guid>
            <pubDate>Wed, 12 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[한 주 소감 및 복습 내용 힘들게 1차 과제를 마무리했다. 재사용성과 가독성에 대한 고민을 더 해보라는 피드백으로 코드를 열심히 만져보았다. 격렬하게 다루었던 커밋 기록은 여기를 확인해보시면 됩니다. 평소라면 생각해보지 않은, 상태관리 변경 시점과 브라우저 동작방식 등에 대해서 자바스크립트로 코드를 작성하며 구현해보았다. 구현하기 너무 어려워서 구글링을 해...]]></description>
            <content:encoded><![CDATA[<br>
<h2 id="한-주-소감-및-복습-내용"><a class="anchor" href="#한-주-소감-및-복습-내용">한 주 소감 및 복습 내용</a></h2>
<p>힘들게 1차 과제를 마무리했다. 재사용성과 가독성에 대한 고민을 더 해보라는 피드백으로 코드를 열심히 만져보았다.</p>
<p>격렬하게 다루었던 커밋 기록은 <a href="https://github.com/jiji-hoon96/mentoring/pull/2">여기를 확인해보시면 됩니다.</a></p>
<p>평소라면 생각해보지 않은, 상태관리 변경 시점과 브라우저 동작방식 등에 대해서 자바스크립트로 코드를 작성하며 구현해보았다. 구현하기 너무 어려워서 구글링을 해본 결과 좋은 자료들도 발견하게 되어 공유 드리니 확인해보시면 좋겠다!</p>
<ul>
<li><a href="https://junilhwang.github.io/TIL/Javascript/Design/Vanilla-JS-Store/#_1-%E1%84%8C%E1%85%AE%E1%86%BC%E1%84%8B%E1%85%A1%E1%86%BC-%E1%84%8C%E1%85%B5%E1%86%B8%E1%84%8C%E1%85%AE%E1%86%BC%E1%84%89%E1%85%B5%E1%86%A8-%E1%84%89%E1%85%A1%E1%86%BC%E1%84%90%E1%85%A2%E1%84%80%E1%85%AA%E1%86%AB%E1%84%85%E1%85%B5">황준일님의 상태관리 시스템 만들기</a></li>
<li><a href="https://www.daleseo.com/js-history-api/">DaleSeo의 History API</a></li>
</ul>
<br>
<p>멘토링을 진행하면서 대답하지 못했거나, 더 공부가 필요한 것들에 관해 공부를 시작해보자! <del>도대체 자바스크립트 킹짱맨은 언제 될 수 있는 건데..ㅠㅠ</del></p>
<p><img src="/content/240612/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<br>
<h3 id="버블링과-캡처링"><a class="anchor" href="#버블링과-캡처링">버블링과 캡처링</a></h3>
<p><img src="/content/240612/3.png" alt="3.png" loading="lazy" decoding="async"></p>
<p>HTML 문서의 각 엘리먼트들은 <strong>계층적 구조</strong>로 이루어져있다. 이러한 구조 때문에 HTML 요소에 이벤트가 발생할 경우 <strong>연쇄적 이벤트 흐름</strong>이 일어날 수 있다.</p>
<p>이렇게 HTML 요소에 이벤트가 발생할 경우, 이벤트가 연쇄적으로 일어나는 현상을 <strong>이벤트 전파</strong>라 부르며, 전파 방향에 따라 <strong>버블링</strong>과 <strong>캡처링</strong>으로 구분한다.</p>
<ul>
<li>버블링(Bubbling) : 자식 요소에서 발생한 이벤트가 바깥 부모 요소로 전파 (기본값)</li>
<li>캡쳐링(Capturing) : 자식 요소에서 발생한 이벤트가 부모 요소부터 시작하여 안쪽 자식 요소까지 도달</li>
</ul>
<p>추가적으로 이벤트 전파 흐름은 캡처링, 버블링 단계가 동시에 발생한다. 추가로 실제 타깃 요소에게 전달되어 실행되는 <strong>타깃 단계</strong>까지 함께 이벤트가 발생한다.</p>
<p>이벤트 전파와 관련된 이벤트 흐름제어에 대한 내용은 알아볼 것이 많다.</p>
<p>하지만 나는 찾아보면서 이해했기때문에.. 자료가 필요하신 분들은 <strong><a href="https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EB%B2%84%EB%B8%94%EB%A7%81-%EC%BA%A1%EC%B3%90%EB%A7%81">Inpa Dev의 한눈에 이해하는 이벤트 흐름 제어 (버블링 &#x26; 캡처링)</a></strong> 을 확인해보면 좋을 듯 하다.</p>
<br>
<h3 id="jsx가-어떤-방식으로-동작할까"><a class="anchor" href="#jsx가-어떤-방식으로-동작할까">JSX가 어떤 방식으로 동작할까?</a></h3>
<p><img src="/content/240612/4.png" alt="4.png" loading="lazy" decoding="async"></p>
<p>기본적으로 JSX는 브라우저에서 직접 실행되지 않기 때문에, JavaScript로 변환된다. 이 작업은 <strong>Babel과 같은 트랜스파일러</strong>가 수행하는데, Babel은 JSX구문을 <code>React.createElement</code> 호출로 변환한다.</p>
<p>그 이후 변환된 React 엘리먼트를 사용해 Virtual Dom을 구성하고, 구성된 Virtual Dom은 UI를 업데이트하는데 사용이 된다.</p>
<p>Virtual Dom은 실제 DOM과의 차이를 계산하고, 필요한 부분을 업데이트하게 된다. 이 과정에서 저번에 공부한 <a href="https://hooninedev.com/240604/#diffing-algorithm">Diffing Algorism</a>이 사용된다.</p>
<p>자세한 내용은 React 공식 홈페이지에 잘 기록되어 있으니 <a href="https://ko.legacy.reactjs.org/docs/introducing-jsx.html">참고자료</a>를 확인하시길 바란다.</p>
<br>
<h2 id="6주차-회고"><a class="anchor" href="#6주차-회고">6주차 회고</a></h2>
<p>스터디 과제들이 많이 딥해지기전에 좋은 코드란? 재사용성이 좋은 코드란? 직관적인 코드란? 이라는 생각을 중점으로 작업을 진행했다. <strong>결과적으로 좋은 피드백을 받아서 기분이 좋다!</strong></p>
<p><img src="/content/240612/2.jpeg" alt="2.jpeg" loading="lazy" decoding="async"></p>
<p>그 중 가장 고민을 한 것은 아래 코드를 하나로 합쳐야 하느냐.. 분리해서 명확하게 하느냐였는데! 이에 대해 좋은 리뷰를 받아 공유한다!</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> incrementTaskCount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">taskType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (taskCounts.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">hasOwnProperty</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(taskType)) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    taskCounts[taskType]</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">++</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> decrementTaskCount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">taskType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (taskCounts.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">hasOwnProperty</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(taskType)) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    taskCounts[taskType]</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">--</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre></figure>
<p>첫 번째 해결 방법</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> *</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> as</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> actionType </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "./taskManger"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">actionType.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">incrementTaskCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(taskType);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">actionType.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">decrementTaskCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(taskType);</span></span></code></pre></figure>
<p>두 번째 해결 방법</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> taskManger</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> incrementTaskCount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">taskType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (taskCounts.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">hasOwnProperty</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(taskType)) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    taskCounts[taskType]</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">++</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> decrementTaskCount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">taskType</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (taskCounts.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">hasOwnProperty</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(taskType)) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    taskCounts[taskType]</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">--</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">   incrementTaskCount,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">   decrementTaskCount</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> controlTask</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> taskManger</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(); </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TaskManger</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">controlTask.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">incrementTaskCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""> </span></code></pre></figure>
<p>어떤 방법을 사용해도 문제가 없지만, 연산에 대한 과정이 다양해질 수 있고, 복잡해질 수 있다고 생각하기에 코드의 특성을 잘 파악해서 구조화 하는 것이 중요하다는 생각이 들었다!</p>
<br>
<h3 id="이번-주-좋은-것과-나쁜-것"><a class="anchor" href="#이번-주-좋은-것과-나쁜-것">이번 주 좋은 것과 나쁜 것</a></h3>
<ul>
<li>약속이 많이 없어서 공부할 여유가 생겼습니다.</li>
</ul>
<br>
<h3 id="이번-주-진행했던-학습개발-내용은"><a class="anchor" href="#이번-주-진행했던-학습개발-내용은">이번 주 진행했던 학습/개발 내용은??</a></h3>
<ul>
<li>이벤트 루프 코드 개선하려 리팩토링했습니다.</li>
<li>도커 실습으로 공부하고있습니다.</li>
<li>navigator 만들면서, 메서드체이닝, location 등을 고민하며 작업했습니다.</li>
</ul>
<br>
<h3 id="가장-고민했던-부분은-무엇이었나요"><a class="anchor" href="#가장-고민했던-부분은-무엇이었나요">가장 고민했던 부분은 무엇이었나요?</a></h3>
<ul>
<li>이력서를 수정해야하는데, 시간을 계속 못내고있어서.. 진행할 예정입니다.</li>
</ul>
<br>
<h3 id="아쉬운-부분을-개선하는-데-필요한-것은-무엇인가요"><a class="anchor" href="#아쉬운-부분을-개선하는-데-필요한-것은-무엇인가요">아쉬운 부분을 개선하는 데 필요한 것은 무엇인가요?</a></h3>
<ul>
<li>테스트코드 작성이 너무 어렵습니다. 공식문서 보면서 메서드 사용하는건 어렵지 않은데, 큰 프로젝트의 기본설정이 어렵습니다</li>
</ul>
<br>
<h3 id="다음-주는-어떻게-보낼-예정인가요"><a class="anchor" href="#다음-주는-어떻게-보낼-예정인가요">다음 주는 어떻게 보낼 예정인가요?</a></h3>
<ul>
<li>토스플랫폼 공채 지원할 것입니다.</li>
<li>레몬베이스 7월초에 과제테스트 진행하기로했습니다.</li>
<li>인프런 커피챗 예정입니다.</li>
</ul>
<br>
<h2 id="출처-및-도움되는-링크들"><a class="anchor" href="#출처-및-도움되는-링크들">출처 및 도움되는 링크들</a></h2>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>멘토링</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[DevOps의 D도 어려운 아기 개발자의 험난한 Docker 스터디]]></title>
            <link>https://hooninedev.com/240607</link>
            <guid isPermaLink="false">https://hooninedev.com/240607</guid>
            <pubDate>Fri, 07 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[회사에서 요즘 프론트개발자들이 DevOps 작업을 하는 것을 권장하고있다. 그래서 급하게 배포환경도 변경해보고, 자동화 배포도해보았다. 그러던 도중 이론이 너무 부족하다고 생각했는데, 현재 참여하고있는 SIPE 동아리에서 나와 비슷한 니즈가 있는 사람들과 함께 Docker와 DevOps에 대해 스터디 진행하기로 했다. 그런 내용들을 조금씩 기록해보도록 하겠...]]></description>
            <content:encoded><![CDATA[<p>회사에서 요즘 프론트개발자들이 DevOps 작업을 하는 것을 권장하고있다. 그래서 급하게 배포환경도 변경해보고, 자동화 배포도해보았다. 그러던 도중 이론이 너무 부족하다고 생각했는데, 현재 참여하고있는 SIPE 동아리에서 나와 비슷한 니즈가 있는 사람들과 함께 Docker와 DevOps에 대해 스터디 진행하기로 했다. 그런 내용들을 조금씩 기록해보도록 하겠다!</p>
<h2 id="따라배우는-docker"><a class="anchor" href="#따라배우는-docker">따라배우는 Docker</a></h2>
<p><img src="/content/240607/44.png" alt="44.png" loading="lazy" decoding="async"></p>
<p>우선 사내에서 Docker를 사용하고 있는데 왜 사용하는지 모름. 귀여운 고래다. 끝</p>
<blockquote>
<p><del>(필자는 귀여운게 싫다, 귀여운 것들은 이유가 있고 까볼수록 해악스럽기에)</del></p>
</blockquote>
<p>그러던 와중 올리브영에서 진행된 <a href="https://docs.google.com/presentation/d/1-7N_Ryb2HvQuz9VUT1OYOVouOEYzuuhgAR8zZR1bgSs/edit?usp=sharing">AWSKRUG의 Next.js와 AWS ECS, CI/CD</a> 행사에 다녀왔고, Git Action와 CDN까지 흐름을 살펴보는 시간을 가졌습니다</p>
<p>첫주에는 Docker가 뭐고, 왜 사용해야 하며, 기본적인 개념에는 어떤 것이 있는지 살펴보겠습니다.</p>
<br>
<h2 id="docker를-왜-써"><a class="anchor" href="#docker를-왜-써">Docker를 왜 써?</a></h2>
<p>간단하게 이해를 하면, 어떠한 프로그램을 다운로드하는 과정을 간단하게 만들기 위해서 사용한다.</p>
<p>Docker를 사용하지 않고 프로그램을 설치할 때는 <strong>Installer 설치 => Installer 실행 => 프로그램 설치 완료</strong> 순서로 진행되지만, 서버, 패키지 버전, 운영체제 등등 여러 가지 에러를 핸들링하게 되고, 설치 과정이 복잡하다.</p>
<p>하지만 Docker를 사용하면 과정이 간단해진다!</p>
<p>ex) 우리는 Redis를 설치할 때 아래의 명령어를 실행한다. 하지만 곧 오류를 마주하게 된다. Why?? wget이 없기때문에..</p>
<p><img src="/content/240607/11.png" alt="11.png" loading="lazy" decoding="async"></p>
<p>하지만 킹왕짱 Docker를 사용하면? <strong>docker run -it redis</strong> 를 하면 다운이 끝난다 ㅋㅋ</p>
<br>
<p>그래서 Docker를 사용하는 이유를 정리하면 아래와 같다.</p>
<ul>
<li>
<p><strong>환경 일관성 유지</strong> : Docker를 사용하면 개발 환경, 테스트 환경, 프로덕션 환경 간의 일관성을 유지할 수 있어, 로컬에서 테스트한 코드가 실제 배포 환경에서도 동일하게 동작함을 보장할 수 있다.</p>
</li>
<li>
<p><strong>의존성 관리</strong> : Docker 컨테이너는 애플리케이션이 필요한 모든 라이브러리와 종속성을 포함하므로, 다른 환경에서 발생할 수 있는 의존성 충돌 문제를 해결할 수 있다.</p>
</li>
<li>
<p><strong>이식성</strong> : Docker 컨테이너는 특정 운영체제나 클라우드 서비스에 종속되지 않아서 어디서나 실행될 수 있고, 애플리케이션을 배포할 수 있다.</p>
</li>
<li>
<p><strong>확장성</strong> : 컨테이너 기반 애플리케이션은 쉽게 확장할 수 있다. 여러 컨테이너를 실행하여 부하를 분산시키고, 필요에 따라 자원을 할당할 수 있다.</p>
</li>
<li>
<p><strong>버전 관리</strong> : Docker 이미지는 버전 관리가 가능하여, 특정 버전의 이미지를 쉽게 롤백하거나 배포할 수 있다.</p>
</li>
<li>
<p><strong>CI/CD 통합</strong>: Docker는 CI/CD(지속적 통합/지속적 배포) 파이프라인과 통합되어, 코드 변경 사항을 자동으로 테스트하고 배포할 수 있다.</p>
</li>
</ul>
<br>
<h2 id="그래서-docker가-머야"><a class="anchor" href="#그래서-docker가-머야">그래서 Docker가 머야?</a></h2>
<p>Docker은 컨테이너를 사용하여 응용프로그램을 더 쉽게 만들고 배포하고 실행할 수 있도록 설계된 도구로 컨테이너 기반의 오픈소스 가상화 플랫폼이며 생태계다.</p>
<p>모르겠다. 쉽게 생각해보면 오픈소스로 컨테이너라는 것을 사용해 배포를 쉽게해주는 것이라 생각한다.</p>
<p>그래서 컨테이너가 뭐야?</p>
<p><img src="/content/240607/22.png" alt="22.png" loading="lazy" decoding="async"></p>
<p>위 내용은 공식문서 발췌한 내용이다. 요약을 해보면 다양한 프로그램, 실행 환경을 컨테이너로 추상화하고 동일한 인터페이스를 제공하여 프로그램의 배포 관리를 단순하게 해주는 소프트웨어의 표준 단위다.</p>
<p>공식문서를 더 살펴보면, 컨테이너 내용과 함께 이미지라는 것도 있다. 이미지는 또 뭘까?</p>
<blockquote>
<p><del>신원이가 이미지 이야기할 때 나는 gif, jpeg 이런 이야기하고 ㅋㅋ 픽셀 중얼중얼 했다. ㅋㅋ 아우 챙피해</del></p>
</blockquote>
<p>이미지는 런타임에 컨테이너가 된다. 기본적인 개념은 코드. 런타임, 시스템 도구, 시스템 라이브러리 및 설정과 같은 응용 프로그램을 실행하는데 필요한 모든 것을 포함하는 가볍고 독립적이며 실행 가능한 소프트웨어 패키지다.</p>
<p>또 한줄요약 해보면, <strong>docker 이미지는 프로그램을 실행하기 위한 설정, 종속성을 가지고 있고, 이미지를 이용해 컨테이너를 생성하며 컨테이너를 이용해 프로그램을 실행한다</strong></p>
<br>
<h2 id="docker-run-hello-world"><a class="anchor" href="#docker-run-hello-world">docker run hello-world</a></h2>
<p>docker를 실행해보면 어떤 일이 일어날지 한번 확인해보자!</p>
<ol>
<li><code>docker run hello-world</code>를 입력하면 클라이언트에서 docker server로 요청을 보낸다.</li>
<li>docker 이미지가 이미 로컬에 cache가 되어있는지 확인한다.</li>
<li>현재는 없기에 <strong>Unable to find image~~</strong> 문구가 출력된다.</li>
<li>만약 있으면 캐시된 이미지를 이용해 컨테이너를 만든 후 프로그램이 실행된다.</li>
</ol>
<br>
<h2 id="추가로-컨테이너-가상화-기술에-대해"><a class="anchor" href="#추가로-컨테이너-가상화-기술에-대해">추가로 컨테이너 가상화 기술에 대해</a></h2>
<p>가상화 기술이 나오기 전에는 한대의 서버, 하나의 용도로만 사용해 서버 공간이 방치되었다. 안정적이지만 효율성이 떨어진다. 그래서 나온 하이퍼 바이저 기반의 가상화가 출현되었다.</p>
<p>공간을 분할하여 VM이라는 독립적인 가상 환경의 서버를 이용하고, 하이퍼 바이저는 호스트 시스템에서 다수의 게스트 OS를 구동할 수 있게 하는 소프트웨어, 그리고 하드웨어를 가상화하면서 하드웨어와 각각의 VM을 모니터링하는 중간 관리자다.</p>
<p>너~~ 무 어렵다.. 가난히 이해하면 하이퍼 바이저는 하드웨어를 직접 제어해서 자원 효율성이 높고, 오버헤드가 적다. 하지만 하드웨어 드라이버를 세팅이 힘들 수 있다</p>
<br>
<p>이런 가상화 기술에서 나온 컨테이너 가상화 기술 도커는 하드웨어에서 격리된 환경 내에 애플리케이션을 배치하는 방법이다.</p>
<br>
<h2 id="가상-머신과-도커-컨테이너의-차이점은"><a class="anchor" href="#가상-머신과-도커-컨테이너의-차이점은">가상 머신과 도커 컨테이너의 차이점은</a></h2>
<p>도커 컨테이너에서 돌아가는 애플리케이션은 컨테이너가 제공하는 격리 기능 내부에 샌드박스가 있지만, 같은 호스트의 다른 컨테이너와 동일한 커널을 공유한다. 결과적으로 컨테이너 내부에서 실행되는 프로세스는 호스트 시스템에서 볼 수 있다.</p>
<p>가상 머신과 함께 VM 내부에서 실행되는 모든 것은 호스트 운영 체제 또는 하이퍼바이저와 독립되어있다. 가상머신 플랫폼은 특정 VM에 대한 가상화 프로세스를 관리하기 위해 프로세스를 시작하고, 호스트 시스템은 그것의 하드웨어 일부를 VM에 할당한다. 그렇기에 VM이 더 커지게 되고..</p>
<p>간단한 예시를 생각해보면 맥에서 윈도우를 설치하거나, 리눅스에서 윈도우를 돌리거나.. 간단하지만 속도가 엄청 느리겠지?</p>
<br>
<h2 id="1주차-소감"><a class="anchor" href="#1주차-소감">1주차 소감</a></h2>
<p>어렵다 너무많이 어렵다!!!... ㅠㅠ 이론이 안잡히고 실습하려하니 너무 어려워서 첫주에는 간단한 이론에 대해 공부해보았다. 해보니 도커가 조금은 귀여운 고래로 보인다. ㅋㅋㅋ 화이팅!!</p>
<p><img src="/content/240607/33.jpeg" alt="33.jpeg" loading="lazy" decoding="async"></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>DevOps</category>
            <category>대외활동</category>
        </item>
        <item>
            <title><![CDATA[비전공 프론트엔드 2년차 개발자 멘티 일지: 5주차 - 회사 & 지구런 & History API]]></title>
            <link>https://hooninedev.com/240604</link>
            <guid isPermaLink="false">https://hooninedev.com/240604</guid>
            <pubDate>Tue, 04 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[한 주 소감 및 복습 내용 | | | | ----------------- | ----------------- | 나 마라톤했다? 10K, 나 회사가 엄청 바빳다, 그리고 Event Loop도 만들었고, History API를 열심히 만들고있다! 추가로 공부할 내용들은 아래와 같다. 상태관리 라이브러리를 내부적으로 구현한다고 가정했을 때 상태는 어떻게 트리거...]]></description>
            <content:encoded><![CDATA[<br>
<h2 id="한-주-소감-및-복습-내용"><a class="anchor" href="#한-주-소감-및-복습-내용">한 주 소감 및 복습 내용</a></h2>
<table>
<thead>
<tr>
<th><img src="/content/240604/2.jpeg" alt="2.jpeg" loading="lazy" decoding="async"></th>
<th><img src="/content/240604/3.jpeg" alt="3.jpeg" loading="lazy" decoding="async"></th>
</tr>
</thead>
</table>
<br>
<p>나 마라톤했다? 10K, 나 회사가 엄청 바빳다, 그리고 Event Loop도 만들었고, History API를 열심히 만들고있다!</p>
<p>추가로 공부할 내용들은 아래와 같다.</p>
<ul>
<li>상태관리 라이브러리를 내부적으로 구현한다고 가정했을 때 상태는 어떻게 트리거하고 UI 반영하게 할 수 있을지
<ul>
<li>observer와 flux 구조, re-render 시킨다면 어떻게 접근할지</li>
</ul>
</li>
<li>react-router-dom을 구현한다고 가정했을 때 인터페이스를 어떻게 설계할지</li>
<li>Router guard, <Link /> <Router /> 는 각각 어떻게 구현할 수 있을지</li>
<li><del>composite Layers</del></li>
<li><del>React Virtual Dom과 탐색 알고리즘</del></li>
<li><del>브라우저의 Javascript 엔진이 스크립트를 어떻게 파싱하고 실행하는지</del></li>
</ul>
<p>추가로 이번주에 react-router가 어떻게 동작하는지 만들어보고, 분석해보자. 아 그리고 프레임워크와 라이브러리 차이와 CSR과 SSR의 차이를 복습해보자!</p>
<br>
<p><img src="/content/240604/1.jpeg" alt="1.jpeg" loading="lazy" decoding="async"></p>
<br>
<h3 id="composite-layers"><a class="anchor" href="#composite-layers">Composite Layers</a></h3>
<p>Reflow, Repaint가 일어나면 아래 과정이 진행된다.</p>
<ol>
<li>Recalculate Style : 요소에 적용할 스타일이 계산된다.</li>
<li>Layout : 요소의 레이아웃을 생성되고, 요소들은 벡터 박스로만 표현된다.</li>
<li>Paint : 생성된 모든 벡터 박스들을 픽셀로 변환하고, 레이어에 올린다.</li>
<li>Composite Layers : 생성한 레이어 계층을 합성하고 화면에 나타내고, 모든 요소가 고유한 위치를 갖는 완전한 웹 페이지로 보여진다.</li>
</ol>
<p>Composite Layers 생성이란? CPU가 애니메이션을 처리하기 위해 GPU와 통신하는 단계로 각각의 layer를 GPU 메모리에 bitmap 형태의 texture로 저장하고 composition layer 작업시 GPU의 메모리에서 관련 작업을 진행하는 것을 의미한다.</p>
<p>Composite Layers를 사용하면 성능이 향상되고, 부드러운 애니메이션으로 시각적 효과를 받고, 리페인트 최소화되어 성능을 최적화할 수 있다.</p>
<p>대표적인 예시로는 3D 변환(transform, translate3d 등), css 애니메이션 및 트랜지션, opacity 속성 변환 등이 있다.</p>
<br>
<h3 id="diffing-algorithm"><a class="anchor" href="#diffing-algorithm">Diffing Algorithm</a></h3>
<p>Diffing Algorithm(비교 알고리즘)이란? React에서 두 개의 트리를 비교할 때 두 엘리먼트의 루트부터 비교한 후, 루트 타입에 따라 트리를 구축하는 방식의 알고리즘이다.</p>
<p>React에서 Diffing Algorithm을 다루는 것은 아래 3가지 상황이 있다.</p>
<ol>
<li>엘리먼트 타입이 다르게 되면, 이전 트리를 버리고 새로운 트리를 구축한다. 그렇게 되면 하위 state도 사라진다.</li>
<li>DOM의 엘리먼트 타입이 같고 속성이 다르면, 엘리먼트 속성을 확인해 동일 내역만 유지하고 변경된 속성만 갱신한다.</li>
<li>자식 요소가 바뀌면, 자식들을 재귀적으로 처리한다. 해당하는 문제는 key prop을 통해 해결할 수 있다.</li>
</ol>
<br>
<blockquote>
<p>key props는 우리가 다양한 상황에 사용한다. map을 이용해서 리스트를 보여줄 때 많이 사용하는데, key 속성을 통해 해당 key가 존재하는지 확인한 후 추가된 요소만을 반영하기 위해 사용된다.</p>
<p>배열의 인덱스를 key로 사용할 수 있겠지만, 배열이 다르게 정렬되더라고 인덱스는 변하지 않을 수 있어, 예상과 다른 동작 할 수 있다. 또한 key를 사용하지 않을 때와 같이 모든 형제 요소들과 함께 재랜더링이 발생할 수 있다.</p>
<p>그렇기에 고유한 값이 될 수 있도록 별도의 id값을 주는 것이 좋다.</p>
</blockquote>
<br>
<p>그러면 Diffing Algorith을 왜 사용하게 되었을까?</p>
<p>기존의 DOM 트리를 새로운 트리로 변환하기 위하여 최소한의 연산을 하는 알고리즘을 사용한다. 이때 알아낸 조작 방식은 알고리즘 <strong>O(n^3)</strong> 의 복잡도를 가지고 있다. 만약,이 알고리즘을 React에 적용한다면, 1000개의 엘리먼트가 있다는 가정하에 실제 화면에 표시하기 위해 1000^3인 10억번의 비교 연산을 해야한다. 이는 너무 비싼 연산이기에 React는 두 가지 가정을 가지고 시간 복잡도 <strong>O(n)의 새로운 Heuristic Algorithm을 구현했다.</strong></p>
<br>
<h3 id="브라우저의-javascript-엔진이-스크립트를-어떻게-파싱하고-실행할까"><a class="anchor" href="#브라우저의-javascript-엔진이-스크립트를-어떻게-파싱하고-실행할까">브라우저의 Javascript 엔진이 스크립트를 어떻게 파싱하고 실행할까?</a></h3>
<p>우선 자바스크립트 엔진으로는 Google의 V8 엔진(Chrome과 Node.js에서 사용), Mozilla의 SpiderMonkey(Firefox에서 사용), Apple의 JavaScriptCore(Safari에서 사용) 등이 있다.</p>
<p>자바스크립트 엔진은 예시코드를 이용해 파싱과 실행에 대해 알아보자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> add</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">a</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">b</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> a </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> b;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">add</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">/*</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">소스 코드 다운로드: 브라우저가 위 코드를 다운로드</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">렉싱과 파싱: function, add, (, a, ,, b, ), {, return, a, +, b, }, console, ., log, (, add, (, 2, ,, 3, ), ) 토큰으로 분할된 후 AST로 변환</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">AST 최적화: add 함수의 호출을 최적화</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">바이트코드 생성: 최적화된 AST가 바이트코드로 컴파일</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">실행: add 함수가 호출되고, 결과가 console.log로 출력</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">JIT 컴파일: add 함수가 반복적으로 호출되면 기계어로 컴파일되어 성능이 최적화</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">*/</span></span></code></pre></figure>
<ol>
<li>
<p>브라우저가 웹 페이지를 로드할 때, HTML 파일에 포함된 <code>&#x3C;script></code> 태그나 외부 JavaScript 파일을 발견하면 해당 JavaScript 코드를 다운로드</p>
</li>
<li>
<p><strong>파싱</strong>은 소스 코드를 읽어들여 이해 가능한 데이터 구조로 변환하는 과정으로 2개의 단계로 나누어진다.</p>
<ul>
<li>
<p><strong>렉싱 (Lexical Analysis)</strong> : 소스 코드가 토큰(Tokens)이라는 작은 단위로 분할되는데, 각 토큰은 키워드, 변수 이름, 연산자, 숫자, 문자열 등 코드의 가장 작은 의미 단위다.</p>
</li>
<li>
<p><strong>파싱 (Syntax Analysis)</strong> : 토큰들을 추상 구문 트리(AST: Abstract Syntax Tree)로 변환하는데, AST는 코드의 문법 구조를 트리 형태로 표현한 것이다. 각 노드는 코드의 요소(예: 함수, 변수, 연산자 등)를 나타낸다.</p>
</li>
</ul>
</li>
<li>
<p>생성된 <strong>AST는 최적화단계를 거친다.</strong> 이 과정에서는 코드의 성능을 향상시키기 위한 다양한 최적화가 적용된다. 예를 들어, 불필요한 코드를 제거하거나 반복되는 패턴을 단순화하는 작업이 수행된다.</p>
</li>
<li>
<p>최적화된 AST는 바이트코드(JavaScript 엔진이 직접 실행할 수 있는 중간 형태의 코드)로 컴파일된다. 바이트코드는 실제 기계어가 아니지만, 더 낮은 수준의 코드로 변환된 것이다.</p>
</li>
<li>
<p>JavaScript 엔진은 생성된 바이트코드를 실행하는데, 최신 JavaScript 엔진은 바이트코드를 실행하는 과정에서도 다양한 최적화 기법을 사용한다.</p>
</li>
<li>
<p><strong>JIT 컴파일 (Just-In-Time Compilation)은</strong> 바이트코드를 실행하면서 성능이 중요한 부분을 탐지하여 해당 부분을 네이티브 기계어로 컴파일한다. 이를 통해 반복적으로 실행되는 코드는 더욱 빠르게 실행된다.</p>
</li>
</ol>
<br>
<h2 id="5월-마지막-주--6월-첫째-주-회고"><a class="anchor" href="#5월-마지막-주--6월-첫째-주-회고">5월 마지막 주 &#x26; 6월 첫째 주 회고</a></h2>
<br>
<h3 id="이번-주-좋은-것과-나쁜-것"><a class="anchor" href="#이번-주-좋은-것과-나쁜-것">이번 주 좋은 것과 나쁜 것</a></h3>
<ul>
<li>화요일 멘토링 이후에 사내 코드에 큰 결함 있어서, 시간을 많이 할애하지 못했습니다.</li>
</ul>
<br>
<h3 id="이번-주-진행했던-학습개발-내용은"><a class="anchor" href="#이번-주-진행했던-학습개발-내용은">이번 주 진행했던 학습/개발 내용은??</a></h3>
<ul>
<li>이벤트 루프 만들었습니다</li>
<li>사내에서 기존의 코드 리팩토링과 기능 수정 위주로 작업했습니다</li>
<li>SPA 과제 진행했습니다</li>
</ul>
<br>
<h3 id="가장-고민했던-부분은-무엇이었나요"><a class="anchor" href="#가장-고민했던-부분은-무엇이었나요">가장 고민했던 부분은 무엇이었나요?</a></h3>
<ul>
<li>html 내부에서 router을 어떻게 만들어야 할까 고민했습니다.</li>
<li>카카오 아티클을 참조해서 router을 만들어 봤는데, 경로 설정하는 부분에서 해결하지 못했습니다.</li>
</ul>
<br>
<h3 id="아쉬운-부분을-개선하는-데-필요한-것은-무엇인가요"><a class="anchor" href="#아쉬운-부분을-개선하는-데-필요한-것은-무엇인가요">아쉬운 부분을 개선하는 데 필요한 것은 무엇인가요?</a></h3>
<ul>
<li>약속 취소하고, 시간 많이 할애하겠습니다ㅠ...</li>
</ul>
<br>
<h3 id="다음-주는-어떻게-보낼-예정인가요"><a class="anchor" href="#다음-주는-어떻게-보낼-예정인가요">다음 주는 어떻게 보낼 예정인가요?</a></h3>
<ul>
<li>이력서 수정</li>
<li>History API 공부</li>
<li>그 이후부터는 사내 업무인 devops 공부, jest를 이용한 테스트코드 작성에 관해 공부 할 것입니다.</li>
<li>완성하지 못한 SPA 마무리도 계획 중입니다.</li>
</ul>
<br>
<h2 id="출처-및-도움되는-링크들"><a class="anchor" href="#출처-및-도움되는-링크들">출처 및 도움되는 링크들</a></h2>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>멘토링</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[이벤트 루프(Event Loop) 동작 원리 실습 with 자바스크립트로 만든 인터랙티브 사이트]]></title>
            <link>https://hooninedev.com/240603</link>
            <guid isPermaLink="false">https://hooninedev.com/240603</guid>
            <pubDate>Mon, 03 Jun 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[화면 UI는 yiseo0님의 오픈소스를 참고했습니다. 감사합니다! 이런게 더 있으면 좋겠다 하거나, 변경이 필요한 것들은 댓글로 적어주세요!! 왜 만들었어!? JavScript을 이용해 Event Loop를 동작해보는 사이트를 만들어보았다. 왜!? 그냥 내가 공부하려고!! 우선은 간단하게 만들어보았는데, 점차 리팩토링도 하면서 해당하는 것들에 관해서 공부하...]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>화면 UI는 <a href="https://github.com/yiseo0">yiseo0</a>님의 오픈소스를 참고했습니다. 감사합니다!</p>
</blockquote>
<br>
<br>
<h3>이런게 더 있으면 좋겠다 하거나, 변경이 필요한 것들은 댓글로 적어주세요!!</h3>
<br>
<p><img src="/content/240603/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<h2 id="왜-만들었어"><a class="anchor" href="#왜-만들었어">왜 만들었어!?</a></h2>
<p>JavScript을 이용해 Event Loop를 동작해보는 사이트를 만들어보았다. 왜!? 그냥 내가 공부하려고!!</p>
<p>우선은 간단하게 만들어보았는데, 점차 리팩토링도 하면서 해당하는 것들에 관해서 공부하며 추가할 것이다!!!</p>
<br>
<p><strong>시연해보기 👉 <a href="https://dontcryjihoon.netlify.app/">Event Loop with JavaScript</a></strong></p>
<br>
<p>직접 만들어보고 싶으면 <a href="https://github.com/jiji-hoon96/mentoring/tree/week5-eventLoop/week5-assingment">Github Code를 참조하세요</a></p>
<br>
<p>많은 분이 Event Loop에 관해서 공부했을 것으로 생각한다. 그렇기에 내가 이벤트 루프를 만들기 위해서 어떤 내용을 참조했는지, 링크를 남겨두겠다!</p>
<br>
<h2 id="추후-업데이트-기록"><a class="anchor" href="#추후-업데이트-기록">추후 업데이트 기록</a></h2>
<p>추가로 공부할 내용을 적어보겠다. 우선은 코드를 더 직관적으로 리팩토링할 예정이다.</p>
<br>
<h3 id="63-업로드-완료"><a class="anchor" href="#63-업로드-완료">6/3 업로드 완료</a></h3>
<p><img src="/content/240603/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<p>1차 배포 및 업로드 완료!!!</p>
<br>
<h2 id="출처-및-도움되는-링크들"><a class="anchor" href="#출처-및-도움되는-링크들">출처 및 도움되는 링크들</a></h2>
<ul>
<li><a href="https://hooninedev.com/240525/">Promise를 만들어보자</a></li>
<li><a href="https://hooninedev.com/230816/">JavaScript 동기와 비동기 완벽 가이드</a></li>
<li><a href="https://inpa.tistory.com/entry/%F0%9F%94%84-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84-%EA%B5%AC%EC%A1%B0-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC">Inpa Dev</a></li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[비전공 프론트엔드 2년차 개발자 멘티 일지: 4주차 - 대외활동과 개인 스터디]]></title>
            <link>https://hooninedev.com/240528</link>
            <guid isPermaLink="false">https://hooninedev.com/240528</guid>
            <pubDate>Tue, 28 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[한 주 소감 및 복습 내용 많은 것을 했고, 많은 것을 하고 있따.. 이것저것 만들고, 공부의 깊이는 더욱 깊어져 주제마다 별도의 페이지로 작성하겠다! 아자아자 화이팅!! 5월 넷째 주 회고 해커톤을 했습니다. 저번에는 참가자로 이번에는 운영진으로 경험해보았습니다. 사람들과 시간이 비어있을 때, CI/CD와 Docker 에 대해 이야기를 나누었습니다. 토요...]]></description>
            <content:encoded><![CDATA[<br>
<h2 id="한-주-소감-및-복습-내용"><a class="anchor" href="#한-주-소감-및-복습-내용">한 주 소감 및 복습 내용</a></h2>
<p>많은 것을 했고, 많은 것을 하고 있따.. 이것저것 만들고, 공부의 깊이는 더욱 깊어져 주제마다 별도의 페이지로 작성하겠다!</p>
<p><img src="/content/240528/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<br>
<p><img src="/content/240528/2.jpeg" alt="2.jpeg" loading="lazy" decoding="async"></p>
<br>
<p>아자아자 화이팅!!</p>
<br>
<h2 id="5월-넷째-주-회고"><a class="anchor" href="#5월-넷째-주-회고">5월 넷째 주 회고</a></h2>
<ul>
<li>해커톤을 했습니다. 저번에는 참가자로 이번에는 운영진으로 경험해보았습니다. 사람들과 시간이 비어있을 때, CI/CD와 Docker 에 대해 이야기를 나누었습니다.</li>
<li>토요일 점심에 크래프톤에서 코딩테스트가 요청되어 진행했습니다. 주관식 3개 알고리즘 3개 진행했는데, 알고리즘 지식이 부족해 어렵게 느꼈습니다.</li>
<li>이팩티브 디버깅 행사에 다녀왔습니다. 디버깅을 위한 접근 방식에 대해 다른 사람들과 경험을 공유해볼 수 있는 시간이었습니다.</li>
<li>공부를 하다 보니 부족하다고 느껴지는 것이 명확해지고 있습니다. 테스트를 다루는 부분과, 회사에서 현재 다루고 있는데 CI/CD에 대한 지식을 넓히려 합니다.</li>
</ul>
<br>
<h3 id="이번-주-좋은-것과-나쁜-것"><a class="anchor" href="#이번-주-좋은-것과-나쁜-것">이번 주 좋은 것과 나쁜 것</a></h3>
<ul>
<li>이번 주에 개인적으로 잡은 약속 없어서 공부할 수 있는 시간을 많이 가져왔습니다.</li>
<li>회사가 너무 바쁩니다. 회사에서 배포와 Docker에 관한 내용을 프론트엔드도 알고 있어야 되고, 더 나아가 분리해서 관리해야겠다는 이야기가 나와서 긴급하게 공부를 시작했습니다.</li>
</ul>
<br>
<h3 id="이번-주-진행했던-학습개발-내용은"><a class="anchor" href="#이번-주-진행했던-학습개발-내용은">이번 주 진행했던 학습/개발 내용은??</a></h3>
<ul>
<li>이번 주에 <strong><a href="https://hooninedev.com/240525/">Promise를 만들어보고</a></strong> 지식을 더해보았습니다.</li>
<li><strong><a href="https://hooninedev.com/240521/">멘토링에서 공부한 내용에서 부족한 것들을 더 채우면서 공부했습니다.</a></strong></li>
<li>이제 막 테스트와 CI/CD에 관해서 공부를 시작했습니다.</li>
<li>멘토링에서 주어진 과제를 진행했습니다.</li>
</ul>
<br>
<h3 id="가장-고민했던-부분은-무엇이었나요"><a class="anchor" href="#가장-고민했던-부분은-무엇이었나요">가장 고민했던 부분은 무엇이었나요?</a></h3>
<ul>
<li>내가 만든 vanilla-javascript를 이용해서 react의 useState를 만들어보기에서 테스트 코드 작성하는 것에 집중했습니다. 하지만 아직 어떻게 테스트를 해야 할지 접근하지 못했습니다.</li>
<li>7개월 전에 회사에서 맡아서 pipeline 적용하는 것에 대해 다시 살펴보았습니다.</li>
<li>요즘 허리통증이 너무 심해져서 병원에 다니기 위해 시간분배를 계획 중입니다.</li>
</ul>
<br>
<h3 id="아쉬운-부분을-개선하는-데-필요한-것은-무엇인가요"><a class="anchor" href="#아쉬운-부분을-개선하는-데-필요한-것은-무엇인가요">아쉬운 부분을 개선하는 데 필요한 것은 무엇인가요?</a></h3>
<ul>
<li>제가 만든 useState를 기반으로 테스트를 진행하는 것을 같이 해보고 싶습니다. 제가 생각한 접근 방법은 mock data를 이용해 기능테스트 해보는 것인데 render 함수를 테스트 환경에서 반영하지 못하고 있습니다.</li>
<li>CI/CD 그리고 Docker에 관한 것과 테스트 공부를 위해 인터넷 강의를 진행 중입니다. 금주 안에 공부해보려 합니다.</li>
</ul>
<br>
<h3 id="다음-주는-어떻게-보낼-예정인가요"><a class="anchor" href="#다음-주는-어떻게-보낼-예정인가요">다음 주는 어떻게 보낼 예정인가요?</a></h3>
<ul>
<li>이벤트 루프 시뮬레이션을 만들고, 관련된 내용을 더 공부할 예정입니다</li>
<li>멘토링 당일에는 코드 리뷰한 것과 부족한 부분 복습할 예정입니다.</li>
<li>매일 CI/CD, 테스트 에 대해서 1시간 ~ 1시간 30분 정도 공부할 예정입니다.</li>
<li>토요일에 마라톤 있습니다.</li>
<li>29일에 Nextjs + CI/CD 관련한 밋업이 무신사에 있어서 다녀올 예정입니다.</li>
</ul>
<br>
<h2 id="출처-및-도움되는-링크들"><a class="anchor" href="#출처-및-도움되는-링크들">출처 및 도움되는 링크들</a></h2>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>멘토링</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[효율적인 디버깅을 위한 팁: Next Step 이펙티브 디버깅 모임 참여 후기]]></title>
            <link>https://hooninedev.com/240527</link>
            <guid isPermaLink="false">https://hooninedev.com/240527</guid>
            <pubDate>Mon, 27 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Next Step의 효율적인 디버깅 모임 Next Step에서 주관하는 효율적인 디버깅을 위한 스터디에 다녀왔다. 코딩을 하면 디버깅은 밥먹듯이 하는 일이라 궁금증을 가지고 행사에 신청했다! 선릉역 테크 살롱에서 행사가 진행되었다. 행사에서는 사람들과 정보를 공유해서 버전1을 만들고, 직접 코드 디버깅을해서 버전2로 수정하고, 최종으로 UI 디버깅을 통해 ...]]></description>
            <content:encoded><![CDATA[<br>
<h2 id="next-step의-효율적인-디버깅-모임"><a class="anchor" href="#next-step의-효율적인-디버깅-모임">Next Step의 효율적인 디버깅 모임</a></h2>
<p><img src="/content/240527/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<p>Next Step에서 주관하는 효율적인 디버깅을 위한 스터디에 다녀왔다. 코딩을 하면 디버깅은 밥먹듯이 하는 일이라 궁금증을 가지고 행사에 신청했다!</p>
<p>선릉역 테크 살롱에서 행사가 진행되었다.</p>
<p>행사에서는 사람들과 정보를 공유해서 버전1을 만들고, 직접 코드 디버깅을해서 버전2로 수정하고, 최종으로 UI 디버깅을 통해 버전3의 나만의 디버깅 환경을 만들자라는 방향성으로 진행되었다.</p>
<br>
<h3 id="내-경험을-공유해보며-버전-1을-만들어보자"><a class="anchor" href="#내-경험을-공유해보며-버전-1을-만들어보자">내 경험을 공유해보며 버전 1을 만들어보자.</a></h3>
<p>평소 디버깅을 하면서 목적없이 작업을 진행한 경험이 대부분이었다. 어떤것을 어떻게 해결하고자 하는 생각은 없이...</p>
<p>행사가 시작되고, 다른 사람들과 디버깅 경험을 공유해보았다. 아래는 내가 디버깅하는 과정이다.</p>
<pre><code>UI 관련한 문제가 발생한다 ⇒ 사내에서 사용하는 UI 프레임워크의 공식문서에 가거나 구글링을 진행한다.

간단한 함수 로직을 작성할 때 기능 구현 오류가 발생하면 수작업으로 console과 debug tool을 이용해 작업을 진행하고 완료가
되면 gpt를 이용해 개선할 사항을 확인 후 반영한다.

타입지정 및 테스트 ⇒ git copilot 을 이용해서 확인해본다.

결론 : sentry 짱짱
</code></pre>
<br>
<h3 id="본격적인-세션-시작"><a class="anchor" href="#본격적인-세션-시작">본격적인 세션 시작</a></h3>
<p><img src="/content/240527/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<p>진행해주시는 분께서 위와 같은 원인을 분석하는 것에 대해 방향을 제시해주었다.</p>
<p>단순하다고 생각하지만, 사소한 디버깅이여도 목적과 해결하는 방법에 대해 생각하는 것이 인상적이었다. (뭐 대부분의 경험에서 버그 발생 => 바로 수정이 대부분인 나였으니..)</p>
<br>
<h3 id="샘플-코드-디버깅에-위-내용을-적용해-버전-2를-만들어보자"><a class="anchor" href="#샘플-코드-디버깅에-위-내용을-적용해-버전-2를-만들어보자">샘플 코드 디버깅에 위 내용을 적용해 버전 2를 만들어보자.</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> flattenArray</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">arr</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> result </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  for</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> i </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; i </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> arr.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">length</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; i</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">++</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (Array.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">isArray</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(arr[i])) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      result </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> result.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">concat</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">flattenArray</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(arr[i]));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      result.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">push</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(arr);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> result;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">flattenArray</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">], [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">4</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">5</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">6</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]]]));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 실제 결과</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// [</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">//  [1, [2, 3], [4, [5, 6]]],</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">//  [2, 3],</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">//  [2, 3],</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">//  [4, [5, 6]],</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">//  [5, 6],</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">//  [5, 6],</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// ];</span></span></code></pre></figure>
<p><strong>1. 내가 어떤 문제를 풀려고 하는지 한 문장으로 적는다.</strong></p>
<ul>
<li>배열 내부의 배열을 모두 평탄화 하는 작업을 하고싶다</li>
</ul>
<br>
<p><strong>2. 올바르게 동작한다면 어떤 일이 벌어져야 하는지 적어본다</strong></p>
<ul>
<li>올바르게 동작하면 [1,2,3,4,5,6]이 된다.</li>
<li>현재는 배열의 조건들이 하나씩 출력이 된다.</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> error 발생</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> []</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">4</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">4</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[[</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">], [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">4</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">5</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">6</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]]] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">4</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">5</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">6</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span></code></pre></figure>
<br>
<p><strong>3. 최소 재현 환경을 구축한다</strong></p>
<ul>
<li>index를 넣어주는 것을 수정해야될 듯?</li>
<li>아니면 현재 코드에서는 concat을 사용했는데 또 다른 javascript method가 없는지 확인해보자</li>
</ul>
<br>
<p><strong>4. 재현 환경에서 벌어지는 일을 관찰하고, 2와는 어떻게 다른지 비교한다</strong></p>
<ul>
<li>현재 코드에서는 concat을 사용하고있는데, concat은 두 개 이상의 배열을 병합할 때 사용되는데 요구사항이랑 다르다.</li>
</ul>
<br>
<p><strong>5. 4번와 2번의 차이를 발생시키는 원인이 될 만한 옵션들을 적어본다</strong></p>
<ul>
<li>concat 또는 index의 for문이 예상하는 것과 다르게 동작함</li>
</ul>
<br>
<p><strong>6. 옵션 중 하나를 골라서 가설을 세우고 검증한다.</strong></p>
<ul>
<li>flat이라는 javascript method를 사용해보자</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> flattenArray</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">arr</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> result </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  result.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">push</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(arr.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">flat</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> result;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<br>
<p>문제가 해결되었다. 하지만 기존의 함수에서 디버깅을 할 수 없을까?</p>
<p>위 과정을 다시 반복해서 기존의 코드에 접근하고 재귀 return문을 아래 처럼 수정해주었다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> flattenArray</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">arr</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> result </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  result.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">push</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(arr.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">flat</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> result;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>해결이 완료되었다.</p>
<br>
<h3 id="위-과정을-통해-배운것"><a class="anchor" href="#위-과정을-통해-배운것">위 과정을 통해 배운것</a></h3>
<p>나는 디버깅을 할 때 접근만 하였다. 목표없이.</p>
<p>그렇기에 왜 문제가 발생했는지? 그리고 현재상황이랑 발생하는 오류에 대한 인지를 확실하게 하고, 내가 생각하는 결과뿐 아니라, 다른 예외에도 대응할 수 있도록 생각해보고 오류에 접근하는 자세가 중요하다는 것을 느꼈다. <strong>추가적으로 다양한 가설을 세우는 것도!</strong></p>
<br>
<h3 id="ui-디버깅으로-버전-3을-만들어보자"><a class="anchor" href="#ui-디버깅으로-버전-3을-만들어보자">UI 디버깅으로 버전 3을 만들어보자.</a></h3>
<p><img src="/content/240527/6.png" alt="6.png" loading="lazy" decoding="async"></p>
<p>위 UI를 디버깅 해보자</p>
<br>
<p><strong>1. 내가 어떤 문제를 풀려고 하는지 한 문장으로 적는다.</strong></p>
<ul>
<li>UI가 정상적으로 동작하도록 구현한다.</li>
</ul>
<br>
<p><strong>2. 올바르게 동작한다면 어떤 일이 벌어져야 하는지 적어본다</strong></p>
<ul>
<li>
<p>header, footer은 상단, 하단에 각각 고정한다.</p>
</li>
<li>
<p>header,footer는 고정높이고, 중간 부분이 전체를 차지하고 있다.</p>
</li>
<li>
<p>중간에서 사이드바와 메인은 좌우로 배치되어 있다.</p>
</li>
<li>
<p>사이드바는 고정폭이고, 메인이 나머지 전체를 차지하고 있다.</p>
</li>
<li>
<p>카드들이 2x2 그리드로 배치되어 있다.</p>
</li>
</ul>
<br>
<p><strong>3. 최소 재현 환경을 구축한다</strong></p>
<ul>
<li>개발환경으로 가져온다.</li>
</ul>
<br>
<p><strong>4. 재현 환경에서 벌어지는 일을 관찰하고, 2와는 어떻게 다른지 비교한다</strong></p>
<ul>
<li>헤더가 중간부분에 좌우로 배치되어 있다.</li>
<li>메인 컨텐트에 카드가 일렬로 나와있다.</li>
<li>폭이 충분히 작으면 한줄로 나오는 게 스펙이었을 수 있음</li>
<li>좌우와 상하 스크롤이 다 생기고 있다.</li>
</ul>
<br>
<p><strong>5. 4번과 2번의 차이를 발생시키는 원인이 될 만한 옵션들을 적어본다</strong></p>
<ul>
<li>
<p>display: flex 와 flex-direction: column 중에 문제가 발생 된 것 같다.</p>
</li>
<li>
<p>html,css 구조에때문?</p>
</li>
<li>
<p>card에서는 나열하는 방법이 잘못된 듯?</p>
</li>
</ul>
<br>
<p><strong>6. 옵션 중 하나를 골라서 가설을 세우고 검증한다.</strong></p>
<ul>
<li>style 옵션들을 변경해보자</li>
</ul>
<br>
<h2 id="마지막으로"><a class="anchor" href="#마지막으로">마지막으로</a></h2>
<p>위 1,2,3 단계를 거쳐서 <strong>장소와 환경 변경의 중요성</strong>과 <strong>원인 ⇒ 원하는 목표와 필요한 것을 명확히 ⇒ 폭넓은 예외처리</strong> 의 디버깅 과정이 우리 경험에 필요하다는 것을 느끼는 활동이었다.</p>
<p>내 경험에서는 Sentry를 사용하면서 디버깅에 대한 접근을 잘못했다는 것을 느껴, 앞으로 진행에 위 경험을 녹여, 다양한 환경에서 접근을 할 수 있도록 신경써봐야겠다!</p>
<hr>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>대외활동</category>
        </item>
        <item>
            <title><![CDATA[자바스크립트 Promise 직접 만들어보자. Promise 너 나와!]]></title>
            <link>https://hooninedev.com/240525</link>
            <guid isPermaLink="false">https://hooninedev.com/240525</guid>
            <pubDate>Sat, 25 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Promise를 직접 만들어보자. 앞서 나는 Promise()에 대한 글을 2번 정도 게시했다. 자바스크립트 Promise를 퀴즈로 쉽게 익히기 JavaScript 동기와 비동기 완벽 가이드 위 글을 쓰고 나서 몇 달이 지난 현재, Promise의 내부는 어떻게 동작하고, 어떻게 구성이 되어있는지 궁금했다. 아티클을 읽던 와중에 푸만능님의 JavaScrip...]]></description>
            <content:encoded><![CDATA[<h2 id="promise를-직접-만들어보자"><a class="anchor" href="#promise를-직접-만들어보자">Promise를 직접 만들어보자.</a></h2>
<p>앞서 나는 Promise()에 대한 글을 2번 정도 게시했다.</p>
<ul>
<li><a href="https://hooninedev.com/240514/">자바스크립트 Promise를 퀴즈로 쉽게 익히기</a></li>
<li><a href="https://hooninedev.com/230816/">JavaScript 동기와 비동기 완벽 가이드</a></li>
</ul>
<p>위 글을 쓰고 나서 몇 달이 지난 현재, Promise의 내부는 어떻게 동작하고, 어떻게 구성이 되어있는지 궁금했다. 아티클을 읽던 와중에 <strong><a href="https://velog.io/@turtle601/JS-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Promise-%EA%B0%9D%EC%B2%B4-%EC%A7%81%EC%A0%91-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EA%B8%B0#1-simplest-promise">푸만능님의 JavaScript Promise 객체 직접 구현해보기</a>를</strong> 알게 되어서, 위 내용을 실습해보며 내가 새롭게 알게 된 내용들에 대해 공부해보려한다.</p>
<br>
<h3 id="promise를-어떻게-만들어볼까"><a class="anchor" href="#promise를-어떻게-만들어볼까">Promise를 어떻게 만들어볼까?</a></h3>
<p>Promise를 만들기 전에, Promise에 대해 이해해보기 위해, 기능 구현 목록을 작성해보자</p>
<p>우선 Promise는 실행 상태를 나타내기 때문에, <strong>Pending(실행 전), fulfilled(실행 후 성공 => resolve), rejected(실행 후 실패 => rejected)이</strong> 필요하다.</p>
<p>그리고 Promise는 JS 이벤트 루프에서 <strong>Microtask Queue에서 비동기적으로 동작</strong>한다. 또한 then, catch, finally이라는 후속처리 <strong>메서드 체이닝</strong>이 필요하다.</p>
<br>
<blockquote>
<p>🤔 <strong>메서드 체이닝(Method Chaining)이란?</strong></p>
<p>연속적인 코드 줄에서 개체의 Method를 반복적으로 호출하는 것을 의미한다. Method가 객체를 반환하면 그 반환 값(객체)</p>
<p>아래 함수를 Method Chaining을 이용하면 <code>store.enter(2).leave(1).enter(2)</code> 로 구현할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> store</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  name: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"see you"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  opacity: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">30</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  peopleCount: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  enter</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">n</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.peopleCount </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> n;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  leave</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">n</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.peopleCount </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> n;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre></figure>
</blockquote>
<br>
<p>물론 Promise를 위한 정적 메서드인 race(), all()등 다양한 함수등이 있지만 그런 것들은 추후에 공부해보자.</p>
<br>
<h2 id="만들어보자"><a class="anchor" href="#만들어보자">만들어보자.</a></h2>
<p><img src="/content/240525/1.jpeg" alt="1.jpeg" loading="lazy" decoding="async"></p>
<p>본격적으로 Promise를 만들어보자. Promise를 구현해보면서 "이게 왜 이렇게 동작하지?", "이 개념은 뭐지?" 등 필요한 내용에 대해 차근차근 기록을 남기며 Promise를 만들어보겠다!</p>
<p>모든 것은 Github의 새로운 Repository와 함께한다. 레포를 통해 확인해봐도 좋다. 이름은 <strong><a href="https://github.com/jiji-hoon96/mentoring">Mentoring 4주차 - PurePromise</a></strong></p>
<br>
<h3 id="간단하게-promise를-만들어보자"><a class="anchor" href="#간단하게-promise를-만들어보자">간단하게 Promise를 만들어보자</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  constructor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">executor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    executor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> testMyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"my resolve"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">testMyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">().</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value));</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// my resolve</span></span></code></pre></figure>
<p>위 코드에서 신경써서 봐야 할 것은 3가지다.</p>
<ul>
<li>#value = null 을 통해 <strong>#value라는 프라이빗 변수를 null로 초기값을 설정</strong>했다.</li>
<li>생성자 함수는 <strong>executor라는 함수를 매개변수</strong>로 받는다.</li>
<li><strong>then 메서드는 콜백 함수를 매개변수</strong>로 받는다.</li>
</ul>
<p>하지만 아직 부족한 점이 많다. 현재 만들어진 MyPromise 함수는 비동기 처리가 안되어있다. 또한 에러처리를 위한 catch문이 없고, 상태를 다루는 코드도 작성되어 있지 않다.</p>
<p>다음 섹션에서 에러처리에 대한 코드를 추가해보자.</p>
<br>
<h3 id="then-catch-resolve-reject-함수를-추가해보자"><a class="anchor" href="#then-catch-resolve-reject-함수를-추가해보자">then, catch, resolve, reject 함수를 추가해보자</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  constructor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">executor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      executor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#resolve.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#reject.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (error) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(error);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> error;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> testMyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"my resolve"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">testMyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">().</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value)); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// my resolve</span></span></code></pre></figure>
<p>resolve, reject, then, catch를 구현해서 메서드 체이닝이 일어날 수 있도록 했다.</p>
<p>위 코드에서 궁금한 점은 executor 내부에 bind 메서드를 사용했다는 점이다. 현재 bind 메서드를 사용했기 때문에 this는 항상 MyPromise 인스턴스를 가리킨다. 사용하지 않으면 resolve, reject 메서드를 호출할 때 this가 바뀔 수 있고, 특히 콜백함수에서 차이가 크게 날 수 있기 때문에 <strong>bind 메서드로 강제로 특정 객체를 지정</strong>하도록 해야 한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> testMyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">input</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (input </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"정상적입니다"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"입력값이 1이 아닙니다"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">testMyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(error));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 입력값이 1이 아닙니다</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 입력값이 1이 아닙니다</span></span></code></pre></figure>
<p>위 코드를 추가해서 실행해보면 then구문과 catch구문 모두 실행된다. 그리고 비동기, 상태 관리 메서드 생성, Error Handling이 부족하다. 다음 세션에서 상태를 추가해 코드를 더 보완해보자.</p>
<br>
<h3 id="promise-상태를-추가해-동작-구현해보자"><a class="anchor" href="#promise-상태를-추가해-동작-구현해보자">Promise 상태를 추가해 동작 구현해보자.</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Object.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">freeze</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  pending: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"PENDING"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  fulfilled: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"fulfilled"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  rejected: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"rejected"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #state</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">PENDING</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  constructor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">executor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      executor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#resolve.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#reject.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (error) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(error);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.fulfilled;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.rejected;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> error;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.fulfilled) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.rejected) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> myPromiseFn2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">input</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (input </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"성공"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"실패"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">myPromiseFn2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">v</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(v);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "체이닝 확인"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">v</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(v))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">v</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(v));</span></span></code></pre></figure>
<p>위 코드에서 <strong>Object.freeze를 이용해 객체를 동결</strong>했다. 이로서 객체 속성 추가,삭제,변경이 불가능해져서, 객체가 항상 일정한 상태 값을 갖도록 보장한다.</p>
<p>코드를 실행해보면 resolve를 통해 then으로 이동하게 되어 '성공'이 출력되고, '체이닝 확인'이 출력될 것 같지만, '성공'이 두번 출력이 된다. 이것은 then 함수에 리턴값이 변경되지 않았기때문이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(callback) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.fulfilled) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value)));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span></code></pre></figure>
<p>then, catch 메소드를 위처럼 변경해 My Promise 객체 인스턴스를 then 메서드의 리턴값으로 설정해 프로미스 체이닝이 가능하도록 구현했다.</p>
<br>
<h3 id="비동기-구현then-함수만"><a class="anchor" href="#비동기-구현then-함수만">비동기 구현(then 함수만)</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> myPromiseFn2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">input</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">`이것은 ${</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">input</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">}초뒤에 실행됩니다.`</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">3000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>myPromise 내부에 setTimeout을 이용해서 코드를 실행해보면 아래와 같이 오류가 발생한다. myPromiseFn2()를 실행시키면 3초 뒤에 resolve함수가 실행됨으로 then에 대한 리턴값이 undefined가 되기 때문이다.</p>
<p><img src="/content/240525/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<p>그래서 비동기를 구현하려면 어떻게 해야될까?</p>
<p>우리는 Promise를 사용할 때 pending 상태를 두어 대기(비동기)를 하고, fulfilled 상태로 변경이 되면 동기가 된다. 위 내용을 코드에 적용해보자</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #state</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.pending;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #lastCallBacks</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  constructor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">executor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      executor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#resolve.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#reject.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (error) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(error);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.fulfilled;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#lastCallBacks.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">forEach</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">lastcall</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> lastcall</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.rejected;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> error;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#lastCallBacks.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">forEach</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">lastcall</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> lastcall</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">asyncResolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.pending) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">        this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#lastCallBacks.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">push</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value))),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">syncResolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.fulfilled) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value)));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#asyncResolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(callback) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">||</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#syncResolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(callback);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.rejected) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>asyncResolve 메서드는 상태가 pending일 때 호출되어, 새로운 MyPromise 인스턴스를 반환하고, 콜백을 lastCallBacks 배열에 저장한다. 또한 syncResolve 메서드는 상태가 fulfilled일 때 호출되어, 새로운 MyPromise 인스턴스를 반환하고, 즉시 콜백을 실행한다.</p>
<p>이후에 then 메서드를 통해 asyncResolve를 호출하여 비동기 처리를 시도하고, 실패하면 syncResolve를 호출하여 동기 처리를 시도한다. (위 코드는 클로저와 스코프의 개념을 적용해서 공부해보면 좋다.)</p>
<p>위 코드를 실행해보면 3초 뒤에 실행이 되고, 체이닝 메서드로 이어지는 것을 확인할 수 있다.</p>
<br>
<h3 id="microtask-queue를-적용해보자"><a class="anchor" href="#microtask-queue를-적용해보자">MicroTask Queue를 적용해보자</a></h3>
<p>MicroTask Queue를 만들기 위해서는 MacroTask Queue와 차이점, 그리고 콜스택 실행 순서에 대해 알고 있어야한다.</p>
<p><strong><a href="https://hooninedev.com/230816/#%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%97%94%EC%A7%84v8">해당 내용</a>을</strong> 숙지하고 아래 코드를 작성해보길 바란다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> myPromiseFn2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Promise 실행"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> test</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"첫번째 콜스택 실행"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"태스크 큐 실행"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  myPromiseFn2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">().</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">result</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(result));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"두번째 콜스택 실행"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">test</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span></code></pre></figure>
<p>예상되는 정답은 첫 번째 콜스택 실행 => 두 번째 실행 => Promise 실행 => Task Queue 실행 이지만 실제 결과는 예상과 다르게 아래처럼 실행된다</p>
<p><img src="/content/240525/3.png" alt="3.png" loading="lazy" decoding="async"></p>
<p>위 문제를 해결하기 위해서 <strong><a href="https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask">MDN-MicroTask Queue</a>을</strong> 이용해 코드를 수정해보면 예상하는 것 처럼 코드가 잘 동작 된다.</p>
<p><img src="/content/240525/4.png" alt="4.png" loading="lazy" decoding="async"></p>
<br>
<h3 id="catch-함수를-구현해보자"><a class="anchor" href="#catch-함수를-구현해보자">catch 함수를 구현해보자</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #state</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.pending;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #catchCallbacks</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #thenCallbacks</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  constructor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">executor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      executor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#resolve.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#reject.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (error) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(error);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">runCallbacks</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.fulfilled) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#thenCallbacks.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">forEach</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value));</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#thenCallbacks </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.rejected) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#catchCallbacks.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">forEach</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value));</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#catchCallbacks </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    queueMicrotask</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> state;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#runCallbacks</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.fulfilled, value);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.rejected, error);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">thenCallback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">catchCallback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#thenCallbacks.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">push</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">thenCallback) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">          return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">thenCallback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (error) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(error);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#catchCallbacks.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">push</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">catchCallback) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">          return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catchCallback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (error) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(error);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">catchCallback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">undefined</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, catchCallback);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>then 메서드는 thenCallback과 catchCallback을 받아 새로운 MyPromise를 반환한다.</p>
<p>thenCallbacks 배열에 thenCallback을 래핑한 콜백을 추가하는데, 이 콜백은 thenCallback이 없으면 resolve를 호출하고, 예외가 발생하면 reject를 호출한다.</p>
<p>catchCallbacks 배열에 catchCallback을 래핑한 콜백을 추가하는데, 이 콜백은 catchCallback이 없으면 reject를 호출하고, 예외가 발생하면 reject를 호출한다.</p>
<p>catch 메서드는 동작 이후에 메서드 체이닝이 가능하도록 구현했다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> myPromiseFn2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">input</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (input </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"성공"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"실패"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">myPromiseFn2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">v</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(v);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> v;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">v</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(v);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "오류 발생!!!"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">v</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(v));</span></span></code></pre></figure>
<p>위 예시코드를 실행해보면 <strong>실패 => 오류 발생!!!</strong> 이 출력된다.</p>
<br>
<h3 id="finally-만들어보기"><a class="anchor" href="#finally-만들어보기">finally 만들어보기</a></h3>
<p>Settled는 Promise가 이행되거나 거부된 상태를 포괄적으로 표현하는 단어로, Promise가 더 이상 pending 상태가 아닌 상태를 말한다.</p>
<p>finally 메서드를 구현하기 위해 fulfilled, rejected 상태일 때 동작하도록 아래 코드를 추가해보자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">finally</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(callback) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        throw</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span></code></pre></figure>
<br>
<h3 id="추가적인-예외처리-기능-구현"><a class="anchor" href="#추가적인-예외처리-기능-구현">추가적인 예외처리 기능 구현</a></h3>
<p>then(callback)의 callback 함수 내부에서 새로운 Promise를 만들어 return 하는 코드를 아래처럼 만들어 실행해보자. 예상하는 결과 값은 <strong>첫번째 Promise => 두 번째 Promise</strong>이다</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"첫 번째 Promise"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">})</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">res</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(res);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"두 번째 Promise"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      }, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">res</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(res);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span></code></pre></figure>
<p>하지만 결과 값은 두 번째 Promise가 아닌 MyPromise 객체가 출력되었다.</p>
<p>이것을 예상하는 것처럼 출력시키기 위해서는 <strong>#update 내부에 지연 실행</strong>을 추가해줘야한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(state, value) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    queueMicrotask</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.pending) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">instanceof</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        value.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#resolve.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#reject.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      }</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> state;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#runCallbacks</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span></code></pre></figure>
<br>
<h3 id="타입스크립트로-변환해보자"><a class="anchor" href="#타입스크립트로-변환해보자">타입스크립트로 변환해보자.</a></h3>
<p>타입스크립트로 변환하며 state를 별도의 utils로 분리하였다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> PROMISES_STATE </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "./utils/state"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">type</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Executor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  resolve</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  reject</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reason</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> unknown</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> unknown</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> {</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #state</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.pending;</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #catchCallbacks</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reason</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> unknown</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)[] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  #thenCallbacks</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)[] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  constructor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">executor</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Executor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      executor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#resolve.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#reject.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (error) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(error);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">runCallbacks</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.fulfilled) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#thenCallbacks.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">forEach</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#thenCallbacks </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.rejected) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#catchCallbacks.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">forEach</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value));</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#catchCallbacks </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">|</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> unknown</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    queueMicrotask</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.pending) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">instanceof</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        value.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#resolve.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#reject.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      }</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> state;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#runCallbacks</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.fulfilled, value);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  #</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">error</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> unknown</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">#update</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">PROMISES_STATE</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.rejected, error);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TResult</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    thenCallback</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TResult</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TResult</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>,</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    catchCallback</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reason</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> unknown</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TResult</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TResult</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  )</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TResult</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TResult</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#thenCallbacks.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">push</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">thenCallback) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> unknown</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> as</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TResult</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">          return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">thenCallback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (error) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(error);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.#catchCallbacks.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">push</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">catchCallback) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">          return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catchCallback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (error) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(error);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TResult</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    catchCallback</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reason</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> unknown</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TResult</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TResult</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  )</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TResult</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">undefined</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, catchCallback);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  finally</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">callback</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> void</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        callback</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        throw</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>error가 발생해 catch를 할 때 타입을 어떻게 잡을지 난감해 unknown과 제네릭을 이용해 폭넓게 설정해두었다. 추후 해당하는 작업은 리팩토링을 진행해볼 예정이다.</p>
<br>
<h3 id="jest를-이용해서-단위테스트를-해보자"><a class="anchor" href="#jest를-이용해서-단위테스트를-해보자">jest를 이용해서 단위테스트를 해보자.</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">describe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"MyPromise"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  test</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"resolve 정상적으로 작동되는지 테스트"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">done</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"resolved value"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">100</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"resolved value"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      done</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  test</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"rejects 정상적으로 작동되는지 테스트"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">done</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"rejected value"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">100</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"rejected value"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      done</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  test</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Method Chain을 이용해 then 구문이 정확히 동작하는지 테스트"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">done</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"first value"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">100</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"first value"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        return</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "second value"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"second value"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        done</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  test</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Method Chain을 이용해 catch 구문이 정확히 동작하는지 테스트"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">done</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"first error"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">100</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"first error"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        throw</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "second error"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"second error"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        done</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  test</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"finally가 then 뒤에 정상적으로 작동되는지 테스트"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">done</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> finallyCalled </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> false</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"resolved value"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">100</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"resolved value"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">finally</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        finallyCalled </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(finallyCalled).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      done</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">200</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  test</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"finally가 catch 뒤에 정상적으로 작동되는지 테스트"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">done</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> finallyCalled </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> false</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"rejected value"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">100</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"rejected value"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">finally</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        finallyCalled </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      expect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(finallyCalled).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">toBe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      done</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">200</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span></code></pre></figure>
<br>
<h2 id="으아-험난했다"><a class="anchor" href="#으아-험난했다">으아 험난했다..</a></h2>
<p><img src="/content/240525/5.jpeg" alt="5.jpeg" loading="lazy" decoding="async"></p>
<p>아주 험난한 길이었다.. 사실 모든 것이 완벽하게 이해가 된 것은 아니다. <strong>클로저, 스코프, 재귀적 알고리즘</strong> 등 내가 부족한 부분의 지식들을 응용해서 적용해야 하기 때문이다.</p>
<p>이번 Promise를 구현해보며 혼자서는 힘들었지만, 좋은 교보재 덕분에 만들어볼 수 있었다. 위 내용을 열심히 복습하고 필요한 공부자료를 추가해보도록 해야겠다!!</p>
<p>다시 한번 좋은 자료를 만들어주신, <strong><a href="https://velog.io/@turtle601/posts">황준승님</a>께 감사하다는 말을 전달한다.</strong></p>
<br>
<h2 id="출처-및-도움되는-링크들"><a class="anchor" href="#출처-및-도움되는-링크들">출처 및 도움되는 링크들</a></h2>
<ul>
<li><a href="https://developerntraveler.tistory.com/116">Method Chaining</a></li>
<li><a href="https://springfall.cc/article/2022-11/easy-promise-async-await">비동기, Promise, async, await 확실하게 이해하기</a></li>
<li><a href="https://velog.io/@gcback/Asyncawait-%EB%82%B4%EB%B6%80">Async/await 내부</a></li>
<li><a href="https://ko.javascript.info/generators">Generator</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise">Promise 생성자</a></li>
<li><a href="https://velog.io/@turtle601/JS-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Promise-%EA%B0%9D%EC%B2%B4-%EC%A7%81%EC%A0%91-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EA%B8%B0#1-simplest-promise">자바스크립트 Promise 객체 직접 구현해보기</a></li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>멘토링</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[WebAssembly가 뭐야? 너가 JavaScript를 대체한다고!?!?]]></title>
            <link>https://hooninedev.com/240522</link>
            <guid isPermaLink="false">https://hooninedev.com/240522</guid>
            <pubDate>Wed, 22 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[본 글은 Will WebAssembly Replace JavaScript? 기반으로 작성되었습니다. WebAssembly? “Because apps compiled to WebAssembly can run as fast as native apps, it has the potential to change the way software is written on...]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>📢 본 글은 <strong><a href="https://five.co/blog/will-webassembly-replace-javascript/">Will WebAssembly Replace JavaScript?</a></strong> 기반으로 작성되었습니다.</p>
</blockquote>
<br>
<h2 id="webassembly"><a class="anchor" href="#webassembly">WebAssembly?</a></h2>
<p><img src="/content/240522/3.png" alt="3.png" loading="lazy" decoding="async"></p>
<br>
<blockquote>
<p>“Because apps compiled to WebAssembly can run as fast as native apps, it has the potential to change the way software is written on the web.”</p>
<p>WebAssembly로 컴파일된 앱은 네이티브 앱만큼 빠르게 실행될 수 있기 때문에 웹에서 소프트웨어가 작성되는 방식을 바꿀 수 있는 잠재력을 가지고 있습니다.</p>
<p>Evan Wallace – CTO and Co-Founder, Figma</p>
</blockquote>
 <br>
<p>WebAssembly 또는 줄여서 WASM은 최근 몇 년간 많은 관심을 받고 있는 새로운 기술으로, 스택 기반 가상 머신(VM)을 위한 로우레벨 바이너리 명령어 형식이다. 월드와이드웹 컨소시엄(W3C)의 창설로 등장해서, 2015년에 처음 발표되었고 2년 후인 2017년에 첫 데모가 시연되었다.</p>
<p>WebAssembly를 이용해 C, C++, Rust와 같은 고급 언어를 미리 컴파일(AOT)하기 위한 휴대용 타깃으로 설계되어 클라이언트 측 및 서버 측 애플리케이션을 웹에 배포할 수 있다.</p>
<p>WebAssembly, 즉 WASM은 웹 개발 업계에서 새롭게 떠오르는 신예로 큰 주목을 받고 있다. C, C++, Rust와 같은 언어에 대한 컴파일 대상을 제공하는 WASM 모듈을 통해 웹 플랫폼은 그 기능에 혁신적인 변화를 경험하고있다. <strong>WebAssembly의 텍스트 형식은 언어 간 커뮤니케이션을 더욱 쉽게 만들어주며,</strong> 주요 브라우저는 이미 이 기술을 지원하고있다.</p>
<br>
<p><img src="/content/240522/7.png" alt="7.png" loading="lazy" decoding="async"></p>
<blockquote>
<h3>WebAssembly는 새로운 프로그래밍 언어일까?</h3>
<p>NO!!! 🙅🏻 <strong>네이티브 코드를 웹에서 실행하게 해주는 도구</strong>로 보는 것이 더 적합하다.</p>
<p>WebAssembly는 개발자가 직접 작성하는 대신 웹 브라우저 내에서 이러한 언어를 <strong>컴파일하고 실행할 수 있게 해줘, 자바스크립트의 성능 한계 해결에 도움을 준다.</strong> WebAssembly는 C++와 같이 전통적으로 대규모 미션 크리티컬 엔터프라이즈 시스템을 구축하는 데 사용되는 언어를 활용함으로써 <strong>브라우저 환경 내에서 접근성을 용이하게한다.</strong></p>
<p>WebAssembly 커뮤니티 그룹은 WASM을 개방형 표준과 웹 개발의 필수적인 부분으로 만들기 위해 열심히 노력하고 있고, 그 결과 WebAssembly는 개발자가 네이티브 앱에 필적하는 원활하고 고성능의 웹 애플리케이션을 만들 수 있는 강력한 도구가 되고 있다.</p>
</blockquote>
 <br>
<p>WebAssembly는 이제 모든 웹 브라우저(거의 모든 브라우저)와 함께 제공되는 멋진 새 가상 머신이다. 성능에 중요한 비즈니스 로직 <strong>코드를 엄청나게 빠른 속도로 실행할 수 있는</strong> 멋진 가상 머신이다. 물론 신뢰할 수 있는 클라이언트 측 렌더링 프레임워크를 사용하여 JavaScript 프론트엔드에 많은 로직을 넣을 수도 있고, Node.js 백엔드에 로직을 넣고 API를 사용하여 프론트엔드에서 통신할 수도 있다.</p>
<br>
<h3 id="webassembly를-어디에서-사용하고-있을까"><a class="anchor" href="#webassembly를-어디에서-사용하고-있을까">WebAssembly를 어디에서 사용하고 있을까?</a></h3>
<p>앞서 사용된 Figma CTO의 인용구 내용처럼 Figma가 WebAssembly 코드로 컴파일할 수 있는 언어 중 하나인 C++로 작성되었다고 설명하고있다. 이 접근 방식을 채택함으로써 <strong>Figma는 로드 시간을 3배까지 단축하여 네이티브에 가까운 성능을 달성</strong>할 수 있었다.</p>
<p>2019년까지만 해도 Google Auth는 Chrome 전용 솔루션인 네이티브 클라이언트(NaCl)를 사용하고 있었기 때문에 다른 브라우저에서는 액세스할 수 없었다. 하지만 Google Auth 엔지니어링 팀은 WebAssembly를 채택해, 모든 브라우저에서 광범위하게 지원되는 WebAssembly를 활용함으로써 사용자가 <strong>어떤 브라우저를 선택하든 Google Auth에 액세스</strong>할 수 있게 되었다.</p>
<p>Figma와 Google Auth는 모두 WebAssembly가 복잡한 엔지니어링 및 비즈니스 과제에 적합한 솔루션으로 어떻게 작용하는지 대표하는 예시다. 성능 저하나 전면적인 재작성 없이 대규모 애플리케이션을 웹으로 마이그레이션할 수 있다. 이러한 성공 사례는 다양한 사용 사례를 처리하는 데 있어 WebAssembly의 혁신적 잠재력을 보여주고있다.</p>
<br>
<h3 id="javascript로-로직을-작성할-수-있을까"><a class="anchor" href="#javascript로-로직을-작성할-수-있을까">JavaScript로 로직을 작성할 수 있을까?</a></h3>
<p>Yes!! WebAssembly가 등장하기 전에도 그렇게 해왔지만 <strong>성능에 중요한</strong> 비즈니스 로직이라고 말씀드렸던 것을 기억하나요?</p>
<p>WebAssembly는 버튼에 애니메이션을 적용하거나 양식을 제출하기 위한 또 다른 스크립팅 환경이 아니라 네이티브에 가까운 속도로 무거운 워크로드를 실행하기 위한 대안으로 거론되고 있다. 앱 개발자들이 <strong>웹 브라우저는 네이티브 애플리케이션만큼 빠르게 프로그레시브 웹 앱(PWA)을 실행할 수 없다고</strong> 말하던 시절을 이제 옛날 말이 되었다.</p>
<p>성능이 중요한 애플리케이션의 비즈니스 로직을 작성할 때 동적으로 타입이 지정된 해석된 프로그래밍 언어(JavaScript)를 사용하면 적절한 성능을 달성하거나 브라우저에서 실행하기조차 어렵다는 것이 문제다.</p>
<p>WebAssembly를 사용하면 네이티브에 가까운 속도로 <strong>복잡한 계산을 수행할 수 있다.</strong> JavaScript는 동적으로 입력되고 해석되는 언어이기 때문에 복잡한 연산을 수행할 때 속도가 느려질 수 있다. 정적으로 타입이 지정되고 컴파일되는 언어인 WebAssembly는 복잡한 연산을 JavaScript보다 훨씬 빠르게 실행할 수 있기 때문에, 비디오 렌더링, 3D 그래픽 및 기타 계산 집
약적인 작업과 같은 작업에 WebAssembly가 적합할 수 있다.</p>
<br>
<h3 id="webassembly의-보안성"><a class="anchor" href="#webassembly의-보안성">WebAssembly의 보안성</a></h3>
<p>WebAssembly를 사용하면 보안과 샌드박스를 강화할 수 있다. JavaScript는 고급 언어이며 브라우저의 JavaScript 엔진에 의해 실행되므로 공격에 취약할 수 있지만, WebAssembly는 브라우저의 WebAssembly 엔진에 의해 실행되므로 더 안전하며 샌드박스를 통해 악성 코드가 실행되지 않도록 차단할 수 있기때문에, WebAssembly는 높은 수준의 보안이 필요한 온라인 뱅킹, 결제 처리, 온라인 게임, 보안 문서 서명과 같은 웹 기반 작업에 적합한 옵션이 된다.</p>
<br>
<h3 id="webassembly와-다양한-언어의-상호-작용"><a class="anchor" href="#webassembly와-다양한-언어의-상호-작용">WebAssembly와 다양한 언어의 상호 작용</a></h3>
<p>필자가 생각하는 WebAssembly의 가장 큰 장점은 <strong>다른 프로그래밍 언어와 상호 작용할 수 있다는</strong> 점이다. WebAssembly 모듈은 C, C++, Rust 등 WebAssembly 바이트코드로 컴파일할 수 있는 모든 언어로 작성할 수 있다. 따라서 개발자는 특정 작업에 선호하는 언어를 사용한 다음 WebAssembly를 사용하여 웹 애플리케이션에 원활하게 통합할 수 있다. 특정 언어에 이미 익숙하고 이를 웹 개발에 사용하려는 개발자에게 특히 유용할 수 있다.</p>
<br>
<br>
<br>
<p><img src="/content/240522/6.png" alt="6.png" loading="lazy" decoding="async"></p>
<br>
<h2 id="webassembly가-javascript을-대체할까"><a class="anchor" href="#webassembly가-javascript을-대체할까">WebAssembly가 JavaScript을 대체할까?</a></h2>
<br>
<p>Rust, Go, C++와 같은 프로그래밍 언어로 작성된 웹 프레임워크가 점점 더 많이 개발됨에 따라 개발자가 JavaScript 이외의 언어로 프론트엔드를 작성하는 것이 점점 더 쉬워지고 있기 때문에 WebAssembly 등장이 부담스러운 상황이 될 수 있다.</p>
<br>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><img src="/content/240522/2.png" alt="2.png" loading="lazy" decoding="async"></td>
<td><img src="/content/240522/1.jpeg" alt="1.jpeg" loading="lazy" decoding="async"></td>
</tr>
</tbody>
</table>
<br>
<p>WebAssembly를 활용하는 프레임워크의 한 예로 웹 앱 제작을 위한 최신 <strong>Rust 프레임워크인 Yew가 있다.</strong> Yew를 사용하면 개발자가 안전하고 성능이 우수한 코드를 작성하여 WASM으로 컴파일하고 브라우저에서 바로 실행할 수 있다. 즉, Rust 개발자는 이제 JavaScript를 배우지 않고도 웹 앱을 구축할 수 있다.</p>
<br>
<p>또 다른 예로는 개발자가 Go로 프런트엔드 코드를 작성한 다음 JavaScript로 컴파일할 수 있는 트랜스파일러인 <strong>GopherJS가 있다.</strong> 즉, Go 개발자도 JavaScript를 배우지 않고도 웹 앱을 구축할 수 있다.</p>
<p>마찬가지로 <strong>C++ 개발자는 이제 C++ WebAssembly 프레임워크(CWasm)를</strong> 사용하여 웹 앱을 빌드할 수 있다. CWasm은 WebAssembly 가상 머신에 C++ 인터페이스를 제공하는 C++ 라이브러리로, C++ 개발자가 브라우저에서 실행되는 웹 앱을 쉽게 작성할 수 있도록 해준다.</p>
<p>이러한 프레임워크와 솔루션의 등장은 WebAssembly가 프론트엔드 웹 개발에서 JavaScript를 대체할 수 있는 대안이 되고 있음을 의미한다. 이는 앞으로 JavaScript 개발자의 기술 수요가 줄어들 수 있다는 것을 의미하기 때문에 JavaScript 개발자에게는 두려운 전망이다.</p>
<p>그러나 JavaScript는 여전히 웹에서 가장 널리 사용되는 프로그래밍 언어이며, WebAssembly로 완전히 대체되지는 않을 것이라는 점에 유의해야 한다. 대신 <strong>WebAssembly는 JavaScript의 기능을 보완하고 향상시키는 데 사용될 가능성이 높다.</strong></p>
<br>
<h3 id="rust--webassembly-코드-만들기"><a class="anchor" href="#rust--webassembly-코드-만들기">Rust + WebAssembly 코드 만들기</a></h3>
<p>아래의 작업은 <strong><a href="https://github.com/jiji-hoon96/wasm_example">해당 Repo에서 Clone 받으셔도 됩니다.</a></strong></p>
<br>
<ol>
<li>
<p>먼저 Rust를 설치해주자.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="bash" data-theme="github-dark github-light"><code data-language="bash" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">curl</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> https://sh.rustup.rs</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -sSf</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> |</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> sh</span></span></code></pre></figure>
</li>
</ol>
<br>
<ol start="2">
<li>
<p>Wasm-pack을 설치해주자. (wasm-pack은 Rust를 WASM으로 컴파일하는 데 사용할 Rust 라이브러리)</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="bash" data-theme="github-dark github-light"><code data-language="bash" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> ></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Rust</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 가</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 있으면</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> Cargo도</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 설치된다.</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">cargo</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> install</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> wasm-pack</span></span></code></pre></figure>
</li>
</ol>
<br>
<ol start="3">
<li>
<p>Rust를 이용해서 새 프로젝트를 만든다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="bash" data-theme="github-dark github-light"><code data-language="bash" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">cargo</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> --lib</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> wasm_example</span></span></code></pre></figure>
</li>
</ol>
<br>
<ol start="4">
<li>
<p>Cargo에서 생성한 <code>Cargo.toml</code> 파일에 wasm-bindgen 라이브러리를 추가해주기 위해 아래 명령줄을 추가해준다.(wasm-bindgen 라이브러리를 설치해야 Rust와 JavaScript가 상호 작용할 수 있다.)</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toml" data-theme="github-dark github-light"><code data-language="toml" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">wasm-bindgen = </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"0.2"</span></span></code></pre></figure>
</li>
</ol>
<br>
<ol start="5">
<li>
<p>그 이후에 아래와 같이 <code>Cargo.toml</code> 코드가 작성되어 있는지 확인하고 수정해준다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toml" data-theme="github-dark github-light"><code data-language="toml" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">package</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">name = </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"wasm_example"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">version = </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"0.1.0"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">edition = </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"2021"</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">lib</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">crate-type = [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"cdylib"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"rlib"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">[</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">dependencies</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">wasm-bindgen = </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"0.2"</span></span></code></pre></figure>
</li>
</ol>
<br>
<ol start="6">
<li>
<p>src 디렉토리에서 <code>lib.rs</code> 파일을 열고 Rust 코드를 작성해준다.(이미 작성되어 있다면 패스!)</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="rust" data-theme="github-dark github-light"><code data-language="rust" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">use</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> wasm_bindgen</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">::</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">prelude</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">::</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">wasm_bindgen;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">#[wasm_bindgen]</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">pub</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> fn</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> add</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(left</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> usize</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, right</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> usize</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> usize</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    left </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> right</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
</li>
</ol>
<br>
<ol start="7">
<li>
<p><code>wasm-pack</code> 도구를 이용해서 빌드를 진행해준다. 이 과정이 진행되면 pkg 디렉토리가 생기고, 내부에는 JavaScript파일과 WebAssembly 파일이있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="bash" data-theme="github-dark github-light"><code data-language="bash" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">wasm-pack</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> build</span></span></code></pre></figure>
</li>
</ol>
<br>
<ol start="8">
<li>
<p>WASM 코드와 상호 작용할 JavaScript 파일을 작성해주기 위해서 <code>glue.js</code> 파일을 만들어주고 아래 코드를 넣는다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> importObject</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> path_to_wasm</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "./pkg/wasm_example_bg.wasm"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">fetch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(path_to_wasm)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">response</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> response.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">arrayBuffer</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">())</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">bytes</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> WebAssembly.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">instantiate</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(bytes, importObject))</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">results</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> wasm</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> results.instance.exports;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(wasm.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">add</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">20</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">4</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span></code></pre></figure>
</li>
</ol>
<br>
<ol start="9">
<li>
<p>Browser에서 확인해보기 위해 <code>index.html</code> 파일을 만들어주고 아래 코드를 입력한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="html" data-theme="github-dark github-light"><code data-language="html" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;!</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">DOCTYPE</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> html</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">html</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">head</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">title</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>WASM example&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">title</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">script</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> src</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"./glue.js"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">script</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">head</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">body</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">body</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">html</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span></code></pre></figure>
</li>
</ol>
<br>
<ol start="10">
<li>
<p>CORS 오류를 방지하기 위해 <code>http-server</code> 라는 간단한 도구를 설치하고 사용한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="bash" data-theme="github-dark github-light"><code data-language="bash" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">npm</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> install</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> http-server</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> -g</span></span></code></pre></figure>
</li>
</ol>
<br>
<p>고생하셨습니다!! 설정은 완료되었습니다~!!</p>
<p><img src="/content/240522/5.png" alt="5.png" loading="lazy" decoding="async"></p>
<br>
<h3 id="결과는-어떻게-될까"><a class="anchor" href="#결과는-어떻게-될까">결과는 어떻게 될까?</a></h3>
<p>JavaScript만 존재하기 때문에, <code>console.log()</code>를 확인해야 한다.</p>
<p>모든 것이 정상적으로 작동했다면 콘솔에 숫자가 표시되어야 하는데, 이 숫자는 <code>glue.js</code>에서 함수에 전달한 두 숫자의 합계인 <code>console.log(wasm.add(20, 4));</code>가 된다. 이 숫자를 변경하여 다른 합계를 얻을 수 있다.</p>
<p>숫자를 변경해도 콘솔에 여전히 같은 숫자가 출력된다면 브라우저가 JavaScript를 캐싱하고 있기 때문이다. 콘솔이 열려 있는 상태에서 Shift 키를 누른 채 새로고침 버튼을 누르면 페이지를 강제 새로고침할 수 있다.</p>
<br>
<h2 id="그래서-결론은"><a class="anchor" href="#그래서-결론은">그래서 결론은?</a></h2>
<p>WebAssembly는 JavaScript의 기능을 보완하고 향상시킬 수 있는 잠재력을 지닌 강력한 신기술이라고 생각한다.</p>
<p><img src="/content/240522/4.png" alt="4.png" loading="lazy" decoding="async"></p>
<p>JavaScript가 WebAssembly로 완전히 대체되지는 않겠지만, 네이티브에 가까운 속도로 복잡한 연산을 수행하고 다른 프로그래밍 언어와 상호 작용하며 향상된 보안 및 샌드박싱을 제공하는 데 사용할 수 있을 것 같다. 웹 개발이 계속 발전함에 따라 웹 애플리케이션 개발에서 WebAssembly의 역할이 점점 더 중요해질 것이다.</p>
<p>기술을 확장하고 앞서 나가고자 하는 개발자는 WebAssembly를 배우고 그 기능을 실험해 보는 것을 고려해봐야 할 것 같다.</p>
<br>
<h2 id="출처-및-도움되는-링크들"><a class="anchor" href="#출처-및-도움되는-링크들">출처 및 도움되는 링크들</a></h2>
<ul>
<li><a href="https://five.co/blog/will-webassembly-replace-javascript/">Will WebAssembly Replace JavaScript?</a></li>
<li><a href="https://five.co/blog/webassembly-low-code/">WebAssembly &#x26; Low-Code</a></li>
<li><a href="https://webassembly.org/">WebAssembly</a></li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>소박한궁금증</category>
        </item>
        <item>
            <title><![CDATA[비전공 프론트엔드 2년차 개발자 멘티 일지: 3주차 - 기초를 위한 과제 폭탄]]></title>
            <link>https://hooninedev.com/240521</link>
            <guid isPermaLink="false">https://hooninedev.com/240521</guid>
            <pubDate>Tue, 21 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[한 주 소감 및 복습 내용 이번 주부터 과제가 엄청나게 많다. 그리고 이전에 했던 과제들에 대한 복습들도 이루어져야 되기 때문에, 개념적인 공부는 위 과제들을 진행하면서 해당 게시글을 여기에 리포스팅할 예정이다. 이번주에 멘토링을 진행하면서 다시 공부해보면 좋을 것 같은 부분은 아래와 같다. 하나씩 차근차근 천천히 준비하면서 폭 깊은 개발자가 되어보자! 자...]]></description>
            <content:encoded><![CDATA[<br>
<h2 id="한-주-소감-및-복습-내용"><a class="anchor" href="#한-주-소감-및-복습-내용">한 주 소감 및 복습 내용</a></h2>
<p><img src="/content/240521/1.jpeg" alt="1.jpeg" loading="lazy" decoding="async"></p>
<p>이번 주부터 과제가 엄청나게 많다. 그리고 이전에 했던 과제들에 대한 복습들도 이루어져야 되기 때문에, 개념적인 공부는 위 과제들을 진행하면서 해당 게시글을 여기에 리포스팅할 예정이다.</p>
<p>이번주에 멘토링을 진행하면서 다시 공부해보면 좋을 것 같은 부분은 아래와 같다. 하나씩 차근차근 천천히 준비하면서 폭 깊은 개발자가 되어보자!</p>
<ul>
<li>
<p><a href="#%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%ED%95%A8%EC%88%98%EC%97%90%EC%84%9C-%EB%8F%99%EC%A0%81%EA%B3%BC-%EC%A0%95%EC%A0%81%EC%97%90-%EB%8C%80%ED%95%B4">자바스크립트에서 함수의 "동적"이랑 "정적"의 의미를 이해하기</a></p>
</li>
<li>
<p><a href="#const%EC%99%80-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85%EC%97%90-%EB%8C%80%ED%95%B4">const 호이스팅에 대해서 깊이 학습</a></p>
</li>
<li>
<p><a href="#%EC%A2%8B%EC%9D%80-%ED%95%A8%EC%88%98%EB%9E%80">좋은 함수에 대해 생각해보기</a></p>
</li>
<li>
<p><a href="https://hooninedev.com/240525/">Promise를 만들어보자</a></p>
</li>
<li>
<p>useState를 직접 구현해보자!</p>
</li>
<li>
<p>자바스크립트 이벤트루프 시뮬레이션 도구를 직접 구현해보자! <strong>(추후 링크 참조)</strong></p>
</li>
<li>
<p>브라우저 렌더링에 관한 아티클들 읽어보자</p>
<ul>
<li><a href="https://d2.naver.com/helloworld/59361">Naver D2 - 브라우저는 어떻게 동작하는가?</a></li>
<li><a href="https://ui.toast.com/fe-guide/ko_PERFORMANCE">Toast UI - 성능 최적화</a></li>
<li><a href="https://www.slideshare.net/deview/125-119068291">웹 성능 최적화에 필요한 브라우저의 모든 것</a></li>
</ul>
<br>
</li>
</ul>
<h3 id="자바스크립트의-함수에서-동적과-정적에-대해"><a class="anchor" href="#자바스크립트의-함수에서-동적과-정적에-대해">자바스크립트의 함수에서 동적과 정적에 대해</a></h3>
<p>자바스크립트에서 <strong>동적(Dynamic)</strong> 과 <strong>정적(Static)</strong> 이라는 용어를 주로 Scope와 bind를 다룰 때 사용한다.</p>
<p>우선 자바스크립트는 정적 스코프를 사용한다. 함수의 호출이 아닌 정의될 때의 스코프 체인 기반으로 변수의 스코프를 정의한다. 동적으로 사용되면, 호출 때마다 스코프의 범주가 예상하지 못하게 동작이 되어 개발자에게 큰 오류가 될 것이다.</p>
<p>정적 스코프에서 함수가 정의될 때의 스코프를 기억하여 함수가 호출될 때도 그 스코프에 접근할 수 있게 해주는 메커니즘을 클로저를 사용하고 있다.</p>
<p>자바스크립트에서는 항상 정적 스코프를 다루고 있다는 것을 명심해야 한다.</p>
<br>
<h3 id="const와-호이스팅에-대해"><a class="anchor" href="#const와-호이스팅에-대해">const와 호이스팅에 대해</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(a);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> a</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "1000"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span></code></pre></figure>
<p>const와 호이스팅에 대한 내용은 위 코드를 예시로 확인해보겠다.</p>
<p>우선 위 코드는 Reference Error가 발생한다. const는 선언과 동시에 초기화가 되어야 하는데, 블록 스코프를 가지는 const는 호이스팅이 되어 선언은 되었지만, 초기화가 되지 않아 일시적 사각지대(TDZ)에 들어가기 때문이다.</p>
<p>const, let, var에 대한 내용은 <strong><a href="https://hooninedev.com/240516/#const-let-var%EC%9D%98-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85%EC%97%90%EC%84%9C-%EC%B0%A8%EC%9D%B4%EC%A0%90">여기에 기록해두었으니, 참고 바랍니다.</a></strong></p>
<br>
<h3 id="좋은-함수란"><a class="anchor" href="#좋은-함수란">좋은 함수란?</a></h3>
<p>우선 구글링을 하다가 아주 좋은 글을 찾아서 공유 드립니다! <strong><a href="https://jojoldu.tistory.com/697">👇 향로님의 좋은 함수란?</a></strong></p>
<p>내가 생각한 좋은 함수란 순수함수여야 한다고 생각했다. 여기서 생각하는 순수함수는 같은 입력일 경우 항상 동일한 출력을 반환하고 부작용이 없는, 명확하게 하나의 기능을 하는 함수를 의미했다.</p>
<p>사실 내가 생각한 것도 좋은 함수를 작성하는 기준이 된다. 하지만 리팩터링과 테스트의 영역에서는 생각해보지 못하였다. 향로님의 글을 읽다 보니 <strong>책임을 명확하게 하고, 테스트를 쉽게 하며, 순수 함수로 모든 것을 작성하기 어려우니 그에 반하는 부작용 함수를 만들어 격리시키는 것</strong>까지 생각을 하는 것이 좋은 함수를 작성할 자세라는 것을 알게되었다.</p>
<p>내가 저 질문에 대답을 못한 이유도 아직 테스트에 대한 생각이 부족해서라고 생각한다. 위 개념을 생각해보며 테스트를 작성해보는 습관을 지녀야겠다!</p>
<br>
<h2 id="5월-셋째-주-회고"><a class="anchor" href="#5월-셋째-주-회고">5월 셋째 주 회고</a></h2>
<h3 id="이번-주-좋은-것과-나쁜-것"><a class="anchor" href="#이번-주-좋은-것과-나쁜-것">이번 주 좋은 것과 나쁜 것</a></h3>
<ul>
<li>본격적으로 공부하기 위해서 주말 동안 고향에 가서 가족과 시간을 보냈습니다.</li>
<li>그동안 해야겠다고 생각하고 있는 복잡한 것들에 대해 정리해서 구체적인 계획을 세웠습니다.</li>
<li>공부하면서 부족하다고 느낀 부분에 대해 다시 복습을 진행했습니다.</li>
<li>주말에 가족과 등산도 가고, 음식도 먹고, 이야기를 나누는 시간을 많이 보내 주말 동안 작업 및 공부를 진행하지 못했습니다만 좋은 시간이고 평일에 시간을 더 많이 할애해서 나쁘지 않다고 생각합니다</li>
</ul>
<h3 id="이번-주-진행했던-학습개발-내용은"><a class="anchor" href="#이번-주-진행했던-학습개발-내용은">이번 주 진행했던 학습/개발 내용은??</a></h3>
<ul>
<li>클래스와 프로토타입을 비교해보면서 공부했습니다.</li>
<li>멘토링에서 주어진 숫자 업앤다운을 코드를 작성했습니다. 어렵다고 생각하지는 않았지만, 평소에 생각하지 못한 컨벤션과 문법을 고려하며 작성해보았습니다</li>
<li>Mock 데이터를 이용해 Test 코드 작성하는 것이 회사의 업무로 주어져서 공부하면서 개발 진행 중입니다.</li>
<li>콜스택, 실행 컨텍스트, 힙메모리, 스코프 등의 개념을 활용해 예시 코드를 작성해 실행 컨텍스트 기반 동작 원리에 관해서 공부했습니다.</li>
</ul>
<h3 id="가장-고민했던-부분은-무엇이었나요"><a class="anchor" href="#가장-고민했던-부분은-무엇이었나요">가장 고민했던 부분은 무엇이었나요?</a></h3>
<ul>
<li>자바스크립트의 엔진이 어떠한 동작 원리로 작동되는지 고민을 해보았습니다.</li>
<li>내가 어떤 것을 집중해서 공부해야 할지 정리하고, 어떤 것에 부족한지 생각해보았습니다</li>
</ul>
<h3 id="아쉬운-부분을-개선하는-데-필요한-것은-무엇인가요"><a class="anchor" href="#아쉬운-부분을-개선하는-데-필요한-것은-무엇인가요">아쉬운 부분을 개선하는 데 필요한 것은 무엇인가요?</a></h3>
<ul>
<li>생각이 집중되고 필요한 부분에 대해 명확해진 만큼 집중할 수 있는 시간을 많이 할애할 예정입니다.</li>
</ul>
<h3 id="다음-주는-어떻게-보낼-예정인가요"><a class="anchor" href="#다음-주는-어떻게-보낼-예정인가요">다음 주는 어떻게 보낼 예정인가요?</a></h3>
<ul>
<li>사내에서 DevOps 관련한 업무를 약간 해보았습니다(frontend 배포환경 구축). 이와 관련해서 <strong><a href="https://www.meetup.com/awskrug/events/301011378/">DevOps 관련해서 AWSKRUG에서 주관하는 밋업이</a></strong> 있어서 수요일에 다녀올 것입니다</li>
<li>이번 주 금요일, 토요일에서 동아리에서 해커톤을 진행하는데, 담당자로 활동할 예정입니다. 행사 시작과 마무리 외에는 관여할 부분이 없어서, 부족한 부분 공부와 프론트엔드 직무 사람들과 이야기를 진행할 것입니다.</li>
<li>5/27에 <strong><a href="https://edu.nextstep.camp/c/vSrYViaQ">넥스트 스텝에서 주관하는 이펙티브 디버깅 행사에</a></strong> 다녀올 것입니다.</li>
<li>이외 시간에는 멘토링 내용에 대해 복습 및 선행학습 진행하고, 회사 업무할 것입니다</li>
</ul>
<br>
<h2 id="출처-및-도움되는-링크들"><a class="anchor" href="#출처-및-도움되는-링크들">출처 및 도움되는 링크들</a></h2>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>멘토링</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[비전공 프론트엔드 2년차 개발자 멘티 일지: 2주차 - 복습의 지옥]]></title>
            <link>https://hooninedev.com/240516</link>
            <guid isPermaLink="false">https://hooninedev.com/240516</guid>
            <pubDate>Thu, 16 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[으아아아ㅏ 멘토링 두번째 회고 및 복습 시작!!! 복습.. 또 복습 복습은 복숭아다.. (복숭아가 너무너무 먹고싶다.) 이번 주도 어김없이 아는 것을 설명하지 못하고, 계속 반복된 이야기만 했다. 아직 잘 몰라서 그런 것 같다. 미숙하지만 더더더더! 공부하자 We Can Do It const, let, var의 호이스팅에서 차이점 우선 호이스팅이란 실행 컨...]]></description>
            <content:encoded><![CDATA[<p>으아아아ㅏ 멘토링 두번째 회고 및 복습 시작!!!</p>
<br>
<h2 id="복습-또-복습"><a class="anchor" href="#복습-또-복습">복습.. 또 복습</a></h2>
<p><img src="/content/240516/5.jpeg" alt="5.jpeg" loading="lazy" decoding="async"></p>
<p>복습은 복숭아다.. <del>(복숭아가 너무너무 먹고싶다.)</del> 이번 주도 어김없이 아는 것을 설명하지 못하고, 계속 반복된 이야기만 했다. 아직 잘 몰라서 그런 것 같다. 미숙하지만 <strong>더더더더! 공부하자 We Can Do It</strong></p>
<br>
<h3 id="const-let-var의-호이스팅에서-차이점"><a class="anchor" href="#const-let-var의-호이스팅에서-차이점">const, let, var의 호이스팅에서 차이점</a></h3>
<p>우선 <strong>호이스팅이란 실행 컨텍스트 생성 시 렉시컬 스코프 내의 선언이 끌어올려 지는 것을 의미</strong>한다.</p>
<p>변수 생성 과정은 선언 단계(Declaration phase) => 초기화 단계(Initialization phase) => 할당 단계(Assignment phase) 이렇게 총 세 단계를 거쳐 생성되고, 변수를 선언 및 초기화 하기전에 사용하게 되는 영역을 <strong>TDZ(Temporal Dead Zone)라고 한다.</strong></p>
<p><img src="/content/240516/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<p><strong>var :</strong> 선언한 변수는 호이스팅 시 undefined로 초기화되어, 변수 선언 이전에 해당 변수를 참조할 수 있다. 변수 선언과 동시에 undefined로 초기화된다.
초기화 이후에 값을 할당할 수 있고, 동일한 변수 이름으로 재선언이 가능하고, 변수에 새로운 값을 할당할 수 있다.</p>
<p><strong>let :</strong> 선언한 변수는 호이스팅 시 초기화되지 않으며, 선언 전에 참조하면 참조 오류(ReferenceError)가 발생한다. 그리고 메모리 공간은 확보되지만 초기화는 선언 단계에서 이루어지지 않는다.
선언 이후에 값을 할당할 수 있고, 선언 시 값을 할당하지 않아도 오류가 발생하지 않는다. 또한 동일한 변수 이름으로 재선언할 수 없지만, 변수에 새로운 값을 할당할 수 있다.</p>
<p><strong>const :</strong> 선언한 변수도 let과 마찬가지로 호이스팅 시 초기화되지 않으며, 선언 전에 참조하면 참조 오류가 발생한다. 또한 선언과 동시에 반드시 초기화(값 할당)가 이루어져야 한다.
선언 시 할당된 값을 변경할 수 없고, 동일한 변수 이름으로 재선언할 수 없으며, 값을 재할당할 수 없다.</p>
<br>
<h3 id="그래서-tdz란"><a class="anchor" href="#그래서-tdz란">그래서 TDZ란?</a></h3>
<p>위에서 말한 대로 변수를 선언, 초기화하기 전 사이의 사각지대를 TDZ라고한다.</p>
<p>TDZ의 영향을 받는 것들은 const 변수, let 변수, class 구문 등이 있고, 영향을 받지 않는 구문은 var 변수, function, import 구문이 있다.</p>
<br>
<h3 id="node_modules의-호이스팅으로-발생하는-문제"><a class="anchor" href="#node_modules의-호이스팅으로-발생하는-문제">node_modules의 호이스팅으로 발생하는 문제</a></h3>
<p>질문을 받자마자 든 생각은 엥..? 이었다. 도대체 node_modules랑 호이스팅이랑 어떠한 문제가 있는 것일까? 내가 알기로 호이스팅은 변수와 함수에 일어나는 것이 아닌가.. 깊게 공부한 내용을 기록해보겠다.</p>
<p><img src="/content/240516/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<p>우선 자바스크립트 내에서 호이스팅은 인터프리터가 코드를 실행하기 전에 함수, 변수, 클래스 또는 임포트(import)의 선언문을 해당 범위의 맨 위로 이동시키는 과정을 의미하는 것을 알아야 한다.</p>
<p>node_modules의 호이스팅으로 발생하는 가장 큰 문제는 <strong>비효율적인 의존성 검색과 유령 의존성 발생이라고 생각한다.</strong></p>
<p>NPM은 패키지를 찾기 위해서 계속 상위 디렉토리의 node_modules 폴더를 탐색하게 되고, 바로 찾지 못하면 호출이 반복이 되어 탐색이 비효율적이게 된다. 또한 상위 디렉토리 환경에 영향을 받아 잘못된 의존성을 불러올 수 있다.</p>
<p>제일 큰 문제점은 <strong>유령 의존성(Phantom Dependency)이다.</strong> 호이스팅에 따라 직접 의존하고 있지 않은 라이브러리를 require(), import() 할 수 있는 현상을 의미하는데, 명시하지않은 라이브러리를 조용히 사라지게 해서, 의존성 관리를 혼란스럽게 하게된다.</p>
<p>구글링해보니 이러한 이유때문에 큰 프로젝트나, 모노레퍼에서는 의존성 문제를 효율적으로 해결하기 위해 pnpm 또는 Yarn Berry 사용을 권장하고 있다.</p>
<br>
<h3 id="lexical-scope에-대해-이해해보자"><a class="anchor" href="#lexical-scope에-대해-이해해보자">Lexical Scope에 대해 이해해보자</a></h3>
<p>렉시컬 스코프(정적 스코프)를 간단히 표현하면 함수를 어디서 호출하는지가 아니라 어디서 선언하였는지에 따라 상위 스코프를 결정하는 것이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">var</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> item </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "첫 번째"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> first</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  var</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> item </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "십만 번째"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  second</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> second</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(item);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">first</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">second</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span></code></pre></figure>
<p>위 코드를 실행하면 "첫 번째"가 두 번 출력된다. 왜냐하면 자바스크립트에서 <strong>실행 단계에 코드들의 스코프를 결정하기 때문이다.</strong></p>
<p>그러므로, second() 함수가 first() 함수 안에서 호출된 것과 상관없이 second() 함수는 global 범위에 선언되어 있으므로, global 범위에 있는 변수 item의 값인 "첫 번째"가 두 번 출력된 것이다.</p>
<p><strong>자바스크립트 엔진은 코드가 로드될 때 실행 컨텍스트를 생성하고 그 안에 선언된 변수, 함수를 실행 컨텍스트 최상단으로 호이스팅하는데, 이러한 범위를 렉시컬 스코프라 한다.</strong></p>
<blockquote>
<p>이와 반대로 함수 호출에 따라 상위 스코프가 정해지는 <strong>Dynamic Scope</strong>도 있다.</p>
</blockquote>
<br>
<h3 id="class와-프로토타입의-개념-차이"><a class="anchor" href="#class와-프로토타입의-개념-차이">Class와 프로토타입의 개념 차이</a></h3>
<p><img src="/content/240516/6.jpeg" alt="7.jpeg" loading="lazy" decoding="async"></p>
<p>자바스크립트에서 클래스와 프로토타입은 객체 지향 프로그래밍을 구현하는 핵심적인 방법이다. 자바스크립트의 다양한 기능과 패턴을 보다 효과적으로 활용하기 위해서는 반드시 알아야 할 개념이라고 생각한다. 위 내용을 아래 키워드를 가지고 비교해보면서 공부해보자!</p>
<br>
<h4>🤔 Q1) 자바스크립트는 프로토타입언어인데 프로토타입이 뭘까?</h4>
<p>정확히는 <strong>자바스크립트는 기존의 객체를 복사하여 새로운 객체를 생성하는 프로토타입 기반 객체지향 프로그래밍 언어</strong>이다. Java, C++과 같은 클래스 기반 객체지향 프로그래밍이랑은 다르다.</p>
<p>Javascript에서 기본 데이터 타입을 제외한 모든 것이 객체다. 객체가 만들어지기 위해서는 자신을 만드는 데 사용된 원형인 프로토타입 객체를 이용하여 객체를 만든다. 이 때 객체 안에 <strong><code>__proto__</code></strong> 속성이 자신을 만들어낸 원형을 의미하는 프로토타입 객체를 참조하는 숨겨진 링크가 존재하는데 이것을 프로토타입이라고 정의한다.</p>
<br>
<h4>🤔 Q2) Class와 프로토타입의 차이가 뭘까?</h4>
<ul>
<li>
<p>프로토타입 기반 상속은 Object.create()나 new 연산자를 사용하여 프로토타입 객체를 상속하거나 프로토타입 체인을 활용하지만, 클래스는 extends 키워드를 사용하여 다른 클래스를 상속한다.</p>
</li>
<li>
<p>생성자 함수와 프로토타입 매서드는 함수 호이스팅의 영향을 받을 수 있지만, 클래스와 클래스 메서드는 블록 스코프를 따르며 호이스팅이 발생하지 않는다.</p>
</li>
<li>
<p>클래스는 new 키워드를 사용하여 인스턴스를 생성한다. 프로토타입도 new 키워드를 사용하지만, 생성자 함수와 프로토타입 체인을 설정하는 과정이 추가로 필요하다.</p>
</li>
</ul>
<br>
<h4>🤔 Q3) 실행 컨텍스트 같은 문법은 자바스크립트에서 왜 필요했을까?</h4>
<p>우선 실행 컨텍스트에 대해 알아보자. 실행 컨텍스트는 아래와 같이 3가지 종류가 있고 자동으로 생성되는 전역공간과 eval을 제외하면 함수를 실행하는 시점에 실행 컨텍스트가 실행 된다.</p>
<ul>
<li>
<p><strong>Global Execution Context (전역 실행 컨텍스트)의</strong> 종류는 브라우저의 경우, window 객체 / Node.js경우, global 객체다.</p>
</li>
<li>
<p><strong>Functional Execution Context (함수 실행 컨텍스트)는</strong> 함수가 실행될 때마다 만들어지는 실행 컨텍스트로 각 함수는 고유의 실행 컨텍스트를 가지며, 함수가 실행되거나 호출될때 생성된다.</p>
</li>
<li>
<p><strong>Eval Function Execution Context는</strong> eval 함수로 실행되는 코드다.</p>
</li>
</ul>
<p>자바스크립트 엔진은 웹 성능의 최적화와 복잡성을 효율적으로 관리하기 위해 실행 컨텍스트를 사용한다. 코드가 잘 실행되려면 스코프, 식별자, 코드 실행 순서등의 관리가 필요한데 이런 것들을 실행 컨텍스트가 관리해준다.</p>
<br>
<h3 id="what-is-heap-memory"><a class="anchor" href="#what-is-heap-memory">What is Heap Memory?</a></h3>
<p>힙 메모리는 프로그램에서 동적으로 할당된 메모리를 관리하는데 사용되는 영역이다. 프로그램이 실행될 때, 운영체제는 프로그램에 메모리 공간을 할당하며, 스택 과 힙으로 구성되어 있다. <strong>스택은 지역 변수와 함수 호출 시 생성되는 변수들을 저장하는 영역이고, 힙은 동적으로 할당되는 메모리를 저장하는 영역이다.</strong></p>
<p>힙 메모리는 프로그램에서 필요한 만큼의 메모리를 동적으로 할당하여 사용한다. 힙 메모리는 스택과 달리, 메모리 블록을 계속 할당하거나 해제할 수 있어서 메모리 누수가 발생하기 쉽다.</p>
<p><img src="/content/240516/3.png" alt="3.png" loading="lazy" decoding="async"></p>
<p>참조형 데이터와 힙 메모리는 어떠한 연관성이 있을까? 위 그림을 살펴보자.</p>
<p>우선 10이라는 <strong>값 자체는 원시 타입이므로 콜 스택에 저장</strong>되고, 변수 a에는 10이 저장된 콜 스택 메모리의 주소값이 저장된다. 변수 식별자 a 자체는 콜스택 상의 실행 컨텍스트(Execution Context)의 렉시컬 환경(Lexical Environment)에 저장된다.</p>
<p><strong>참조 타입 데이터인 b(배열), c(객체), d(함수)는 참조 타입이므로 메모리 힙에 저장</strong>된다. 참조타입 데이터가 저장된 메모리 힙의 주소값은 콜스택에 각각 저장되고, 메모리힙의 주소 값이 저장된 콜 스택의 주소값은 각각 변수 b, c, d에 저장된다. 마찬가지로, 변수 식별자 b, c, d 이름 자체는 콜스택 상의 '실행 컨텍스트(Execution Context)의 렉시컬 환경(Lexical Environment)에 저장된다.</p>
<br>
<br>
<h3 id="객체와-배열의-연관성"><a class="anchor" href="#객체와-배열의-연관성">객체와 배열의 연관성</a></h3>
<p>자바스크립트에서 객체는 아래와 같은 특징들을 가지고 있다.</p>
<ul>
<li>변수지만, 많은 값이 포함될 수 있고, 변수처럼 단일 값을 포함 할 수 있다.</li>
<li>중괄호 표기를 이용하여 만들 수 있다.</li>
<li>각각의 key/value에 대한 정보를 나열할 수 있어야 하고, key는 문자열 또는 기호여야 하며, value는 모든 유형이 될 수 있다.</li>
<li>한 쌍의 key/value 뒤에 쉼표를 이용하여 그 뒤에 오는 key/value와 구분해주어야 한다.</li>
<li>객체에서 명명된 값을 Properties라고 한다.</li>
<li>변수는 예약어의 이름을 가질 수 없지만 객체는 어떠한 이름이어도 상관없다.</li>
<li>객체 변수를 복사하면 참조가 복사되고 객체가 복제되지 않는다.</li>
</ul>
<p>배열은 객체와 구조는 같지만, 키는 항상 숫자이며, 값은 요소다. 그러면 배열은 객체가 될 수 있을까?</p>
<p>질문의 대답은 <strong>YES다.</strong> 배열은 Array 클래스의 인스턴스로서, 배열의 프로토타입 체인에 Object가 있으며, Object의 모든 메서드와 속성을 상속받기 때문이다.</p>
<br>
<h3 id="객체를-어떻게-분류하고-ecma-script에서는-객체를-어떻게-구성하고-있을까"><a class="anchor" href="#객체를-어떻게-분류하고-ecma-script에서는-객체를-어떻게-구성하고-있을까">객체를 어떻게 분류하고 ECMA Script에서는 객체를 어떻게 구성하고 있을까?</a></h3>
<ol>
<li>
<p><strong>JavaScript 내장 객체 (Built-in Object)는</strong> JavaScript 엔진이 구동되는 시점에서 바로 제공되며 JavaScript코드 어디에서든 사용이 가능하다. 아래의 내장 객체들 외에도 많은 내장 객체들이 있다.</p>
<ul>
<li>Global</li>
<li>Object</li>
<li>String</li>
<li>Number</li>
<li>Boolean</li>
<li>Date</li>
<li>Array</li>
</ul>
</li>
<li>
<p><strong>브라우저 내장 객체 (Native Object)도</strong> JavaScript가 구동되는 시점에서 바로 사용이 가능한 객체들이며 내장 객체이다. 하지만 브라우저마다 브라우저 내장 객체를 사용함에 있어 구성을 달리하는 경우가 있기 때문에 JavaScript 내장 객체와 구분되고 있다.. 하지만 브라우저 내장 객체는 브라우저, 즉 JavaScript 엔진을 구동하는 것들에서 build되는 객체들이다. 이 객체들은 자바스크립트 내장객체가 구성된 후에 구성이 된다.</p>
<ul>
<li>브라우저 객체 모델 (BOM)</li>
<li>문서 객체 모델 (DOM)</li>
</ul>
</li>
<li>
<p><strong>사용자 정의 객체 (Host Object)는</strong> 말 그대로 사용자가 생성한 객체들로 생성자 함수 또는 객체 리터럴을 통해 사용자가 객체를 정의하고 확장시킨 객체들이기 때문에 내장 객체들이 구성된 이후에 구성되어 진다.</p>
</li>
</ol>
<br>
<h2 id="5월-둘째-주-회고"><a class="anchor" href="#5월-둘째-주-회고">5월 둘째 주 회고</a></h2>
<p><img src="/content/240516/4.jpeg" alt="4.jpeg" loading="lazy" decoding="async"></p>
<p>회고를 하기 전에 이런저런 많은 생각을 하였다. 나 스스로에 대해서 잘 알고 있다. 마음 잡히면 잘하지만, 잡히는 과정이 오래 걸린다는 것을.. 또한 하고 싶은 게 너~~무 많아서 걱정에 앞서 모든 일을 저지르고 생각한다. 이러한 나의 문제점을 잘 해결해서 더 좋은 방향으로 갈 수 있도록 여유 있는 일정을 잡아야겠다.</p>
<h3 id="이번-주-좋은-것과-나쁜-것"><a class="anchor" href="#이번-주-좋은-것과-나쁜-것">이번 주 좋은 것과 나쁜 것</a></h3>
<ul>
<li>날씨가 좋은 것 빼고는 별 차이가 없는 1주일이었습니다.</li>
<li>5/6에 쉬는날이여서 멘토링을 빨리 진행해 7일까지 멘토링에 대한 복습을 진행할 수 있었습니다.</li>
</ul>
<h3 id="이번주-진행했던-학습개발-내용은"><a class="anchor" href="#이번주-진행했던-학습개발-내용은">이번주 진행했던 학습/개발 내용은??</a></h3>
<ul>
<li>동아리에서 5~6주 활동의 결과 발표를 진행했습니다. (<a href="https://hooninedev.com/240509/">https://hooninedev.com/240509/</a>)</li>
<li>멘토링 끝난 2일동안 멘토링 과정 복습을 진행했습니다.</li>
<li>멘토링 과제들을 진행했습니다.</li>
<li>우당탕탕 책방 스터디에서 CSS 라이브러리에 대한 이야기를 1시간동안 진행하기 위해 제가 자주 사용위해서
제가 자주 사용하는 라이브러리 styled.component, tailwindcss 에 대해 공부했고, 사람들의 인사이트를 공유했습니다</li>
<li>깊은 복사와 얕은 복사의 개념과 자료형,참조형 데이터에 대한 내용이 헷갈려서 코드를 작성하며 복습을 했습니다.</li>
</ul>
<h3 id="가장-고민했던-부분은-무엇이었나요"><a class="anchor" href="#가장-고민했던-부분은-무엇이었나요">가장 고민했던 부분은 무엇이었나요?</a></h3>
<ul>
<li>사내에서 공통적으로 사용할 수 있도록 zod를 작성하는 것에 대해 고민 했습니다.</li>
<li>테스트 코드를 작성해보려 하는데, api 와 UI 테스트 코드를 작성하고 있습니다. 특히 목데이터를 어떻게 다루는지와 외부라이브러리는 어떻게 테스트 할 것인지 고민중입니다.</li>
</ul>
<h3 id="아쉬운-부분을-개선하는-데-필요한-것은-무엇인가요"><a class="anchor" href="#아쉬운-부분을-개선하는-데-필요한-것은-무엇인가요">아쉬운 부분을 개선하는 데 필요한 것은 무엇인가요?</a></h3>
<ul>
<li>심리적으로 여유가 부족한 것 같습니다.</li>
<li>제가 생각한 일정에서 어긋난다고 생각하면 스트레스가 됩니다.</li>
<li>이번주부터 러닝을 진행하려 합니다.</li>
</ul>
<h3 id="다음-주는-어떻게-보낼-예정인가요"><a class="anchor" href="#다음-주는-어떻게-보낼-예정인가요">다음 주는 어떻게 보낼 예정인가요?</a></h3>
<ul>
<li>멘토링에 대한 복습을 할 예정입니다</li>
<li>Promise에 대해 복습하려합니다.</li>
<li>이력서 수정하려합니다.</li>
</ul>
<br>
<h2 id="출처-및-도움되는-링크들"><a class="anchor" href="#출처-및-도움되는-링크들">출처 및 도움되는 링크들</a></h2>
<ul>
<li><a href="https://charming-kyu.tistory.com/19">힙 메모리</a></li>
<li><a href="https://medium.com/@limsungmook/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%8A%94-%EC%99%9C-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EC%9D%84-%EC%84%A0%ED%83%9D%ED%96%88%EC%9D%84%EA%B9%8C-997f985adb42">자바스크립트는 왜 프토토타입을 선택했을까? - 미디움(임성묵님)</a></li>
<li><a href="https://f-lab.kr/insight/understanding-javascript-class-and-prototype">자바스크립트 클래스와 프로토타입의 이해 - Flab</a></li>
<li><a href="https://evan-moon.github.io/2019/10/23/js-prototype/">evan님의 프로토타입 정리</a></li>
<li><a href="https://www.datoybi.com/execution-context/">Steady-Dev의 실행 컨텍스트</a></li>
<li><a href="https://velog.io/@thumb_hyeok/%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EC%9D%98-%EC%9E%A5%EC%A0%90%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C">thumb-hyeok님이 프로토타입 장점</a></li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>멘토링</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[자바스크립트 Promise를 퀴즈로 쉽게 익히기 with 예제 코드]]></title>
            <link>https://hooninedev.com/240514</link>
            <guid isPermaLink="false">https://hooninedev.com/240514</guid>
            <pubDate>Tue, 14 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Promise에 대해서 공부해보자 나는 개인적으로 프론트엔드의 꽃은 Promise와 비동기라고 생각한다. 백엔드를 공부하는 친구들이 가장 어려워하는 부분이기도 하고, 나의 러닝 커브가 가장 길었기 때문이다! 어차피 해당하는 내용은 공부를 미리 진행한 적 있어서 블로그 링크를 첨부하고 퀴즈를 통해 작동에 방법을 익히고, 활용해볼 예정이다!! Promise를 ...]]></description>
            <content:encoded><![CDATA[<p><img src="/content/240514/1.jpeg" alt="1.jpeg" loading="lazy" decoding="async"></p>
<br>
<h2 id="promise에-대해서-공부해보자"><a class="anchor" href="#promise에-대해서-공부해보자">Promise에 대해서 공부해보자</a></h2>
<p>나는 개인적으로 프론트엔드의 꽃은 Promise와 비동기라고 생각한다. 백엔드를 공부하는 친구들이 가장 어려워하는 부분이기도 하고, 나의 러닝 커브가 가장 길었기 때문이다!</p>
<p>어차피 해당하는 내용은 공부를 미리 진행한 적 있어서 블로그 링크를 첨부하고 퀴즈를 통해 작동에 방법을 익히고, 활용해볼 예정이다!!</p>
<blockquote>
<p>Promise를 이해하기 위한 선행학습자료는 <strong><a href="https://hooninedev.com/230816/">여기에</a></strong> 첨부하니 확인해보시길 권장드립니다.</p>
</blockquote>
<br>
<h3 id="promiseall"><a class="anchor" href="#promiseall">Promise.all()</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">async</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  await</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">all</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([]).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(error);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  await</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">all</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">4</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)]).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(error);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  await</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">all</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"error"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)]).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(error);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">})();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 결과값</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// []</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// [1,2,3,4]</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// "error"</span></span></code></pre></figure>
<p>위 코드의 결과값에서 3번째 Promise.all()에서 1,2,3이 출력되지 않고 error만 출력된 이유는 <strong>Promise.all()에서 입력 값으로 들어온 프로미스 중 하나라도 거부당하면 즉시 거부하기 때문이다.</strong></p>
<p>Promise.all()의 개념이 더 궁금하면 <strong><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/all">MDN 자료를</a></strong> 확인해보시면 됩니다. ✔️</p>
<br>
<h3 id="promise-order"><a class="anchor" href="#promise-order">Promise Order</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">_</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">4</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">5</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">6</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">})</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">7</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">8</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">11</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(console.log)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">finally</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">12</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">13</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 결과값</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 1</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 4</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 6</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 13</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 8</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 9</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 11</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// undefined</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 12</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 3</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 2</span></span></code></pre></figure>
<p>위 코드는 어떤 순서로 값이 출력될까?</p>
<p>우선 동기식 코드(synchronous code) 1이 출력되고나서 3,2가 태크로테스크 대기열에 들어가는데 3이 2보다 지연값이 적기 때문에 먼저 들어간다.</p>
<p>4,6은 Promise 내부에서 출력되고, Promise는 마이크로테스크 대기열에 들어가고 13이 동기식 코드로 출력된다.</p>
<p>이렇게 되면 콜 스택은 비어있다. <strong>우선순위가 먼저인 마이크로테스크 대기열의 프로미스가 먼저 실행되고,</strong> reject 상태이기 떄문에 catch로 넘어가게 되고 8이 출력된다.</p>
<p>오류가 발생하지 않기 때문에 프로미스 체인으로 인해 9, 11 그리고 undefined가 실행되고 12가 출력이 된다.</p>
<p>마지막으로 큐에 대기 중인 setTimeouts이 실행되어 3과 2가 출력된다.</p>
<p>위 코드를 실행해보니 우선순위는 <strong>동기식 코드(synchronous code) > 마이크로 테스크 => 매크로 테스크</strong> 순서대로 실행되는 것을 확인해 볼 수 있다.</p>
<br>
<h3 id="promiseprototypefinally"><a class="anchor" href="#promiseprototypefinally">Promise.prototype.finally()</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">finally</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(data);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"error"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(error);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    throw</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "error2"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">finally</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(data);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(console.log);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  })</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(console.log)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(console.log);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 결과값</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// undefined;</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// error;</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// undefined;</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 2;</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// error2;</span></span></code></pre></figure>
<p>우선 Promise의 finally에 대해서 알아보면, <strong>finally의 특징에는 fulfilled 또는 rejected 되든 상관없이 항상 실행되는 콜백 함수를 지정할 수</strong> 있게 해준다. 그리고 <strong>인자를 받지 않고 이전 상태를 유지해준다.</strong></p>
<p>위 내용을 알고 코드의 실행 과정을 살펴보자</p>
<p>처음에 Promise.resolve(1)이 호출되면서 즉시 이행된 Promise가 생성된다. 그리고 <strong>finally 블록이 Promise의 최종 상태와 관계없이 실행된다.</strong> 하지만 finally는 인자를 받지 않기 때문에 data는 undefined가 출력된다.</p>
<p>finally 블록에서 Promise.reject('error')를 반환하여, 체인은 거부된 상태가 된다.</p>
<p>catch 블록은 거부된 Promise를 처리하는데, 여기서 error는 'error'가 되어 출력되고, catch 블록에서 'error2'를 던지는데, 이로 인해 이후의 체인은 다시 거부된 상태가 된다.</p>
<p>그리고 finally 블록도 실행되지만, 인자를 받지 않기 때문에 data는 undefined가 출력된다. 그 이후 finally 블록에서 Promise.resolve(2).then(console.log)를 반환하여 2라는 값을 가지게 된다.</p>
<p>Promise.resolve(2)는 이행된 Promise를 반환하며, then 블록에서 console.log를 호출하여 2를 출력한다. 두 번째 finally 블록은 여전히 'error2'로 인해 거부된 상태로 catch가 실행되어 'error2' 인자를 받아 error2가 출력된다.</p>
<br>
<h3 id="promise-then-callbacks"><a class="anchor" href="#promise-then-callbacks">Promise then callbacks</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> value </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">*</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">4</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(console.log);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 결과값</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 6</span></span></code></pre></figure>
<p>위 코드는 어떻게 작동하는지 알아보기 전에 <strong>then 메서드에 전달되는 인자는 함수여야 한다.</strong> 함수가 아닌 값은 무시가 되고 이전 값이 그대로 전달된다. 그리고 <strong>Promise.resolve로</strong> 생성된 Promise를 then에 전달하면 <strong>체인에 영향을 주지 않고 무시</strong>된다는 것을 알고 코드를 실행해보자!</p>
<p>Promise.resolve(1)은 즉시 이행된 Promise를 생성하며, 그 값은 1이 된다.</p>
<p>.then(()=>2)에서 이전 값 1을 사용하지 않고, 대신 2를 반환한다.</p>
<p>.then(3)에서 then은 함수여야 하는데, 3은 함수가 아니므로 이 단계는 무시되어 이전 값 2가 그대로 전달된다.</p>
<p>then((value) => value _ 3)에서 이전 값 2를 받아서 2 _ 3 = 6을 반환하여 6이 된다.</p>
<p>then(Promise.resolve(4))에서 then은 함수여야 하는데, Promise.resolve(4)는 함수가 아니므로 즉시 이행된 Promise가 반환되어 이전 값 6만 전달되고 6이 출력된다.</p>
<br>
<h2 id="여러분들은-어떻게-쓰고-계신가요"><a class="anchor" href="#여러분들은-어떻게-쓰고-계신가요">여러분들은 어떻게 쓰고 계신가요!?</a></h2>
<p>우선 저는 사내에서 Promise를 직접적으로 구현해서 쓰지 않습니다. 왜? 라는 생각을 해보았는데 한줄로 설명하면 <strong>더 높은 수준에서 추상화한 라이브러리나 기능들을 사용하고 있기 때문이라</strong> 생각합니다.</p>
<p>Client에서 데이터 페칭을 위해서 React-Query를 사용하고, 비동기 코드 작성할 때 복잡성과 가독성을 위해 Promise 대신 async/await 문법을 더 많이 사용합니다.</p>
<p>전역에서 상태관리 할 때에는 Context API를 사용하거나 상태관리 라이브러리를 사용하고 있습니다. 이런 이유 때문에 직접적으로 Promise를 다루지는 않네요.</p>
<p>올해 1월에 아이슬란드 여행 가기 전에 신입분들이 입사할 것 같다는 말을 듣고, Promise 관련한 재사용 코드를 만들어서 전달해야겠다고 생각했습니다. 여러가지 조건들을 찾아본 결과 아래와 같이 네트워크 오류가 발생하여 데이터를 제대로 호출하지 않았을 때 자동 재 호출하는 <strong>FetchAutoRetry 코드를 작성해서</strong> Promise의 개념을 설명해주곤 합니다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">async</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> fetchAutoRetry</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  fetcher</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>,</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  maximumRetryCount</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">  delay</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> number</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">T</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> fetcher</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (e) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (maximumRetryCount </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      throw</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> e;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      await</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(resolve, delay));</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> fetchAutoRetry</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(fetcher, maximumRetryCount </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, delay);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<blockquote>
<p>현재에는 React-Query를 사용하기에 위 코드를 사용하지는 않지만, 좋은 예시가 된다고 생각합니다!</p>
</blockquote>
<p>이번에 Promise를 공부하면서, Promise가 어떻게 어떠한 원리로 만들어졌는지 궁금해서 MDN 사이트를 참조했습니다. 만들어진 Promise에 대해 구경을 할 수 있지만 구현 방법에 대해서 많이 궁금해졌습니다.</p>
<p>그러다가 Promise 동작을 하나하나 구현해보면서 실습할 예시 코드를 확인하게 되어서 주말에 진행해보려 합니다. 해당하는 작업은 Class 내부에 Promise를 구현하는 방법인데 최대한으로 작업해보고 공유하도록 하겠습니다.</p>
<p>다른 분들도 해보실 수 있도록 <strong><a href="https://youtu.be/1l4wHWQCCIc">링크 전달해드리니</a></strong> 시간 되실 때 해보시면 좋을 것 같습니다!</p>
<br>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>소박한궁금증</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[IT동아리 SIPE 전반기 회고 & SIPE 1차 미션 YouDontKnowJsYet팀 활동 후기]]></title>
            <link>https://hooninedev.com/240509</link>
            <guid isPermaLink="false">https://hooninedev.com/240509</guid>
            <pubDate>Thu, 09 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[나는 SIPE라는 IT 동아리에서 1차 미션으로 YouDontKnowJS 라는 책을 읽고, React의 hook의 작동원리에 대해서 스터디하고 있다. 5주 정도 시간이 흘러 그동안 공부한 내용들을 정리해보려고 한다. 앞서 말한 데로 책은 카일 심슨의 YouDontKnowJs Yet(2024.01.12)으로 시작하였다. => 넌 아직 JS를 몰라, ㅋㅋ 아직...]]></description>
            <content:encoded><![CDATA[<p>나는 SIPE라는 IT 동아리에서 1차 미션으로 <strong>YouDontKnowJS</strong> 라는 책을 읽고, <strong>React의 hook의 작동원리에 대해서</strong> 스터디하고 있다. 5주 정도 시간이 흘러 그동안 공부한 내용들을 정리해보려고 한다.</p>
<p><img src="/content/240509/1.avif" alt="1.avif" loading="lazy" decoding="async"></p>
<p>앞서 말한 데로 책은 카일 심슨의 YouDontKnowJs Yet(2024.01.12)으로 시작하였다. <del>=> 넌 아직 JS를 몰라, ㅋㅋ 아직도!!! 라는 뜻인가?!?</del>
2판인 Yet에서는 자바스크립트에 대한 설명과 코드 패턴들 그리고 의문점들이 추가되었다. 1판만 가지고 계신 분들에게도 좋은 내용이 많으니 구매해서 읽어보시는 것을 추천한다!</p>
<br>
<h2 id="책을-읽고-새롭게-알게-된-지식들"><a class="anchor" href="#책을-읽고-새롭게-알게-된-지식들">책을 읽고 새롭게 알게 된 지식들</a></h2>
<ul>
<li>JavaScript는 ECMAScript의 스펙을 지원하는 개발언어이며, 하위호환을 존중하는 언어이다.</li>
<li>렉시컬 스코프 스펙을 지원하기 위해 구문 분석과 같은 컴파일이 필요하고 <strong>실제 동작은 인터프린터 방식을 취한다.</strong></li>
<li><strong>Function에 대한 일급 객체를 지원한다.</strong></li>
<li>클로저, 프로토타입을 활용하여 function을 추상화 및 캡슐화하였고 상속 개념처럼 활용할 수 있도록 발전하다가 추후에는 class 문법을 지원하게 되었다.</li>
<li>클로저를 변수의 순간 상태를 기록한 것을 스냅숍이라 착각하지만, 언제든지 접근할 수 있도록 관계를 맺어주는 <strong>라이브 링크</strong>의 개념이 더 적합하다. 그렇기 때문에 <strong>값을 읽는 것뿐만 아니라 수정(재할당)도 가능</strong>하다.</li>
<li>클로저는 2가지 관점에서 정의 가능하다
<ul>
<li><strong>관찰 가능성 관점</strong>
<ul>
<li>반드시 함수와 관련이 있어야된다.</li>
<li>외부 스코프의 변수를 적어도 하나 이상 참조해야된다.</li>
<li>참조하려는 변수가 있는 스코프 체인의 다른 분기에서 함수를 호출해야한다.</li>
</ul>
</li>
<li><strong>구현 관점</strong>
<ul>
<li>프로그램의 다른 부분에서 해당 함수 인스턴스에 대한 참조가 존재하는 한 함수 인스턴스와 그 전체 스코프 환경 체인을 살아있게 유지하는 마법</li>
</ul>
</li>
</ul>
</li>
<li><strong>필자는 var 사용을 권장함.</strong> 굳이 불변성이라는 이름에 맞춰서 let, const에 집중해야할까? => 해당 내용은 꼭 읽어보시기를 추천합니다 <del>(이런거 신입 면접 때 말하면 간지나겠다)</del></li>
<li><strong>동기 클로저</strong> : 함수 내부에서 비동기적으로 처리되는 작업이 있을 때, 그 작업이 완료된 후에 특정 동작을 수행해야 할 경우 사용한다. 주로 콜백 함수(callback function)나 이벤트 핸들러(event handler)에서 사용한다.</li>
<li><strong>AMD(비동기 모듈 정의) 와 UMD 패턴</strong> => <strong>한번 찾아보시는 것 추천!👍👍</strong>
<ul>
<li>UMD 패턴은 모듈이 AMD, CommonJS, 그리고 전역 객체(Global object) 등 다양한 환경에서 사용될 수 있도록 하기 위해 모듈이 있는지 여부를 체크하고, 해당 환경에 맞게 모듈을 정의하는 방식</li>
<li>AMD는 자바스크립트 모듈을 비동기적으로 로드하고 정의하는 모듈화 방식으로 브라우저 환경에서 모듈화를 구현하는 데 사용</li>
</ul>
</li>
</ul>
<br>
<p><img src="/content/240509/2.jpeg" alt="2.jpeg" loading="lazy" decoding="async"></p>
<br>
<p>작년 초에 클로저를 사용하는 것을 지양해야 된다는 글을 보았다. 글에서 클로저 지양을 주장한 이유는 클로저를 남발해서 사용하다 보면, 변화에 대한 인지가 어려울 수 있고, 변수의 변화가 GC(가비지 컬랙터)를 예기치 않게 막아 메모리 사용을 급증시키는 요인이 되기 때문이었다. 1년여 시간이 지난 지금 IT 분야에서 메모리 사용에 대한 중요성이 주목받으면서 해당 내용이 어떤 의미로 작성된 것인지 간략하게 알게되었다.</p>
<p>그래도 개인적인 생각에는 클로저의 특성을 잘 사용하면 효율적이고 성능이 뛰어난 프로그램을 구축하는 데 큰 영향을 준다고 생각한다. 단, 필요하지 않은 함수 참조와 그에 따른 클로저를 제때 잘 삭제하는 것이 중요하다고 생각한다!</p>
<br>
<h2 id="react-hooks-파go파go"><a class="anchor" href="#react-hooks-파go파go">React hooks 파GO파GO</a></h2>
<br>
<p><img src="/content/240509/3.png" alt="3.png" loading="lazy" decoding="async"></p>
<br>
<p>클로저의 개념을 이용한 것들이 아주아주 많다. 비동기 처리, React의 다양한 훅들, 컴포넌트의 구성 등등....</p>
<p>많이 사용하는 것 중에 어떤 것이 있고, 어떻게 사용하고 또 어떻게 개선할 수 있을까 등에 관해서 공부해 보았다.</p>
<br>
<h3 id="usestate"><a class="anchor" href="#usestate">useState</a></h3>
<p>우선 useState는 예~~전에 블로그에 작성해두어서, 해당 부분 복습할겸 다시 찾아보았다. 해당하는 내용은 <strong><a href="https://hooninedev.com/230820/">여기를</a></strong> 참조해보시면 됩니다</p>
<p>비동기 처리와 오래된 클로저 함수를 사용할 때 캡처에 관해서 문제가 생긴다. 그래서 캡처하는 시점을 파악해서 지연을 주거나, 새롭게 값을 할당할 수 있도록 해야 한다. 자세한 건 아래의 예시 코드를 확인해보자</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> DelayedCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">count</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> handleClickAsync</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> delay</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(count </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      {count}</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{handleClickAsync}>Increase async&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    &#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>버튼을 클릭할 때마다 setTimeout이 1초의 delay()의 실행을 예약한다. 위 경우에서 버튼을 연달아 2번 클릭하게 되면 두 번째 클릭의 delay()가 오래된 count의 값을 0으로 캡처하기 때문에 count가 1만 올라가게된다</p>
<p>위 오류를 해결해주기 위해서는 delay() 내부를 아래와 같이 바꾸면 된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> delay</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  setCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">count</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> count </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span></code></pre></figure>
<p><strong>이전 상태를 기반으로 새 상태를 반환하는 콜백이 상태 업데이트 함수에 제공되면 React는 최신 상태 값이 해당 콜백의 인수로 제공되도록 변경이 필요해 위 처럼 변경해야된다.</strong> 그렇게 하면 빠르게 2번 클릭해도 값이 정상적으로 count가 2가 된다.</p>
<p>간단하지만 예시지만 복잡한 코드를 구현할 때 비동기 처리와 여러 상태를 동시에 다루다 보면 캡처의 시점을 제대로 파악하지 못하는 경우가 종종 있다. 그래서 이전값에서 새로운 값을 업데이트해 줄 경우에 <strong>Arrow Function을 사용하는 것을 권장한다!</strong></p>
<br>
<h3 id="useeffect"><a class="anchor" href="#useeffect">useEffect</a></h3>
<p>useEffect는 hook을 사용하면서 useCallback과 함께 제일 많은 사이드 이펙트가 발생한다. 그러므로 많이 중요하다고 생각해서 하나의 블로그 주제로 다룰 예정이기에 여기에서는 새롭게 알게 된 부분과 유의해서 사용될 부분에 대해서 기록하도록 하겠다!</p>
<p>우선 아래 useEffect 코드를 실행해보고 작동 순서를 파악해보자</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> React, { useEffect, useState } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "react"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Home</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(state);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    setState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, []);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(state);</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(state);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">100</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, []);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> default</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Home;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 0</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 0</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 1</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 0</span></span></code></pre></figure>
<p>일단 첫 번째 렌더링에서 console.log(state)에 의해서 <strong>console.log(0)이 출력된다.</strong> 그 후에 setState의 callback 함수가 호출되고 여전히 <strong>console.log(state)는 0으로 출력된다.</strong> 그 이후 setTimeout으로 0.1초의 기다림 후에 <strong>setState가 1이 된다.</strong> 2번째 렌더링이 발생하고 <strong>console.log(state)는 1이 출력</strong>되고 setTimeOut 함수의 클로저 때문에 <strong>마지막으로 0이 출력된다.</strong></p>
<p>그럼 조금 더 응용해보자. 우리는 useEffect를 사용하지만 <strong>useInsertionEffect, useLayoutEffect도 사용한다.</strong> 작동의 우선순위를 생각해보며 아래 코드를 실행해보자</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  useEffect,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  useInsertionEffect,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  useLayoutEffect,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  useState,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "react"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Home</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    setState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> state </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, []);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">3</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, [state]);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">4</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">5</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, [state]);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useLayoutEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">6</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">7</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, [state]);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useInsertionEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">8</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">9</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, [state]);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> default</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Home;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// **첫 번째 렌더링 출력값**</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 1</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 10</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 8</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 6</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 2</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 4</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 첫 번째 렌더링 출력값</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 1</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 10</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 9</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 8</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 7</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 6</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 3</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 5</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 2</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 4</span></span></code></pre></figure>
<p>일단 알아야 될 것은 렌더링 우선순위는 높은 순서에서 낮은 순서대로 Main thread => useInsertionEffect => useLayoutEffect => useEffect 이다.</p>
<p>그리고 리렌더링의 우선순위는 높은 순서에서 낮은 순서대로 Main thread => useInsertionEffect 정리 후 즉시 실행 => useLayoutEffect 정리 후 즉시 실행 => useEffect 모두 정리 => 순서대로 하나씩 useEffect 실행</p>
<br>
<h3 id="번외로-useeffectonce"><a class="anchor" href="#번외로-useeffectonce">번외로.. useEffectOnce</a></h3>
<p>여러분들은 useEffectOnce를 사용하시나요? 저는 한번의 useEffectOnce를 custom Hook으로 사용합니다. 사내에서 useEffect를 많이 사용하다보니, 꼭 사이드 이펙트를 방지해야되는 코드를 명시적으로 보기 위해서 사용하는데..!!</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { useEffect } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "react"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useEffectOnce</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">effect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(effect, []);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<blockquote>
<p>이 부분에 대해서 피드백 해주시면 감사하겠습니다🙇🙇‍♂️</p>
</blockquote>
<br>
<h2 id="아-그리고-번외로"><a class="anchor" href="#아-그리고-번외로">아 그리고.. 번외로!</a></h2>
<p>나 생각해보니까 5주동안 동아리에서 이것저것 많이 했어요.. 사이프 공식 행사도 진행하고, 진짜 아티클도 많이 읽고 책도 읽고!!! 제가 위 주제로 사이프 1차 미션을 진행하면서 읽은 아티클 링크 걸어두겠습니다! 그중에 좋다고 생각하는 것들 따로 표시해놓을 거니깐 꼭 읽어보세여~!!</p>
<p>5주동안 어떤 것을 열심히 했는지 대략적으로 확인해보겠습니다~!</p>
<h3 id="sipe-운영진"><a class="anchor" href="#sipe-운영진">SIPE 운영진</a></h3>
<p>처음으로 IT 동아리 운영진을 했습니다! 여러 가지 행사에 참여해서, 대화를 이끌어가 보기도 해보고, 행사의 사진 찍는 역할도 해보고, MC도 해보고, 사람들이랑 맛있는 것도 먹고, 스터디도 진행하고, 같이 운동도 하는 보람찬 시간을 보냈네요!!</p>
<table>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><img src="/content/240509/8.JPG" alt="8.JPG" loading="lazy" decoding="async"></td>
<td><img src="/content/240509/9.JPG" alt="9.JPG" loading="lazy" decoding="async"></td>
<td><img src="/content/240509/10.JPG" alt="10.JPG" loading="lazy" decoding="async"></td>
</tr>
</tbody>
</table>
<br>
<h3 id="closure"><a class="anchor" href="#closure">closure</a></h3>
<p>사실 내가 클로저를 공부하면서 느낀 부분 중 하나는 생각보다 다양한 곳에서 쓰고 있다라는 것을 알게 되었다. 그리고 작년 10월쯤 파랑님의 스터디에서 <strong>커링</strong> 이라는 개념에 대해서 들은 적 있는데 해당 개념이 클로저의 원리를 잘 사용한 문법이라고 생각해서 재미있는 샘플 코드를 작성해두니 꼭 해보시면 좋겠다!</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> dev </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "bfe"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> a</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> dev </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "BFE"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(dev);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">dev </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "bigfrontend"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">a</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()(); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// "BFE"</span></span></code></pre></figure>
<p>위 코드의 출력값은 뭐가 될까? 정답은 <strong>"BFE"가</strong> 출력된다.</p>
<p>왜냐하면 클로저는 함수 실행이 완료된 후에도 함수 내부의 반환 함수가 함수 변수 dev = BFE에 대한 참조를 유지하도록 함수 a가 정의될 때 생성되기 때문이다. 위 코드에서 let을 사용하여 선언된 dev라는 외부 변수도 있는데, 이 변수는 나중에 bigfrontend에 재할당된다.</p>
<p>그러나 a()() 커링함수를 실행할 때 외부 dev 값 bigfrontend에 관계없이 클로저 값 "BFE"를 로깅하고 있기 때문에 "BEF"가 출력된다.</p>
<br>
<h3 id="closure을-이용한-데이터-은닉화"><a class="anchor" href="#closure을-이용한-데이터-은닉화">closure을 이용한 데이터 은닉화</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">var</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Account </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  var</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> balance </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> checkIsPositive</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">amount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> amount </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">!==</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "number"</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> ||</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> amount </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      throw</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Invalid amount"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    deposit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">amount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      checkIsPositive</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(amount);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      balance </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> amount;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Deposit:"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, amount);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    withdraw</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">amount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      checkIsPositive</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(amount);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (balance </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">>=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> amount) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        balance </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> amount;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Withdraw:"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, amount);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Insufficient funds"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    getBalance</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> balance;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">})();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Current Balance:"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, Account.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getBalance</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 0</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Account.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">deposit</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">100</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Current Balance:"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, Account.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getBalance</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 100</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Account.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">withdraw</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">50</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Current Balance:"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, Account.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getBalance</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 50</span></span></code></pre></figure>
<p>이 코드에서 Account 모듈은 클로저를 사용하여 balance 변수를 숨기고, deposit, withdraw, getBalance 메서드를 통해 이 변수에만 접근할 수 있도록 할 수 있다.</p>
<p>이렇게 함으로써 외부에서 balance 변수를 직접 수정하는 것을 방지하고, 데이터를 안전하게 은닉할 수 있다.</p>
<p><strong>모듈은</strong> 관련 있는 기능을 묶어서 캡슐화하고, 외부에는 필요한 인터페이스만 노출하여 사용자가 쉽게 사용할 수 있도록 하는 것을 의미하는데, 모듈 패턴은 프로그램을 더 모듈화하고 유지보수하기 쉽게 만들어주는 디자인 패턴 중 하나로 코드의 재사용성을 높이고, 의도치 않은 상태 변화나 충돌을 방지할 수 있다.</p>
<br>
<h3 id="usehook-ts"><a class="anchor" href="#usehook-ts">useHook-ts</a></h3>
<p><img src="/content/240509/5.png" alt="5.png" loading="lazy" decoding="async"></p>
<p>저희 팀 미션에서 <strong><a href="https://github.com/juliencrn/usehooks-ts">useHook-ts에 PR도 올려보고,</a></strong> 이슈에 태클을 걸어보기로 했어요!! 그래서.. 엄청 대단한 거는 아니지만, 회사에서 배열을 관리하는 코드를 자주 사용하는데 그것을 훅으로 간단하게 만들어서 PR 올려두고 다른 분들의 코드를 보면서 이런 것도 훅이 될 수 있구나.. 하고 찾아보았습니다! 그리고 이슈에 댓글도 달아보았는데 사람들이 생각보다 답장을 잘 해주셔서 놀랬다는?!</p>
<p>제가 만든 훅은 <strong>useArray</strong> 입니다. 해당 코드는 <strong><a href="https://github.com/juliencrn/usehooks-ts/pull/597">요기를👇👇</a></strong> 보면 확인 할 수 있습니다!</p>
<p>useArray 훅을 만들어보면서 테스트도 작성해보고, 다른 custom TS Hook들을 살펴볼 수 있었습니다. 평상시에 쉽게 사용하던 부분인데 타입, 사이드 이팩트에 대해서 생각해볼 수 있는 좋은 기회였습니다~!</p>
<p>그리고 다른 분들의 PR과 Issues에 댓글 남겨보면서 대화도 해보았습니다. <del>ㅋㅋ 사소한 행복..</del></p>
<p><img src="/content/240509/4.png" alt="4.png" loading="lazy" decoding="async"></p>
<blockquote>
<p>사랑스럽게 댓글 달아주시는.. 좋은 분들</p>
</blockquote>
<br>
<h3 id="기본적이지만-짚고-넘어가자"><a class="anchor" href="#기본적이지만-짚고-넘어가자">기본적이지만 짚고 넘어가자!!</a></h3>
<p>작업을 하다 보면 기본적인 것을 잊을 때가 있다. 2가지를 생각하고 코드를 구현했으면 좋겠다. 첫 번째는 <strong>최상위에서만 Hook을 호출해야 한다.</strong> 나처럼.. 잊는 실수 안 했으면 좋겠다.</p>
<p>두 번째는 <strong>오직 React 함수 내에서 Hook을 호출해야 한다.</strong> 우리가 구현한 코드에서는 필수적이지 않지만, 항상 코드의 어떤 부분이 상태 관련 로직에 의존하는지 명확하게 구분하는 습관이 중요하다고 생각한다.
(긍정적인 부수효과로, 반복문과 조건문 내부에서 일반 JavaScript 함수처럼 이름 붙여진 상태 관련 함수를 호출하는 실수를 하지 않게 되기 때문이다.)</p>
<br>
<h2 id="회고"><a class="anchor" href="#회고">회고..</a></h2>
<p>스스로에 대한 피드백..을 진행해볼 때! 솔직히 열심히 했다!!!!! 책도 읽고, PR도 남겨보고, 아티클도 많이 읽고, 사내 코드에서도 다양한 관점으로 접근했다.</p>
<p>뭐 잘했다는 이야기만 하고 싶은 것은 아니다. 명확하지 못한 주제와 계획으로 팀 활동을 병행하면서 소통이 너무 적었다. 아쉽아쉽..ㅠㅠ 미션 외에서 팀원들과 외부 활동을 할 때 너무너무 좋았는데!!!</p>
<p>이러한 경험을 잘 피드백해서 2차 미션도 잘 해내고 싶다!! 1차미션 함께해준 팀원들 감사했습니다~!!!</p>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><img src="/content/240509/6.jpg" alt="6.jpg" loading="lazy" decoding="async"></td>
<td><img src="/content/240509/7.jpg" alt="7.jpg" loading="lazy" decoding="async"></td>
</tr>
</tbody>
</table>
<br>
<h2 id="출처-및-도움되는-링크들"><a class="anchor" href="#출처-및-도움되는-링크들">출처 및 도움되는 링크들</a></h2>
<ul>
<li><a href="https://jser.dev/series/react-source-code-walkthrough">JSer.dev</a></li>
<li><a href="https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js#L141">React Github의 ReactHooks.js</a></li>
<li><a href="https://hewonjeong.github.io/deep-dive-how-do-react-hooks-really-work-ko/">React Hook의 실제 동작 분석</a> => <strong>꼭 읽어보시길!</strong></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures">MDN Closure</a></li>
<li><a href="https://medium.com/@srinumadhav36/memory-management-with-closures-in-node-js-6bf09b3adfde">Memory Management wit Closure</a></li>
<li><a href="https://www.codeguage.com/courses/js/functions-closures-quiz">Closure 관련 퀴즈</a></li>
<li><a href="https://www.youtube.com/watch?v=bGzanfKVFeU&#x26;ab_channel=BeJS">Goodbye, useEffect</a>
<ul>
<li><a href="https://github.com/davidkpiano">위 영상 발표자 : David Khourshid</a></li>
</ul>
</li>
<li><a href="https://medium.com/@emmanuelodii80/bye-bye-usestate-useeffect-revolutionizing-react-development-d91f95891adb">Bye-bye useState &#x26; useEffect</a> => <strong>꼭 읽어보시길!</strong></li>
<li><a href="https://www.youtube.com/watch?v=-yIsQPp31L0&#x26;pp=ygUKdXNlRWZmZWN0IA%3D%3D">useState &#x26; useEffect 사용할 때 실수하는 것들</a></li>
</ul>
<br>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>대외활동</category>
        </item>
        <item>
            <title><![CDATA[비전공 프론트엔드 2년차 개발자 멘티 일지: 1주차 - 으아 떨려...]]></title>
            <link>https://hooninedev.com/240507</link>
            <guid isPermaLink="false">https://hooninedev.com/240507</guid>
            <pubDate>Tue, 07 May 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[독학으로 공부를 계속하다 회사에 다닌 지 1년이 넘은 현시점에서, 스스로를 객관적으로 돌이켜보기 위해서 멘토링을 신청하게 되었다! 멘토링을 하면서 느낀 경험을 1주일 단위 회고 형식으로 진행해보려 한다. 첫 번째 주차의 회고를 진행해보겠다 (24.04.30 ~ 24.05.06) 일단.. 다시 공부가 필요한 것들 복습하자 돌이켜 생각해보면 잘 몰랐던 것들은 ...]]></description>
            <content:encoded><![CDATA[<p>독학으로 공부를 계속하다 회사에 다닌 지 1년이 넘은 현시점에서, 스스로를 객관적으로 돌이켜보기 위해서 멘토링을 신청하게 되었다!</p>
<p>멘토링을 하면서 느낀 경험을 1주일 단위 회고 형식으로 진행해보려 한다.</p>
<p>첫 번째 주차의 회고를 진행해보겠다 <strong>(24.04.30 ~ 24.05.06)</strong></p>
<br>
<h2 id="일단-다시-공부가-필요한-것들-복습하자"><a class="anchor" href="#일단-다시-공부가-필요한-것들-복습하자">일단.. 다시 공부가 필요한 것들 복습하자</a></h2>
<p>돌이켜 생각해보면 잘 몰랐던 것들은 계속 모르거나 헷갈리고 있다. <del>(나 자신을 똥 멍청이라 생각한다ㅠ.. 그래도 천재가 되기 위해서 차근차근 복습해보자)</del></p>
<p><img src="/content/240507/1.gif" alt="1.gif" loading="lazy" decoding="async"></p>
<br>
<h3 id="깊은-복사와-얕은-복사의-차이"><a class="anchor" href="#깊은-복사와-얕은-복사의-차이">깊은 복사와 얕은 복사의 차이</a></h3>
<p><strong>얕은 복사(Shallow Copy)는</strong> 원본 객체의 최상위 수준의 속성들을 복사하고, 내부의 객체는 참조를 공유한다. 즉, 내부 객체에 대한 변경은 복사된 객체와 원본 객체 양쪽에 모두 영향을 주고, 최상위 수준의 속성이 필요한 경우에 사용한다.</p>
<ul>
<li>Object.assign()을 이용한 얕은 복사</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> item</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  a: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"메론"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  price: {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    small: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"20000"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    large: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"30000"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> copyItem</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Object.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">assign</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({}, item);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">copyItem.price.small </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "22000"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(item </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> copyItem); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// false</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(item.price.small </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> copyItem.price.small); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">//true</span></span></code></pre></figure>
<ul>
<li>Spread 연산자를 이용한 얕은 복사</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> item</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  a: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"메론"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  price: {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    small: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"20000"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    large: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"30000"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> copyItem</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">item };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(item </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> copyItem); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// false</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(item.price.small </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> copyItem.price.small); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">//true</span></span></code></pre></figure>
<br>
<p><strong>깊은 복사(Deep Copy)는</strong> 원본 객체의 모든 속성들을 재귀적으로 복사해 원본 객체와 완전한 독립이다.(원본과의 참조가 완전히 끊어진 객체) 내부 객체까지 새로운 객체로 복사되어, 다른 객체에 영향을 주지 않고 복잡한 구조를 가진 객체를 다루기위해 사용한다.</p>
<ul>
<li>JSON.parse, JSON.stringify를 이용한 깊은 복사</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> item</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  a: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"메론"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  price: {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    small: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"20000"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    large: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"30000"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  place: [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"강남점"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"역삼점"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"송파점"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">],</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> copyItem</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> JSON</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">parse</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">JSON</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">stringify</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(item));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">copyItem.price.small </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "24000"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">copyItem.place.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">push</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"잠실점"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(item </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> copyItem); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">//false</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(item.price.small </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> copyItem.price.small); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">//false</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(item.place </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> copyItem.place); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">//false</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(item); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// {"a": "메론","price": {"small": "20000","large": "30000"},"place": ["강남점","역삼점","송파점"]}</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(copyItem); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// {"a": "메론","price": {"small": "24000","large": "30000"},"place": ["강남점","역삼점","송파점","잠실점"]}</span></span></code></pre></figure>
<ul>
<li>재귀 함수를 이용해 깊은 복사</li>
<li>lodash 라이브러리 <strong><a href="https://lodash.com/docs/4.17.15#cloneDeep">cloneDeep 사용</a></strong> 해서 깊은 복사</li>
</ul>
<br>
<h3 id="왜-모던-웹에서-불변성을-지켜야할까"><a class="anchor" href="#왜-모던-웹에서-불변성을-지켜야할까">왜 모던 웹에서 불변성을 지켜야할까?</a></h3>
<p>내가 뭐라고 했는지 정확히 기억나지 않지만, 불변성을 지켜야 의도하지 않은 데이터 변경을 막을 수 있다고 했던 것 같다. 비슷하긴 하지만 찾아보니 더 다양한 내용이 있다는 걸 알게되었다~!</p>
<ol>
<li>불변성을 유지하면 <strong>상태 변경을 추적하고 이전 상태로 되돌리는 것이 수월</strong>해 관리하는 데 도움이 된다.</li>
<li>불변성을 유지하면 <strong>코드의 동작이 예측 가능해 디버깅에 수월</strong>하다.</li>
<li>불변성 덕분에 변경이 없는 객체 또는 배열을 공유할 수 있고, 메모리를 절약해 변경 감지를 위한 <strong>추가적인 비용을 줄일 수 있다.</strong></li>
<li>불변성은 다중 스레드 환경에서 데이터 공유를 안전하게 만들기 때문에, 여러 스레드가 동시에 데이터를 변경하려고 할 때와 같이 <strong>예기치 않은 결과를 방지하고 동기화 문제를 피할 수 있다.</strong></li>
<li><strong>순수 함수</strong>(동일한 입력에 대해 항상 동일한 출력을 반환하는 함수이다. 외부 상태에 의존하지 않고, 상태를 변경하지 않아 영향을 미치지 않아서 부작용이 없다)와 함께 사용 가능하다.</li>
</ol>
<br>
<h3 id="실행-컨텍스트에-대해서"><a class="anchor" href="#실행-컨텍스트에-대해서">실행 컨텍스트에 대해서</a></h3>
<p>실행 컨텍스트 공부 엄청했다ㅠㅠ.. 그런데 말을 못하겠어.. 일단 영어단어도 너무너무 어렵고.. 하지만 더 공부해서 그림까지 완벽하게 그려보도록 하겠다.</p>
<p>해당하는 자료들은 책에도 너무너무 많고. 동영상도 너무 많다. 내가 여기에 기록하지 않고 좋은 자료를 첨부하도록 하겠다! => 솔직히 이거 2개만 다 이해하면 당신은 JS엔진</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=pfQfEwnJHRs&#x26;t=1597s">설명 킹왕짱 시니어코딩님</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLcqDmjxt30Rt9wmSlw1u6sBYr-aZmpNB3">이게 공짜라니.. 제로초님의 인간 JS 엔진 되기</a></li>
</ul>
<br>
<blockquote>
<p><strong>콜 스택과 실행 컨텍스트는 어떤 연관이 있을까??</strong></p>
<p>실행 컨텍스트와 콜 스택은 서로 상호 작용하여 코드의 실행 흐름을 관리한다. 함수가 호출되면 해당 함수의 실행 컨텍스트가 콜 스택에 푸시(Push)되고, 해당 함수의 실행이 완료되면 콜 스택에서 팝(Pop)된다. 이러한 과정을 통해 자바스크립트 엔진은 코드의 실행 순서를 유지하고 실행 중인 함수의 상태를 추적한다.</p>
<p>실행 컨텍스트는 실행 환경을 추상화하고 관리하며, 콜 스택은 실행 중인 실행 컨텍스트의 순서를 관리하여 코드의 실행 흐름을 유지</p>
</blockquote>
<br>
<blockquote>
<p><strong>자바스크립트 한 함수의 생명주기 (with 실행 컨텍스트)</strong></p>
<p>생성(Create) => 호출(Call) => 실행(Execute) => 완료(Complete)</p>
<p>함수가 정의되어, 메모리가 로드되고 함수 객체가 생성된다. 함수 객체는 코드와 스코프 체인을 포함한 실행 컨텍스트를 생성 (생성)</p>
<p>함수가 호출되면 새로운 실행 컨텍스트가 생성되는데, 이 실행 컨텍스트는 함수의 변수, 매개변수, 스코프 체인 등을 포함하며, 콜 스택에 푸시된다. (호출)</p>
<p>실행 컨텍스트가 활성화되면 함수의 코드가 실행되고, 그 동안 변수의 값이 변경되고, 새로운 함수가 호출되거나 반환될 수 있다. (실행)</p>
<p>함수의 실행이 완료되면 실행 컨텍스트가 콜 스택에서 팝되고, 메모리에서 제거되어 함수의 결과가 반환됨 (완료)</p>
</blockquote>
<br>
<blockquote>
<p><strong>이벤트 루프(Event Loop)</strong></p>
<p>콜 스택과 백그라운드(Task Queue 또는 Microtask Queue)를 모니터링하고, 이벤트 루프가 비어있을 때 백그라운드에서 실행된 작업(Task Queue)을 콜 스택으로 이동시켜주는데, Task Queue에 있는 작업들은 비동기적인 작업의 결과뿐만 아니라, 이벤트 핸들러나 타이머 콜백 등도 포함된다.</p>
<p>또한 마이크로테스크 큐(Microtask Queue)를 모니터링하여 콜 스택의 작업이 완료된 후에 마이크로테스크를 우선적으로 처리합니다.</p>
</blockquote>
<br>
<h3 id="마이크로테스크와-매크로테스크-차이점은"><a class="anchor" href="#마이크로테스크와-매크로테스크-차이점은">마이크로테스크와 매크로테스크 차이점은??</a></h3>
<p>솔직히!! 콜스텍에 대해서 공부하면서 보았는데..ㅠㅠ 말로 대답하려니 엄~~청 햇갈려서 대답 못했다ㅠㅠ.. 다시 복습하자!!!</p>
<p><img src="/content/240507/2.jpeg" alt="2.jpeg" loading="lazy" decoding="async"></p>
<p>우선 마이크로테스크(Microtask)와 매크로테스크(Macrotask)는 자바스크립트 비동기 처리에 사용되는 용어다!</p>
<p><strong>실행 시점의 차이</strong></p>
<ul>
<li>매크로테스크: 매크로테스크는 브라우저의 이벤트 루프에서 처리되는 비동기 작업으로, 주로 큐(Queue)에 들어간 이벤트 핸들러(DOM 이벤트 핸들러)나 타이머 콜백(setTimeout, setInterval) 등이 해당된다.</li>
<li>마이크로테스크: 매크로테스크보다 더 높은 우선순위를 가지며, Promise의 then, catch, finally 메서드와 같은 비동기 작업이 해당된다. 마이크로테스크는 현재 매크로테스크 큐의 처리가 끝나고 다음 태스크를 실행하기 전에 처리된다.</li>
</ul>
<br>
<p><strong>실행 순서의 차이</strong></p>
<ul>
<li>매크로테스크: <strong>이벤트 루프의 한 사이클이 완료된 후에 실행된다.</strong> 현재 실행 중인 코드 블록이 완료되지 않아도 처리된다.</li>
<li>마이크로테스크: 매크로테스크보다 더 높은 우선순위를 가지므로, <strong>현재 실행 중인 코드 블록이 완료된 후에 처리된다.</strong> 따라서 마이크로테스크는 매크로테스크보다 먼저 실행됩니다.</li>
</ul>
<br>
<h3 id="스코프체인과-스코프"><a class="anchor" href="#스코프체인과-스코프">스코프체인과 스코프</a></h3>
<p><strong>스코프(Scope)는</strong> 변수와 함수에 대한 접근성 및 가시성을 결정하는 개념으로 자바스크립트에서는 스코프를 정적으로 지정한다. 코드가 실행되기 전에 어디서 변수나 함수에 접근할 수 있는지 결정된다. 스코프는 코드의 블록(함수, if문, for문 등) 내에서 변수와 함수에 접근할 수 있는 범위를 정의한다.</p>
<p><strong>스코프체인(Scope Chain)은</strong> 변수와 함수에 대한 접근을 결정하기 위한 메커니즘으로, 변수나 함수를 참조할 때 해당 식별자를 검색하는 순서를 결정한다. 일반적으로 스코프체인은 현재 실행 중인 컨텍스트의 변수 객체와 그 부모 컨텍스트의 변수 객체를 연결한 체인이다.</p>
<blockquote>
<p>간단하게 생각하면 <strong>스코프는 해당 코드의 범위를 의미</strong> 하고 <strong>스코프체인은 자기 자신의 스코프를 제외한 자신과 가장 가까운 변수 객체의 모든 스코프들이라고 생각한다!</strong></p>
</blockquote>
<br>
<h3 id="함수-스코프와-블록-스코프"><a class="anchor" href="#함수-스코프와-블록-스코프">함수 스코프와 블록 스코프</a></h3>
<p>내가 뭐라고했더라.. ㅋㅋㅋ 함수선언 내부 범위가 함수 스코프고 블록 스코프는 함수를 제외한 변수, 객체의 내부 범위가 블록 스코프라고 했다. 뭐 얼추만 비슷하기에 정확히 알아보자!</p>
<p><strong>함수 스코프는</strong> 변수가 선언된 함수 내에서만 유효한 스코프로 함수 내에서 선언된 변수는 함수 내에서만 접근할 수 있다. 함수 스코프 내부에서 선언된 변수는 내부에서만 유효하기 때문에 함수 외부에서는 접근할 수 없다</p>
<p><strong>블록 스코프는</strong> 변수가 선언된 블록 내에서만 유효한 스코프로, 블록은 중괄호 {}로 묶인 코드 영역을 의미한다. 블록 스코프는 주로 if, for, while과 같은 제어문의 블록 내에서 변수를 선언할 때 사용되고 블록 스코프 내부의 변수는 블록 내에서만 유효하면 블록 외부에서는 접근할 수 없다</p>
<blockquote>
<p>간단하게 요약하면 <strong>함수 스코프는 변수가 선언된 함수 내에서만 유효</strong>하고, <strong>블록 스코프는 변수가 선언된 블록 내에서만 유효</strong>하다.</p>
</blockquote>
<br>
<h3 id="생성자-함수의-this-메서드의-this"><a class="anchor" href="#생성자-함수의-this-메서드의-this">생성자 함수의 this, 메서드의 this</a></h3>
<p><strong>메서드(Method)에서의 this는</strong> 해당 메서드가 속한 객체를 가리키고 메서드가 호출된 객체에 바인딩된다. 이를 통해 메서드는 자신이 속한 객체의 속성에 접근할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> obj</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  name: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Jihoon"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  greet</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Hello, "</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> +</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.name);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">obj.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">greet</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 출력: "Hello, Jihoon"</span></span></code></pre></figure>
<p><strong>생성자 함수(Constructor Function)에서의 this는</strong> 새로 생성되는 객체를 가리킨다. 생성자 함수는 new 키워드를 사용하여 호출될 때마다 새로운 객체가 생성되고, 이 객체가 this에 바인딩된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Person</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">name</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.name </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> name;</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">greet</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Hello, "</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> +</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.name);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Jihoon</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Person</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Jihoon"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Jihoon.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">greet</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 출력: "Hello, Jihoon"</span></span></code></pre></figure>
<br>
<h3 id="일급-함수first-class-function"><a class="anchor" href="#일급-함수first-class-function">일급 함수(First-class Function)</a></h3>
<p>사이드 이팩트가 없고 부작용이 없는 순수함수는 들어보았지만, 1급 함수에 대한 개념은 잡혀있지 않았다. 과연 뭘까?</p>
<p>일급 함수는 프로그래밍 언어에서 함수를 일반 값과 동등하게 취급하는 것을 의미하고 아래 조건을 만족한다.</p>
<p><strong>1. 변수에 할당하여 함수를 변수로 사용할 수 있다.</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> myFunc</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Hello, world!"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">myFunc</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 출력: "Hello, world!"</span></span></code></pre></figure>
<p><strong>2. 함수를 다른 함수의 매개변수로 전달할 수 있다.</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> greet</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">func</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  func</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">greet</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Hello, world!"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 출력: "Hello, world!"</span></span></code></pre></figure>
<p><strong>3. 함수가 다른 함수의 반환값으로 사용될 수 있다.</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createFunc</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Hello, world!"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> myFunc</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> createFunc</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">myFunc</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 출력: "Hello, world!"</span></span></code></pre></figure>
<br>
<h2 id="5월-첫째-주-회고"><a class="anchor" href="#5월-첫째-주-회고">5월 첫째 주 회고</a></h2>
<p><img src="/content/240507/5.jpeg" alt="5.jpeg" loading="lazy" decoding="async"></p>
<blockquote>
<p>멘토링 처음 받아본 한주 회고 시작!</p>
</blockquote>
<h3 id="이번-주-좋은-것과-나쁜-것"><a class="anchor" href="#이번-주-좋은-것과-나쁜-것">이번 주 좋은 것과 나쁜 것</a></h3>
<ul>
<li>휴일이 많아서 회사에서 못한 부분을 개인 시간에 진행할 수 있었다.</li>
<li>멘토링을 본격적으로 시작하기전 사람들과 사람들을 많이 만나고, 이야기를 나누는 시간을 가져서 체력적으로 좀 피곤했다.</li>
</ul>
<h3 id="이번주-진행했던-학습개발-내용은"><a class="anchor" href="#이번주-진행했던-학습개발-내용은">이번주 진행했던 학습/개발 내용은??</a></h3>
<ul>
<li>사내의 Devextreme 으로 구성된 것을 Mui + react-hook-form + zod 를 이용하는 것으로 바꾸고 있으며, Custom UI를 만들고 있다.</li>
<li>사내 동료들에게 실행컨텍스트, 콜스택, 클로저의 개념에 대해서 발표하는 시간을 가졌다.</li>
<li>코어 자바스크립트 1~3장 읽으며 멘토링을 준비하였다.</li>
</ul>
<h3 id="가장-고민했던-부분은-무엇이었나요"><a class="anchor" href="#가장-고민했던-부분은-무엇이었나요">가장 고민했던 부분은 무엇이었나요?</a></h3>
<ul>
<li>제네릭을 효율적으로 쓰는 방법? 그리고 어떠한 상황에 써야 되는지 고민 진행 중입니다.</li>
<li>사내 프로젝트에서 상태관리가 너무 복잡하게 되어있어서, 이것을 top-down, bottom-up 방식을 동시에 써야 할지 고민 중입니다.</li>
<li>동아리에서 youdontknowjs 의 클로저 부분을 읽고 자료를 정리해야 되는데, 어떤 내용을 중점으로 다룰지 고민 중입니다.</li>
</ul>
<h3 id="아쉬운-부분을-개선하는-데-필요한-것은-무엇인가요"><a class="anchor" href="#아쉬운-부분을-개선하는-데-필요한-것은-무엇인가요">아쉬운 부분을 개선하는 데 필요한 것은 무엇인가요?</a></h3>
<ul>
<li>어떤 것을 집중해서 맡은 것을 끝내려는 집중력이 필요합니다.</li>
<li>여러 가지를 동시에 해내는 것은 불가능하다고 생각하니, 여유를 가지는 것도 중요하다고 생각합니다</li>
<li>다양한 회사에서 원하는 JD 파악을 해야 합니다.</li>
</ul>
<h3 id="다음-주는-어떻게-보낼-예정인가요"><a class="anchor" href="#다음-주는-어떻게-보낼-예정인가요">다음 주는 어떻게 보낼 예정인가요?</a></h3>
<ul>
<li>멘토링이 끝난 당일과 다음날은 복습 위주로 진행할 것입니다</li>
<li>동아리에서 맡은 역할이 있어서 해결할 예정입니다. (클로저, react hook에 대한 자료정리)</li>
<li>동아리에서 진행하는 행사 계획에 대한 회의를 진행할 것 입니다</li>
<li>앞의 복습내용을 바탕으로 코어 자바스크립트 4~6장을 읽을 예정입니다</li>
<li>5/9 우당탕탕 책방이라는 모임에서 CSS 라이브러리에 관한 이야기를 진행해서 준비할 예정입니다.</li>
</ul>
<br>
<h3 id="메타인지"><a class="anchor" href="#메타인지">메타인지</a></h3>
<p>메타인지를 위해서 자기평가를 진행해보았다. 내가 어떠한 사람이 되고 싶을까? 생각했을 때 모든 형태가 고르게 있는 것도 중요하겠지만 기술적인 부분과 팀 내부의 역할(역량)을 키우는 것이 내 성향에 적합하다고 생각한다.</p>
<p>해당하는 메타인지하는 사이트는 <strong><a href="https://www.engineeringladders.com/">여기를 참고하면 됩니다👇👇</a></strong></p>
<p><img src="/content/240507/4.png" alt="4.png" loading="lazy" decoding="async"></p>
<blockquote>
<p>자기 객관화에 짱이다..</p>
</blockquote>
<p><strong>Technology</strong> - <strong>Evangelizes</strong>: 연구 조사, 개념 증명(POC)을 만들고, 새로운 기술을 팀에 소개</p>
<ul>
<li>사내 기술 스터디 운영중</li>
<li>외부 활동으로 스터디 하는 내용들 사내에 공유 및 개인 시간에 사내 로직 적용해보고 회의를 통해 기술 도입(react-hook-form, react-query, zustand, zod, uuid v4 등등)</li>
<li>사내에서 발생한 일(빌드 속도, 오류, 캐싱, 성능)을 파악해서 정리 ⇒ 발생할 수 있는 이유를 분석 ⇒ 개선점 분석 후 FE 사수에게 제안 ⇒ 팀 발표 진행</li>
<li>시스템에서 맡은 부분의 지식은 있지만, 접근해보지 않은 Socket, Qr, DocuSign, DevOps Tool, Server, Network에 대한 이해도가 낮음</li>
<li><strong>추구하는 방향은 Creates입니다.</strong> 팀에서 사용하는 데 문제가 있거나, 더 좋은 성능과 효과를 위해서 진행할 수 있는 방향이 있으면 제안하고 디자인하고 만들 의향이 있고, 제가 추구하는 공동의 편의성에 적합한 개념이라고 생각합니다</li>
</ul>
<p><strong>System</strong> - <strong>Designs</strong>: 중형에서 대형 사이즈의 기능을 기술 부채를 제거하면서 디자인하고 구현</p>
<ul>
<li>사내에 도입되어있는 devextreme를 제거해 디자이너와 MUI를 이용한 디자인시스템 논의 후 구현</li>
<li>사내에서 사용하는 jQuery 문법으로 동작하는 DOM 직접 접근 방식을 Virtual DOM을 이용해 접근하도록 코드 수정하고 라이브러리 충돌 문제 해결</li>
<li>하지만 프로덕션의 운영을 해본 경험과 결정에 대한 책임 경험 없음</li>
<li><strong>추구하는 방향은 Evolves입니다.</strong> 미래의 요구사항을 미리 파악하거나, 대응할 수 있도록 넓은 시야를 가져서 빠르고 정확하게 대응하고 싶습니다.</li>
</ul>
<p><strong>People</strong> - <strong>Supports</strong>: 다른 팀원들을 적극적으로 지원하고 성공할 수 있도록 돕습니다</p>
<ul>
<li>사실 Mentors와 고민을 했습니다. 사내에 FE에 신입 2명이 입사해서, 하지만 Mentor로 정확히 명하기 어렵고, 제가 공부한 내용에 대한 가이드 역할만 해주어서 Supports를 선택했습니다.</li>
<li><strong>제가 이 역할에서 Manages가 되고 싶습니다.</strong> 사람의 역량을 끌어올리고, 분배하고, 공동의 목표를 긍정적인 방향으로 진행하는 것을 추구하기 때문입니다.</li>
<li>지금은 팀원에게 방향을 잡아주고 적응을 할 수 있도록 안내해주고 있습니다.</li>
</ul>
<p><strong>Process</strong> - <strong>Challenges</strong>: 팀 프로세스를 개선할 수 있는 방법을 찾고 시도해봅니다.</p>
<ul>
<li>사내 입사했을 때 개발에 관한 Jira &#x26; Atlassian의 체계가 잡혀있지 않았습니다. 배포하는 과정, 테스트 과정, 개발하는 과정의 시간 시점을 분리하는 것을 제안하였고, 각자의 역할에 대해 분석해 발표를 진행했습니다.</li>
<li>putty를 이용해 배포하는 과정에서 많은 시간이 소비되는 문제점을 파악했습니다. 해당 과정을 변경하는 작업을 사내에서 진행했습니다.</li>
<li><strong>추구하는 방향은 Adjusts입니다.</strong> 서로 대화를 통한 피드백으로 공동의 프로세스의 긍정적 방향을 위해 조정하고 제안하고 싶습니다. 이러한 변화를 통해 불필요한 시간을 사전에 제거하고 더 많은 소통의 환경을 만들고 싶습니다</li>
</ul>
<p><strong>Influence</strong> - <strong>Team</strong>: 특정 부분만이 아니라 팀 전체에 영향을 끼칩니다</p>
<ul>
<li>사내는 nodejs 기반이다. 백앤드는 nest, 프론트엔드는 react/vite 바쁘거나, 코드의 문제점이 있으면 백앤드 코드를 살펴보고 백앤드에 제안을합니다. 그런 과정을 통해 문제해결 시간을 단축합니다.</li>
<li>Figma, XD를 다뤄본 경험이 있습니다. 이를 토대로 디자이너, 퍼블리셔에게 요구사항을 전달할 때 보다 필요하지 않은 시간 소비를 최대한 줄인 채로 문재해결을 진행하고 있습니다.</li>
<li><strong>추구하는 방향은 Community입니다.</strong> 사내에서뿐만 아니라 개인적으로 선호하는 기술스택(vanilla-extract, zustand)등 과 같은 다양한 개발 생태계에 관심을 가지고 참여하고 싶습니다.</li>
</ul>
<br>
<br>
<p><img src="/content/240507/3.jpeg" alt="3.jpeg" loading="lazy" decoding="async"></p>
<h2 id="출처-및-도움되는-링크들"><a class="anchor" href="#출처-및-도움되는-링크들">출처 및 도움되는 링크들</a></h2>
<ul>
<li><a href="https://velog.io/@thumb_hyeok/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%97%90-%EB%B6%88%EB%B3%80%EC%84%B1%EC%9D%B4-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C">thumb-hyeok님의 리엑트 불변성</a></li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>멘토링</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[LinkedList vs ArrayList : 어떤 것을 선택해야 할까요?]]></title>
            <link>https://hooninedev.com/240424</link>
            <guid isPermaLink="false">https://hooninedev.com/240424</guid>
            <pubDate>Wed, 24 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[링크드인을 접속할려고 했다. Linked... 라고 치는데 밑에 LinkedList가 나왔다.. (그 와중에 린다닷컴은 뭐지?) 그 이후에 새로 알게된 내용이 많아 기록을 남겨볼려고한다. LikedList LinkedList는 내부적으로 양방향의 연결 리스트로 구성되어 있어 참조하려는 원소에 따라 처음부터 정방향 또는 역순으로 순회할 수 있다. 적은 양의 ...]]></description>
            <content:encoded><![CDATA[<p><img src="/content/240424/2.png" alt="3.png" loading="lazy" decoding="async"></p>
<br>
<h4>링크드인을 접속할려고 했다.</h4>
<br>
<p><img src="/content/240424/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<br>
<p>Linked... 라고 치는데 밑에 LinkedList가 나왔다.. <del>(그 와중에 린다닷컴은 뭐지?)</del></p>
<p>그 이후에 새로 알게된 내용이 많아 기록을 남겨볼려고한다.</p>
<br>
<h2 id="likedlist"><a class="anchor" href="#likedlist">LikedList</a></h2>
<p>LinkedList는 내부적으로 양방향의 연결 리스트로 구성되어 있어 참조하려는 원소에 따라 처음부터 정방향 또는 역순으로 순회할 수 있다.</p>
<p>적은 양의 데이터를 추가하거나 삭제할 때는 문제가 없지만, 양이 많아질수록 속도가 늦어지는 배열의 단점을 보완하기 위해 LinkedList가 생겨났다.</p>
<p>LinkedList는 순차적 접근이고, 해당 위치의 노드를 찾는 데에 시간이 소비되어 검색의 속도가 느리다. 그렇지만 LinkedList는 데이터를 추가·삭제시 가리키고 있는 주소 값만 변경해주면 되기 때문에 ArrayList에 비해 상당히 효율적이고 중간에 있는 노드를 삭제해주면 양쪽에 있는 노드를 서로 이어주면 되기 때문에 ArrayList보다 상대적으로 작업이 빠르다.</p>
<br>
<h3 id="단점"><a class="anchor" href="#단점">단점</a></h3>
<p>데이터를 get 하는 과정에서 ArrayList는 무작위 접근이 가능하지만, <strong>LinkedList에서는 순차접근만 가능</strong>하다. 그렇기 때문에 여러 곳은 산재해 저장되어있는 노드들을 접근하는데 있어서 긴 시간이 소요된다. 특히 singly LinkedList는 단방향성을 지니고 있기 때문에, index를 이용하여 자료를 검색하는 애플리케이션에는 적합하지 않다.</p>
<p>참조자(next, prev)를 위해 추가적인 메모리를 할당해야되어서 <strong>추가적인 공간이 필요</strong>하다.</p>
<br>
<h2 id="arraylist"><a class="anchor" href="#arraylist">ArrayList</a></h2>
<p>ArrayList는 기본적으로 배열을 사용하지만 일반 배열과 차이점이 있다.</p>
<p>일반 배열은 처음에 메모리를 할당할 때 크기를 지정해주어야 하지만, ArrayList는 크기를 지정하지 않고 동적으로 값을 삽입하고 삭제할 수 있다.</p>
<p>ArrayList는 각 데이터의 index를 가지고 있고 무작위 접근이 가능하기 때문에, 해당 index의 데이터를 한번에 가져올 수 있고 데이터의 삽입과 삭제시 위치에 따라 그 위치까지 이동하는 시간이 발생한다. 삽입과 삭제가 많다면 ArrayList는 비효율적이다.</p>
<h3 id="단점-1"><a class="anchor" href="#단점-1">단점</a></h3>
<p>데이터들이 지속적으로 삭제되는 과정에서 ArrayList에서는 그 공간 만큼 낭비되는 메모리가 많아지게 되고, <strong>리사이징 처리에서 시간이 많이 소모</strong>된다.</p>
<br>
<h2 id="그래서-뭘-써야할까"><a class="anchor" href="#그래서-뭘-써야할까">그래서 뭘 써야할까?</a></h2>
<p><img src="/content/240424/4.png" alt="4.png" loading="lazy" decoding="async"></p>
<p>ArrayList 와 LinkedList 중에 어느걸 사용하면 되냐고 묻는다면, <strong>삽입 / 삭제가 빈번하면 LinkedList</strong>를, <strong>요소 가져오기가 빈번하면 ArrayList</strong>를 사용하면 된다.</p>
<p>하지만 실질적으로 별 차이가 없다(테스트 진행할 때 나노초까지 비교해야 차이가 보이기 때문)</p>
<br>
<h2 id="출처-및-도움되는-링크들"><a class="anchor" href="#출처-및-도움되는-링크들">출처 및 도움되는 링크들</a></h2>
<ul>
<li><a href="https://opentutorials.org/module/1335/8821">LinkedList</a></li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>소박한궁금증</category>
            <category>자료구조</category>
        </item>
        <item>
            <title><![CDATA[useEffect의 Dependency로 useRef를 사용해보자!]]></title>
            <link>https://hooninedev.com/240419</link>
            <guid isPermaLink="false">https://hooninedev.com/240419</guid>
            <pubDate>Fri, 19 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[갑자기 작업하다가 궁금해져서 적는 첫번째 오이소박한 궁금증!! useEffect의 dependency에 useRef를 넣으면? useEffect 의 []에 의존성(dependency)를 넣을 수 있다! 이건 다 알고 있겠죠!? 그러면 리렌더링이 될 때 어느 시점에 의존성이 작동 될 까!? 긴말 말고 예시코드를 보자! 위 코드는 useRef의 current,...]]></description>
            <content:encoded><![CDATA[<br>
<p>갑자기 작업하다가 궁금해져서 적는 첫번째 오이소박한 궁금증!!</p>
<p><img src="/content/240419/1.jpeg" alt="1.jpeg" loading="lazy" decoding="async"></p>
<br>
<h2 id="useeffect의-dependency에-useref를-넣으면"><a class="anchor" href="#useeffect의-dependency에-useref를-넣으면">useEffect의 dependency에 useRef를 넣으면?</a></h2>
<p>useEffect 의 []에 의존성(dependency)를 넣을 수 있다! <del>이건 다 알고 있겠죠!?</del></p>
<p>그러면 리렌더링이 될 때 어느 시점에 의존성이 작동 될 까!?</p>
<p>긴말 말고 예시코드를 보자!</p>
<p><img src="/content/240419/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<br>
<p>위 코드는 useRef의 current, useState의 state를 handleButtonClick 함수를 이용해서 1씩 올려주는 코드다.</p>
<p>useEffect의 dependency에 useRef의 ref와 ref.current 의 값을 넣어보고 동작을 비교해보자~!!</p>
<br>
<h3 id="ref"><a class="anchor" href="#ref">[ref]</a></h3>
<br>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  conosle.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Ref의 값:"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, ref);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, [ref]);</span></span></code></pre></figure>
<br>
<video width="700" height="500" controls>
  <source src="3.mov" type="video/mp4">
</video>
<br>
<p>useRef의 ref 객체를 dependency에 직접 넣어주게 되면 useEffect가 변경사항을 감지하지 못하고 리렌더링 되지 않는다.. 하지만 값은 변경된다!!!</p>
<br>
<h3 id="refcurrent"><a class="anchor" href="#refcurrent">[ref.current]</a></h3>
<br>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  conosle.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Ref의 값:"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, ref);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, [ref.current]);</span></span></code></pre></figure>
<br>
<video width="700" height="500" controls >
  <source src="4.mov" type="video/mp4">
</video>
<br>
<p>그리고 useRef의 ref.current 값 을 dependency에 직접 넣어주게 되면 useEffect가 변경사항을 감지하고 리렌더링을 하게된다.</p>
<br>
<h2 id="왜-이럴까"><a class="anchor" href="#왜-이럴까">왜 이럴까??</a></h2>
<p><img src="/content/240419/5.jpeg" alt="5.jpeg" loading="lazy" decoding="async"></p>
<p>useEffect의 의존성 배열에 ref를 직접 넣으면 리렌더링이 발생하지 않는 이유는 useEffect의 의존성 배열은 값의 변경 여부를 기준으로 한다.</p>
<blockquote>
<p>⚠️⚠️ useEffect dependency</p>
<p>값들의 변경 여부를 판단할 때 얕은 비교(shallow comparison)가 이루어지기 때문에 배열 내부의 값들이 단순한 원시값(숫자, 문자열 등)이거나 참조 타입의 경우에는 그 참조가 동일한지를 확인한다.</p>
<p>만약 배열 내부의 객체나 배열과 같은 참조 타입의 값이 변경되었다고 해도, 배열 내의 객체나 배열의 참조 자체가 변경되지 않으면 useEffect는 다시 실행되지 않는다.</p>
</blockquote>
<p>ref 변수 자체는 컴포넌트 내부에서 변경되는 것이 아니라, 그 안에 있는 current 속성이 변경될 때만 의존성이 작동해야 한다. 그러나 ref 변수의 참조 자체는 항상 동일하므로 의존성 배열에 ref를 직접 넣는다고 해서 useEffect가 리렌더링을 유도하지 않는다.</p>
<p>반면에 ref.current를 의존성 배열에 넣으면 useEffect는 ref.current 값이 변경될 때마다 다시 실행된다. ref.current 값은 직접 변경되는 값이므로 이를 의존성으로 사용하면 useEffect는 해당 값의 변경을 감지하여 다시 실행되고, 리렌더링이 발생한다.</p>
<p>이를 통해 useEffect의 의존성 배열에는 값의 변경을 감지할 수 있는 항목을 포함해야 하며, ref 변수의 경우에는 주로 ref.current와 같은 값을 사용하여 의존성을 설정하는 것이 적절하다.</p>
<br>
<p><strong>한줄요약 : ref 변수의 참조 자체가 변경되는 것이 아니라 current 속성이 변경될 때만 의존성이 작동 </strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>소박한궁금증</category>
            <category>React</category>
        </item>
        <item>
            <title><![CDATA[웹 렌더링 방식: CSR vs SSR vs SSG vs ISR 등 모든 것 알아보기]]></title>
            <link>https://hooninedev.com/240409</link>
            <guid isPermaLink="false">https://hooninedev.com/240409</guid>
            <pubDate>Tue, 09 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[해당 게시글은 브라우저 렌더링의 모든 것의 후편이니 읽고 오시면 이해가 더 잘됩니다!! 먼저, 웹 렌더링이란 웹 페이지를 사용자가 볼 수 있는 형태로 변환하는 프로세스를 의미한다. 렌더링 방식에 따라 로딩 속도, 검색 엔진 최적화(SEO), 사용자 경험 등이 달라지기 때문에 우리는 방식을 선택할 때 많이 고민할 수 밖에 없다. 고민을 하다보면 CSR,SSR...]]></description>
            <content:encoded><![CDATA[<br>
<blockquote>
<p>🚨 해당 게시글은 <a href="https://hooninedev.com/240329/">브라우저 렌더링의 모든 것</a>의 후편이니 읽고 오시면 이해가 더 잘됩니다!!</p>
</blockquote>
<br>
<p><img src="/content/240409/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<br>
<p>먼저, 웹 렌더링이란 웹 페이지를 사용자가 볼 수 있는 형태로 변환하는 프로세스를 의미한다. 렌더링 방식에 따라 로딩 속도, 검색 엔진 최적화(SEO), 사용자 경험 등이 달라지기 때문에 우리는 방식을 선택할 때 많이 고민할 수 밖에 없다.</p>
<p>고민을 하다보면 CSR,SSR,SPA,MPA,SSG 등등.. 다양한 단축어에 대해 당황하게 된다. 모든 것들에 대해서 살펴보고 어떤 차이점이 있는지 알아보자.</p>
<p>해당 글은 <strong>웹 애플리케이션의 특성과 차이점(SPA,MPA)</strong> 에 대해 설명을 한 후에 <strong>웹 렌더링 방식(CSR,SSR,SSG,ISR,Universal Rendering)</strong> 에 대해 설명해보겠다!</p>
<br>
<br>
<h2 id="spa-vs-mpa-웹-애플리케이션-특성"><a class="anchor" href="#spa-vs-mpa-웹-애플리케이션-특성">SPA vs MPA (웹 애플리케이션 특성)</a></h2>
<br>
<p><img src="/content/240409/9.gif" alt="9.gif" loading="lazy" decoding="async"></p>
<br>
<h3 id="single-page-applicationspa"><a class="anchor" href="#single-page-applicationspa">Single Page Application(SPA)</a></h3>
<p>SPA는 하나의 브라우저 내에서 동작하는 애플리케이션이며 사용하는 동한 페이지 로딩을 필요로 하지 않는다. Gmail, Google Maps, Facebook, GitHub 등이 SPA에 해당한다.</p>
<p>모든 SPA들은 브라우저에 자연스러운 환경을 모방하려고 하며, 훌륭한 UX를 제공하면서 페이지 리로드가 없으며, 별도의 기다리는 시간도 없다. 우리가 방문하고 사용하는 모든 컨텐츠는 하나의 웹페이지 이며, Javascript를 사용해서 로드 되기 때문에 크게 의존적이다.</p>
<p>SPA는 마크업과 데이터를 각각 요청후, 브라우저에서 바로 페이지를 그린다. 우리는 AngularJS, Ember.js, Meteor.js, Knockout.js와 같은 진보한 Javascript를 이용하여 이 작업을 진행할 수 있다.</p>
<p>단일 페이지 사이트들은 사용자에게 심플하면서도 쉽고 실용성있는 컨텐츠를 제공해주는 편안한 웹 공간에 사용자가 머물도록 돕는다는 장점을 가지고 있다.</p>
<br>
<h3 id="multi-page-applicationmpa"><a class="anchor" href="#multi-page-applicationmpa">Multi Page Application(MPA)</a></h3>
<p>멀티페이지 애플리케이션들은 브라우저에서 변경사항이 있을 때서버로 서브밋 데이터를 전달하여 새로운페이지 렌더링을 요청하고 그 데이터를 표시한다. MPA는 SPA보다는 규모가 더 크다.</p>
<p>많은 컨텐츠로 인해, 많은 UI 레벨을 갖게 된다. AJAX 덕분에 우리는 크고 복잡한 애플리케이션들이 서버와 브라우저사이에서 많은 데이터를 전송해야 하는 문제 없이 필요한 것만 데이터를 요청 할 수 있다.</p>
<br>
<h2 id="client-side-rendering-csr"><a class="anchor" href="#client-side-rendering-csr">Client Side Rendering (CSR)</a></h2>
<p><img src="/content/240409/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<br>
<p>CSR은 클라이언트(브라우저)에서 웹 페이지를 렌더링 하는 것이다. 모든 로직, 데이터, 템플릿, 라우팅 등을 서버가 아닌 클라이언트에서 처리하게 된다.</p>
<p>CSR은 브라우저가 페이지 초기 HTML을 받은 후에 Javascript 파일을 다운로드하고 해석해야 되는데, 번들 크기가 크면 초기 로딩 시간이 길어지고 사용자 인터렉션이 감소해 이탈율이 높아 질 수 있기 때문에 번들 크기를 고려해야 한다.</p>
<br>
<h3 id="csr-동작-방식"><a class="anchor" href="#csr-동작-방식">CSR 동작 방식</a></h3>
<br>
<p><strong>1. 브라우저가 index.html 다운로드</strong></p>
<p>사용자가 웹 페이지를 방문하면, 브라우저는 해당 웹 서버에 HTTP 요청을 보내고, 서버는 클라이언트에게 index.html 파일을 포함한 초기 응답을 보낸다. index.html 파일은 <code>&#x3C;script></code>, <code>&#x3C;meta></code>, <code>&#x3C;link></code> 와 같은 태그들을 포함하고 있으며, 일반적으로 기본 레이아웃 및 초기 구조를 정의한다.</p>
<p><strong>2. 자바스크립트 번들 파일 다운로드</strong></p>
<p>브라우저는 index.html을 다운로드한 후, 이 파일에 포함된 자바스크립트 번들 파일을 다운로드하는데, 해당 자바스크립트 파일에는 웹 애플리케이션의 논리 및 동작 방식에 대한 코드들이 포함되어 있다.</p>
<p><strong>3. API 요청 및 동적 컨텐츠 가져오기</strong></p>
<p>자바스크립트가 다운로드되면, 브라우저는 해당 파일을 해석하고 실행하게 되는데 AJAX나 Fetch API를 사용하여 서버로부터 동적 데이터를 가져오는 API 요청을 수행한다. 이러한 API 요청을 통해 서버로부터 데이터를 받아오고, 이 데이터를 파싱하여 웹 페이지에 동적으로 추가될 수 있는 형식으로 가공한다.</p>
<p><strong>4. 최종 컨텐츠 렌더링</strong></p>
<p>API 요청을 통해 받은 데이터를 가지고, 자바스크립트는 웹 페이지의 최종 컨텐츠를 동적으로 생성한다. 이 컨텐츠는 보통 사용자에게 보여질 데이터이며, 브라우저는 이를 HTML DOM(Document Object Model)에 추가하여 렌더링한다.</p>
<p><strong>5. 페이지 이동 시 자바스크립트 재사용</strong></p>
<p>사용자가 페이지를 이동할 때, 새로운 HTML 파일을 서버에 요청하는 대신, 이미 다운로드된 자바스크립트를 재사용하여 새로운 데이터를 가져오고 렌더링한다. 페이지 이동 시에는 전체 페이지를 다시 로드하는 것이 아니라, 필요한 부분만을 업데이트하여 사용자에게 빠른 반응성을 제공한다.</p>
<br>
<h3 id="csr-장점"><a class="anchor" href="#csr-장점">CSR 장점</a></h3>
<ul>
<li>
<p>CSR은 이미 모든 <code>script</code>, <code>meta</code>, <code>link</code> 등이 사전에 로드되었기 때문에 로드 시간이 줄어들고, 서버를 호출할 때마다 전체 UI를 다시 로드할 필요가 없다.</p>
</li>
<li>
<p>스크립트가 이미 캐싱된 경우 인터넷 없이도 해당 CSR 웹 애플리케이션을 실행할 수 있어서 지연 로딩 모듈이나 별도 API가 필요하지 않다.</p>
</li>
</ul>
<br>
<h3 id="csr-단점"><a class="anchor" href="#csr-단점">CSR 단점</a></h3>
<ul>
<li>
<p>CSR을 사용하면 브라우저는 브라우저에서 사용 가능한 컨텐츠로 HTML을 컴파일하기 전에 기본 HTML, CSS 및 모든 필수 스크립트를 로드하기 때문에 초기 로드 시간이 SSR보다 느리다.</p>
</li>
<li>
<p>브라우저가 페이지를 표시하기 전에 HTML 및 JavaScript 파일을 다운로드하고 프레임 워크를 실행하는 동안 사용자는 빈페이지를 보게 되므로 사용자 경험이 좋지 않다.</p>
</li>
<li>
<p>한 페이지에서 다른 페이지로 변할 경우 이를 인지 시켜주기 위해 각 페이지에 대한 메타 데이터를 설정하고 클라이언트에서 렌더링하기 위해 추가 노력이 필요하다.</p>
</li>
<li>
<p>검색 엔진 크롤러가 해당 페이지에 처음 방문했을때는 빈 페이지이기 때문에 SEO에 친화적이지 않다.</p>
<blockquote>
<p>물론 자바스크립트를 실행시킬 수 있는 구글 크롤러와 같은 검색엔진 크롤러가 등장하고 있긴하지만, 아직 많은 검색 엔진 크롤러들이 지원되지 않는다.</p>
</blockquote>
</li>
</ul>
<br>
<h2 id="server-side-rendering-ssr"><a class="anchor" href="#server-side-rendering-ssr">Server Side Rendering (SSR)</a></h2>
<br>
<p><img src="/content/240409/3.png" alt="2.png" loading="lazy" decoding="async"></p>
<p>클라이언트 측이나 유니버셜 앱(Universal App)을 서버의 HTML로 렌더링하는 방식으로, 웹 애플리케이션의 <strong>초기 렌더링을 서버에서 처리하고, 클라이언트에 전달하는 방식</strong> 을 의미한다. 이를 통해 클라이언트가 서버로부터 응답을 받기 전에 데이터 패칭 및 템플릿 작성이 처리되므로 클라이언트에서 추가적인 네트워크 왕복이 발생하지 않는다.</p>
<br>
<h3 id="ssr-동작-방식"><a class="anchor" href="#ssr-동작-방식">SSR 동작 방식</a></h3>
<br>
<p><strong>1. 사용자 요청 처리</strong></p>
<p>사용자가 웹 페이지를 방문하면, 해당 요청은 서버로 전송된다.</p>
<p><strong>2. 서버 측 렌더링</strong></p>
<p>서버는 필요한 데이터를 데이터베이스 또는 다른 소스로부터 가져오는 걸 시작으로 해당 요청에 대한 처리를 시작한다.
서버는 페이지에 포함된 서버 측 스크립트를 실행하여 데이터를 가져와 페이지의 동적인 부분을 채워 초기 상태를 생성하고, 이 초기 상태와 함께 HTML 템플릿을 사용하여 전체 페이지의 HTML을 컴파일한다.</p>
<p><strong>3. HTML 응답 전송</strong></p>
<p>서버는 생성된 HTML을 클라이언트에게 응답으로 보내고, 이 HTML은 초기 상태와 함께 클라이언트에게 전송됩니다.</p>
<p><strong>4. 브라우저에서 HTML 및 자바스크립트 처리</strong></p>
<p>브라우저는 서버로부터 받은 HTML을 받은 후 화면에 표시를 하게되는데, 이 단계에서 사용자는 초기 페이지를 볼 수 있다.
페이지를 대화형으로 만드는 데 필요한 자바스크립트 코드를 다운로드하고 실행한다. 이후에는 클라이언트 측 자바스크립트가 활성화되어 사용자의 상호작용에 따라 페이지를 업데이트하고 추가적인 데이터를 요청하고 렌더링한다.</p>
<p><strong>5. 페이지 이동 시 작업 반복</strong></p>
<p>사용자가 페이지를 이동할 때, 클라이언트는 서버로 새로운 요청을 보내고 SSR 프로세스가 다시 시작된다. 새로운 HTML이 서버로부터 다운로드되고 브라우저에 의해 처리되며, 이 과정에서 추가적인 상호작용이 가능한 페이지를 사용자에게 제공한다.</p>
<br>
<h3 id="ssr-장점"><a class="anchor" href="#ssr-장점">SSR 장점</a></h3>
<ul>
<li>
<p>렌더링이 준비된 HTML 파일을 브라우저에서 로드하기 때문에 CSR에 비해 초기 페이지 로드시간이 더 빠르다.(FP 및 FCP가 더 빠르다)</p>
<blockquote>
<p>FP(First Paint): 첫번째 픽셀이 스크린에 페인팅 되었을 때(시간)</p>
<p>FCP(First Contentful Paint): DOM에 속해있는 컨텐츠 조각이 스크린에 페인팅 되는 순간(시간)</p>
<p>FPM(First Meaningful Paint): 브라우저가 유저가 관심있어 할 만한 컨텐츠를 페인트하여 나타냈을 때까지의 시간</p>
</blockquote>
</li>
<li>
<p>서버에서 페이지 로직 및 렌더링을 실행하면 많은 자바스크립트를 클라이언트에 보내지 않아도 되므로 TTI를 빠르게 수행할 수 있다.</p>
<blockquote>
<p>TTI(Time to Interactive)는 페이지가 안정적으로 사용자 상호 작용이 가능한 상태가 된 첫 번째 콘텐츠 페인트(FCP) 이후 가장 빠른 시간을 측정</p>
</blockquote>
</li>
<li>
<p>이미 다 만들어진 페이지를 검색엔진 크롤러가 요청에 대한 응답으로 받기 때문에 SEO에 친화적이다.</p>
</li>
<li>
<p>컴퓨팅 성능이 훨씬 더 뛰어난 서버를 이용해 소프트웨어 성능 영향을 줄이게 되고, 클라이언트의 부담감이 줄게 된다.</p>
</li>
</ul>
<br>
<h3 id="ssr-단점"><a class="anchor" href="#ssr-단점">SSR 단점</a></h3>
<ul>
<li>
<p>페이지 이동시마다 서버에서 페이지를 생성하는데 시간이 걸리기 때문에 TTFB(Time to First Byte)가 느리다.</p>
</li>
<li>
<p>초기 페이지 로드시 많은 데이터가 필요하다면 사용자 경험을 해칠 위험이 있다.</p>
</li>
<li>
<p>서버는 항상 각 요청이 올때마다 HTML파일을 생성하기 때문에 CDN 수준에서의 컨텐츠 캐시가 되지 않는다.</p>
</li>
<li>
<p>클라이언트에서 자바스크립트를 이용해 렌더링하는 CSR에 비해 서버 사이드에서 HTML파일과 안에 내용을 생성해야 하기 때문에 서버 호스팅이 필요하다.</p>
</li>
<li>
<p>SSR 프레임워크를 사용한다면 추가적인 러닝 커브에 대한 비용이 발생하고 개발 노력이 필요하다.</p>
</li>
</ul>
<br>
<h2 id="static-site-generatorssg"><a class="anchor" href="#static-site-generatorssg">Static Site Generator(SSG)</a></h2>
<p><img src="/content/240409/4.png" alt="4.png" loading="lazy" decoding="async"></p>
<p>Next.js와 같은 프레임워크에서도 권장되는 렌더링 방식 중 하나로 SSR과는 다르게 HTML 파일의 생성 시점이 빌드 타임(build time)에 이루어진다. 그리고 사이트 생성 후 백엔드가 필요하지 않으므로 콘텐츠 전송 네트워크(CDN)을 사용한다.</p>
<p>대체로 SSG는 제품 페이지, 뉴스 기사, 소프트웨어 문서, 블로그 등 거의 변경되지 않는 정보성 콘텐츠에 사용된다.</p>
<br>
<h3 id="ssg-동작-방식"><a class="anchor" href="#ssg-동작-방식">SSG 동작 방식</a></h3>
<br>
<p><strong>1. 빌드 타임 생성</strong></p>
<p>SSG에서 웹 페이지의 HTML은 빌드 타임(build time)에 생성된다. 빌드 타임에는 웹 애플리케이션의 모든 페이지가 사전에 렌더링되고, 필요한 데이터가 사전에 수집되어 페이지의 내용이 정적인 HTML로 컴파된.</p>
<p><strong>2. 정적 파일 생성</strong></p>
<p>빌드 타임에 생성된 HTML 파일들은 서버에서 호스팅되는 정적 파일로 저장되는데, 이 파일들은 클라이언트에 의한 요청이 있을 때마다 서버에서 바로 제공되고 각 HTML 파일은 해당 페이지에 대한 모든 콘텐츠와 구조를 포함하고 있으며, 독립적으로 사용될 수 있다.</p>
<p><strong>3. 엣지 캐싱</strong></p>
<p>SSG에서는 엣지 캐싱(edge caching)이 일반적으로 사용되는데, <strong>엣지 캐싱은 정적 파일을 CDN(Content Delivery Network)의 엣지 서버에 저장하여 전 세계의 사용자에게 빠르게 제공하는 기술</strong> 이다.
CDN은 사용자의 요청이 해당하는 정적 파일을 요구할 때, 가장 가까운 엣지 서버에서 캐시된 파일을 반환하여 응답 시간을 최소화한다.</p>
<p><strong>4. HTML 응답 전송</strong></p>
<p>클라이언트가 웹 페이지를 요청하면, 엣지 캐싱된 HTML 파일이 클라이언트에게 반환되고 받은 HTML 파일을 브라우저에서 렌더링하여 사용자에게 페이지를 표시한다.</p>
<p><strong>5. 자바스크립트 처리</strong></p>
<p>페이지가 이미 빌드 타임에 정적으로 생성되었기 때문에, 서버에 추가적인 요청을 보내지 않고도 초기 페이지를 표시할 수 있다. 그렇기 때문에 클라이언트 측 JavaScript의 사용이 제한된다. 하지만 필요에 따라 클라이언트 측 JavaScript를 사용하여 추가적인 동적 상호작용이나 기능을 구현할 수도 있다.</p>
<br>
<h3 id="ssg-장점"><a class="anchor" href="#ssg-장점">SSG 장점</a></h3>
<ul>
<li>
<p>빌드 타임에 HTML 파일이 생성되기 때문에 빠른 FP, FCP, TTI를 제공한다. 또한 매 요청마다 생성하는 것이 아니므로, SSR과 달리 일관성있게 빠른 TFB를 달성할 수 있다.</p>
</li>
<li>
<p>이미 생성된 HTML 파일을 받기 때문에 SEO 친화적이다.</p>
</li>
<li>
<p>build 명령은 실제로 사이트를 방문하는 사람의 워크플로를 벗어나므로 시간이 좀 걸리더라도 문제되지 않는다.</p>
</li>
</ul>
<br>
<h3 id="ssg-단점"><a class="anchor" href="#ssg-단점">SSG 단점</a></h3>
<ul>
<li>모든 URL에 대해 개별 HTML 파일을 생성해야 한다. 따라서 URL을 미리 예측할 수 없거나 URL을 예측할 수 없으면 적용이 어렵다.</li>
</ul>
<br>
<h2 id="universal-rendering"><a class="anchor" href="#universal-rendering">Universal Rendering</a></h2>
<br>
<p><img src="/content/240409/5.jpeg" alt="5.jpeg" loading="lazy" decoding="async"></p>
<br>
<p>Universal Rendering 또는 Isomorphic Rendering이라고도 불리는 개념으로 서버 측 렌더링(Server-Side Rendering, SSR)과 클라이언트 측 렌더링(Client-Side Rendering, CSR)의 장점을 결합한 접근 방식이다. 한 마디로 <strong>브라우저와 서버 양쪽에서 동일한 코드를 실행하여 웹 페이지를 구성하는 방식</strong> 을 의미한다</p>
<br>
<h3 id="univeral-rendering-장점"><a class="anchor" href="#univeral-rendering-장점">Univeral Rendering 장점</a></h3>
<ul>
<li>초기 로딩 속도가 빠르고 SEO에 유리한 SSR의 장점을 유지하고, 클라이언트 측 자바스크립트를 사용하여 동적 상호작용성을 제공함으로써 CSR의 장점을 활용 할 수 있다.</li>
<li>사용자에게 빠르고 부드러운 페이지 로딩 및 상호작용을 제공하여 사용자 경험을 향상시킬 수 있다.</li>
</ul>
<h3 id="univeral-rendering-단점"><a class="anchor" href="#univeral-rendering-단점">Univeral Rendering 단점</a></h3>
<ul>
<li>
<p>별도의 서버가 필요하며, 구현 또는 구현을 위한 프레임워크 학습에 들어가는 비용이 크다.</p>
</li>
<li>
<p>페이지가 빨리 로드되며 인터렉션이 가능한 것처럼 보이지만, 실제로 클라이언트에서 자바스크립트가 실행되고 이벤트 핸들러가 적용될 때까지 입력에 응답할 수 없어, 사용자 경험이 안좋아질 수 있다.</p>
</li>
</ul>
<br>
<h2 id="incremental-static-regeneration-isr"><a class="anchor" href="#incremental-static-regeneration-isr">Incremental Static Regeneration (ISR)</a></h2>
<br>
<p><img src="/content/240409/6.webp" alt="6.webp" loading="lazy" decoding="async"></p>
<p>ISR(증분 정적 재생성)은 런타임 중에 정적 페이지를 만들거나 업데이트 수 있도록 해주는 SSG과 SSR의 하이브리드 솔루션</p>
<br>
<h3 id="isr-동작-방식"><a class="anchor" href="#isr-동작-방식">ISR 동작 방식</a></h3>
<ol>
<li>
<p>사용자가 웹 페이지를 방문하면(request), 요청에 의해 페이지가 생성되지만 데이터가 오기를 기달려야하는 SSR과 달리 즉시 대체 페이지(fallback page)가 제공된다. 이 단계에서 대부분 placeholder 및 스캘래톤을 표시한다.</p>
</li>
<li>
<p>데이터가 확인되면 최종 페이지가 캐시되고, 사용자는 SSG와 마 찬가지로 캐시된 버전의 페이지를 받게 된다.</p>
</li>
<li>
<p>재검증시에도 사용자는 먼저 캐시된 버전을 받고 업데이트된 버전을 받는다. (캐싱 전략: Stale-while-revalidate)</p>
<blockquote>
<p>stale-while-revalidate는 개발자가 캐싱된 콘텐츠를 즉시 로드하는 즉시성과 갱신된 최신 콘텐츠가 향후에 사용되도록 보장하는 최신성 간의 균형을 유지하는데 도움을 주는 HTTP Cache-Control 확장 디렉티브</p>
</blockquote>
</li>
</ol>
<br>
<h3 id="isr-장점"><a class="anchor" href="#isr-장점">ISR 장점</a></h3>
<ul>
<li>SSR과 달리 페이지가 즉시 제공되며(fallback page), 빠른 경험으로 사용자 경험도 좋아진다.</li>
</ul>
<br>
<h3 id="isr-단점"><a class="anchor" href="#isr-단점">ISR 단점</a></h3>
<ul>
<li>페이지 디자인에 따라 FP를 지연시킬 수도 있다.</li>
</ul>
<br>
<h2 id="그래서-뭘-써야될까"><a class="anchor" href="#그래서-뭘-써야될까">그래서 뭘 써야될까??</a></h2>
<p>사실 어떤 것을 쓰는지 정답은 없다. 사용하는 프로젝트의 특성에 대해서 먼저 분석하고 그 결과로 렌더링방식을 결정하면 될 것 같다.</p>
<p>웹/앱 애플리케이션을 만들 때 사용자 경험(UE), 검색 엔지 최적화(SEO), 서버 부하, 동적 데이터 요구, 개발 및 유지보수 용이성 등에 대해서 생각해보면 좋다.</p>
<p>사내에서는 SM,SI 개발을 동시에 하다보니 SEO 특성에 중점을 두는 것 보다는 자주 데이터가 변경되는 ISR 렌더링 방식같이 동적 컨텐츠와 데이터 변경을 다루는 방식을 선호한다. 선택하는 과정이 쉽지 않지만~! 큰 고민은 좋은 공부법이니 여러분들도 많이 고민해보면 좋겠다~</p>
<hr>
<h2 id="출처-및-도움되는-링크들"><a class="anchor" href="#출처-및-도움되는-링크들">출처 및 도움되는 링크들</a></h2>
<ul>
<li><a href="https://ajdkfl6445.gitbook.io/study/web/csr-vs-ssr-vs-ssg">콥 노트</a></li>
<li><a href="https://dev-ellachoi.tistory.com/28">Dev. Ella</a></li>
<li><a href="https://web.dev/articles/rendering-on-the-web?hl=ko">web.dev</a></li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
        </item>
        <item>
            <title><![CDATA[UNITHON 11기]]></title>
            <link>https://hooninedev.com/240407</link>
            <guid isPermaLink="false">https://hooninedev.com/240407</guid>
            <pubDate>Sun, 07 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Welcome Unithon 평소와 똑같이 회사, 집을 무한하게 반복하던 삶에서 UNITHON이라는 해커톤에 대한 정보를 듣게 되었다!! 운영진을 해볼까.. 아니면 참여자가 되어볼까.. 고민을 하던 중 아직 경험해보지 않았으니 이번에는 해커톤 참여자를 해보자!! 라는 비장한 다짐과 함께 신청서를 작성했다! 경쟁률이 엄청 높다고 들어서 될까? 라는 걱정이 있...]]></description>
            <content:encoded><![CDATA[<h2 id="-welcome-unithon"><a class="anchor" href="#-welcome-unithon">✋ Welcome Unithon</a></h2>
<br>
<p><img src="/content/240407/12.jpeg" alt="12.jpeg" loading="lazy" decoding="async"></p>
<br>
<p>평소와 똑같이 회사, 집을 무한하게 반복하던 삶에서 UNITHON이라는 해커톤에 대한 정보를 듣게 되었다!!
운영진을 해볼까.. 아니면 참여자가 되어볼까.. 고민을 하던 중 아직 경험해보지 않았으니 이번에는 해커톤 참여자를 해보자!! 라는 비장한 다짐과 함께 신청서를 작성했다!</p>
<p>경쟁률이 엄청 높다고 들어서 될까? 라는 걱정이 있었지만.. 근무 중에 산뜻한(!?) 이메일을 받았다!!</p>
<br>
<p><img src="/content/240407/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<br>
<p>아니!!!!!! 이런 말은 부끄럽지만... 요즘 이런저런 생각을 하는 나에게!!!!!! 엄청 오랜만에 합격 이메일이..ㅋㅋㅋㅋㅋ <strong>너무너무너무너무너무 x 1000</strong> 기분이 좋았다!!!</p>
<br>
<h4>Love UNITHON, Thank UNITHON , Together UNITHON</h4>
<br>
<p><img src="/content/240407/3.gif" alt="3.gif" loading="lazy" decoding="async"></p>
<br>
<h2 id="해커톤-준비-과정"><a class="anchor" href="#해커톤-준비-과정">해커톤 준비 과정</a></h2>
<p>나는 UNITHON의 7조가 되었다! 처음 디스코드에 초대되었을 때 서로에 대한 정보가 없어서 긴장되었다..
하지만 우리 팀은 해커톤에 대한 열정이 넘쳤다. 미리 기술스택과 주제에 대한 이야기도 해보고 다양한 이야기를 디코에서 나누었다.</p>
<blockquote>
<p>민규님께서는 vite+tailwind+zustand의 세팅도 해주셨다!!! (너무너무 감사합니다 ㅋㅋ) 🙏🙏</p>
</blockquote>
<br>
<h3 id="첫-만남"><a class="anchor" href="#첫-만남">첫 만남</a></h3>
<p>이번 해커톤은 성수동 엘리스랩에서 19시에 시작되었다. 총 6명씩 x 10팀이 참가하였다. 주제가 정해지지 않은 채 사람들과 아이스브레이킹을 진행했다!</p>
<p>사실 너무너무 긴장해서 뭐 말이라도 할까 싶었지만, 이 사람들 텐션이 너무 좋다.. 뭘 말해도 잘 반응해주고 좋은 팀이라는 게 느껴지는.. ㅋㅋ</p>
<br>
<h3 id="해커톤-소개와-주제-발표"><a class="anchor" href="#해커톤-소개와-주제-발표">해커톤 소개와 주제 발표</a></h3>
<br>
<p><img src="/content/240407/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<p>이번 UNITHON은 다양한 후원사와 함께한다. (능력자 UNITHON 운영진들 칭찬합니다!!) 대경님의 UNITHON 소개가 너무너무 재미있고 긴장되는 마음을 해소시켜주어서 감사했다</p>
<p>그리고 중간 쉬는 시간에 팀원들과 수다 떨면서 어떤 주제가 안 나오면 좋겠어요?? 이야기를 했을 때 기후와 평화가 대부분이었다..</p>
<p>뭐.. 그 다음은 예상가겠지만 <strong>주제는 기후와 평화</strong>가 되었다 ㅋㅋㅋ</p>
<br>
<p><img src="/content/240407/4.jpeg" alt="4.jpeg" loading="lazy" decoding="async"></p>
<br>
<p>그래도 마음을 가다듬고 아이데이션을 시작했다. 본격적인 해커톤 시작!</p>
<br>
<h2 id="해커톤-시작"><a class="anchor" href="#해커톤-시작">해커톤 시작!</a></h2>
<h3 id="day-1-아이데이션"><a class="anchor" href="#day-1-아이데이션">Day-1 아이데이션</a></h3>
<p>거의 22시 30분부터 아이데이션을 진행했다. 2박 3일 해커톤이여서 첫날은 무리없이 귀가하자는 의견이 많아 12시면 끝나겠지? 라고 생각했지만 열정적인 팀원들과 새벽 3시까지 회의하고 이야기를 진행했다.</p>
<p><img src="/content/240407/5.png" alt="5.png" loading="lazy" decoding="async"></p>
<p>팀명은 성수 불막 사우나로 결정했다. <del>(중간에 피곤할 때 불막 사우나를 갔기 때문 ㅋㅋ)</del></p>
<p>디자인 초안을 몇개 확인했는데 우리 갓 능력자 디자이너 예지님 덕분에 너무너무 편한 마음으로 찜질방으로 달려갔다! (짧은 시간동안 몇마디로 구름을 그려내고 로고를 만들어내는 당신 존경..)</p>
<p>이렇게 Day-1을 마무리 짓고(사실 Day-1,2,3가 의미 있나 싶긴하지만) 4시간 정도 취침 후에 다시 모였다!</p>
<blockquote>
<p>뭐 중간마다 에피소드가 있는데 사우나에서도 수다떨다가 아이데이션도 하고, 매점앞에서 자다가 오픈시간이여서 쫓겨나서 일찍 시작한 것도 있지만 <del>이미지 포장..</del></p>
</blockquote>
<br>
<h3 id="day-2-디자인-개발"><a class="anchor" href="#day-2-디자인-개발">Day-2 디자인, 개발</a></h3>
<p>전날 아이데이션을 기반으로 기획에 대한 최종 회의를 오전까지 진행했다. 그 이후에 점심 식사를 하고나서 산책을 다녀오고 바로 개발을 시작하였다.</p>
<br>
<p><img src="/content/240407/7.jpeg" alt="7.jpeg" loading="lazy" decoding="async"></p>
<br>
<blockquote>
<p>좋은 사진을 찍어주신 미경님 Thank you 😁</p>
</blockquote>
<p>FE, BE가 프로젝트 세팅에 필요한 것들을 정리하면서 기획, 디자인이 이루어졌고 먼저 소셜로그인과 공통으로 쓰는 UI를 만들었다. 사실 해커톤에 처음 참여해서 정보가 없었다. 사람들과 잘 아이데이션하면서 개발을 진행하면 되겠다 싶어서 중간에 산책도 다니고 사진도 찍으러 다녔다.. 수다도 많이 떨구... 다른 조 분들에게는 너무 정신 없어서 시끄러웠을 것 같아서 죄송한 마음이 든다..ㅠㅠ</p>
<p>어찌저찌해서 개발을 16~17시까지 진행하고 디자인이 정리가 되어서 본격적인 개발이 시작되었다. (물론 저녁먹고 ㅋㅋ)</p>
<p>저녁먹고나서 급한 마음에 개발을 계속했다~!! UNITHON에서 불타는 경쟁이 아닌 사람들과 잘 어울려서 해커톤을 진행하면 좋겠다고 안내를 받았는데 중간중간 재미있는 미션, 게임을 진행해서 상품도 받고 사진도 많이 찍었다.</p>
<br>
<p><img src="/content/240407/8.jpeg" alt="8.jpeg" loading="lazy" decoding="async"></p>
<br>
<h3 id="day-3-초-집중"><a class="anchor" href="#day-3-초-집중">Day-3 초 집중</a></h3>
<br>
<p><img src="/content/240407/13.jpeg" alt="13.jpeg" loading="lazy" decoding="async"></p>
<blockquote>
<p>해커톤이니 맥주 탈락! 비타민!</p>
</blockquote>
<p>이제는 BE와 API도 연결해야 되고 그전까지 UI 작업은 마무리 지어야되어서 정신없이 불꽃 코딩을 했다. 그러고 새벽에 또 중랑천으로 산책을 나갔다 ㅋㅋ (우리팀 애정해..) 산책 다녀와서는 거의 아침 9시 발표할때까지 미친듯이 작업을 했다.(중간에 체력적 이슈가 있지만... 잘 버텼다 30분뺴고 ㅋㅋ)</p>
<p>그렇게 해서 우리의 <strong>속삭임(Whisper)</strong> 가 완성되었다!!!</p>
<p><img src="/content/240407/6.png" alt="6.png" loading="lazy" decoding="async"></p>
<p>너무너무 감사하게도 디자이너 예지님이랑 PM 상환님께서 발표를 위한 자료들을 다 만들어주셨다.. 그래서 조금 여유가 생겼고.. 바로 발표가 진행되었다.</p>
<p><strong>발표자료가 궁금하면 <a href="https://unit-center.notion.site/4b29b6b0050b409fbb004aa13cf3458c?pvs=4">여기를</a> 확인해보세요!</strong></p>
<br>
<h3 id="day-3-발표-시연-수상"><a class="anchor" href="#day-3-발표-시연-수상">Day-3 발표, 시연, 수상</a></h3>
<p>사람들의 발표를 보는데 다들 열심히 하신게 보이고 저런 아이디어를 내고 기획, 구현한 것에 대해서 너무너무 존경스러웠다. 우리팀 상환님께서 발표를 너무 재미있고 잘 진행해주셧다 (감사합니다!)</p>
<p>평가하시는 분들이 와서 엄청 떨면서 시연발표를 했다! 반응도 좋았고 끝나자마자 긴장이 풀려서 주저앉았버렸다. 우리팀 열심히 해서 수상에 대해 욕심을 내보았지만!! 아쉽게도 수상은 못했다. 그래도 시연 때 돌아다니면서 우와 여기팀 잘했다!! 생각했던 팀들이 수상해서 너무 기분이 좋았다 특히 9팀이 인상 깊었다!! (혹시.. 9팀분들 제 블로그 보시면 연락한번 주세요ㅠㅠ 친해지고싶어요..ㅋㅋ)</p>
<br>
<h2 id="짧은-편지"><a class="anchor" href="#짧은-편지">짧은 편지</a></h2>
<p><img src="/content/240407/10.jpeg" alt="10.jpeg" loading="lazy" decoding="async"></p>
<h3 id="to-7조"><a class="anchor" href="#to-7조">To.. 7조</a></h3>
<p>이렇게 내 첫 번째 해커톤이 마무리되었다. 중간에 재미있는 게임을 진행해주시고 너무너무 맛있는 음식들을 준비해주신 <strong>UNITHON 운영진분들께 너무 감사하다.</strong> 뽑아준 것도 너무너무!</p>
<p>백앤드에서 든든하게 작업해주고 분위기 좋도록 텐션 이끌어주시고 새벽에 자는 거 깨워서 API 수정요청했을 때 웃으면서 해주신 <strong>미경님과 준수님</strong> 너무 감사합니다. 제가 장난도 많이 치고 정신 없었을 텐데 감사합니다.</p>
<p>우리의 나약한 아이디어를 효과적으로 디자인해주신 <strong>예지님</strong> 너무너무 존경하고 멋집니다!!! 집중하는 모습 존경스러웠습니다. 당신은 좋은 디자이너가 될 것 같아요.</p>
<p>우리 아이디어에서 요점을 잘 잡아주시고 좋은 방향으로 이끌어주고, 자료를 만들고 발표도 재미있게 해주신 우리 <strong>상환님</strong> 너무 감사합디다</p>
<p>저와 같이 FE하느라 고생 많으신 <strong>민규님</strong> 구름 둥둥 띄우는 모습 열정 있고 너무 보기 좋았습니다. 다음 주에 만나기로 했으니!! 그때 또 커피챗 진행해보아요~!!!!</p>
<p><img src="/content/240407/11.png" alt="11.png" loading="lazy" decoding="async"></p>
<br>
<h3 id="to-me"><a class="anchor" href="#to-me">To.. me</a></h3>
<p>이번 해커톤으로 엄청나게 힐링을 많이하고 많은 생각을 하게 되었다. 요즘 이것저것 해야될 것이 많아서 힘들다는 생각을 많이했는데 어떤 것에 집중해서 작업을 했더니 첫 개발을 시작한 추억이 생각이 조금씨 회상되었다.</p>
<p>그때 당시에는 뭘해도 불타고 열심히 했는데 스스로가 기만하여 요즘 나를 힘들게 하지 않았나? 라는 반성을 하기도 하고.. 좋은 사람들을 1년동안 너무 많이 만나서 내가 어떤 것을 할 수 있는 원동력이 되고 있다. 뭐 주절주절.. 말하는 거지만 이런 과정들 덕분에 앞으로의 삶이 너무 기대되고 많은 것을 하고싶다는 생각이 든다!</p>
<blockquote>
<p>이런 좋은 감정 해커톤 마지막에 발표하고싶었는데 심장이 작아서 못했다. 다음에는 핑계대지말고 하자ㅠ</p>
</blockquote>
<p>참고로 요즘 rust랑 go 도 관심이 많아서 하나씩 두들겨 볼 예정!!!</p>
<p>좋은 추억과 많은 생각을 하게 된 해커톤 이야기</p>
<br>
<p><img src="/content/240407/9.jpeg" alt="9.jpeg" loading="lazy" decoding="async"></p>
<br>
<br>
<br>
<h2>그리고 운영진이나 해커톤 참여하신 분들 연락주시면 감사하겠습니다! 친해지고싶어요!</h2>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>대외활동</category>
        </item>
        <item>
            <title><![CDATA[브라우저 렌더링]]></title>
            <link>https://hooninedev.com/240329</link>
            <guid isPermaLink="false">https://hooninedev.com/240329</guid>
            <pubDate>Fri, 29 Mar 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[Web에 대해 공부하면서 렌더링이 무엇이고 어떻게 작동되는가? 에 대한 궁금증을 가지지 못했다. 취업을 해야된다는 막연한 생각때문에 과정없이 면접에 자주나오는 키워드만 암기했다. CS에 기초지식이 없으니 누군가에게 대답하는 형식으로 달달 외우기만 했다. 조금만 응용되어도 이론이 복잡하게 꼬여 버렸다. 그래서 언젠가는 이런 정리 안 된 내 지식을 정리해야지 ...]]></description>
            <content:encoded><![CDATA[<p>Web에 대해 공부하면서 <strong>렌더링이 무엇이고 어떻게 작동되는가?</strong> 에 대한 궁금증을 가지지 못했다. 취업을 해야된다는 막연한 생각때문에 과정없이 면접에 자주나오는 키워드만 암기했다.</p>
<p>CS에 기초지식이 없으니 누군가에게 대답하는 형식으로 달달 외우기만 했다. 조금만 응용되어도 이론이 복잡하게 꼬여 버렸다. 그래서 <strong>언젠가는 이런 정리 안 된 내 지식을 정리해야지🔥🔥</strong> 라는 계획을 하다 이번에 렌더링이라는 키워드를 연관성에 맞춰서 상세하게 기록해보려고 한다.</p>
<p>작은 소망이라면 렌더링, 브라우저, DOM 등 다양한 키워드에 대해 궁금할 때 나의 글이 생각나고 흐름을 파악하면 좋겠다.</p>
<p><img src="/content/240329/1.jpeg" alt="1.jpeg" loading="lazy" decoding="async"></p>
<p>웹 렌더링이라는 주제를 정한 후 <strong>어떠한 순서로 웹 렌더링에 대해서 글을 써야 할까?</strong> 에 대해 많은 고민을 하였다. 이론적인 단계로 쓸 수 있고, 중요한 순서대로도 쓸 수 있다. 하지만 내가 선택한 방법은 내가 공부하면서 느꼈던 키워드의 연관성을 흐름에 맞춰서 기록해보려 한다.</p>
<p>예를들어! 🍎 사과에 대해서 글을 쓰고 싶을때 <strong>사과는 이름이 왜 사과고 => 어떠한 특성이 있고 => 그래서 어떠하다</strong> 라는 흐름으로 사과를 알아보는 것이 아닌, <strong>사과는 뭘까? => 사과는 과일이구나, 뭘 과일이라고 하지? => 과일은 또 어떤 게 있을까?</strong> 이런 식으로 질문에 꼬리에 꼬리를 무는 방식(꼬꼬무)으로 글을 작성할 예정이다!!</p>
<p>서론이 너무 길었다!! 자칫하면 너무 어렵고, 진부 할 수 있는 내용이지만 누군가에게 필요할 것이라 생각해 자세히 설명해보려고 한다. 만약 필요한 내용만 찾고싶으면 <code>목차</code>, <code>command + f</code> , <code>ctrl+f</code> 를 적극적으로 사용해서 필요한 내용을 알차게 찾아가면 좋겠다!!</p>
<br>
<h2 id="중요하게-알아야-될-키워드"><a class="anchor" href="#중요하게-알아야-될-키워드">중요하게 알아야 될 키워드</a></h2>
<br>
<h3 id="파싱과-렌더링"><a class="anchor" href="#파싱과-렌더링">파싱과 렌더링</a></h3>
<p><strong>파싱</strong>은 HTML 파일을 해석하여 DOM(Document Object Model) Tree를 구성하는 단계를 말하고, <strong>렌더링</strong>이란 HTML, CSS, JS로 작성된 문서를 파싱하여 브라우저에 사용자가 눈으로 볼 수 있도록 시각적으로 출력하는 것을 말한다.</p>
<br>
<h3 id="요청과-응답"><a class="anchor" href="#요청과-응답">요청과 응답</a></h3>
<p><img src="/content/240329/3.png" alt="3.png" loading="lazy" decoding="async"></p>
<p>브라우저에 URL을 입력하면 URL에 적힌 값을 파싱하고 HTTP 요청 메시지를 만들어 웹 서버로 전송한다. 브라우저는 메시지를 네트워크에 직접 송출할 수 없기때문에 OS에 의뢰해서 메시지를 전달한다. OS에 송신을 의뢰하기 위해서는, IP 주소로 메시지를 받을 상대를 지정해야되기 때문에, DNS(Domain Name Server)를 조회해야 한다.</p>
<p>이후, 브라우저가 요청한 메시지가 웹서버 측 LAN에 도착하면 방화벽이 검사를 하는데, 캐시서버를 통해 웹서버까지 가야하는 지 아닌지 조사한다. 페이지의 데이터 중 다시 이용할 수 있는 것은 캐시 서버에 저장되고 액세스한 페이지의 데이터가 캐시서버에 있으면 웹서버에 의뢰하지 않고 바로 값을 읽을 수 있다.</p>
<p>웹서버에 도착하면 패킷에 담긴 메시지를 복원해서 WAS에 넘기고, WAS은 요청 메시지에 따른 데이터를 응답 메시지에 넣어 클라이언트로 다시 보낸다. 웹 브라우저는 역으로 웹 서버로부터 HTTP를 따라 데이터를 전송받고, 렌더링 엔진을 사용해 이를 텍스트 및 이미지 등으로 변환한다. 브라우저는 최종 화면을 사용자에게 표시하는 역할을 한다.</p>
<p>한마디로 브라우저를 열고 URL을 입력하는 순간부터 <strong>HTTP 요청(Request)이</strong> 시작되고, 브라우저는 사용자가 입력한 URL주소에 해당하는 목적지(웹서버)에 도착해서 데이터를 요청하고, 그 <strong>목적지(웹서버)에서 응답한 데이터를 응답 받아서(Response)</strong> 화면에 보여주게 된다.</p>
<h2 id="브라우저-렌더링-과정"><a class="anchor" href="#브라우저-렌더링-과정">브라우저 렌더링 과정</a></h2>
<p><img src="/content/240329/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<p>우리는 왜 브라우저 렌더링 과정의 원리를 알아야할까??</p>
<p>C언어, JAVA와 같은 프로그래밍 언어는 OS나 가상머신 위에서 실행된다. 물론 자바스크립트도 런타임 환경에서 사용하게 되면 이야기가 다르겠지만 프론트엔드 개발자의 관점에서는 웹 브라우저에서 실행된다. 웹 브라우저를 다루는 프론트엔드 입장에서는 어느 시점에서 어떻게 렌더링이 되는지 파악하고 있어야 효율적인 코드를 작성할 수 있고, 수정할 수 있다.</p>
<p>브라우저가 렌더링을 하게 되면 <strong>DOM(The Document Object Model)</strong> 을 만들어내는데 만들어지는 과정에 대해 알아보자.</p>
<blockquote>
<p><strong>🙋🏻‍♂️ 잠깐! DOM은 무엇일까?</strong></p>
<p>문서의 구조화된 표현을 제공하며 프로그래밍 언어가 DOM 구조에 접근할 수 있는 방법을 제공하여 그들이 문서 구조, 스타일, 내용 등을 변경할 수 있게 돕는다.</p>
<p>nodes와 objects로 문서를 표현하는데 이들은 웹 페이지를 스크립트 또는 프로그래밍 언어들에서 사용될 수 있게 연결시켜주는 역할을 담당한다.</p>
</blockquote>
<br>
<h3 id="html-파싱"><a class="anchor" href="#html-파싱">HTML 파싱</a></h3>
<p>HTML 파싱은 웹 브라우저가 서버에서 받은 HTML 문서를 문자열로 받아들이고, 이를 파싱하여 이해 가능한 구조로 변환하는 과정을 의미하는데 브라우저는 HTML 문서를 위에서 아래로 읽으며, 태그와 내용을 파악하고 각 요소의 의미와 관계를 이해한다.</p>
<blockquote>
<p>⚠️ 파싱(Parsing)은 컴퓨터 과학 및 프로그래밍에서 특정 형식으로 구성된 데이터를 분석하고 그 의미를 이해하는 과정을 의미한다. 그 중에서 HTML 파싱에 대해서 설명한 것 뿐이니 <a href="http://wiki.hash.kr/index.php/%ED%8C%8C%EC%8B%B1">👉 여기서</a>에서 자세한 내용을 찾아보는 것을 권장한다</p>
</blockquote>
<p>이 과정에서 오류가 발생할 경우 브라우저는 오류 복구 기능을 사용하여 최대한 문제를 해결하고, 유효한 HTML 문서 구조를 만든다. 결과적으로 <strong>HTML 문서를 기반으로 브라우저가 메모리 상에 DOM 트리를 구축하는 번역하는 과정(DOM 생성 과정)이 필요하다</strong></p>
<br>
<h3 id="dom-생성-과정"><a class="anchor" href="#dom-생성-과정">DOM 생성 과정</a></h3>
<p><img src="/content/240329/4.png" alt="4.png" loading="lazy" decoding="async"></p>
<ol>
<li>서버는 브라우저가 요청한 HTML 파일을 읽어 들여 메모리에 저장한 다음, 메모리에 저장된 <strong>바이트(2진수)로</strong> 응답한다.</li>
<li>브라우저는 응답받은 바이트 형태의 HTML 문서를 <strong>meta태그의 charset 어트리뷰트로 지정해 둔 인코딩 방식(ex. UTF-8)을 기준으로 문자열로</strong> 변환한다.</li>
<li>문자열로 변환된 HTML 문서를 읽어 들여 문법적으로 더 이상 나눌 수 없는 기본적인 언어 요소인 <strong>토큰(token)들로</strong> 분해한다.</li>
<li>각 토큰들을 객체로 변환하여 컴퓨터 과학에 쓰이는 기초적인 단위인 <strong>노드(node)를</strong> 생성한다. <strong>노드는 이후 DOM을 구성하는 기본 요소</strong>가 된다.</li>
<li>이러한 노드들은 body 태그 안에 div를 넣고 그 안에 또 p를 넣듯이 중첩될 수 있는데, 이러한 중첩 관계에 의해 부자관계가 형성된다. 부자관계를 반영하여 모든 노드들을 <strong>트리 자료구조로</strong> 구성한다.</li>
</ol>
<br>
<h3 id="css파싱과-cssomcss-object-model-생성"><a class="anchor" href="#css파싱과-cssomcss-object-model-생성">CSS파싱과 CSSOM(CSS Object Model) 생성</a></h3>
<p><img src="/content/240329/5.png" alt="5.png" loading="lazy" decoding="async"></p>
<p>렌더링 엔진은 처음부터 한 줄씩 순차적으로 파싱하며 DOM을 생성하는데, 중간에 CSS를 로드하는 link나 style 태그를 만나면 DOM 생성을 일시 중단한다.</p>
<p>그 후 CSS 파일을 서버에 요청하고 응답받은 CSS 파일을 HTML과 동일한 파싱과정을 거치며 해석해 CSSOM을 생성한다. 그리고 CSS 파싱이 완료되면 HTML 파싱이 중단된 시점으로 돌아가 다시 HTML을 파싱한다.</p>
<blockquote>
<p><strong>브라우저 엔진</strong></p>
<blockquote>
<p>브라우저에서 웹 콘텐츠의 표시 및 상호 작용을 처리하는 소프트웨어 구성요소이다. 여기에는 네트워킹 계층, JS 엔진 및 렌더링 엔진과 같은 여러 구성 요소가 포함된다.</p>
</blockquote>
<blockquote>
<p>웹 서버에서 리소스를 가져오고, HTML, CSS, JS 및 기타 웹 기술을 파싱하고, 사용자 장치에서 콘텐츠를 렌더링하는 역할을 한다.</p>
</blockquote>
<p><strong>렌더링 엔진</strong></p>
<blockquote>
<p>웹 콘텐츠의 렌더링을 처리하는 브라우저 엔진의 특정 구성 요소이다. HTML 및 CSS 코드 구문 분석, DOM 트리 및 CSSOM 생성, 페이지에 요소 배치 및 스타일 지정, 최종적으로 사용자 장치에 페이지 렌더링을 담당한다.</p>
</blockquote>
<p><strong>결과: 렌더링 엔진은 브라우저 엔진에 포함되는 개념인 것 같다!</strong></p>
</blockquote>
<br>
<h3 id="렌더-트리"><a class="anchor" href="#렌더-트리">렌더 트리</a></h3>
<br>
<p><img src="/content/240329/6.png" alt="6.png" loading="lazy" decoding="async"></p>
<p><strong>렌더링 트리 구축</strong></p>
<ul>
<li>DOM 트리의 루트에서부터 시작하여 페이지를 렌더링하는데 필요한 노드만 남기는데 이때 스크립트 태그나 메타 태그 같은 요소는 렌더링에 반영되지 않고, css의 display: none; 속성도 렌더링 트리에 포함되지 않는 (⚠️ visibility: hidden;은 보이지는 않지만 공간을 차지하기 때문에 렌더링 트리에 포함)</li>
<li>화면에 표시되는 모든 노드의 컨텐츠 및 스타일 정보들을 포함하는 구조로 이루어진 렌더링 트리가 최종적으로 구축된다.</li>
</ul>
<p><strong>렌더링 트리 배치</strong></p>
<ul>
<li>사용자의 뷰포트 기준으로 렌더링 트리가 그려질 위치와 크기들을 계산하는 단계이다.</li>
<li>페이지에서 각 객체의 정확한 크기와 위치를 파악하기 위해 렌더링 엔진은 다시 렌더링 트리의 루트에서부터 계산에 들어간다.</li>
<li>각 노드들의 정확한 위치와 크기가 계산되어 레이아웃이 완료되면 렌더링 엔진은 Paint Setup 및 Paint 이벤트를 발생시킨다.</li>
</ul>
<p><strong>렌더링 트리 그리기</strong></p>
<ul>
<li>이 과정에서 스타일이 복잡할 수록 페인팅에 걸리는 시간이 늘어난다. 이렇게 렌더링 과정을 거치게 되며 만약 DOM이나 CSSOM이 수정되면 화면에 다시 렌더링할 필요가 있는 픽셀을 파악하려면, 전부 처음부터 반복해야한다.</li>
</ul>
<br>
<blockquote>
<p>✋ <strong>Render Tree와 DOM Tree의 차이점</strong></p>
<ul>
<li>Render Tree는 DOM과 CSSOM을 합한 후에 최종적으로 브라우저에 표기할 노드들만 선별되어 트리가 생성이 된다.</li>
<li>Render Tree는 브라우저에 보이지만, DOM은 브라우저에 보이지 않는다</li>
<li>뷰포트에 무엇을 렌더링할지 결정하기 위해 사용되는 것이 DOM이고 Render Tree는 뷰포트에 무엇을 렌더링할지 결정된 최종적인 트리다.</li>
</ul>
</blockquote>
<br>
<h3 id="자바스크립트-엔진-처리-방식"><a class="anchor" href="#자바스크립트-엔진-처리-방식">자바스크립트 엔진 처리 방식</a></h3>
<p>DOM API는 DOM의 각 노드와 상호작용하기 위한 인터페이스, 또는 HTML을 JS에서 제어하기 위한 명령들의 집합이며 대표적으로 자바스크립트에서 자주 사용하는 <code>document.querySelector()</code> 등을 예로 들 수 있다. 이러한 DOM API를 사용하면 이미 생성된 DOM을 동적으로 조작할 수 있다.</p>
<p>CSS를 파싱할때 DOM 생성을 멈추고 CSSOM을 생성한 것처럼 <code>script 태그</code>도 동일하다. 하지만 CSSOM은 렌더링 엔진이 만들었고, script는 렌더링 엔진이 자바스크립트 엔진에게 제어를 넘겨서 HTML 파싱이 중단된 시점부터 다시 DOM 생성을 시작한다.</p>
<br>
<p><strong>자바스크립트 엔진 처리 순서</strong></p>
<ol>
<li>토큰화(Tokenization): 코드를 토큰 단위(변수, 키워드, 연산자 등의 의미 있는 단위)로 나누는 과정</li>
<li>파싱(Parsing): 토큰을 구문 트리 또는 AST(코드의 구조를 이해하고 트리로 표현하는 과정)로 변환</li>
<li>컴파일(Compilation): 일부 엔진은 AST를 중간 코드로 컴파일하는데 이 단계에서 최적화가 수행되기도 함</li>
<li>실행(Execution)
<ul>
<li>파싱된 코드를 실행하는 단계로, 엔진은 파싱된 AST를 기반으로 실제로 코드를 실행하며, 이를 위해 다양한 작업을 수행함</li>
<li>변수와 함수를 메모리에 할당하고, 연산을 수행하며, 조건문과 반복문을 실행하는 등이 포함되고, 실행 단계에서는 코드의 결과를 생성하고 반환함</li>
</ul>
</li>
</ol>
<br>
<h3 id="자바스크립트-리플로우-리페인트"><a class="anchor" href="#자바스크립트-리플로우-리페인트">자바스크립트 리플로우, 리페인트</a></h3>
<p>자바스크립트 코드에 DOM이나 CSSOM을 변경하는 DOM API가 사용된 경우 DOM이나 CSSOM이 변경된다. 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합되며 리렌더링이 되는데 이것을 <strong>레이아웃을 다시 계산해주는 리플로우</strong> 와 <strong>재결합된 렌더 트리를 기반으로 다시 페인트 해주는 리페인트</strong>가 있다.</p>
<br>
<p><img src="/content/240329/7.png" alt="7.png" loading="lazy" decoding="async"></p>
<br>
<p><strong>리플로우(Reflow)</strong></p>
<p>레이아웃의 변경으로 인해 해당 요소와 그 자식 요소의 위치와 크기를 다시 계산하는 과정을 말하며 페이지의 레이아웃을 재조정하므로 비용이 많이 드는 작업이고 성능에 영향을 미칠 수 있으니 주의해야 한다. 리플로우가 발생하는 상황은 아래와 같으니 주의해야한다!</p>
<ul>
<li>요소의 크기, 위치, 혹은 내용이 변경되는 경우</li>
<li>윈도우의 크기를 조절하거나 스크롤을 하는 경우</li>
<li>요소의 추가, 제거, 숨김, 보임과 같은 레이아웃 변경이 발생하는 경우</li>
<li>CSS 속성 변경이 발생하여 레이아웃에 영향을 주는 경우</li>
</ul>
<br>
<p><strong>리페인트(Repaint)</strong></p>
<p>요소의 스타일이 변경되어 새로운 픽셀로 그려지는 과정을 말한다. 하지만 요소의 크기나 위치는 변경되지 않는다. 리페인트는 리플로우보다 비용이 적게 드는 작업이지만, 렌더링 성능에는 영향을 미친다. 리페인트가 발생하는 상황은 아래와 같다!</p>
<ul>
<li>요소의 색상, 투명도, 그림자 등의 스타일 속성이 변경되는 경우.</li>
<li>요소의 클래스가 변경되어 스타일이 변경되는 경우.</li>
<li>브라우저 창이 활성화되거나 숨겨진 요소가 다시 표시되는 경우.</li>
</ul>
<br>
<h2 id="마무리"><a class="anchor" href="#마무리">마무리</a></h2>
<p>사실 여기서 <strong>웹 렌더링 방식(CSR, SSR 등)</strong> 에 대한 것과 <strong>React Virtual DOM</strong>에 대해서도 기록하고 공부하려고 했다. 하지만 공부하다 보니 브라우저 자체의 렌더링 방식도 알아야 할 것들이 많다고 생각해서 주제를 분리해서 기록, 공부할 예정이다.</p>
<p><img src="/content/240329/8.png" alt="8.png" loading="lazy" decoding="async"></p>
<br>
<p><img src="/content/240329/9.png" alt="9.png" loading="lazy" decoding="async"></p>
<p>브라우저에 관해서 공부를 해보면서 Javascript 언어로 브라우저 개발을 할 때 렌더링을 다루는 것에 대해서 잘 알고 있어야 된다고 생각하기는 했지만, 리 렌더링에까지 생각하지는 못하고 급급하게 렌더링이라는 1차 목표만 다뤘다고 생각한다. React에서 hook을 다루거나 복잡한 코드를 다룰수록 작동, 호출 방식에 신경을 써야 되는데 좋은 개발자(재사용성이 높은 코드를 작성하는)가 되기 위해서 어떤 것에 관심을 두어야 할까를 생각해볼 좋은 기회였다고 생각한다.</p>
<p>윗글은 24년 4월 9일에 마무리를 지었지만, 몇 번 읽으면서 브라우저 렌더링에 관해 내용이 필요하면 더 기록해봐야겠다. 분리해서 작성할 필요를 느끼면 링크를 남겨놓겠다!</p>
<p><img src="/content/240329/10.jpeg" alt="10.jpeg" loading="lazy" decoding="async"></p>
<hr>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
        </item>
        <item>
            <title><![CDATA[Good bye CRA, Hello Vite For Migration]]></title>
            <link>https://hooninedev.com/240304</link>
            <guid isPermaLink="false">https://hooninedev.com/240304</guid>
            <pubDate>Mon, 04 Mar 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[CRA(Create React App) 2016년 이전에는 Create React App(CRA)가 등장하기 전에 프론트엔드 개발 환경이 통합되어 있지 않아서, 기존 앱에 리액트를 추가하기 위해 npm에서 import한 후 기존 빌드 구성을 수정하는 번거로움이 있었다고 한다! 여러 도구를 설치하고 JSX를 사용하기 위한 사전 설정을 진행하며, 개발과 프로덕...]]></description>
            <content:encoded><![CDATA[<h2 id="cracreate-react-app"><a class="anchor" href="#cracreate-react-app">CRA(Create React App)</a></h2>
<br>
<p><img src="/content/240304/1.svg" alt="1.svg" loading="lazy" decoding="async"></p>
<p>2016년 이전에는 Create React App(CRA)가 등장하기 전에 프론트엔드 개발 환경이 통합되어 있지 않아서, 기존 앱에 리액트를 추가하기 위해 npm에서 import한 후 기존 빌드 구성을 수정하는 번거로움이 있었다고 한다!</p>
<p>여러 도구를 설치하고 JSX를 사용하기 위한 사전 설정을 진행하며, 개발과 프로덕션 환경을 다르게 구성해야 했다. 또한, 린트 설정 등 다양한 작업이 필요했는데, 이는 일반 사용자들에게는 쉽지 않은 작업이었을 것 같다. 시간이 지나 똑똑한 개발자들이 보일러플레이트 저장소를 만들어 공유하고 복제함으로써 시간을 단축할 수 있었지만, 업데이트가 어려워 빠르게 변화하는 생태계에 대응하는 것도 쉽지 않았을 것 같다.</p>
<p>이러한 어려움을 해결하기 위해, <strong>CRA(Create React App)</strong> 가 등장하게 되었다. CRA를 사용하면 리액트 애플리케이션을 쉽게 생성하고 필요한 설정 작업을 자동화할 수 있다. 이를 통해 개발자들은 더욱 빠르고 효율적으로 프로젝트를 시작하고 관리할 수 있게 되었다. 사내에서도 React 프로젝트를 시작할 때 위와 같은 환경 세팅에 시간 소비를 방지하기 위해서 CRA를 통해 작업을 해왔고 2~3년이 지난 시기에 여러 가지 문제점이 발견되어 마이그레이션에 대한 생각을 조금씩 하게 되었다!</p>
<br>
<h2 id="마이그레이션을-하기로-결심한-이유"><a class="anchor" href="#마이그레이션을-하기로-결심한-이유">마이그레이션을 하기로 결심한 이유</a></h2>
<br>
<p><img src="/content/240304/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<p>마이그레이션을 결정한 이유를 고려해보면, React 16 버전을 사용하는 사내 플랫폼이 사용에는 문제가 없지만 성능과 속도 측면에서는 효율성이 많이 떨어져 개선할 부분이 필요하다는 것을 느끼게 되었다!</p>
<p>성능과 속도를 개선하는 방법은 다양하다. 우선, 빌드 도구를 변경하여 더 효율적인 빌드를 수행할 수 있고, 프로젝트의 의존성을 최적화하여 번들 크기를 줄이고 불필요한 모듈을 삭제할 수도 있다. 더 나아가, 코드 스플릿팅을 통해 로딩 시간을 최소화하거나 프로젝트의 구조를 변경하여 성능을 향상시킬 수도 있다.</p>
<p>이러한 다양한 방법들 중에서 마이그레이션을 통해 Vite로 전환하는 것이 선택된 이유는 무엇일까? 아마도 Vite가 제공하는 빠른 개발 서버와 빌드 속도, ES 모듈 사용 등의 장점이 React 애플리케이션의 성능 및 속도 개선에 큰 도움이 될 것이라고 생각했기 때문이다! 또한, Vite의 확장성과 다양한 프레임워크 지원은 향후 프로젝트 유지보수 및 확장성에도 긍정적인 영향을 줄 것으로 예상된다.</p>
<p>지금부터 마이그레이션을 진행하기 위해 여러 가지 고민에 대해서 같이한 번 살펴보자!</p>
<br>
<h2 id="️-잠깐-번들러가-뭐야"><a class="anchor" href="#️-잠깐-번들러가-뭐야">🙋🏻‍♂️ 잠깐! 번들러가 뭐야!?</a></h2>
<br>
<p><img src="/content/240304/7.png" alt="7.png" loading="lazy" decoding="async"></p>
<p>브라우저 모듈 시스템(Module system)이 아직 표준화되지 않았던 시기에는, 여러 자바스크립트 파일을 단순하게 HTML <code>&#x3C;script></code> 태그로 연결하는 방식을 사용했는데, 이러한 방식은 전역 오염이나 의존성 관리가 어려워지는
등의 여러 문제가 있었다. 그래서 CommonJS, AMD, UMD 등의 모듈 시스템이 등장했고, 이러한 모듈 시스템을 효율적으로 브라우저에서 사용하기 위해 번들러가 등장했다.</p>
<p><strong>번들러(Bundler)</strong> 는 웹 애플리케이션을 개발하기 위해 필요한 HTML, CSS, JS 등의 파편화된(모듈화된) 자원들을 모아서, 하나 혹은 최적의 소수 파일로 결합(번들링)하는 도구다. 그리고 결합을 위해 프로젝트를 해석하는 과정에서, 불필요한 주석이나 공백 제거, 난독화, 파일 압축 등의 기본적인 작업뿐만 아니라, 최신 문법이나 기타 개발에 편리한 특수 기능 등을 브라우저가 지원하는 형태로 변환하는 작업도 수행할 수 있도록 확장되어 개발자의 작업 효율성을 높이고 브라우저의 호환성이나 성능 등을 개선하는데 크게 도움을 주고있다!</p>
<br>
<h2 id="여러가지-빌드도구를-비교해보자"><a class="anchor" href="#여러가지-빌드도구를-비교해보자">여러가지 빌드도구를 비교해보자</a></h2>
<p>빌드 도구가 속도 개선에 직접적 영향을 준다고 생각해 여러가지 빌드 도구를 속도, 번들 최적화 ,확장성, 커뮤니티와 같은 요소들을 생각해 비교해보자!</p>
<br>
<h3 id="esbuild"><a class="anchor" href="#esbuild">ESBuild</a></h3>
<p><img src="/content/240304/3.svg" alt="3.svg" loading="lazy" decoding="async"></p>
<p>ESBuild는 피그마의 CTO인 Evan Wallace가 개발한 빌드 도구로, 기존의 노드 기반 번들러보다 10~100배 더 빠른 빌드 속도를 제공한다. Go 언어를 이용하여 동시성을 지원하므로 멀티 코어 처리를 효율적으로 수행도 가능하다. 또한, 번들링, 경량화, 코드 변환 등 다양한 빌드 작업을 하나의 단계에서 처리하여 프로세스를 간소화했다.</p>
<p>그러나 ESBuild는 CRA에서 제공되는 편의성이 부족하다. 따라서 esbuild 관련 스타터들이 많이 나오고 있는데, 이는 사용자들이 esbuild의 빠른 속도와 효율성을 살리면서도 CRA의 편의성을 일부 보충하기 위한 것이라 생각한다!</p>
<p>또한, ESBuild는 용량이 상대적으로 작고 번들러에 대한 학습 곡선이 높은 것 같다. <del>(문서를 읽어보아도 너무 어렵다..)</del> 또한, 아직은 정식 버전이 출시되지 않아 안정성 면에서 위험성을 가지고 있다고 생각한다. 또한 PostCSS와 같은 후처리기와 es5 이하의 문법에 대한 완벽한 지원이 되지 않는 등의 문제도 짚고 넘어가야할 문제라고 생각한다!</p>
<p>따라서 ESBuild를 사용할 때에는 번들러 설정에 대한 이해와 경험이 필요하며, 현재의 한계와 위험 요소를 고려하여 적절한 상황에서 사용하는 것이 중요하다고 보인다. 이러한 요소때문에 서비스가 진행중인 플랫폼에 ESBuild를 적용시키는 것은 적합하지 않다고 판단했다!</p>
<br>
<h3 id="webpack"><a class="anchor" href="#webpack">Webpack</a></h3>
<img src="4.svg" width=200/>
<p>webpack은 SPA(Single Page Application)가 급부상함에 따라 번들을 통합해서 관리해야 한다는 고민으로 출발하게 되었다. 이를 통해 webpack은 SPA 프로젝트의 구축과 관리를 간편하게 만들어주었다.</p>
<ul>
<li>하나의 설정 파일 내에서 원하는 번들을 생성할 수 있도록 컨트롤할 수 있고, entry와 output을 명시하고 필요한 <strong>plugin과 loader</strong>를 설정함으로써 간편하게 프로젝트를 구성할 수 있다</li>
<li>다양한 plugin과 loader를 지원하며, 이를 통해 강력한 <strong>개발 커뮤니티의가 활성화</strong>되어있다. 이를 활용하여 다양한 파일 형식(css, image, font 등)을 변환하고 번들링할 수 있다.</li>
<li>소스코드의 변화를 감지하여 브라우저를 자동으로 새로고침해주는 <strong>Hot Module Replacement(HMR)를 제공</strong>해서, 실시간으로 변화를 확인할 수 있으며, 다양한 파일의 변화도 감지할 수 있다.</li>
<li>코드 분할을 통해 여러 번들 파일로 분리하여 병렬로 스크립트를 로드하여 <strong>페이지 로딩 속도를 개선</strong>할 수 있다. 또한, 초기에 구동될 필요가 없는 코드를 분리하여 lazy loading을 통해 페이지 초기 로딩 속도를 개선할 수도 있다.</li>
</ul>
<p>팀에서도 webpack을 사용하는 의견에 호의적이였다. 하지만 webpack을 사용하다보면 빌드 시간이 증가하고 복잡한 설정에 대한 학습 곡선이 존재한다는 문제점이 있다는 부분이랑 특히 프로젝트 규모가 커지고 loader와 plugin의 사용이 증가할수록 이러한 문제점이 더욱 부각될 수 있다는 점에서 고민을 하게 되었다.</p>
<br>
<h3 id="rollup"><a class="anchor" href="#rollup">Rollup</a></h3>
<img src="8.svg" width=200/>
<br>
<p>Rollup은 2017년부터 개발이 시작된 모듈 번들러로, webpack의 인기에 가려져 있었지만 확장성이 높아 차세대 번들러로 주목받았다.</p>
<blockquote>
<p>"compiles small pieces of code into something larger and more complex, such as a library or application"</p>
</blockquote>
<p>Rollup은 작은 코드 조각들을 거대하고 복잡한 어플리케이션 혹은 라이브러리로 만들어 주는데, 이는 같은 소스 코드를 환경에 따라 다르게 빌드하는 것을 의미한다. Rollup을 사용할 때 "어플리케이션을 만들 땐 webpack으로, 라이브러리를 만들 땐 rollup으로!" 라는 문구를 자주 보이는데 라이브러리 빌드 쪽에서는 강한 자신감이..보인다 ㅋㅋㅋㅋ</p>
<p>Rollup의 사용 및 구성 방식은 webpack과 유사하지만 내부적으로 사용하는 도구들이 많이 다르다. 특히 라이브러리 개발 시에는 ES6 모듈 형태로 빌드하는 것이 중요한데, Rollup은 ES6 모듈 형태로 빌드할 수 있어 매우 유용하다. 이를 통해 라이브러리 사용자는 최적화된 애플리케이션을 더욱 쉽게 만들 수 있다. 또한, Rollup은 모듈을 호이스팅하여 한 번에 평가하기에 webpack보다 더 빠르며, AST 트리에 사용되는 모듈만 포함시켜 불필요한 코드를 제거하는 방식을 사용하여 더 가벼운 번들을 생성하는데, 이러한 특징들은 Rollup을 라이브러리 개발에 적합한 도구로 만들어 준다.</p>
<p>Rollup에 대한 이야기를 해보았는데 Vite 내부에서 Rollup 번들러를 사용하고 있다! 자세한 것은 Vite에서 다시 확인해보자~!</p>
<br>
<h3 id="parcel"><a class="anchor" href="#parcel">Parcel</a></h3>
<p><img src="/content/240304/9.png" alt="9.png" loading="lazy" decoding="async"></p>
<p>Parcel은 Webpack과 달리 별도의 구성 파일 없이 간단하게 사용할 수 있는 번들러로, 최소한의 구성(Zero config)을 지향한다. 주로 사용하는 기능이나 필요한 기능을 자동으로 설치하고 처리하기 때문에 쉽고 빠르게 프로젝트를 구성할 수 있다. Parcel 번들러를 통해, 작은 규모의 프로젝트를 빠르게 시작하기 좋은 것 같다.</p>
<p>하지만, 사내의 프로젝트의 규모가 커서 파이프라인과 다른 요구사항에 유연하게 대응하지 못할 수 있고, 성능적으로 번들링 속도가 메모리에 영향을 줄 수 있다고 생각이 들어서 직접적으로 도입하지 않기로 결정했다!</p>
<br>
<h2 id="최종적으로-vite를-선택한-이유"><a class="anchor" href="#최종적으로-vite를-선택한-이유">최종적으로 Vite를 선택한 이유</a></h2>
<p><img src="/content/240304/5.png" alt="5.png" loading="lazy" decoding="async"></p>
<p>Vite는 ESBuild의 단점을 보완한 라이브러리로, 개발 서버 구동 시간이 거의 0에 가깝고, 모든 CommonJS 및 UMD 파일을 ESM으로 불러올 수 있도록 변환하고, 다양한 리소스를 별도의 설정 없이 import할 수 있으며, CSS 빌드 최적화를 하고, Direct Import 구문에 대해 Preload 하도록 함으로써 네트워크 비용을 줄일 수 있다. Vite의 장점에 대해서 살펴보도록 하자!</p>
<br>
<h3 id="서버-구동"><a class="anchor" href="#서버-구동">서버 구동</a></h3>
<p>Vite의 가장 큰 특징이라고 한다면 Dev Server에서 Native ESM을 사용하여 소스를 제공한다는 점이다. ESM (EcmaScript Modules)이란 ES6에서 도입되었으며, import/export를 사용하여 모듈을 동적으로 로드할 수 있는 모듈 시스템이다.</p>
<p>Webpack과 같은 기존의 번들 기반 방식에서는 모든 소스코드가 빌드되어서 한번에 번들링된 형태로 서비스를 제공했다면, Native ESM 기반 방식의 Vite에서는 그럴 필요가 없다. 번들링이 필요가 없고 브라우저에서 필요한 모듈의 소소코드를 import할때 이것을 전달만 하면 되는 방식이다.</p>
<br>
<p><img src="/content/240304/10.jpeg" alt="10.jpeg" loading="lazy" decoding="async"></p>
<br>
<p>이것은 결국 현대 대부분의 브라우저에서 ESM일 지원하기에 가능한 것이다. ESM이 나오기 전에는 자바스크립트 언어레벨에서 지원하는 모듈시스템이 없었기 때문에 번들링이 필요했던 것이고, 지금은 자바스크립트 언어레벨에서 모듈시스템이 들어가 있고 거의 모든 브라우저에서 이것을 지원을 하기 때문에 Vite에서는 ESM을 기반으로 만들 수 있게 된 것이다.</p>
<ul>
<li>
<p>Dependencies: 개발 시 그 내용이 바뀌지 않을 일반적인 JavaScript 소스 코드다. 기존 번들러로는 컴포넌트 라이브러리와 같이 몇 백 개의 JavaScript 모듈을 갖고 있는 매우 큰 디펜던시에 대한 번들링 과정이 매우 비효율적이었고 많은 시간을 필요로 했다. Vite의 <strong>사전 번들링 기능은 Esbuild를 사용</strong>하고 있다.</p>
</li>
<li>
<p>Source code: JSX, CSS 또는 Vue/Svelte 컴포넌트와 같이 컴파일링이 필요하다.</p>
</li>
</ul>
<p>결과적으로 프로젝트 규모가 커지면 Dev Server 구동 속도가 매우 느려서 기다리는 시간이 길어지고 지루해진다. 하지만 Vite에서는 Dev Server로 서버구동과 HMR 속도가 엄청 빨라진 것을 확인해볼 수 있다. 이러한 이유만으로도 Vite로 넘어가는데 충분한 이유라고 생각한다!</p>
<br>
<h3 id="hmr-hot-module-replacement"><a class="anchor" href="#hmr-hot-module-replacement">HMR (Hot Module Replacement)</a></h3>
<p><img src="/content/240304/11.webp" alt="11.webp" loading="lazy" decoding="async"></p>
<p>Vite도 당연히 HMR 기능을 지원한다. 개발하면서 소스코드를 수정하면 vite는 수정된 모듈과 관련된 부분만 교체하고 브라우저에 전달한다. Native ESM을 이용하기 때문에 프로젝트 사이즈가 크더라도 HMR 시간에 영향을 주지 않아서 매우 빠르게 진행이 된다. 프론트 개발을 하다보면 HMR 기능을 정말 많이 사용할 수 밖에 없다. 보통 프론트 개발작업을 할때 소스코드 에디터, Dev Server, 브라우저 이렇게 3개는 기본적으로 열어놓고 작업을 하는데 소스코드를 수정하면 Dev Server에서 HMR이 일어나고 브라우저에서 변경된 내용을 확인한다. 그래서 Dev Server 구동보다도 훨씬 더 자주 사용하는 기능이 HMR이다. 그래서 HMR 속도가 정말 중요하다고 말할 수 밖에 없다.</p>
<p>또한 vite는 HTTP 헤더를 활용하여 전체 페이지의 로드 속도를 높인다. 필요에 따라 소스 코드는 <code>304 Not Modified</code>로, 디펜던시는 <code>Cache-Control: max-age=31536000,immutable</code>을 이용해 캐시가 되고, 이렇게 함으로써 요청 횟수를 최소화하여 페이지 로딩을 빠르게 만들어 준다.</p>
<br>
<h2 id="마이그레이션-할-때-유의-사항-및-과정"><a class="anchor" href="#마이그레이션-할-때-유의-사항-및-과정">마이그레이션 할 때 유의 사항 및 과정!</a></h2>
<p>CRA을 통해 부트스트래핑된 React 어플리케이션의 경우 번들링을 위해 기본 제공되는 react-scripts을 사용하는데, 이 react-scripts 라이브러리는 쉽고 간편하지만, 라이브러리 내부에서 사용되는 Webpack 번들러가 느리고 무거운 편이다.
저희 플랫폼에서는 Webpack에 크게 의존성을 가지고 있지 않았기 때문에 굳이 Webpack(즉, react-scripts)을 고집해야 할 이유가 없어 보여서 Vite로 번들러 수정 작업을 진행했는데 혹시나 Webpack 의존도가 높은 경우는 고려해봐야 할 것 같습니다!</p>
<p>Vite를 사용하기 위해서 CRA와의 차이점에 대해서 찾아보았는데 환경과 문법에서 차이가 있다. process.env, EJS 템플릿 문법 지원, 다양한 플러그인, 폴더와 파일의 위치 등등 이었습니다. 마이그레이션하면서 다뤄본 큰 변경점들에 대해서 간략하게 기록해보겠습니다~</p>
<blockquote>
<p>🤔 저는 문제가 없어서 기록해두지 않았습니다만, Node,Vite 버전 호환성과 디렉터리 구조등을 생각해봐야 됩니다! 호환성은 각자의 환경에 따라 다르니 참고용으로만 봐주세요~!</p>
</blockquote>
<br>
<h3 id="processenv-호환성"><a class="anchor" href="#processenv-호환성">process.env 호환성</a></h3>
<p>Vite는 import.meta.env 객체를 이용해 환경 변수에 접근할 수 있고, 이러한 환경 변수는 <code>VITE_SOME_KEY=123</code>처럼 VITE_라는 접두사를 붙여 선언해야 된다.</p>
<blockquote>
<p>🚨 사내에서 Cypress로 테스트 코드를 작성하는데 import.meta.env를 통한 환경 변수 접근이 잘 작동하지 않는 이슈가 있어서, process.env를 통해서도 환경 변수를 참조할 수 있도록 호환성을 추가하는 작업을 진행했습니다. 관련한 정보는 <a href="https://github.com/vitejs/vite/issues/1149#issuecomment-857686209">Vite Issue</a> 확인해보시면 좋을 것 같습니다.</p>
</blockquote>
<br>
<h3 id="ejs-템플릿-문법을-위한-플러그인-추가-및-변경"><a class="anchor" href="#ejs-템플릿-문법을-위한-플러그인-추가-및-변경">EJS 템플릿 문법을 위한 플러그인 추가 및 변경</a></h3>
<p>CRA에서는 기본적으로 <a href="https://ccusean.tistory.com/entry/Express-%ED%85%9C%ED%94%8C%EB%A6%BF-%EC%97%94%EC%A7%84-ejs-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0">EJS 템플릿 문법</a>을 지원하는 반면, Vite에서는 EJS 플러그인을 추가해주어야 템플릿 문법을 사용할 수 있습니다. 이를 위해 vite-plugin-ejs 패키지를 설치하고, 다음과 같이 플러그인을 적용해두었습니다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">plugins</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      ViteEjsPlugin</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">        // 여기에는 사용하고자 하는 변수를 선언해주면 됩니다. 예컨대,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        exampleVar: </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">meta</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.env?.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">VITE_EXAMPLE_VAR</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> ||</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      }),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    ],</span></span></code></pre></figure>
<p>그리고 Vite에서 지원하지 않는 기능을 사용하기 위해서 플러그인을 추가했습니다. (23.06.02에는 적용이 안되었는데 지금은 적용이 될 수 있습니다.)</p>
<ul>
<li>vite-plugin-svgr: SVG 그래픽을 리액트 컴포넌트처럼 사용하기 위한 플러그인</li>
<li>vite-plugin-eslint: ESLint와 관련된 오류를 알려주는 플러그인</li>
<li>vite-tsconfig-paths: tsconfig.json에 정의된 paths 매핑을 사용하기 위한 플러그인</li>
</ul>
<blockquote>
<p>추가로 사내 프로젝트에 Jqeury가 있어서 @rollup/plugin-inject을 사용하고 있습니다. 이 플러그인은 코드 번들링 과정 중에 지정된 변수를 주입하여 코드를 변경하는 데 사용되고, jQuery를 $ 및 jQuery 변수로 주입하여 전역으로 사용할 수 있도록 해주고 있습니다. <del>제거해야되는데!!!!!!!</del></p>
</blockquote>
<p>CRA 에서 Vite로 플러그인과 함께 알아보니 지원되는 것이 많다는 것을 알게되었네요..ㅋㅋ</p>
<br>
<h3 id="indexhtml-수정-및-경로-변경"><a class="anchor" href="#indexhtml-수정-및-경로-변경">index.html 수정 및 경로 변경</a></h3>
<p>vite는 개발모드에서 ESBuild를 사용하기 때문에 추가 번들링 없이 index.html 파일이 앱의 진입점이 되도록 변경했다. 그렇기 때문에 public 폴더에 위치해 있는 index.html 파일을 프로젝트 최상단에 위치하도록 이동시키고, Vite는 CRA와 다른 메커니즘으로 assets를 다루기 때문에 index.html 파일에 포함된 <code>%PUBLIC_URL%</code> 경로는 모두 지워준다. 그 후 파일의 <code>&#x3C;body></code> 를 아래와 같이 수정해주면 됩니다!</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">body</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">noscript</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>You need to enable JavaScript to run </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> app.</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">noscript</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  &#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">div id</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'root'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  &#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">script type</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'module'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> src</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'/src/index.jsx'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;/</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">script</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">body</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span></code></pre></figure>
<br>
<h3 id="config-수정"><a class="anchor" href="#config-수정">config 수정</a></h3>
<p>vite.config.ts 와 tsconfig.json을 루트 디렉토리에 만들어주고 세부설정을 추가해주면 된다. 사내에서 사용하는 설정이여서 공개하기는 염려되기 때문에 몇가지 설정만 공유해볼려고 한다.</p>
<ul>
<li><strong>vite.config.ts</strong>
<ul>
<li>터미널 명령어로 script를 실행했을 때 변경 될 환경들을 defineConfig로 정의한다.(sentry, sourceMaps, version, branch)</li>
<li>build에 연관 되어있는 react-router-dom과 assets 폴더의 chunkFile 생성에 관여한다.</li>
<li>Module Alias를 설정해서 긴 경로에 별칭을 사용할 수 있도록 지정한다.</li>
</ul>
</li>
<li><strong>tsconfig.json</strong></li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "compilerOptions"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "experimentalDecorators"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">//클래스 및 클래스 멤버에 메타데이터를 추가를 위한 실험적 데코레이터(decorator) 문법을 활성</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "target"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"ESNext"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// TypeScript가 생성하는 JavaScript 코드의 ECMAScript 목표 버전을 지정하는데, 여기서는 "ESNext"를 지정하여 가장 최신의 ECMAScript 표준을 대상으로 컴파일하도록 설정되어 있다.</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "lib"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"dom"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"dom.iterable"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"esnext"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">], </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 컴파일러가 사용할 라이브러리를 지정하는데, 여기서는 DOM 및 ESNext와 함께 dom.iterable도 사용하고 이것은 컴파일러가 코드에서 사용되는 기능 및 API에 대한 정적 검사와 IntelliSense를 제공하기 위해서 사용한다.</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "types"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"vite/client"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"vite-plugin-svgr/client"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">], </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 프로젝트에서 사용하는 타입 정의 파일을 지정하는데, 여기서는 Vite와 SVGR 관련 타입 정의 파일을 명시</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "baseUrl"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"./src"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 상대 모듈 참조 및 별칭을 해결할 기본 경로를 설정하는데, 여기서는 ./src 폴더를 기본 경로로 설정하고 있다.</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "allowJs"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// JavaScript 파일을 컴파일할 수 있도록 허용하는 옵션</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "skipLibCheck"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 리이브러리 파일의 검사를 건너뛰도록 설정</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "esModuleInterop"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// ES 모듈을 CommonJS 모듈과 상호 운용할 수 있도록 설정</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "allowSyntheticDefaultImports"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// import 문에서 default를 사용할 수 있도록 허용</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "strict"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 엄격한 타입 검사 옵션을 활성화</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "forceConsistentCasingInFileNames"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 파일 이름의 일관된 대소문자를 강제하기 위해서 사용</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "noFallthroughCasesInSwitch"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// switch 문에서의 case 절에 대한 fall-through를 허용하지 않기 위해서 사용</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "module"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"ESNext"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">//  모듈 코드 생성 방식을 지정하는데, 여기서는 "ESNext"로 설정</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "moduleResolution"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"node"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 모듈 해결 방식을 설정하는데, 여기서는 "node"로 설정</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "resolveJsonModule"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// JSON 파일을 모듈로 불러올 수 있도록 설정</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "isolatedModules"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 파일 간의 독립적인 컴파일을 활성화</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "noEmit"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 컴파일 결과물을 생성하지 않도록 설정</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "noImplicitAny"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">false</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "noImplicitReturns"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">false</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "strictNullChecks"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">false</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // noImplicitAny, noImplicitReturns, strictNullChecks은 TypeScript의 엄격한 타입 검사 관련 옵션들을 설정</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "jsx"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"react-jsx"</span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D"> // JSX 파일의 파싱 방법을 설정하는데, 여기서는 "react-jsx"로 설정되어 있으므로 React의 JSX 문법을 사용할 수 있음</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "include"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"src"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">]</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<br>
<h3 id="해치웠나"><a class="anchor" href="#해치웠나">해치웠나..?</a></h3>
<p><img src="/content/240304/13.jpeg" alt="13.jpeg" loading="lazy" decoding="async"></p>
<br>
<p>이 정도면 얼추 CRA에서 Vite로 변경하는 데에 큰 문제는 없었다. 하지만 생각하지 못한 문제들이 너무 많았다. 라이브러리, node 버전(사내에서는 Back-End가 node.js 기반이어서 더 신경을 써야 됐는데...) 등등... 여러분들은 이런 실수없어서 순탄한 마이그레이션을 했으면 좋겠다!!</p>
<p>참고로 위 과정에서 dependencies를 수정해줘야 되고, react-scripts 처럼 사용하지 않는 모듈을 삭제해 줘야 된다. 꼭 확인해서 불필요한 모듈을 지워주면 좋겠다!</p>
<br>
<h2 id="그-다음은-어떤-것이-더-필요할까"><a class="anchor" href="#그-다음은-어떤-것이-더-필요할까">그 다음은 어떤 것이 더 필요할까?</a></h2>
<p>우여곡절 끝에 마이그레이션에 성공했다! 과정은 짧아 보이지만... 엄청 시간이 오래 걸렸다. <del>잘 알아보지 않아서.. 특히 BE와.. 라이브러리 호환성.. 등등</del> 작업을 하고 나서 피드백을 해보았다 빌드시간이 평균 40% 정도 줄었고 모듈 해석 속도가 향상되고 HMR 덕분에 개발 생산성을 향상했다. 점차 플러그인 생태계가 Vite에 적합하게 발전함에 따라 프로젝트가 안정성을 찾지 않을까 하는 기대가 있다.</p>
<p>이후에는 어떤 것을 다뤄야 될까? 라는 고민을 잠깐해보았는데 변화된 생태계에 맞추고 프로젝트 안정성을 높이기 위해서 테스트 작성과 캐싱, 프리픽싱 설정을 하고 여러가지 플러그인에 대해서 알아 볼 예정이다!</p>
<hr>
<h2 id="출처-및-도움되는-링크들"><a class="anchor" href="#출처-및-도움되는-링크들">출처 및 도움되는 링크들</a></h2>
<p><a href="https://ko.vitejs.dev/guide/why.html">Vite 공식 홈페이지</a></p>
<p><a href="https://www.heropy.dev/p/x8iedW">번들러와 빌드 도구</a></p>
<p><a href="https://junghan92.medium.com/%EB%B2%88%EC%97%AD-create-react-app-%EA%B6%8C%EC%9E%A5%EC%9D%84-vite%EB%A1%9C-%EB%8C%80%EC%B2%B4-pr-%EB%8C%80%ED%95%9C-dan-abramov%EC%9D%98-%EB%8B%B5%EB%B3%80-3050b5678ac8">Create React App 권장을 Vite로 대체하자는 PR 관련 글</a></p>
<p><a href="https://rajaraodv.medium.com/webpack-hot-module-replacement-hmr-e756a726a07">Webpack 과 HMR</a></p>
<p><a href="https://github.com/reactjs/react.dev/pull/5487">CRA 대신에 Vite를 쓰자는 React issue</a></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>React</category>
        </item>
        <item>
            <title><![CDATA[프론트엔드 개발자의 고난했던 DevOps 경험 : "행군보다 힘들었던 반자동화 배포"]]></title>
            <link>https://hooninedev.com/240227</link>
            <guid isPermaLink="false">https://hooninedev.com/240227</guid>
            <pubDate>Tue, 27 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[첫 회사에서는 Azure 환경으로 배포했고, 개인 프로젝트에서는 클라우드와 인프라에 대한 개념이 부족해 heroku, github, netlify, vercel 등 간단하게 배포해서 프로젝트를 완성했다. 지금 회사에서는 pipeline을 사용하지 않고 pem,ppk로 인증을 한 후에 PuTTY와 winSCP를 이용해서 배포했다. 사용해보니 불편한 점도 너무...]]></description>
            <content:encoded><![CDATA[<p><img src="/content/240227/3.png" alt="3.png" loading="lazy" decoding="async"></p>
<p>첫 회사에서는 Azure 환경으로 배포했고, 개인 프로젝트에서는 클라우드와 인프라에 대한 개념이 부족해 heroku, github, netlify, vercel 등 간단하게 배포해서 프로젝트를 완성했다.</p>
<p>지금 회사에서는 pipeline을 사용하지 않고 pem,ppk로 인증을 한 후에 PuTTY와 winSCP를 이용해서 배포했다. 사용해보니 불편한 점도 너무너무 많아서 점진적으로 바꿔야겠다는 생각이 들게 되어 바꾸었고, FE 개발자의 아등바등 DevOps 경험을 기록으로 남겨볼려고 한다. 사내의 요구사항으로 인해 자동화로 CI/CD를 하지는 않았지만, 반자동화로서 성능과 효율성을 위해 변하는 배포과정을 살펴보자!!</p>
<blockquote>
<p>참고로 저는 DevOps, 백앤드에 대한 지식이 부족합니다. 큰 프로젝트는 아니었지만, 필요성을 느껴서 사내 스터디처럼 공부처럼 이루어낸 것이므로 잘못된 정보가 있을 수 있으니, 모든 충고와 조언 감사히 받아들이겠습니다!!</p>
</blockquote>
<p> </p>
<h2 id="처음에는요"><a class="anchor" href="#처음에는요">처음에는요..</a></h2>
<p><img src="/content/240227/4.png" alt="4.png" loading="lazy" decoding="async"></p>
<p>FE에서 develop에 개발 서버, master에 실 서버를 연결해 각각의 AWS ec2 주소에 build 파일을 올렸다. PuTTY에 pem,ppk를 이용해서 인증 한 후에 ec2로 접근하고 winSCP를 이용해 각각의 파일을 전송해주었다.</p>
<p>회사에서는 기획과 개발이 동시에 일어나기 때문에 적은 인원으로 많은 일을 해야 했다. 그렇기에 배포를 할 때에는 cache, 효율적 배포에 대해 신경 쓰지 않고 기능개발에 목표를 두었다. 이런 시간 동안 배포하는 것에 많은 시간을 소비하게 되었다.</p>
<p>build를 통해 node_modules, img, font 등 변경 사항이 존재하면 모든 것을 새로 ec2로 전송했고, 평균 배포 시간 15분 정도 가지게 되었다.</p>
<p> </p>
<h3 id="pemppkputty"><a class="anchor" href="#pemppkputty">Pem,Ppk,PuTTY</a></h3>
<p>pem,ppk와 같이 암호화된 파일을 이용해 PuTTY로 가상 서버로 접근하게 되면 안정성이 올라가기 때문에 사용한다.</p>
<ul>
<li>
<p>PEM(Privacy Enhanced Mail) : 암호화된 데이터를 저장하는 파일 형식으로 SSH 키 쌍을 생성할 때 개인 키가 pem 방식으로 저장된다.</p>
</li>
<li>
<p>PPK(PuTTY Private Key) : PuTTY에서 사용하는 개인 키 파일 형식</p>
</li>
<li>
<p>PuTTY : SSH, Telnet, rlogin, SCP 및 기타 네트워크 프로토콜을 사용하는 클라이언트 애플리케이션으로 Window, macOS 및 Linux 등 다양한 운영체제에서 사용 가능</p>
</li>
</ul>
<p> </p>
<h3 id="winscp-ssh"><a class="anchor" href="#winscp-ssh">WinSCP, SSH</a></h3>
<p>WinSCP와 SSH는 모두 원격 서버에 연결하고 서버의 파일과 폴더를 탐색해 편집 할 수 있는 것이다.</p>
<ul>
<li>
<p>WinSCP : Windows용 무료 오픈 소스 SFTP(SSH File Transfer Protocol) 클라이언트로, 원격서버의 파일과 폴더를 탐색,편집에 사용</p>
<blockquote>
<p>SCP(Secure Copy Protocol)이란?</p>
<blockquote>
<ul>
<li>ssh 원격 접속 프로토콜을 기반으로 한 파일 전송 프로토콜로 원격으로 파일과 디렉토리 등을 보내고, 가져오는데 사용</li>
<li>네트워크가 연결되어 있는 환경에서 ssh와 동일한 22번 포트와 identity file을 사용하여 파일을 송수신하기 때문에 보안적으로도 안정적</li>
</ul>
</blockquote>
</blockquote>
</li>
<li>
<p>SSH(Secure Shell) : 네트워크를 통해 안전하게 로그인하고 명령을 실행할 수 있는 프로토콜로 암호화를 사용하여 데이터를 보호한다.</p>
</li>
</ul>
<p> </p>
<h2 id="압축을-해볼까"><a class="anchor" href="#압축을-해볼까">압축을 해볼까?</a></h2>
<p><img src="/content/240227/5.jpeg" alt="5.jpeg" loading="lazy" decoding="async"></p>
<p>평균적으로 개발 서버는 10~15분의 시간이, 실 서버는 20분 정도의 시간이 걸려서 배포되었다. 상당히 많은 시간이 불 필요한 곳에 들어가고 있다는 것을 알게되어서, 배포 방법을 변경하기로 했다.</p>
<p>그중에 내가 선택한 방법은 build 파일을 압축해서 ec2에 접근해서 배포하는 것이었다. 검색을 해보니 zip, gzip, zstd 등 많은 압축 방법이 있었지만 나는 gzip+tar 의 tar.gz 을 사용하게 되었다. 내가 왜 tar.gz 방식을 선택했는지 알기 전에 먼저 각 압축법에 대해서 알아보자</p>
<ul>
<li>zip: 가장 일반적인 압축 형식이며, 대부분의 운영 체제에서 지원된다. 압축률은 상대적으로 낮지만, 압축 및 압축 해제 속도가 빠름</li>
<li>gzip: zip 형식보다 높은 압축률을 제공하며, 압축 및 압축 해제 속도도 비교적 빠르다.</li>
<li>zstd: 최근에 등장한 압축 형식으로, gzip보다 더 높은 압축률을 제공하며, 압축 속도도 빠르다. 운영 체제 지원 여부를 확인해봐야 함</li>
<li>tar.gz: tar 형식으로 파일을 묶고 gzip으로 압축하는 방식으로 압축률은 높지만, 압축 및 압축 해제 속도가 느리다.</li>
</ul>
<p>회사에서 프로젝트의 build 파일은 보통 3.5GB 정도로 용량이 크기 때문에, 압축률이 높은 형식을 선택해야 했다. 또한, 운영체제에 대한 제약이 없어야 했기 때문에 MacOS나 Windows에서 모두 지원되는 형식이어야 했다. 그리고 프로젝트를 EC2 인스턴스에서 압축 해제할 계획이었으므로, 가상 서버에서의 압축 해제 속도에 대한 우려는 크지 않았다. 이에 따라 리눅스에서 기본적으로 지원되는 tar 압축 형식을 선택하게 되었다.</p>
<blockquote>
<p>🤘 ec2 에서 tar.gz 압축 해제하는 명령어 tar -zxvf build.tar.gz을 사용했다!</p>
</blockquote>
<p>결과적으로 압축방식으로 build 폴더를 build.tar.gz으로 만들어서 terminus를 가지고 명령어로 SFTP로 인증 한 후에 SSH로 폴더에 접근해 가상 서버에 파일을 넣는 형식으로 배포 방법을 바꾸었다. 해당 작업으로 개발 서버 평균 10 ~ 15분, 실 서버 평균 15 ~ 20분 걸리던 시간을 <strong>3분 이내로 400% 넘는 효율성</strong>을 얻게 되었다.</p>
<p> </p>
<h2 id="흠-pipeline을-사용해보는게-좋을-것-같은데"><a class="anchor" href="#흠-pipeline을-사용해보는게-좋을-것-같은데">흠.. pipeline을 사용해보는게 좋을 것 같은데?</a></h2>
<p><img src="/content/240227/6.jpeg" alt="6.jpeg" loading="lazy" decoding="async"></p>
<p>압축시간을 많이 줄였다. 하지만 휴먼에러가 많이 발생해 해결법이 필요했다. 여기서 휴먼에러라고 했을 때 SFTP 인증을 할 때 잘못된 접근을 하거나, SSH를 통해 파일을 전달하는 과정에서 경로가 잘못된 것들을 의미한다. 이런 오류들 때문에 효율적이지 못한 시간을 소비하게 되었다. 또한 pem,ppk를 모든 개발자들이 가지고 있으면 노출 위험도도 커질 수 있다고 판단했다. <del>편하기도 하니깐</del></p>
<p>그래서 어떻게 이것을 해결할 수 있을까? 생각해보고 낸 결론은 <strong>우리는 Jira&#x26;Atlassian을 이용하기에 bitbucket의 pipeline을 가지고 CI/CD 환경을 구축하자</strong> 를 계획하고 인프라를 구축하기로 했다.</p>
<p>(사실 bitbucket의 pipeline을 사용한 가장 큰 이유는 Jenkins, circleCI와 같은 보조서버가 없기 때문이였다.)</p>
<blockquote>
<p>여기서 프로젝트의 자동화 CI/CD가 아닌 반자동화 CI/CD 환경을 구축했다. 미국과 한국의 개발자들이 시차때문에 잘못된 배포를 했을 경우에 대응이 어려워, commit&#x26;push를 이용해 배포하는 것이 아닌 pipeline을 직접 실행해 배포하는 형식으로 구현하기로 했다.</p>
</blockquote>
<p> </p>
<p><img src="/content/240227/8.jpeg" alt="8.jpeg" loading="lazy" decoding="async"></p>
<p> </p>
<h3 id="배포-환경-만들기"><a class="anchor" href="#배포-환경-만들기">배포 환경 만들기</a></h3>
<p> </p>
<p><img src="/content/240227/13.png" alt="13.png" loading="lazy" decoding="async"></p>
<ul>
<li>AWS S3 Bucket을 이용해서 정적인 파일들(이미지,CSS파일,Font 등)을 호스팅</li>
<li>AWS EC2를 이용해 웹을 호스팅할 때 사용</li>
<li>AWS CodeDeploy을 이용해 웹을 EC2인스턴스 또는 온프레미스 서버로 자동으로 배포하는데 사용</li>
<li>Jira &#x26; Bitbucket Pipeline을 이용해 프로젝트 관리 협업에 사용하고 변경 사항을 테스트 한 후에 CI/CD 파이프라인 설정해서 배포</li>
</ul>
<p> </p>
<h3 id="인프라-구성하면서-주의한-점"><a class="anchor" href="#인프라-구성하면서-주의한-점">인프라 구성하면서 주의한 점</a></h3>
<ol>
<li>AWS 리소스 관리</li>
</ol>
<p>사용하지 않는 리소스를 찾아서 정리를 해 비용 절감을 하고 IAM 역할 및 권한을 확인해 네트워크 보안을 고려해야 된다. <del>AWS 과금문제가 무서워서 제일 많이 찾아보고 신경쓴 부분이기도 하다.. 무섭😱😱</del></p>
<ol start="2">
<li>CI/CD 파이프라인 설정</li>
</ol>
<p>코드 변경 사항을 버전 관리 시스템에 체계적으로 관리하고, 실 버전과 개발 버전 환경을 구분해서 배포할려고 script를 각각 만들었다. 혹시 모를 상황에 배포 중 문제가 생기면 롤백하고 Deploy pause 할 수 있도록 해당 script도 만들어 적용했다.</p>
<ol start="3">
<li>Jira 및 Bitbucket Pipeline 연동</li>
</ol>
<p>제일 어려운 부분이었다. Jira에서 변경사항을 확인하자마자 자동으로 파이프라인이 실행되는 것이 아닌 사용자가 파이프라인을 직접 실행해 실,개발 서버를 배포할 수 있도록 설정했다. 해당 설정은 Bitbucket Pipelines YAML 파일을 작성하여 CI/CD 작업을 정의했다.</p>
<ol start="4">
<li>추가적으로</li>
</ol>
<p>현재 캐싱은 CloudFront를 사용하여 웹사이트 콘텐츠 캐싱을 통해 성능을 향상할 수 있도록 했다. 캐싱에 대한 내용은 <a href="#%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%EC%BA%90%EC%8B%9C%EA%B4%80%EB%A6%AC%EB%A5%BC-%EC%9C%84%ED%95%B4-ooooo%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%BC-%EC%98%88%EC%A0%95%EC%9E%85%EB%8B%88%EB%8B%A4">여기서 확인해보자</a></p>
<p> </p>
<h3 id="끝이자-새로운-시작"><a class="anchor" href="#끝이자-새로운-시작">끝이자 새로운 시작..</a></h3>
<p><img src="/content/240227/14.png" alt="14.png" loading="lazy" decoding="async"></p>
<p>결과적으로!! 반자동화 CI/CD 를 구현했다!!! 작업하는 동안에 모르는 것이 많아서 사내 직원들에게 도움을 받고 많은 검색을 해보았다. 작은 프로젝트지만 스스로 CI/CD 인프라를 구축해보면서 왜 이런 작업들이 필요한지 알게되었다. <strong>압축을 통해서 400%의 속도를 향상</strong>시키고, <strong>반자동화 CI/CD를 통해서 휴먼에러를 줄여서 배포 환경에 많은 시간을 소비하지 않는 것</strong>에 기여했다는 것이 가장 뿌듯하다!</p>
<p> </p>
<h2 id="캐시-cache"><a class="anchor" href="#캐시-cache">캐시!!! cache..!!!</a></h2>
<p><img src="/content/240227/7.jpeg" alt="7.jpeg" loading="lazy" decoding="async"></p>
<p>CI/CD 프로세스가 완료되었다!! 특별한 문제 없이 진행되었으나, EC2 EBS에 캐시가 과도하게 누적되어 있음을 확인하게 되었다. 캐시 보존 필요성을 인지하고 초기 설정에 따라 10일 이상 경과된 캐시는 자동 삭제되도록 설정했지만, 보다 효율적인 캐시 관리 방안을 찾게 되었다.</p>
<p><del>하지만 현생이 너무 바빠서.. 추후 작업을 위해 공부한 것들을 기록해보자!</del></p>
<p> </p>
<h3 id="그래서-ebs가-뭐고-왜-사용할까"><a class="anchor" href="#그래서-ebs가-뭐고-왜-사용할까">그래서 EBS가 뭐고 왜 사용할까?</a></h3>
<p><img src="/content/240227/9.png" alt="9.png" loading="lazy" decoding="async"></p>
<p>EBS(Elastic Block Storage)는 EC2의 연산처리를 한 후에 해당 데이터를 저장하는 역할을 하는 것이다. 쉽게 표현하면 EC2는 가상 서버라면 EBS는 가상 하드디스크라고 할 수 있다. 저렴한 비용으로 사용량을 확장할 수 있고 영구 블록 스토리지 볼륨을 제공한다.</p>
<blockquote>
<p>영구 블록 스토리지 볼륨은 실제 하드 드라이브와 유사하게 작동하는 클라우드 기반 스토리지, EC2 인스턴스에서 사용할 수 있는 영구적 (데이터가 삭제되지 않음) 저장 공간을 제공하며, 다양한 용량, 성능, 가격 옵션으로 제공</p>
</blockquote>
<p>EBS는 디스크 하나하나 저장 단위인 EBS 볼륨으로 구성되어있다. EBS 타입도 5가지 제공되는데, <del>너무 어렵다..</del> 구글링 해보니 용량과 MAX IOPS 수치를 확인해서 상황에 따라 적합한 타입을 사용하면 된다고 한다! <del>하지만 나는 요금을 아끼기 위해서 마그네틱 타입을 사용하고 있다.</del></p>
<p>EBS를 사용하는 이유는 아주 많을 것 같다. 다양한 옵션을 사용할 수 있고 적은 비용으로 확장하기도 쉽다. 데이터 손실을 방지하는 것도 훌륭한 기능이다. 그 중에 EC2가 종료되어도 별개의 네트워크로 연결되어 데이터가 유지되는 EBS의 영구성이 큰 장점이라고 생각한다. 그리고 EC2가 변경되어도 재연결하기도 쉽고 하나의 EBS를 여러개의 EC2에 장착도 가능하다.</p>
<p><img src="/content/240227/10.jpeg" alt="10.jpeg" loading="lazy" decoding="async"></p>
<p> </p>
<h3 id="효율적인-캐시관리를-위해-ooooo을-사용해볼-예정입니다"><a class="anchor" href="#효율적인-캐시관리를-위해-ooooo을-사용해볼-예정입니다">효율적인 캐시관리를 위해 OOOOO을 사용해볼 예정입니다~!</a></h3>
<p>EBS에 캐시가 누적될 때마다 든 생각이 있다. <code>그냥.. 차이가 생긴 파일, 폴더만 옮겨주면 되잖아. 그럼 캐시도 줄고 속도도 향상되지 않을까??</code> 라는 생각을 가지고 불타는 구글링을 해보다가 찾은 방법이 있다.</p>
<img src="https://post-phinf.pstatic.net/MjAxODEyMTFfMTAw/MDAxNTQ0NTA0MDgyOTMz.bD3_qYAKhivyntOXUfWOxIDab4msHT-KNMDDwq3oMnIg.4tDkeh83lxHseQFLoy4c9JAXtfdy3iyPzGlN_2JWLqYg.GIF/IzLmo84UQg10xLdWQeK45JJ9bXgU.jpg?type=w400" height="300">
<p> </p>
<p> </p>
<h1>그건 바로!!!!!</h1>
<p> </p>
<h2>그건 바로!!!</h2>
<p> </p>
<h3>그건 바로!</h3>
<p> </p>
<p><img src="/content/240227/11.jpeg" alt="11.jpeg" loading="lazy" decoding="async"></p>
<p><strong>Rsync(Remoe Sync)</strong> 는 원격에 있는 파일과 디렉토리를 복사하고 동기화 하기 위해서 사용하는 툴이며 동시에 네트워크 프로토콜로서 커맨드 라인의 옵션들을 이용해서 배치 프로그램을 개발하기 쉬다는 장점이 있다. 이 스크립트를 cron 등에 올리는 걸로 간단하게 백업 혹은 미러(mirror) 시스템을 구축할 수 있다고 한다!</p>
<p>그리고 내가 rsync를 선택한 가장 이유는 scp보다 빠르고, remote-update 프로토콜을 이용해서 차이가 있는 파일만 복사해서 처음에는 모든 파일과 디렉토리를 복사하겠지만, 다음부터는 차이가 있는 파일만 복사하기 때문에 더 빠르고 효율적으로 작동한다는 점이다. 아직 사용해보지는 않았지만, 추후 작업을 하게 되면 업데이트 할 예정이니..</p>
<p><img src="/content/240227/12.jpeg" alt="12.jpeg" loading="lazy" decoding="async"></p>
<p>rsync를 찾아보면서 도움이 된 자료들을 올려둘테니 <a href="https://velog.io/@inhwa1025/Linux-rsync%EB%9E%80-rsync-%EC%82%AC%EC%9A%A9%EB%B2%95-rsync%EB%A1%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B0%B1%EC%97%85%ED%95%98%EA%B8%B0">개발괘발개발새발님의 rsync</a> 이랑 <a href="https://feccle.tistory.com/96">Feccle님의 rsync</a> 을 읽어봐도 좋을 것 같다.</p>
<p> </p>
<blockquote>
<h4>출처 및 도움되는 링크들</h4>
</blockquote>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>DevOps</category>
        </item>
        <item>
            <title><![CDATA[넌 상태관리를 어떻게 해? 난 Zustand를 활용하고 있어!]]></title>
            <link>https://hooninedev.com/240222</link>
            <guid isPermaLink="false">https://hooninedev.com/240222</guid>
            <pubDate>Thu, 22 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[&nbsp; 난 왜 zustand 공부를 시작했을까? &nbsp; 회사에서 위처럼 회사에서 useState, useEffect, useRef 등 Hook을 하나의 컴포넌트에서 남발해서 사용하고 있다는 것을 알게 되었다. (위 사진은 극히 일부의 코드를 수정해서 보여준란걸..) 그렇게 되니 컴포넌트 내부의 요소를 변경하거나, 렌더링이 일어날 때 state, ...]]></description>
            <content:encoded><![CDATA[<p><img src="/content/240222/1.jpeg" alt="1.jpeg" loading="lazy" decoding="async"></p>
<p> </p>
<h2 id="난-왜-zustand-공부를-시작했을까"><a class="anchor" href="#난-왜-zustand-공부를-시작했을까">난 왜 zustand 공부를 시작했을까?</a></h2>
<p> </p>
<h3 id="회사에서"><a class="anchor" href="#회사에서">회사에서</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> tabOneRef</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> React.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useRef</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ITabOneRef</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> rmTimerRef</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> React.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useRef</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ITimerRef</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> cmTimerRef</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> React.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useRef</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">ITimerRef</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> printRef</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> React.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useRef</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">HTMLDivElement</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> sessionOutAlertRef</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useRef</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">IAlertRef</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> isRMLogAdded</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useRef</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">false</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> timeLogId</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useRef</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">uuidv4</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">memoList</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setMemoList</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">handleSave</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setHandleSave</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">boolean</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">false</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">claim</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setClaim</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">pageStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setPageStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">false</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">rmTimeLogs</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setRmTimeLogs</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">cmTimeLogs</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setCmTimeLogs</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">totalRMTime</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setTotalRMTime</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">totalCMTime</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setTotalCMTime</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">nextURL</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setNextURL</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">measureList</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setMeasureList</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">as</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> any</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">rmInfo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setRmInfo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">RpmInformation</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>();</span></span></code></pre></figure>
<p>위처럼 회사에서 useState, useEffect, useRef 등 Hook을 하나의 컴포넌트에서 남발해서 사용하고 있다는 것을 알게 되었다. <strong>(위 사진은 극히 일부의 코드를 수정해서 보여준란걸..😂)</strong></p>
<p>그렇게 되니 컴포넌트 내부의 요소를 변경하거나, 렌더링이 일어날 때 state, ref의 동적인 값들이 초기화되거나 생각하지 못한 데로 작동하게 되어버리게 되었다.</p>
<p>거기에 새로운 기능 개발을 하게 되었을 때 많은 요소 영향력을 생각해야 되게 되고, useEffect의 의존성 배열이 무한히 추가되는 현실까지 직면하게 되었다. ㅠㅠ</p>
<p>이런 상황까지 되어버리니 회사 내부에서는 프로젝트에 상태관리를 도입해서 렌더링이 발생하는 시점, 변수의 선언, 전역 변수의 props 전달 등 많은 것들을 효율적으로 관리 할 수 있다고 판단해 상태를 중앙에서 변수를 관리하자는 이야기가 나왔다.</p>
<p>이런 회의를 거쳐서 여러 가지 상태관리 라이브러리가 후보군에 올라오게 되었다.</p>
<p><img src="/content/240222/3.png" alt="3.png" loading="lazy" decoding="async"></p>
<blockquote>
<p>recoil, redux, zustand, jotai 등등..</p>
</blockquote>
<p> </p>
<h3 id="top-bottom-vs-bottom-top"><a class="anchor" href="#top-bottom-vs-bottom-top">Top-Bottom vs Bottom-Top</a></h3>
<p><img src="/content/240222/4.jpeg" alt="4.jpeg" loading="lazy" decoding="async"></p>
<blockquote>
<p>Top-bottom 방식 : 단일 중앙 스토어를 사용하여 모든 컴포넌트의 상태를 관리</p>
<p>Bottom-top 방식 : 각 컴포넌트가 자체 상태를 관리하고 컴포넌트는 자식 컴포넌트에 상태를 전달하고 필요에 따라 상태를 업데이트</p>
</blockquote>
<p>회사에서 추구하는 상태관리 방식은 <strong>top-bottom</strong> 이였다. 선택한 이유는 아래와 같다 👇</p>
<ul>
<li>중앙 스토어를 통해 데이터 흐름을 추적을 용이</li>
<li>모든 컴포넌트는 중앙 스토어에 동일한 데이터에 접근하기 때문에 데이터 불일치 문제 해결 가능</li>
<li>여러 컴포넌트에서 재사용하기 쉬움</li>
<li>코드의 모듈화와 유지 관리를 용이</li>
<li>중앙 스토어를 이용해 확장, 병렬이 가능</li>
<li>Redux 와 같은 라이브러리는 강력한 개발자 도구가 있어서 상태 변경 추적, 디버깅에 효율적</li>
</ul>
<p>top-bottom을 선택하는데 반대의 의견도 있었다. 러닝 커브가 높고 구조가 상당히 복잡할 수도 있다. 또한 많은 양의 보일러 플레이트 코드를 작성해야 되고 유연성이 부족할 수 있다.</p>
<p><strong>하지만 이미 구조화된 프로젝트에 중앙 스토어적인 라이브를 선택하는 것이 더 효율적이라고 판단해 top-bottom 구조를 선택하게 되었다</strong></p>
<p><del>대부분 사람이 상태관리 라이브러리를 사용해봐서 러닝 커브는 문제가 되지않는다는 판단을..</del></p>
<p>그러므로 Bottom-Top 구조인 recoil 이랑 jotai는 탈락</p>
<p> </p>
<h3 id="redux-와-zustand-중-선택-과정"><a class="anchor" href="#redux-와-zustand-중-선택-과정">redux 와 zustand 중 선택 과정</a></h3>
<p>redux 와 zustand 를 가지고 아래와 같이 토론하게 되었다. Top-Bottom 라이브러리에서는 어떤 것을 고르면 좋을까?</p>
<p>먼저 기준표를 작성하고 그것에 대해 점수를 부여하게 되었는데 대략 아래와 같다 👇</p>
<ul>
<li>러닝 커브가 있어도 되지만 도입이 시급하다 => redux의 러닝 커브는 높은 편이라서 <strong>zustand 선택</strong></li>
<li>보일러 플레이트 성향이 낮아야 된다. => redux에서 보일러 플레이트 성향이 높은 편이라서 유연성 떨어진다고 판단해서 <strong>zustand 선택</strong></li>
<li>구조적으로 컴포넌트에서 상태를 관리하는 성향의 라이브러리 찾아야 된다. => <strong>zustand 선택</strong></li>
<li>사용자가 많고 github release가 잘 이루어져야 된다. => 사용자의 수와 Npm trends에서 높은 점수를 받는 Redux 이지만 선점효과라 판단해서 <strong>zustand 선택</strong></li>
<li>렌더링을 관리하고 줄일 수 있어야 한다. => 판단 기준에 미흡, 별 차이 없음</li>
</ul>
<blockquote>
<p>🙋‍♂️ 잠깐! 공식 홈페이지에서도 zustand와 redux는 개념적으로 상당히 유사하며, 변경 불가능한 상태 모델(immutable state model)을 기반으로 하고있지만 redux는 래핑 방법이 context providers로 된다는 점에서 차이가 보인다고 기록되어있다</p>
</blockquote>
<p>이런 이유들 때문에 내가 맡고 있는 회사 프로젝트에서는 <strong>zustand</strong> 을 선택하기로 결정되었다!</p>
<p><img src="/content/240222/5.jpeg" alt="5.jpeg" loading="lazy" decoding="async"></p>
<p> </p>
<h2 id="zustand-그래서-넌-누군데"><a class="anchor" href="#zustand-그래서-넌-누군데">zustand 그래서 넌 누군데??</a></h2>
<p><a href="https://zustand-demo.pmnd.rs/">공식 홈페이지</a> / <a href="https://github.com/pmndrs/zustand?tab=readme-ov-file">Github</a> / <a href="https://www.npmjs.com/package/zustand">Npm</a></p>
<ul>
<li>간결한 플럭스(Flux) 원칙을 바탕으로 작고 빠르게 확장 가능한 상태 관리 라이브러리</li>
<li>특정 라이브러리에 종속되어 만들어진 도구가 아니여서 Vanilla Javascript 에서도 사용 가능</li>
<li>zustand는 발행/구독 모델을 기반으로 만들어졌다. 스토어의 상태 변경이 일어날 때 실행할 리스너 함수를 모아 두었다가, 상태가 변경되었을 때 등록된 리스너에게 상태가 변경되었다고 알려준다.</li>
<li>스토어를 생성하는 함수 호출 시 클로저를 사용해서 상태를 변경, 구독, 조회하는 인터페이스를 통해서만 상태를 다루고, 실제 상태는 생명 주기에 따라 처음부터 끝까지 의도하지 않는 변경에 대해 막을 수 있다.</li>
<li>보일러 플레이트가 최소화된 상태관리 solution. 스토어 형태임에도 굉장히 간단하게 상태관리 구성이 가능하다.</li>
<li>익히기가 굉장히 쉽고 그만큼 공식문서 정리도 잘 되어 있다.</li>
</ul>
<hr/>
<blockquote>
<h2>💡 더 알아보자!</h2>
&nbsp;
<h4>Provider로 감쌀 필요가 없는 이유</h4 >
<p>각 컴포넌트가 자체 상태를 캡슐화하도록 설계되어있음.</p>
<p>트리 구조를 기반으로 상태를 관리하기 때문</p>
<p>useSelector hook을 제공하여 컴포넌트가 필요한 상태만 선택적으로 구독할 수 있도록 하기 때문</p>
<h4 >zustand가 context 보다 리렌더링이 줄어드는 이유</h4 >
<p>zustand는 컴포넌트가 구독한 상태가 변경되었는지 자동으로 감지</p>
<p>memoization을 사용해 컴포넌트가 리렌더링될 때마다 컴포넌트 재실행을 방지</p>
<p>shallowEqual 비교를 사용해 상태 변경이 실제로 발생했는지 여부를 확인</p>
<p>결과적으로 50%이상의 리렌더링을 줄이게 되었고, 페이지 로딩 속도 20% 향상, 사용자 인터페이스 반응 속도 향상에 10%를 기여하게 될 수 있다.</p>
</blockquote>
<p> </p>
<h2 id="어떻게-쓰는지-알아보자"><a class="anchor" href="#어떻게-쓰는지-알아보자">어떻게 쓰는지 알아보자!!</a></h2>
<p>zustand의 사용법은 간단하다. 스토어를 만들고 원시 타입. 객체, 함수를 넣으면 된다. 공식문서에서도 zustand를 사용하는데 41줄의 예제코드만 다루고 있다. 한번 시작해보자!!</p>
<h3 id="기본-문법"><a class="anchor" href="#기본-문법">기본 문법</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> useDataFilterState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> create</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">DataFilterProps</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  dataId: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"all"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  setDataId</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">dataId</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ dataId }),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  dataList: [],</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  loadDataList</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">async</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">dataParameter</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> responseData</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> fakeDataAPI.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(dataParameter);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (responseData.res </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 200</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ dataList: [] });</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ dataList: responseData.data });</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> responseData.data;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { useDataFilterState };</span></span></code></pre></figure>
<p>위 코드는 zustand 공식 문서에서 기록된 사용법을 보고 작성한 코드다.</p>
<p><code>useDataFilterState</code> 라는 객체 안에 state로 사용할 키와 값을 담아주고, create 함수로 묶어준다. parameter로 넘겨주는 set을 이용해서 state 내부에 있는 값을 변경해줄 수 있고, get 을 이용해서 state 내부에 직접 접근해서 값을 조회하고 변경할 수 있다. 그리고 액션도 store의 키 값 형태로 담기며, sync action도 포함 가능하다.</p>
<p> </p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">dataId</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useDataFilterState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  dataId: state.dataId,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">dataId</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useDataFilterState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(({ </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">dataId</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  dataId,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">dataId</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> useDataFilterState.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">().dataId;</span></span></code></pre></figure>
<p>위 코드로 사용할 컴포넌트에 <code>useDataFilterState</code>을 import 한 후에 dataId 를 가져와서 사용할 수 있다.</p>
<p>상태 값을 가져와서 사용하는 방법에는 다양한 방법이 있는데 위에 보여주는 3가지 문법 모두 사용 가능 하지만, <code>setState</code>와 <code>getState</code>를 분리하는 3번째 방법으로 통일하거나, 2번째 방법을 사용하는 것이 좋지 않을까 싶다!!</p>
<p> </p>
<h3 id="immer--persist"><a class="anchor" href="#immer--persist">Immer &#x26; Persist</a></h3>
<p>사실 사용법은 저게 전부다 ㅋㅋ</p>
<p><img src="/content/240222/7.png" alt="7.png" loading="lazy" decoding="async"></p>
<p>하지만 깊게 들어가면 무진장~~~ 많다 자체에서 지원가능한 미들웨어부터.. 시작해서 라이브러리까지!</p>
<p>일단 Immer 이랑 Persist에 대해서 먼저 알아보자</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> useDataFilterState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> create</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">DataFilterProps</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>(</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  persist</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      dataId: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"all"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setDataId</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">dataId</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">:</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> string</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">        set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          produce</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            state.dataId </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> dataId;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">          }),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        ),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      dataList: [],</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      loadDataList</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">async</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">dataParameter</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> responseData</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> fakeDataAPI.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(dataParameter);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (responseData.res </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 200</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">            produce</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">              state.dataList </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            }),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">          );</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">          return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">          set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">            produce</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">              state.dataList </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> responseData.data;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">            }),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">          );</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">          return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> responseData.data;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      name: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"여기에 로컬스토리지 key name이 들어갑니다"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      storage: </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">createJSONStorage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> sessionStorage),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  ),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span></code></pre></figure>
<h4>1. Immer</h4>
<blockquote>
<p>zustand middleware Immer 와 npm에서 Immer 라이브러리 둘다 사용 가능</p>
</blockquote>
<ul>
<li>불변성을 유지하는 데 사용함.</li>
<li>Immer는 변경하려는 상태를 수정하는 대신 수정하려는 상태의 "draft"를 작성할 수 있고 이 draft는 불변성이 보장된 상태이며, Immer가 이 변경 사항을 처리하여 최종적으로 새로운 불변 상태를 생성한다.</li>
<li>produce 함수를 사용하여 immer 미들웨어를 추가하여 함수 안에서 상태를 직접 수정할 수 있도록 만들 수 있다.</li>
</ul>
<h4>2. Persist</h4>
<ul>
<li>상태를 로컬 스토리지에 저장하고 복원하는 데 사용</li>
<li>persist 함수는 두 번째 매개변수로 name, storage을 받는데 name을 사용해 로컬 스토리지에 저장할 때 사용할 키로 지정, 랜더링 되면 상태값이 복원된다. 그리고 storage는 기본값으로 localStorage이고 원하는 storage에 저장할 수 있다.</li>
<li>사용자가 페이지를 새로 고칠 때도 상태가 유지된다 <del>(일반적으로 브라우저의 스토리지를 사용하여 상태를 저장하고 복원)</del></li>
</ul>
<p> </p>
<h3 id="subscribewithselector"><a class="anchor" href="#subscribewithselector">subscribeWithSelector</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> useStore</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> create</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">&#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">TodoState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>()(</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">	subscribeWithSelector</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    todo: {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      todoItems: [],</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      loading: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      error: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">false</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">	)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">	);</span></span>
<span data-line=""> </span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useEffect</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  useStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">subscribe</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> state.todo.todoItems,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">todoItems</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(todoItems.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">length</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    useStore.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">destroy</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, []);</span></span></code></pre></figure>
<p>todoItems가 변경될 경우 subscribe 2번째 인자의 callback 함수 호출하게 된다. 상태관리를 구독하게 되어 상태값이 변경될 경우 호출될 수 있도록 처리가 가능하다. (modx의 autorun/computed와 동일한 기능)</p>
<p> </p>
<h3 id="reset-state"><a class="anchor" href="#reset-state">reset state</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> initialState</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  salmon: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  tuna: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> useSlice</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> create</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  ...</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">initialState,</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  addSalmon</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">qty</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ salmon: </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">().salmon </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> qty });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  addTuna</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">qty</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ tuna: </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">get</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">().tuna </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> qty });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  reset</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    set</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(initialState);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}));</span></span></code></pre></figure>
<p>위 코드를 살펴보면 초기값으로 <code>initialState</code>를 넣는 것이 아닌 <code>...initialState</code>을 넣어준다. 그냥 <code>initialState</code>를 넣어주게 되면 업데이트 할 때 해당 속성들의 값을 덮어쓸 수도 있기때문에 스프레드 연산자를 사용한다.</p>
<blockquote>
<p>사용하면서 느낀 부분인데 set 함수를 직접적으로 사용해서 <code>useSlice.setState().initialState</code> 방식을 사용하게 되면 코드 값에 직접적으로 접근해 동작이 원하는대로 되지 않아서 자체적으로 getState 또는 state를 처음 많들어줄 때 얕은복사를 통해 값을 복사해서 사용하고 있다.</p>
</blockquote>
<p> </p>
<h3 id="useshallow를-이용해-rerender-방지하기"><a class="anchor" href="#useshallow를-이용해-rerender-방지하기">useShallow를 이용해 reRender 방지하기</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { create } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'zustand'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> useMeals</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> create</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  papaBear: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'large porridge-pot'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  mamaBear: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'middle-size porridge pot'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  littleBear: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'A little, small, wee pot'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> BearNames</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> names</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useMeals</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Object.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">keys</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(state));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>{names.join(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">', '</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)}&#x3C;/div>;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">useMeals.setState({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  papaBear: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'a large pizza'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span></code></pre></figure>
<p>위 코드를 실행하게 되었을 때 selector는 출력이 변경되면 rerender을 발생시킨다.
실제로 names의 출력이 shallow equality에 따라 변경되지 않았더라도 BearNames가 다시 렌더링되게 된다. 이것을 <code>useShallow</code>을 사용하게 되면</p>
<blockquote>
<p>🙋‍♂️ 잠깐! shallow equality 이란 두 객체가 같은 메모리 위치를 참조하는지 비교하는 것을 의미한다!</p>
</blockquote>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { create } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'zustand'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { useShallow } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'zustand/react/shallow'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> useMeals</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> create</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  papaBear: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'large porridge-pot'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  mamaBear: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'middle-size porridge pot'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  littleBear: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'A little, small, wee pot'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> BearNames</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> names</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useMeals</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useShallow</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Object.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">keys</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(state)));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">div</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>{names.join(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">', '</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)}&#x3C;/div>;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre></figure>
<p>이렇게 바꾸게 되면 불필요한 rerender을 방지할 수 있다.</p>
<p> </p>
<h2 id="그래서-6개월-정도-써본-소감은"><a class="anchor" href="#그래서-6개월-정도-써본-소감은">그래서 6개월 정도 써본 소감은??</a></h2>
<p>일단 나는 recoil, jotai 그리고 이번에 zustand 까지 3가지 라이브러리를 사용해 보았다.</p>
<p>사실 Top-Bottom 방식이 낯설었지만, 러닝 커브도 짧은 편이고, ~새로운 FE가 회사에 왔는데 예시 코드를 통해 배우는 속도가 빠르다. 뭐 사람마다 다르겠지만, 러닝 커브 짧다고 생각~</p>
<p>몇 가지 좋은점을 생각해보았을 때 상태 내부에 접근하는 방식도 쉽고, 라이브러리 지원, 가독성 그리고 중앙 집중 방식이어서 이미 진행되고 있는 프로젝트에 투입하는데 부담이 적었다는 점에서 좋은 점수를 주고 싶다!</p>
<p>~한글 버전도 만들어주면 좋겠다.~</p>
<p>사용을 처음 해본 이후 docs를 많이 찾아보지 못했는데, 그사이에 버전 5의 알파버전이 나와버렸다.. 계속해서 발전하고, 수정되는 것은 큰 장점이지 않을까 싶다!</p>
<p><img src="/content/240222/8.png" alt="8.png" loading="lazy" decoding="async"></p>
<p>공식문서에서 <a href="https://docs.pmnd.rs/zustand/integrations/third-party-libraries">Third-party Libraries</a>에 대한 정보가 있는데, 확장적인 부분에서 사용하기 좋다고 생각한다. 그리고 <a href="https://docs.pmnd.rs/zustand/integrations/persisting-store-data">여기 링크도 참고해보면 좋을 것 같다</a></p>
<p>그리고 블로그를 작성하고, zustand를 공부하면서 내부동작에 대해서 심오깊게 들어가보지 못했는데, <a href="https://www.nextree.io/zustand/">nextree-zustand</a>을 보면서 많이 배웠다. 다른분들도 많이 보면 좋을 것 같다!!</p>
<p> </p>
<p><img src="/content/240222/9.png" alt="9.png" loading="lazy" decoding="async"></p>
<blockquote>
<h4>출처 및 도움되는 링크들</h4>
<p><a href="https://github.com/pmndrs/zustand">https://github.com/pmndrs/zustand</a></p>
<p><a href="https://zustand-demo.pmnd.rs/">https://zustand-demo.pmnd.rs/</a></p>
<p><a href="https://www.nextree.io/zustand/">https://www.nextree.io/zustand/</a></p>
</blockquote>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
        </item>
        <item>
            <title><![CDATA[React 19의 새로운 Hooks 완벽 가이드를 읽고 변경될 React를 맞이해보자]]></title>
            <link>https://hooninedev.com/240216</link>
            <guid isPermaLink="false">https://hooninedev.com/240216</guid>
            <pubDate>Fri, 16 Feb 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[&nbsp; React 19에 도입될 Hooks &nbsp; use(Promise) & use(Context) Form actions useFormState useFormStatus useOptimistic Bonus: Async transitions 마무리 잠깐!!! 모든 코드는 테스트 환경 에서 테스트 가능합니다. (단, React 19에 포함될 예정이...]]></description>
            <content:encoded><![CDATA[<p><img src="/content/240216/1.png" alt="react-hooks" loading="lazy" decoding="async"></p>
<p> </p>
<h1 id="react-19에-도입될-hooks"><a class="anchor" href="#react-19에-도입될-hooks">React 19에 도입될 Hooks</a></h1>
<p> </p>
<ul>
<li><a href="#usepromise--usecontext">use(Promise) &#x26; use(Context)</a></li>
<li><a href="#form-actions">Form actions</a></li>
<li><a href="#useformstate">useFormState</a></li>
<li><a href="#useformstatus">useFormStatus</a></li>
<li><a href="#useoptimistic">useOptimistic</a></li>
<li><a href="#bonus-async-transitions">Bonus: Async transitions</a></li>
<li><a href="#%EB%A7%88%EB%AC%B4%EB%A6%AC">마무리</a></li>
</ul>
<blockquote>
<p>✨ 잠깐!!! <span> 모든 코드는 <a href="https://react.dev/community/versioning-policy#canary-channel">테스트 환경</a></span> 에서 테스트 가능합니다. (단, React 19에 포함될 예정이지만 최종 release 전에 API가 변경될 수 있음)</p>
</blockquote>
<p> </p>
<h2 id="usepromise--usecontext"><a class="anchor" href="#usepromise--usecontext">use(Promise) &#x26; use(Context)</a></h2>
<p> </p>
<h3 id="reference"><a class="anchor" href="#reference">Reference</a></h3>
<ul>
<li>컴포터는의 호출을 사용하여 Promise 또는 Context 와 같은 리소스의 값을 읽음</li>
<li>use는 if문과 같은 조건문 루프 안에서 호출 가능</li>
<li>use를 호출하는 함수는 컴포넌트 또는 Hook 이어야 함</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { use } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'react'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MessageComponent</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">messagePromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> message</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> use</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(messagePromise);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> theme</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> use</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(ThemeContext);</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // ...</span></span></code></pre></figure>
<p>Promise와 함께 호출될 때, use는 Suspense 및 Error Boundaries와 통합된다.</p>
<p>use가 pending 상태일 때 Promise가 전달완료 될 때까지 일시중단되고, use를 호출하는 컴포넌트가 Suspense 경계로 래핑된 경우 대체 내용이 표시된다.</p>
<p>Promise가 해결되면 Suspense 대체 내용이 use에서 반환한 데이터를 사용하여 렌더링된 컴포넌트로 대체되고 거부되면 가장 가까운 Error Boundaries의 대체 내용이 표시된다.</p>
<p> </p>
<blockquote>
<h3>✋ 그럼 client에서 데이터를 가져올 때 third-party library가 필요 없을까?</h3>
</blockquote>
<blockquote>
<p>TanStack Query 는 단순히 Promise 를 해결하는 것 이상의 기능을 수행하므로 아직은 지켜봐야 된다고 생각함!</p>
<p>하지만 올바른 방향으로 나아가는 훌륭한 단계라고 생각하며, REST 또는 GraphQL API를 기반으로 단일 페이지 앱을 더 쉽게 구축할 수 있게 해줄 거라고 생각함</p>
</blockquote>
<p> </p>
<h3 id="use-문법"><a class="anchor" href="#use-문법">use 문법</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> use</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(resource);</span></span></code></pre></figure>
<h4>1. 매개변수</h4>
<p>resource : Promise, Context 형태의 데이터의 소스가 들어감</p>
<h4>2. 함수 호출 출력 값</h4>
<p>use Hook 을 사용하면 Promise 의 return 값이나 Context 의 return 값을 반환</p>
<p> </p>
<h3 id="use-사용해보기"><a class="anchor" href="#use-사용해보기">use 사용해보기</a></h3>
<p><span style="color: gray"><a href="https://codesandbox.io/p/sandbox/silly-dijkstra-68ryxj?file=/src/App.js">context로 use를 사용하는 예시코드</a></span></p>
<p>context가 use로 전달되면 useContext와 유사하게 작동된다. 단, useContext는 컴포넌트의 최상위 수준에서 호출해야 하지만, use는 if와 같은 조건문과 for와 같은 루프 내부에서 호출할 수 있고 사용이 더 유연하기 때문에 useContext보다 선호됨</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { use } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'react'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Button</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> theme</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> use</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(ThemeContext);</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// ...</span></span></code></pre></figure>
<p>Context value를 결정하기 위해 React는 Component tree를 검색하고 특정 Context의 위에서부터 가장 가까운 Context Provider를 찾음</p>
<p>버튼에 Context 전달하려면 버튼 또는 그 부모 컴포넌트 중 하나를 해당 Context Provider 로 래핑해야됨</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> MyPage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    &#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">ThemeContext.Provider value</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'dark'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      &#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Form </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    &#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">ThemeContext.Provider</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Form</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // ... renders buttons inside ...</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>Provider 와 버튼 사이에 얼마나 많은 컴포넌트 레이어가 있는지는 중요하지 않다. Form 내부의 버튼이 use(ThemeContext)를 호출하면 <strong>dark</strong>를 값으로 받음</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> HorizontalRule</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">show</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (show) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> theme</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> use</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(ThemeContext);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">hr</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> className</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">={</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">theme</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">} />;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> false</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p> </p>
<h3 id="use-사용시-주의점"><a class="anchor" href="#use-사용시-주의점">use 사용시 주의점</a></h3>
<ul>
<li>
<p>use Hook 은 Component 또는 Hook 안에서 호출해야 됨</p>
</li>
<li>
<p>Server Component 에서 데이터를 가져올 때는 use 보다 async 와 await 사용해야 됨</p>
</li>
<li>
<p>React 팀에서는 Client Component에서 Promise를 생성하는 것보다 Server Component에서 Promise를 생성하고 이를 Client Component로 전달하는 것을 선호함</p>
</li>
<li>
<p>Client Component에서 생성된 Promise는 렌더링할 때마다 재생성됨</p>
</li>
<li>
<p>Server Component에서 Client Component로 전달된 Promise는 다시 렌더링할 때 안정적임</p>
</li>
<li>
<p>useContext와 마찬가지로 use(context)는 항상 이를 호출하는 컴포넌트 위에서 가장 가까운 Context Provider 을 찾음 => 위쪽으로 검색하며 use를 호출하는 컴포넌트 내의 Context Provider는 고려하지 않음</p>
</li>
</ul>
<p> </p>
<h3 id="server--client로-데이터-스트리밍"><a class="anchor" href="#server--client로-데이터-스트리밍">Server => Client로 데이터 스트리밍</a></h3>
<p><span style="color: gray"><a href="https://codesandbox.io/p/sandbox/happy-shadow-8dg646">Server => Client로 데이터 스트리밍하는 예시코드</a></span></p>
<p>Server Component에서 Client Component로 Promise를 props로 전달하여 Server에서 Client로 데이터를 스트리밍할 수 있음</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { fetchMessage } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> './lib.js'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { Message } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> './message.js'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> default</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> App</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> messagePromise</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> fetchMessage</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    &#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Suspense fallback</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{&#x3C;p>waiting for message</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">p</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      &#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Message messagePromise</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{messagePromise} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    &#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Suspense</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>그런 다음 Client Component는 받은 Promise를 props로 가져와서 use에 전달, 이렇게 하면 Client Component는 Server Component가 처음에 생성한 Promise에서 값을 읽을 수 있음</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// message.js</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'use client'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { use } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'react'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Message</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({ </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">messagePromise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> }) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> messageContent</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> use</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(messagePromise);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">p</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>Here is the message: {messageContent}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">p</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>Message는 Suspense로 래핑되어 있으므로, Promise가 해결될 때까지 대체내용이 표시되고 Promise가 해결되면 Hook을 사용하여 값을 읽고 메시지 컴포넌트가 Suspense를 대체함.</p>
<p> </p>
<blockquote>
<h3>📝 Note</h3>
<p>Server Component에서 Client Component로 Promise를 전달할 때, Server와 Client 간에 전달하려면 값이 <a href="https://developer.mozilla.org/ko/docs/Glossary/Serializable_object">직렬화</a>가 가능해야 함</p>
<p>함수와 같은 데이터 유형은 직렬화할 수 없으며 이러한 것은 Promise의 값이 될 수 없다.</p>
</blockquote>
<p> </p>
<blockquote>
<h3>🤔 Server 또는 Client Component 에서 Promise를 해결해야 하나요? </h3>
<p>Promise는 Server Component에서 Client Component로 전달할 수 있고, Client Component에서 Hook을 사용하여 확인할 수 있고, Server Component에서 await을 사용하여 Promise를 확인하고 필요한 데이터를 Client Component에 프로퍼티로 전달할 수도 있다.</p>
<p>하지만 Server Component에서 await을 사용하면 await 문이 완료될 때까지 렌더링이 차단된다. 하지만 Server Component에서 Client Component로 Promise를 전달하면 Promise가 Server Component의 렌더링을 차단하지 못하게 됨</p>
</blockquote>
<p> </p>
<h3 id="거부된-promise-처리하기"><a class="anchor" href="#거부된-promise-처리하기">거부된 Promise 처리하기</a></h3>
<h4 style="color:red">🚨 주의할 점 🚨</h4>
<div style="opacity:0.5; margin-bottom:30px">
<p>use Hook 은 try-catch 로 호출할 수 없어서 아래 2가지 방법을 사용해야 됨.</p>
<p>사용하게 되면 <strong>“Suspense Exception: This is not a real error!”</strong> 에러가 발생함</p>
</div>
<div style="margin-bottom:40px">
<h4>1. Error Boundary 가 있는 사용자에게 오류를 표시</h4>
<p><span style="color: gray"><a href="https://codesandbox.io/p/sandbox/funny-cherry-dzl7jd?file=/src/message.js">예시코드</a></span></p>
<p>Promise가 거부될 때 사용자에게 오류 표시를 Error Boundary 를 사용하면 되고, 사용하려면 use을 호출하는 컴포넌트를 Error Boundary로 감싸면 된다.</p>
<p>그러면 사용하도록 전달된 Promise가 거부될 때 Error Boundary에 대한 대체내용이 표시된다.</p>
</div>
<h4>2. Promise.catch 를 사용하여 대체 값 제공</h4>
<p>Promise의 catch 메서드를 사용하려면 Promise 객체에서 catch를 호출 한 후, catch를 통해 오류 메시지를 인자로 받는 함수인 단일 인수를 받음.</p>
<p>catch에 전달된 함수가 반환하는 값은 무엇이든 Promise의 확인된 값으로 사용</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { Message } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> './message.js'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> default</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> App</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> messagePromise</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'no new message found.'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    &#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Suspense fallback</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{&#x3C;p>waiting for message</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">p</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      &#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Message messagePromise</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{messagePromise} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    &#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Suspense</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  );</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p> </p>
<h2 id="form-actions"><a class="anchor" href="#form-actions">Form actions</a></h2>
<p>Form actions을 사용하면 <code>&#x3C;form></code>요소의 <code>action</code> 속성에 함수를 전달할 수 있고, 폼이 제출될 때 React가 이 함수를 호출함.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">form action</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{handleSubmit} </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/></span></span></code></pre></figure>
<p>React 18에서 <code>&#x3C;form action></code> 프로퍼티를 추가하면 아래 경고가 표시됨</p>
<blockquote>
<p>Warning: Invalid value for prop <code>action</code> on <code>&#x3C;form></code> tag. Either remove it from the element or pass a string or number value to keep it in the DOM.</p>
</blockquote>
<p> </p>
<h3 id="form-actions-사용해보기"><a class="anchor" href="#form-actions-사용해보기">Form actions 사용해보기</a></h3>
<p><span style="color:gray"><a href="https://stackblitz-starters-j6yogy.stackblitz.io">Form action 예시코드</a></span></p>
<p>addToCart 함수는 Server action이 아니고, 클라이언트 측에서 호출되는 비동기 함수다.
이렇게 하면 검색 양식과 같이 React에서 AJAX 양식 처리를 크게 간소화할 수 있지만, 이것만으로는 Form 제출(유효성 검사, 부작용 등)을 처리하는 것 이상의 기능을 하는 <span style="color:gray"><a href="https://react-hook-form.com">React Hook Form</a></span>과 같은 서드파티 라이브러리를 제거하기에 충분하지 않다.</p>
<blockquote>
<p>위 예시에서 Form 제출 중 Form 제출 버튼이 비활성화되지 않는 것, Confirm 메시지 누락, Cart 업데이트 지연 등 오류를 발견할 수 있지만, react 19 와 추후 업데이트에서 hook이 추가될 예정이라고 함.</p>
</blockquote>
<p> </p>
<h3 id="reference-1"><a class="anchor" href="#reference-1">Reference</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">form action</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{search}</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  &#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">input name</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'query'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> /></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  &#x3C;</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">button type</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'submit'</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Search</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">button</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">&#x3C;/</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">form</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">></span></span></code></pre></figure>
<h4>1. Props</h4>
<p><code>&#x3C;form></code> 은 모든 <span><a href="https://react.dev/reference/react-dom/components/common#props">common element props</a></span>을 지원함</p>
<p><code>action</code>: URL 또는 함수이다. URL이 <code>action</code>으로 전달되면 폼은 HTML 폼 구성 요소처럼 동작. 함수가 <code>action</code>으로 전달되면 해당 함수가 폼 제출을 처리한다. <code>action</code>으로 전달된 함수는 async일 수 있으며 제출된 폼의 폼 데이터가 포함된 단일 인수로 호출된다. <code>&#x3C;button></code> , <code>&#x3C;input type="submit"></code> 또는 <code>&#x3C;input type="image"></code> 컴포넌트의 <code>formAction</code> 속성으로 <code>action</code> prop을 재정의할 수 있습니다.</p>
<h4>2. 주의사항 </h4>
<p>함수가 <code>action</code> 또는 <code>formAction</code>으로 전달되면 메서드 속성의 값과 관계없이 HTTP 메서드는 POST로 설정됩니다.</p>
<p> </p>
<h2 id="useformstate"><a class="anchor" href="#useformstate">useFormState</a></h2>
<p><span style="color: gray"><a href="https://codesandbox.io/p/sandbox/damp-tree-yv2vzy?file=/src/App.js">예시코드</a></span></p>
<p>useFormState Hook은 비동기 동작 기능을 지원하기 위해 만들어졌고, useFormState를 호출하여 마지막으로 제출된 <code>&#x3C;form></code>의 동작 결과에 접근할 수 있다.</p>
<p>컴포넌트의 최상위 수준에서 useFormState를 호출하여 <code>&#x3C;form></code> 동작이 호출될 때 업데이트되는 컴포넌트 상태를 생성해 useFormState에 기존의 <code>&#x3C;form></code> 동작 함수와 초기 상태를 전달하고, 이는 새로운 동작을 반환
이 동작은 <code>&#x3C;form></code> 내에서 사용되며, 최신 <code>&#x3C;form></code> 상태도 함께 전달됩니다. 전달한 함수에도 최신 <code>&#x3C;form></code> 상태가 전달됩니다.</p>
<h4 style="color:red">🚨 주의할 점 🚨</h4>
<div style="opacity:0.5; margin-bottom:30px">
<p>useFormState는 반드시 react가 아닌 react-dom에서 import를 해야된다.</p>
</div>
<p> </p>
<h3 id="useformstate-문법"><a class="anchor" href="#useformstate-문법">useFormState 문법</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">formAction</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useFormState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(fn, initialState, permalink</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">?</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span></code></pre></figure>
<h4>매개변수</h4>
<p><strong>fn</strong> : <code>form</code>이 제출되거나 버튼이 눌렸을 때 호출할 함수이고 호출될 때 초기 인수로 <code>form</code> 이전 상태(처음에는 전달한 initialState, 이후에는 이전 반환 값)를 받은 후 <code>form</code> 동작이 받는 인수를 받음</p>
<p><strong>initialState</strong> : 초기 상태로 설정하려는 값이고 어떤 직렬화 가능한 값이든 될 수 있다. 인수는 동작이 처음 호출된 후에는 무시됨.</p>
<p><strong>optional permalink</strong> : <code>form</code> 수정하는 고유한 페이지 URL을 포함하는 문자열. 동적 콘텐츠가 있는 페이지에서 사용되며, 점진적 개선과 함께 사용된다. fn이 서버 동작이고 JavaScript 번들이 로드되기 전에 양식이 제출된 경우, 브라우저는 현재 페이지 URL 대신 지정된 permalink URL로 이동함. 상태를 전달하는 방법을 React가 알 수 있도록 동일한 Form Component가 대상 페이지에서 렌더링되도록 해야됨. Form 이 hydrated 된 후에는 이 매개변수가 영향을 미치지 않음.</p>
<h4>함수 호출 출력 값</h4>
<p>두 개의 값을 포함하는 배열을 반환</p>
<ul>
<li>첫 번째 렌더링에서는 전달한 initialState이 되고 호출된 후에는 동작이 반환한 값이 된다.</li>
<li><code>form</code> 컴포넌트의 action prop으로 전달하거나 <code>form</code> 내의 버튼 컴포넌트의 formAction prop으로 전달할 수 있는 새로운 동작이 반환됨</li>
</ul>
<p> </p>
<h3 id="useformstate-사용시-주의점"><a class="anchor" href="#useformstate-사용시-주의점">useFormState 사용시 주의점</a></h3>
<p>React 서버 컴포넌트를 지원하는 프레임워크와 함께 사용할 때, useFormState를 사용하면 JavaScript가 클라이언트에서 실행되기 전에도 양식을 인터랙티브하게 만들 수 있고, 서버 컴포넌트 없이 사용할 때는 컴포넌트의 로컬 상태와 동등함.</p>
<blockquote>
<p>✋ 자세한 hook 사용을 확인할려면 <a href="https://react.dev/reference/react-dom/hooks/useFormState">useFormState()</a>을 참조해주세요!</p>
</blockquote>
<p> </p>
<h2 id="useformstatus"><a class="anchor" href="#useformstatus">useFormStatus</a></h2>
<p><span style="color: gray"><a href="https://codesandbox.io/p/sandbox/serene-satoshi-2qyqy4?file=/src/App.js">예시코드</a></span></p>
<p>Form submit 중에 pending 상태를 표시할 때 렌더링된 컴포넌트에서 useFormStatus Hook을 호출하고 반환된 pending 속성을 읽을 수 있음</p>
<p> </p>
<h3 id="useformstatus-문법"><a class="anchor" href="#useformstatus-문법">useFormStatus 문법</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">pending</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">method</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">action</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useFormStatus</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span></code></pre></figure>
<p><strong>pending</strong> : boolean 값. true이면 부모 <code>&#x3C;form></code>이 submit을 pending 중 인것으로 나타냄.</p>
<p><strong>data</strong> : FormData 인터페이스를 구현하는 객체로, 부모 <code>&#x3C;form></code>이 제출하는 데이터를 포함합니다. submit 할 data가 없거나 부모 <code>&#x3C;form></code> 이 없는 경우 null</p>
<p><strong>method</strong> : <code>get</code> 또는 <code>post</code> 중 하나의 문자열 값. 부모 <code>&#x3C;form></code>이 GET 또는 POST HTTP 메서드로 제출되는지 여부를 나타냄. 기본적으로 GET 메서드를 사용하며 method 속성으로 지정할 수 있다.</p>
<p><strong>action</strong> : 부모 <code>&#x3C;form></code>의 action prop에 전달된 함수에 대한 참조. 부모 <code>&#x3C;form></code>이 없는 경우 속성은 null. action prop에 URI 값이 제공되거나 action prop이 지정되지 않은 경우, status.action은 null</p>
<p> </p>
<h3 id="useformstatus-사용시-주의점"><a class="anchor" href="#useformstatus-사용시-주의점">useFormStatus 사용시 주의점</a></h3>
<p>useFormStatus hook은 <code>&#x3C;form></code> 요소 내에서 렌더링된 컴포넌트에서 호출되어야 한다. 이 훅은 부모 <code>&#x3C;form></code>에 대한 상태 정보만 반환하며, 동일한 컴포넌트나 하위 컴포넌트 내에서 렌더링된 <form>에 대한 상태 정보는 반환하지 않음!</p>
<blockquote>
<p>✋ 자세한 hook 사용을 확인할려면 <a href="https://react.dev/reference/react-dom/hooks/useFormStatus">useFormStatus()</a>을 참조해주세요!</p>
</blockquote>
<p> </p>
<h2 id="useoptimistic"><a class="anchor" href="#useoptimistic">useOptimistic</a></h2>
<p><span style="color: gray"><a href="https://codesandbox.io/p/sandbox/blue-wave-g9tj8k?file=/src/App.js">예시코드</a></span></p>
<p>서버의 응답을 기다리는 대신, 인터페이스는 예상된 결과와 함께 즉시 업데이트한다. 사용자가 폼에 메시지를 입력하고 "보내기" 버튼을 클릭할 때, useOptimistic Hook을 사용하면 메시지가 실제로 서버로 전송되기 전에 "전송 중..." label과 함께 화면에 즉시 나타남. 이런 Optimistic 접근 방식은 속도와 반응성으로부터 사용자에게 긍정적 영향을 주기때문에 사용하는 것이 좋아보임!</p>
<p> </p>
<h3 id="useoptimistic-문법"><a class="anchor" href="#useoptimistic-문법">useOptimistic 문법</a></h3>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">optimisticState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">addOptimistic</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useOptimistic</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(state, updateFn);</span></span></code></pre></figure>
<h4>매개변수</h4>
<ul>
<li><strong>state</strong> : 초기에 반환되는 값이며 action이 pending 중이지 않을 때마다 반환됨.</li>
<li><strong>updateFn(currentState, optimisticValue)</strong> : 현재 상태 및 addOptimistic에 전달된 Optimistic 값을 사용하여 결과적인 Optimistic 상태를 반환하는 함수이며 순수함수여야 함. updateFn은 currentState와 optimisticValue 두 개의 매개변수를 받습니다. 반환 값은 currentState와 optimisticValue의 병합된 값이 됨.</li>
</ul>
<h4>함수 호출 출력 값</h4>
<ul>
<li><strong>optimisticState</strong> : 결과적인 Optimistic 상태. action이 pending일 때 state 와 동일하고 아닐 때에는 updateFn에서 반환된 값과 같음.</li>
<li><strong>addOptimistic</strong> : Optimistic 업데이트가 있는 경우 호출할 dispatching function. optimisticValue라는 하나의 인수를 받으며, 이는 모든 유형의 값이며 updateFn을 state와 optimisticValue와 함께 호출</li>
</ul>
<blockquote>
<p>✋ 자세한 hook 사용을 확인할려면 <a href="https://react.dev/reference/react/useOptimistic#noun-labs-1201738-(2)">useOptimistic()</a>을 참조해주세요!</p>
</blockquote>
<p> </p>
<h2 id="bonus-async-transitions"><a class="anchor" href="#bonus-async-transitions">Bonus: Async Transitions</a></h2>
<p><a href="https://stackblitz-starters-qrc5hb.stackblitz.io">예시코드</a></p>
<blockquote>
<p>😂 이 기능은 아직 React 문서에 문서화되지 않았지만, 관련 <a href="https://github.com/facebook/react/pull/26621">풀 리퀘스트</a>에서 더 많은 정보를 확인할 수 있다.</p>
</blockquote>
<p>React의 Transition API는 UI를 차단하지 않고 상태를 업데이트할 수 있다. 예를 들어, startTransition 호출로 상태 변경을 감싸 사용자의 마음이 변해서 이전 상태 변경을 취소할 수 있도록 할 수 있다.</p>
<p>다음 예제는 이 Transitions API를 사용한 탭 내비게이션을 만드는 것이다. Posts를 클릭한 다음 즉시 Contact를 클릭하게 되면 Posts의 느린 렌더링이 중단된 것을 확인해 볼 수 있다. 그러게 되면 Contact 탭이 즉시 표시된다.</p>
<p>상태 업데이트가 transition이 되었기 때문에 느린 재렌더링이 사용자 인터페이스를 멈추지 않는 결과가 나오게 된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="typescript" data-theme="github-dark github-light"><code data-language="typescript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> TabContainer</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">isPending</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">startTransition</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useTransition</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">tab</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setTab</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"about"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> selectTab</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">nextTab</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // instead of setTab(nextTab), put the state change in a transition</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    startTransition</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">      setTab</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(nextTab);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // ...</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>React 18.2에서는 이미 useTransition hook이 사용 가능하다. React 19에서 startTransition에 async 함수를 전달할 후 React가 transition 시작하기 위해 기다릴 수 있는 기능이 새로 추가될 예정이라고 함.</p>
<p>이 기능은 AJAX 호출을 통해 데이터를 제출하고 transition에서 결과를 렌더링하는 데 유용해보임. transition pending 상태에서는 데이터를 submit 하는 것으로 시작된다.</p>
<p>React가 startTransition으로 래핑된 <code>&#x3C;form action></code> 핸들러를 호출하여 현재 페이지를 차단하지 않도록 해준다.</p>
<p> </p>
<h2 id="마무리"><a class="anchor" href="#마무리">마무리</a></h2>
<p><img src="/content/240216/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<p>이러한 기능들은 클라이언트 전용 React 앱(Vite로 번들링된 앱 등등)에서도 사용할 수 있습니다. 이러한 기능을 사용하기 위해 Next나 Remix와 같은 SSR 프레임워크가 필요하지는 않지만, 서버 통합형 React 앱에서도 작동함</p>
<p>이러한 기능들을 사용하면 React에서 데이터 가져오기와 양식 처리가 상당히 쉬워질 것 처럼 보이지만 hooks을 통합하여 훌륭한 사용자 경험을 만들려면 아주 복잡해보임.</p>
<blockquote>
<p>반면에 <a href="https://marmelab.com/react-admin/">react-admin</a>과 같은 프레임워크를 사용하면 낙관적 업데이트가 내장된 사용자 친화적인 양식을 사용할 수 있을 것 같음!</p>
</blockquote>
<p>좋은 기능이고 특히! Form 관련해서는 원하는 요구사항이 많은 것으로 보이는데 너무 늦게 나오지 않았는지.. 생각하게 된다! <del>뭐 그만큼 오류가 많고 많이 싸웟나보지?</del></p>
<p>위 기능들은 현재도 업데이트 되었지만 아직은 테스트 단계이므로 React 19를 조용히 기다려보자!</p>
<blockquote>
<p>17.0.0이 20년 10월 20일 이고 18.0.0이 22년 3월 29일이니깐... 뭐 곧 나오지 않을까????</p>
</blockquote>
<p> </p>
<blockquote>
<h4>출처</h4>
<p><a href="https://marmelab.com/blog/2024/01/23/react-19-new-hooks.html">https://marmelab.com/blog/2024/01/23/react-19-new-hooks.html</a></p>
<p><a href="https://react.dev/">https://react.dev/</a></p>
<p><a href="https://github.com/facebook/react/issues">https://github.com/facebook/react/issues</a></p>
</blockquote>
<h1 id=""></h1>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>React</category>
        </item>
        <item>
            <title><![CDATA[1년차 회고]]></title>
            <link>https://hooninedev.com/240101</link>
            <guid isPermaLink="false">https://hooninedev.com/240101</guid>
            <pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[2023 회고록 사실 이런 거 처음 쓴다. 어색하다. 그런데 회고록을 작성해야겠다고 생각한 이유가 있어서 작성해보려고 한다! 2022년 겨울부터 2023년 1년 전체를 생각해보면 많은 것이 변화했다. 나의 회사, 취미, 대인관계, 삶의 공간 등등.. 너무너무 많은 것들이 변해서 되짚어보고 2024년 계획을 작성해 보려고한다. 많이 솔직하게 작성할 예정이어서...]]></description>
            <content:encoded><![CDATA[<h1 id="2023-회고록"><a class="anchor" href="#2023-회고록">2023 회고록</a></h1>
<p>사실 이런 거 처음 쓴다. 어색하다.</p>
<p>그런데 회고록을 작성해야겠다고 생각한 이유가 있어서 작성해보려고 한다!</p>
<p>2022년 겨울부터 2023년 1년 전체를 생각해보면 많은 것이 변화했다.</p>
<p>나의 회사, 취미, 대인관계, 삶의 공간 등등.. 너무너무 많은 것들이 변해서 되짚어보고 2024년 계획을 작성해 보려고한다.</p>
<p>많이 솔직하게 작성할 예정이어서, 비공개로 할지 공개를 할지 모르겠다(일단 쓰자)</p>
<br/>
<h3 id="첫-회사-이직준비"><a class="anchor" href="#첫-회사-이직준비">첫 회사, 이직준비</a></h3>
<p>사실 2021년 초반에 컴퓨터 관련 학원을 다니기도 했고, 국비도 해보았지만 결과도 별로고 시간도 길지 않아서 패스하겠다</p>
<p>제대로 공부를 시작한것은 2021년 12월에 생활코딩, 드림코딩과 같은 유튜브를 보면서 개발공부를 시작하였다.</p>
<p>그 이후에 클론코딩만 주구장창하다가 이대로는 복사기가 될 것 같기도 하고 실력이 멈출것 같아서 OKKY 라는 개발 커뮤니티에서 팀원을 모집해 운동정보 공유 및 기록하는 웹사이트(자칭 : 헬쓰리) 를 만들기 시작했다.</p>
<p>헬쓰리를 만드는 동안 클론코딩에서는 느끼지 못할 무수한 에러에 대해서 경험해보기도 하고 지독하게 다른포지션 개발자와 회의도 진행해보고.. 아주 많은 일이 있었던거같다.</p>
<p>그중에 기억에 남는 부분은 직군의 Song 에게 프로젝트에 대한 방향성, 정신적인 응원 등 많은 도움을 받았다.</p>
<p>그렇게 시간이 흘러 난 독학으로 공부를 하다보니 코딩테스트에 대해서 제대로 알지도 못하고 , 네트워크, 자료구조, 컴퓨터 이론, 웹개발 CS 지식에 제대로 알지 못한 상태로 헬쓰리만 주구장창 만들었다.</p>
<p>하나 둘씩 취업에 성공하고 그 때야, 급하게 과제전형,코딩테스트 등을 준비하고 먼접준비를 한 후에 급하게 이력서를 작성하고 관심있어하는 회사에 지원을 하게 되었는데 10%만 서류합격이 되었을뿐 광탈의 연속이었다!!!!</p>
<p>그중 운이 좋게 C# 관련 인공지능 챗봇을 만드는 회사에 입사하게되었다.</p>
<p>지금 생각해보면 내가 왜 node 쪽이 아닌 c# 과 .net 쪽으로 입사를 하였을 까 싶은데 저 때도 막 도전정신이 끓어 올랐던거 같다.</p>
<p>그렇게 회사에 가서 일을 통해 도파민을 얻을 정도로 일을 사랑하고 즐겁게 하시는 분들을 만나서 즐겁게 코딩하고</p>
<p>재미있는 활동을 참 많이했다.</p>
<p>그렇게 회사를 열심히 다니고 이런저런 사유때문에 퇴사를 진행한 후에 조금 쉬었다 바로 이직준비를 시작하였다.</p>
<p>이직 준비하는 1달 정도의 시간동안 코테도 보고, 면접도 보고, 과제전형도 하고~~ 등등 여러가지 활동을 한 이후에 현재 회사에서 비대면 헬스케어분야에서 FE 로 근무중이다.</p>
<br/>
<h3 id="두번째-회사"><a class="anchor" href="#두번째-회사">두번째 회사</a></h3>
<p>두번째 회사에 왔을때 처음 맡은 프로젝트가 있었는데 C# ⇒ react/vite 로 마이그레이션하는 것이였다.</p>
<p>진짜 C# 을 깔짝해봐서 어려웠는데 어찌저찌해서 해결했구..</p>
<p>위 프로젝트와 함께 jquery 기반 프로젝트를 nextjs 로 마이그레이션 하는 프로젝트까지 해서 온보딩 과정을 진행했다.</p>
<p>(내가 공부 시작할때만해도 next 12 였는데 저 때 13 이 정식으로 release 되어서 했는데 어려웠따..)</p>
<p>다시 생각해보면 저 3달이라는 시간은 정신없이 지나간것 같다</p>
<ul>
<li>잘했다고 했는데.. 뭘 잘한건지 잘모르지만~ 그만큼 열심히했으니 뿌듯했다.</li>
</ul>
<p>현재 다니는 회사의 프로젝트 몇개는 상당히 복잡하다. jqeury 도 쓰고 devextreme 이라는 css 라이브러리 사용하고있고 상태관리보다는 전역으로 storage 를 통해 데이터를 관리하고, ssh 를 통해 배포를 하고.. 거의 terminal 명령어를 열심히 알아야 될 정도로 오래된 코드들이 많이 있었다.</p>
<p>바로 리팩토링하거나 기능개선쪽으로 시간을 할애했으면 좋겠지만, 유저 사용에 대한 처리와 기능개발을 동시에 진행하고 회사 특성상 미국에서 간호사,의사들이 사용하고 원하는 기능을 개발하고 수정하는 형태이고, 그에 따라 계속해서 새로운 기능에 짧은 DeadLine 을 부여받다 보니 Refactoring의 R 도 꺼내보지 못한채 3~4개월을 보내버렸다.</p>
<p>10월 후반부터 3~5년차분들의 동료들이 입사해서 회의를 통해 프로젝트 구조를 새로잡고 체계적으로 하나씩 진행중이다.</p>
<br/>
<h3 id="스터디-동아리-및-회사생활"><a class="anchor" href="#스터디-동아리-및-회사생활">스터디, 동아리 및 회사생활</a></h3>
<p>올해 5월까지 회사만 열심히 다니고 있었다. 그런데 이게 뭔지 회사를 다니니깐 더욱 불안해졋다.</p>
<p>(내가 하고있는게 맞을까? 아 요즘 이런 기술스택이 강세이던데.. 왜 이런거 안쓰지 등등)</p>
<p>그러던 도중에 Teo 스프린트를 참여하게 되었고!! 디자이너를 비롯한 다양한 스터디원들을 만나게되었다.</p>
<p>5일동안 기획 디자인 개발 모든 작업을 했다.</p>
<p>사람들과 뜻도 맞아서 오프라인으로 모각코도 진행하고 자연스러운 취준, 개발이야기를 했다</p>
<p>시간이 흘러서 이런 행사를 기획한 teo 의 생각을 들어보고 싶다고 생각을 했다</p>
<p>약속을 잡고 1시간30분정도 테오와 함께 나의 현재 상황에 대해서 이야기를 해보았고, 스프린트 인원들과 실제로 만나서 커피챗도 진행했다</p>
<p>결과적으로 주니어때 우물속 개발자가 아닌 다른사람 이야기를 많이 들어보는 개발자가 되었으면 좋겠다는 조언을 듣게 되었다</p>
<p>그래서 찾아봣다.. 어떻게 하면 견해를 넓힐까!</p>
<p>동아리를 하기로 결정을 했고 좋은 기회로 Sipe 라는 동아리에서 활동하게 되었다.</p>
<p>sipe 덕분에 운동,대인관계,개발견해 등등 많은 것을 배웠다</p>
<p>적극추천</p>
<br/>
<h3 id="2023년-아쉬운점-이어나갈점"><a class="anchor" href="#2023년-아쉬운점-이어나갈점">2023년 아쉬운점, 이어나갈점</a></h3>
<p><strong>아쉬운점</strong></p>
<p>코 앞에 취업만 바라보고 2022년을 보내고, 2023년에 직장에서 자리를 잡았다.</p>
<p>그러다 보니 내가 목표한 부분이 어떤건지에 대한 상실감과 그런 상실감을 해소하기 위해서 숨쉴틈 없이 잡아논 나의 일정이 나를 지치게했다</p>
<p>뭐.. 남들보다 빠르게 번아웃이 와서 그 순간을 결과적으론 잘 보낸거 같아서 다행이라고 생각한다.</p>
<p>여행으로 해결하든, 아니면 또 다른 계획으로 해결하든 결과만 좋다면야!</p>
<p><strong>좋고 이어나갈 점</strong></p>
<p>올해 최고로 잘한 부분은 사람을 많이 만낫다는 것.</p>
<p>우물안 개구리가 되고 나에대한 평가를 스스로에게 너무 착하게 해준 것 같은데 사람들을 만나면서 목표할 부분을 다시 잡게 되었다.</p>
<br/>
<h3 id="남은-연말--내년"><a class="anchor" href="#남은-연말--내년">남은 연말 &#x26; 내년..</a></h3>
<p>이걸 쓰는 동안에 연말이다. 올해가 2주도 안남았네 ㅋㅋ</p>
<p>금방쓸줄 알았는데 개뿔 ㅋㅋㅋ</p>
<p>내년 목표나 작성해보자</p>
<p>내년에 내가 열심히 해야겠다고 다짐 한 부분은</p>
<ol>
<li>링크드인에 회고록 작성하기
<ol>
<li>아무래도 나 자신에 대한 피드백을 작성하고, 그런 기록물을 만드는것은 링크드인이 적합하다고 생각</li>
</ol>
</li>
<li>코테 코테 코테 코테, 과제테스트
<ol>
<li>취업하자마자 내 던져버린 코테를 다시 꺼낸다 나의 목표는 백준 플레다.</li>
<li>과제테스트도 열심히해보쟈</li>
</ol>
</li>
<li>필요한 부분에 대해서 공부하기
<ol>
<li>자바스크립트, 리엑트와 같이 내가 주로 사용하는 부분에 대해서 구체적이고 깊게 다시 해볼려고함</li>
</ol>
</li>
<li>** 준비
<ol>
<li>이것을 준비하기 위해서 목표점을 확실히 잡아서 전략적으로 준비해야겠다</li>
<li>2024년은 이것을 위해서 올인하겟다 라는 생각</li>
</ol>
</li>
</ol>
<br/>
<h3 id="to-me"><a class="anchor" href="#to-me">To Me…</a></h3>
<p>고생했다 지훈아..</p>
<p>근데 끝까지 맡은부분 열심히하자 뭐든지</p>
<p>인간관계, 공부하는것 등등</p>
<p>그리고 운동좀 해라(클라이밍,헬스,러닝 다시 할 예정)</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>일상</category>
        </item>
        <item>
            <title><![CDATA[Singleton Pattern을 간단하게 알아보자!]]></title>
            <link>https://hooninedev.com/231010</link>
            <guid isPermaLink="false">https://hooninedev.com/231010</guid>
            <pubDate>Tue, 10 Oct 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Singleton Pattern 싱글톤 패턴은 특정 클래스의 인스턴스가 오직 하나만 생성되도록 보장하며, 이 인스턴스에 전역적으로 접근할 수 있게 하는 디자인 패턴이다. 싱글톤 패턴은 전역 변수를 사용하지 않고도 전역적인 접근을 제공하여, 공유 자원에 대한 동시 접근을 제어할 수 있어, 자원 관리와 동시성 제어에 중요한 시스템이다. 자바스크립트에서는 클래스...]]></description>
            <content:encoded><![CDATA[<h2 id="singleton-pattern"><a class="anchor" href="#singleton-pattern">Singleton Pattern</a></h2>
<p>싱글톤 패턴은 특정 클래스의 인스턴스가 오직 하나만 생성되도록 보장하며, 이 인스턴스에 전역적으로 접근할 수 있게 하는 디자인 패턴이다.</p>
<p>싱글톤 패턴은 전역 변수를 사용하지 않고도 전역적인 접근을 제공하여, 공유 자원에 대한 동시 접근을 제어할 수 있어, 자원 관리와 동시성 제어에 중요한 시스템이다.</p>
<p>자바스크립트에서는 클래스를 사용하지 않고도 싱글톤 패턴을 구현할 수 있다. 왜냐하면 자바스크립트는 프로토타입 기반 언어이기 때문에 일반적인 함수와 객체, 클로저를 사용하여 싱글톤 패턴을 구현할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">class</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Singleton</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  constructor</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (Singleton.instance) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Singleton.instance;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.data </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">||</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "Initial data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    Singleton.instance </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.data;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  setData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.data </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> singleton</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Singleton</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"First data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(singleton.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> anotherSingleton</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Singleton</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Second data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(anotherSingleton.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">singleton.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Updated data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(anotherSingleton.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span></code></pre></figure>
<p>위 예시에서 <code>Singleton</code> 클래스는 처음 생성될 때만 새로운 인스턴스를 생성하며, 이후에는 기존 인스턴스를 반환한다. <code>getData</code>와 <code>setData</code> 메서드를 통해 데이터를 가져오고 설정할 수 있다.</p>
<br>
<p>또한 <strong>자바스크립트의 함수와 프로토타입을 사용</strong>하여 싱글톤 패턴을 구현할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Singleton</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (Singleton.instance) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> Singleton.instance;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.data </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">||</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "Initial data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  Singleton.instance </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Singleton</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">prototype</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.data;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Singleton</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">prototype</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setData</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.data </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> singleton</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Singleton</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"First data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(singleton.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> anotherSingleton</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Singleton</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Second data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(anotherSingleton.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">singleton.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Updated data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(anotherSingleton.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span></code></pre></figure>
<br>
<p>그리고 <strong>즉시 실행 함수 표현식(IIFE)을</strong> 사용하여 싱글톤 패턴을 구현할 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Singleton</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> SingletonClass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (SingletonClass.instance) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">      return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> SingletonClass.instance;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.data </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">||</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "Initial data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    SingletonClass.instance </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  SingletonClass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">prototype</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.data;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  SingletonClass</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">prototype</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setData</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    this</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.data </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> SingletonClass;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">})();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> singleton</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Singleton</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"First data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(singleton.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> anotherSingleton</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Singleton</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Second data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(anotherSingleton.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">singleton.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Updated data"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(anotherSingleton.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span></code></pre></figure>
<p>위 예시는 IIFE를 사용하여 <code>SingletonClass</code>를 내부에 캡슐화하고, 외부에서 직접 접근할 수 없도록 한다. <code>Singleton</code> 변수는 <code>SingletonClass</code>의 인스턴스를 생성하고 반환하는 역할을 한다.</p>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>소박한궁금증</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[Dependencies vs DevDependencies vs PeerDependencies]]></title>
            <link>https://hooninedev.com/231002</link>
            <guid isPermaLink="false">https://hooninedev.com/231002</guid>
            <pubDate>Mon, 02 Oct 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Dependencies (종속성) 목적: 애플리케이션이 프로덕션에서 실행되기 위해 필수적 설치 방법: npm install 명령어를 플래그 없이 실행할 때 설치 사용 사례: 웹 프레임워크, 유틸리티 라이브러리 등 애플리케이션이 정상적으로 작동하는 데 필요한 패키지를 포함 예시 DevDependencies (개발 종속성) 목적: 개발 및 테스트에만 필요 설치...]]></description>
            <content:encoded><![CDATA[<h3 id="dependencies-종속성"><a class="anchor" href="#dependencies-종속성">Dependencies (종속성)</a></h3>
<ul>
<li>
<p><strong>목적</strong>: 애플리케이션이 프로덕션에서 실행되기 위해 필수적</p>
</li>
<li>
<p><strong>설치 방법</strong>: <code>npm install</code> 명령어를 플래그 없이 실행할 때 설치</p>
</li>
<li>
<p><strong>사용 사례</strong>: 웹 프레임워크, 유틸리티 라이브러리 등 애플리케이션이 정상적으로 작동하는 데 필요한 패키지를 포함</p>
</li>
<li>
<p><strong>예시</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"dependencies"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "express"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"4.17.1"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "lodash"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"4.17.21"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
</li>
</ul>
<br>
<h3 id="devdependencies-개발-종속성"><a class="anchor" href="#devdependencies-개발-종속성">DevDependencies (개발 종속성)</a></h3>
<ul>
<li>
<p><strong>목적</strong>: 개발 및 테스트에만 필요</p>
</li>
<li>
<p><strong>설치 방법</strong>: <code>npm install --save-dev</code> 또는 <code>npm install -D</code> 명령어를 실행할 때 설치</p>
</li>
<li>
<p><strong>사용 사례</strong>: 테스트 라이브러리, 빌드 도구, 린터 등 프로덕션 환경에서는 필요하지 않은 패키지를 포함</p>
</li>
<li>
<p><strong>예시</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"devDependencies"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "jest"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"26.6.3"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "eslint"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"7.20.0"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<br>
</li>
</ul>
<h3 id="peerdependencies-피어-종속성"><a class="anchor" href="#peerdependencies-피어-종속성">PeerDependencies (피어 종속성)</a></h3>
<ul>
<li>
<p><strong>목적</strong>: 패키지를 사용하는 소비자가 특정 종속성을 제공해야 함을 명시</p>
</li>
<li>
<p><strong>설치 방법</strong>: 자동으로 설치되지 않으며, 패키지를 사용하는 프로젝트에서 설치</p>
</li>
<li>
<p><strong>사용 사례</strong>: 플러그인이나 라이브러리가 호스트 애플리케이션이 특정 버전의 종속성을 사용하도록 보장할 때 유용하며, 여러 버전의 동일한 패키지가 사용되는 것을 피할 수 있다.</p>
</li>
<li>
<p><strong>예시</strong></p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"peerDependencies"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "react"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"17.0.0"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "react-dom"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"17.0.0"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<br>
</li>
</ul>
<h3 id="요약"><a class="anchor" href="#요약">요약</a></h3>
<ul>
<li><strong>Dependencies</strong>: 런타임에 필수적이며, 프로덕션 환경에서도 포함한다. 또한 모든 환경에서 애플리케이션이 예상대로 실행되도록 필요한 모든 패키지가 제공된다.</li>
<li><strong>DevDependencies</strong>: 개발 중에만 필요하며, 프로덕션 환경에서는 포함되지 않는다. 또한 프로덕션 번들에서 개발 관련 도구를 제외하여 성능을 개선하고 잠재적 공격 벡터를 줄일 수 있다.</li>
<li><strong>PeerDependencies</strong>: 패키지를 사용하는 프로젝트가 제공해야 하는 종속성으로, 기본적으로 설치하지 않는다. 그리고 소비하는 애플리케이션 전반에 걸쳐 단일 버전의 종속성을 유지하여 여러 버전으로 인한 충돌과 잠재적 버그를 피할 수 있다.</li>
</ul>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>소박한궁금증</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[Ecmascript에서 Modules랑 CommonJs를 비교해보자!]]></title>
            <link>https://hooninedev.com/230928</link>
            <guid isPermaLink="false">https://hooninedev.com/230928</guid>
            <pubDate>Thu, 28 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[모듈은 현대 소프트웨어 개발에서 코드의 재사용성과 유지보수를 높이기 위해 필수적인 요소다. 그리고 자바스크립트에는 CommonJS와 ES 모듈이 있다. 두개가 어떤 차이가 있는지 비교해보자! CommonJS Node.js의 기본 모듈 시스템으로 CommonJs를 사용한다. require를 사용하여 모듈을 로드하고 module.exports를 사용하여 변수와...]]></description>
            <content:encoded><![CDATA[<p>모듈은 현대 소프트웨어 개발에서 코드의 재사용성과 유지보수를 높이기 위해 필수적인 요소다. 그리고 자바스크립트에는 CommonJS와 ES 모듈이 있다. 두개가 어떤 차이가 있는지 비교해보자!</p>
<br>
<h3 id="commonjs"><a class="anchor" href="#commonjs">CommonJS</a></h3>
<ul>
<li>
<p>Node.js의 기본 모듈 시스템으로 CommonJs를 사용한다.</p>
</li>
<li>
<p><code>require</code>를 사용하여 모듈을 로드하고 <code>module.exports</code>를 사용하여 변수와 함수를 내보낸다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">module</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">exports</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">add</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">a</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">b</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> a </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> b;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">module</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">exports</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">subtract</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">a</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">b</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> a </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> b;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">add</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">subtract</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> require</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"./util"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">add</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">5</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">5</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">subtract</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">5</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span></code></pre></figure>
</li>
<li>
<p><strong>동기적 로딩</strong>: 모듈을 하나씩 순차적으로 로드하고 처리하므로 성능에 민감한 애플리케이션에서 블로킹이 발생할 수 있다.</p>
</li>
<li>
<p><strong>유연성</strong>: <code>require</code>는 코드의 어디서나 호출할 수 있어 조건부 또는 동적으로 모듈을 로드할 수 있다.</p>
</li>
</ul>
<br>
<h3 id="es-모듈"><a class="anchor" href="#es-모듈">ES 모듈</a></h3>
<ul>
<li>
<p><strong>표준화된 포맷</strong>: ES 모듈은 자바스크립트 코드 재사용을 위한 공식 표준</p>
</li>
<li>
<p><strong>문법</strong>: <code>import</code>와 <code>export</code> 문을 사용</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> add</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">a</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">b</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> a </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> b;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">export</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> subtract</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">a</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">b</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> a </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> b;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">import</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> { add, subtract } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">from</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "./util.mjs"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">add</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">5</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">5</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">subtract</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">10</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">5</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span></code></pre></figure>
</li>
<li>
<p><strong>비동기적 로딩</strong>: 모듈을 비동기적으로 로드하여 고성능 웹 애플리케이션의 성능을 향상시킬 수 있다.</p>
</li>
<li>
<p><strong>네이티브 브라우저 지원</strong>: 모든 최신 웹 브라우저에서 지원하여 서버와 클라이언트 환경 간에 코드를 쉽게 공유할 수 있다.</p>
</li>
<li>
<p><strong>파일 확장자</strong>: 일반적으로 <code>.mjs</code> 확장자를 사용하거나 <code>package.json</code> 파일에 <code>"type": "module"</code>을 설정하여 <code>.js</code> 파일을 ES 모듈로 취급</p>
</li>
</ul>
<br>
<h3 id="자바스크립트-모듈의-진화"><a class="anchor" href="#자바스크립트-모듈의-진화">자바스크립트 모듈의 진화</a></h3>
<ol>
<li><strong>IIFE (즉시 실행 함수 표현)</strong>: 전역 스코프 오염 방지와 코드 캡슐화를 위해 사용</li>
<li><strong>모듈 패턴</strong>: 모듈의 공용 및 개인 구성 요소를 분리하여 더 명확한 구조를 제공했지만, 종속성 관리를 위한 표준 방법이 없다.</li>
<li><strong>CommonJS</strong>: 서버 측 개발을 위해 동기적 로딩을 도입</li>
<li><strong>AMD (Asynchronous Module Definition)</strong>: 브라우저 환경에서 비동기 로딩을 중점적으로 개선했</li>
<li><strong>UMD (Universal Module Definition)</strong>: 서버와 브라우저 환경 모두에서 작동하도록 설계</li>
<li><strong>ES 모듈</strong>: 클라이언트와 서버 환경 모두에서 네이티브 지원을 제공하며 명확한 문법과 비동기 로딩을 제공</li>
</ol>
<br>
<h3 id="nodejs와-es-모듈"><a class="anchor" href="#nodejs와-es-모듈">Node.js와 ES 모듈</a></h3>
<ul>
<li><strong>지원 버전</strong>: Node.js는 버전 13.2.0부터 ES 모듈을 안정적으로 지원</li>
<li><strong>호환성</strong>: 이전 버전의 Node.js(v9 이하)에서는 ES 모듈을 네이티브로 지원하지 않는다.</li>
<li><strong>듀얼 모드 라이브러리</strong>: <code>package.json</code>의 조건부 exports를 사용하여 CommonJS와 ES 모듈 모두를 지원하도록 할 수 있다.
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "name"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"my-library"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "exports"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "."</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      "browser"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">        "default"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"./lib/browser-module.js"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">      }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    },</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "module-a"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      "import"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"./lib/module-a.mjs"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">      "require"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"./lib/module-a.js"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
</li>
</ul>
<br>
<h3 id="정리"><a class="anchor" href="#정리">정리</a></h3>
<ul>
<li><strong>CommonJS</strong>
<ul>
<li><strong>장점</strong>: <code>require</code>의 유연성, 쉬운 동기적 코드 작성.</li>
<li><strong>단점</strong>: 동기적 로딩으로 인해 성능 문제 발생 가능.</li>
</ul>
</li>
<li><strong>ES 모듈</strong>
<ul>
<li><strong>장점</strong>: 비동기적 로딩, 브라우저에서 네이티브 지원, 표준화된 문법.</li>
<li><strong>단점</strong>: Node.js v13.2.0 이상에서만 지원, 기존 코드베이스와의 호환성 문제.</li>
</ul>
</li>
</ul>
<br>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>소박한궁금증</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[git rev-parse 명령어 들어본적 있니!?]]></title>
            <link>https://hooninedev.com/230918</link>
            <guid isPermaLink="false">https://hooninedev.com/230918</guid>
            <pubDate>Mon, 18 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[나는 사실 저 명령어를 써본 적이 없다. 그리고 처음 본다.. git rev-parse는 Git 참조를 구문 분석하고 쿼리하는 데 사용되는 다용도 명령어다. 이해가 안 가니 예시를 보자 git rev-parse HEAD : 현재 브랜치에 있는 최신 커밋의 전체 SHA-1 해시가 반환한다. HEAD 키워드를 태그 또는 브랜치로 변경하면 해당 커밋 ID를 얻을...]]></description>
            <content:encoded><![CDATA[<p><img src="/content/230918/1.jpeg" alt="1.jpeg" loading="lazy" decoding="async"></p>
<p>나는 사실 저 명령어를 써본 적이 없다. 그리고 처음 본다..</p>
<p><strong>git rev-parse</strong>는 Git 참조를 구문 분석하고 쿼리하는 데 사용되는 다용도 명령어다. <del>이해가 안 가니 예시를 보자</del></p>
<ul>
<li>
<p>git rev-parse HEAD : 현재 브랜치에 있는 최신 커밋의 전체 SHA-1 해시가 반환한다. HEAD 키워드를 태그 또는 브랜치로 변경하면 해당 커밋 ID를 얻을 수 있다.</p>
</li>
<li>
<p>git rev-parse --abbrev-ref HEAD : 현재 브랜치의 이름을 반환</p>
</li>
<li>
<p>git rev-parse 축약된 커밋명 : 레파지토리 내에서 고유한 전체 커밋 ID 반환</p>
</li>
</ul>
<p>사실 이거 왜 쓰는지 모르겠다고 생각했다. 터미널을 확장해서 쓰면 복잡한 명령어를 작성 안해도 확인 할 수 있으니까! 그런데 다음 예시를 보니 터미널 명령어 내부에서 "조건문을 사용하는데 이런 이유로 사용하는구나" 라는 생각을 했다.</p>
<ul>
<li>유효성 확인을 위해 아래처럼 사용 가능</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="bash" data-theme="github-dark github-light"><code data-language="bash" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">if</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> git</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> rev-parse</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> --verify</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> feature-branch</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> ></span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">/dev/null</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> 2>&#x26;1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">; </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">then</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  echo</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "The branch exists."</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">else</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  echo</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "The branch does not exist."</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">fi</span></span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>소박한궁금증</category>
            <category>DevOps</category>
        </item>
        <item>
            <title><![CDATA[What is useState?]]></title>
            <link>https://hooninedev.com/230820</link>
            <guid isPermaLink="false">https://hooninedev.com/230820</guid>
            <pubDate>Sun, 20 Aug 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[함수형 컴포넌트는 렌더링이 일어날 때마다 컴포넌트 함수가 다시 호출됩니다. 그래서 함수 내부의 일반변수는 호출이 끝나면 사라지고, 다음 렌더에서 처음부터 다시 만들어집니다. 그렇기에 렌더 사이에 유지되어야 하는 값을 일반 변수로 들고 있으면 값이 유지되지 않습니다. 반대로 useState는 값이 컴포넌트 함수 안에 저장되는 게 아니라, React가 렌더 사...]]></description>
            <content:encoded><![CDATA[<p>함수형 컴포넌트는 <strong>렌더링</strong>이 일어날 때마다 컴포넌트 함수가 다시 호출됩니다. 그래서 함수 내부의 일반변수는 호출이 끝나면 사라지고, 다음 렌더에서 처음부터 다시 만들어집니다. 그렇기에 <strong>렌더 사이에 유지되어야 하는 값</strong>을 일반 변수로 들고 있으면 값이 유지되지 않습니다.</p>
<p>반대로 <code>useState</code>는 값이 컴포넌트 함수 안에 저장되는 게 아니라, React가 렌더 사이에 따로 보관하고 있다가, 해당 렌더 시점에 스냅샷처럼 꺼내서 컴포넌트에 제공합니다. 그래서 상태값은 <strong>변수처럼 바로 바뀌는 것</strong>이 아니라, <code>setState</code>를 호출하면 다음 렌더를 예약하고, 다음 렌더에서 바뀐 상태 스냅샷을 받게 됩니다.</p>
<p>여기서 중요한 점은, props도 마찬가지로 <strong>그 렌더의 입력값</strong>입니다. 함수형 컴포넌트의 관점에서 props는 읽기 전용 입력이고, 컴포넌트는 props와 state로 UI를 계산해 내는 구조입니다. 상태를 바꾸고 싶다면 props를 건드리는 게 아니라 <code>setState</code>를 활용해 다음 렌더의 상태를 바꾸는 방식으로 접근해야 합니다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Counter</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> local </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">count</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  const</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    local </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+=</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    setCount</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(count </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">+</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> 1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(local, count);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> &#x3C;</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> onClick</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{onClick}>{count}&#x3C;/</span><span style="--shiki-dark:#85E89D;--shiki-light:#22863A">button</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">>;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>여기서 <code>local</code> 변수는 렌더 사이에 유지되지 않기 때문에, 클릭할 때마다 <code>0</code>으로 초기화됩니다. 하지만 <code>count</code>는 <code>useState</code>를 통해 렌더 사이에 유지되므로, 클릭할 때마다 증가합니다. 하지만 <code>count</code>는 클릭하게되면 <code>setCount</code>가 현재 실행 중인 코드의 변수를 바꾸는게 아니라 다음 렌더를 트리거하기 때문에 클릭 즉시 바뀌지 않습니다.</p>
<p>이 글에서 useState에 대해서 공식문서를 기준으로 값들이 어떻게 유지되고, 이어지는지에 대해서 이야기해보려합니다.</p>
<h2 id="usestate-란-무엇인가"><a class="anchor" href="#usestate-란-무엇인가">useState 란 무엇인가</a></h2>
<p><code>useState</code>는 <strong>함수형 컴포넌트에 상태(state)를 추가</strong>하기 위한 Hook입니다. React는 상태를 컴포넌트 함수 내부 변수로 두는 게 아니라 <strong>렌더 사이에 별도로 보관</strong>해두고, 각 렌더마다 그 시점의 상태 <strong>스냅샷</strong>을 컴포넌트에 제공합니다. 그래서 상태를 바꾸는 행위는 <strong>현재 변수 값을 즉시 바꾸는 것</strong>이 아니라, <strong>다음 렌더에서 사용할 상태를 예약</strong>하는 방식으로 동작합니다.</p>
<p><code>useState</code>는 길이가 2개인 배열을 반환합니다. <code>state</code>는 현재 렌더에서의 상태 값(첫 렌더에서는 <code>initialState</code>), <code>setState</code>는 상태를 업데이트하고 리렌더를 트리거하는 함수입니다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">const</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">setState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(initialState);</span></span></code></pre></figure>
<p><code>initialState</code>는 <code>useState</code>가 처음 렌더링 될 때 반환하는 <code>state</code>의 초기값입니다. 초기 렌더 이후에는 무시됩니다. 그리고 <code>initialState</code>로 함수를 넘기면 그 함수는 <strong>initializer function</strong>으로 취급되어, React가 <strong>초기화 시점에 호출한 반환값을 초기 상태로 저장</strong>합니다. 이 방식은 초기값 계산이 비싼 경우에 유용합니다.</p>
<p>아래 코드가 어떤 차이점을 보이는지 살펴보겠습니다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">createInitialTodos</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">());</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(createInitialTodos);</span></span></code></pre></figure>
<p>두 함수 <code>initialState</code>의 차이점은 언제 호출되어 초기값이 설정되는지에 있습니다.</p>
<p>첫 번째 코드는 매 렌더마다 <code>createInitialTodos()</code>가 호출되어 초기값이 설정됩니다. 하지만 두 번째 코드는 초기화 시점에만 <code>createInitialTodos</code>를 호출하여 초기값을 만듭니다. (Lazy Initialization)</p>
<details><summary>Lazy Initialization</summary><p>Lazy Initialization 은 <strong>useState의 초기값으로 값 대신 함수를 전달하여, 초기 로직이 첫 렌더링 시에만 실행되도록 하는 최적화 기법입니다.</strong></p><p><code>useState(heavyCalculation())</code>은 렌더링 될 때마다 heavyCalculation이 호출되어 낭비됩니다. 그래서 <code>useState(() => heavyCalculation())</code>로 최초 마운트 시에만 함수가 실행되고, 이후 리렌더링 때는 실행되지 않고 무시됩니다.</p></details>
<p><code>setState</code>는 <strong>현재 실행 중인 코드의 state 변수를 즉시 바꾸지 않습니다.</strong> 따라서 <code>setState</code> 직후에 <code>state</code>를 읽으면 이전 렌더의 값이 그대로 나올 수 있습니다.</p>
<p>또한 <code>nextState</code>로 함수를 넘기면 React는 그 함수를 Queue에 쌓아두고, 다음 렌더에서 쌓인 업데이터들을 순서대로 적용해 최종 상태를 계산합니다.</p>
<p>그렇게되면 setState가 여러번 호출되어도 실제로는 한 번만 렌더링되는데, 이것을 성능 향상을 위해 여러 개의 상태 업데이트(setState)를 하나의 그룹으로 묶어서 한 번만 렌더링하는 React의 처리 방식인 <strong>배칭(batch)</strong> 이라고 합니다.</p>
<blockquote>
<p><a href="https://react.dev/blog/2022/03/29/react-v18">React 18</a>부터는 기본적으로 배칭 범위가 더 넓어져서, React 이벤트 핸들러뿐 아니라 setTimeout, Promise 등에서도 자동 배칭이 적용될 수 있습니다</p>
</blockquote>
<p>새로 넣은 값이 현재 state와 동일한지 <code>Object.is</code>로 비교하고 판단되면, React는 리렌더를 스킵할 수 있습니다.</p>
<p>여기서 중요한 포인트는 <strong>원시값(number,string,boolean)은 값 자체 비교</strong>라는 것 입니다, 하지만 <strong>객체/배열은 값이 아니라 참조(reference)가 같아야 동일한 값</strong>으로 취급됩니다. 객체를 직접 수정(mutation)하면 참조가 유지되기 때문에 바뀐 것처럼 보이는데 리렌더가 안되거나/반대로 상태 불변성이 깨져 디버깅이 어려워지는 문제가 발생합니다.</p>
<h2 id="내부-동작"><a class="anchor" href="#내부-동작">내부 동작</a></h2>
<p>React를 사용하면 가장 쉽게 접하는 Hook 중 하나인 <code>useState</code>의 내부 코드를 살펴보면 수많은 자바스크립트 로직이 서로 얽혀져 있는 모습을 볼 수 있습니다. 내부 코드를 통해 <code>useState</code>가 상태를 어떻게 관리하고 있는지 살펴봅시다.</p>
<p><code>useState</code>를 이해하기 위해서는 <strong>클로저(Closure)를</strong> 이해해야 합니다.</p>
<p>함수형 컴포넌트는 렌더링될 때마다 다시 호출됩니다. 그럼에도 부구하고 이전 상태값을 기억하는 이유는 무엇일까요? 아래 자바스크립트로 <code>useState</code>를 구현한 코드를 살펴보겠습니다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">initialValue</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  var</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> _val </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> initialValue; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 1. _val은 지역 변수이지만 클로저에 의해 캡처됨</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> state</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // state는 내부 함수이자 클로저</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> _val; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 부모 함수의 _val을 참조</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> setState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">newVal</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // _val 값을 직접 수정 (이 변수는 외부에서 접근 불가)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    _val </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> newVal;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [state, setState]; </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 외부로 함수 노출</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">var</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [foo, setFoo] </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">foo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 0</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setFoo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">foo</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 1</span></span></code></pre></figure>
<p><code>useState</code>는 함수가 호출되고 종료되어도, 반환된 state, setState 함수는 <strong>자신이 생성될 당시의 스코프(Lexical Environment)를 기억</strong>합니다. 따라서 <code>_val</code>이라는 변수는 메모리 어딘가에 살아남아 값을 유지하게 됩니다.</p>
<h3 id="reacthooksjs"><a class="anchor" href="#reacthooksjs">ReactHooks.js</a></h3>
<p>실제 React에서는 수많은 컴포넌트의 수많은 Hook들을 순서대로 관리해야하기에 내부 구조가 많이 복잡합니다. 그래서 <a href="https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js">모든 내부 구조</a>를 살펴보는 것 보다는 필요한 부분만 살펴보도록 하겠습니다.</p>
<p>React는 컴포넌트가 <strong>처음 렌더링될 때(Mount)와 업데이트 될 때(Update) 서로 다른 Dispatcher를 사용</strong>합니다.</p>
<p>아래 코드는 컴포넌트가 처음 마운트될 때 실행되는 <code>useState</code>의 진입점입니다</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">useState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">initialState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    currentHookNameInDev </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> 'useState'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    mountHookTypesDev</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// DevTools를 위한 타입 추적</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // 현재의 Dispatcher를 가져옴</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    var</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> prevDispatcher </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> ReactCurrentDispatcher$1.current;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    ReactCurrentDispatcher$1.current </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> InvalidNestedHooksDispatcherOnMountInDEV;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">        // 실제 상태 생성 로직인 mountState 호출</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">        return</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> mountState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(initialState);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">finally</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">        ReactCurrentDispatcher$1.current </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> prevDispatcher;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>여기서 주목할 점은 <code>useState</code> 자체가 로직을 다 갖는게 아니라, 상황에 맞는 <code>Dispatcher(mountState)</code>를 호출한다는 점입니다. 그럼 Hook 객체를 생성하고 초기화하는 역할을 하는 <code>mountState</code> 로직의 중요한 부분만 살펴보겠습니다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> mountState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">initialState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 1. 현재 작업 중인 Hook 객체를 가져옴 (Fiber와 연결됨)</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  var</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> hook </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> mountWorkInProgressHook</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 2. 초기값 설정 (함수형 초기화 지원 - Lazy Initialization)</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">typeof</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> initialState </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">===</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "function"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    initialState </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> initialState</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 3. Hook 객체에 상태 저장</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  hook.memoizedState </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> hook.baseState </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> initialState;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 4. 업데이트 대기열(Queue) 생성</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  var</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> queue </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    pending: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    interleaved: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    lanes: NoLanes,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    dispatch: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    lastRenderedReducer: basicStateReducer,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    lastRenderedState: initialState,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  };</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  hook.queue </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> queue;</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 5. dispatch 함수 생성 (우리가 아는 setState)</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // dispatchSetState에 현재 Fiber와 Queue를 바인딩함</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  var</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> dispatch </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (queue.dispatch </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> dispatchSetState.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">bind</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    null</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    currentlyRenderingFiber$1,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    queue,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  ));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 6. [상태값, setState함수] 반환</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> [hook.memoizedState, dispatch];</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>React는 컴포넌트 내의 여러 Hook들을 <strong>Linked List(연결 리스트)</strong> 형태로 관리합니다.</p>
<p><code>mountWorkInProgressHook()</code>은 새로운 Hook 객체를 생성하고, 현재 Fiber 노드의 Hook 리스트 끝에 추가합니다. 이것이 바로 <strong>"Hook은 최상위에서, 순서대로 호출되어야 한다"</strong> 이라는 규칙입니다.</p>
<p>우리가 사용하는 실제 state 값은 <code>hook.memoizedState</code>에 저장됩니다. 컴포넌트가 다시 렌더링될 때 React는 이 값을 가져옵니다. setState 함수(여기서는 dispatch)는 생성될 때 현재 컴포넌트의 Fiber(currentlyRenderingFiber$1)와 자신의 Queue 정보를 미리 바인딩해둡니다.</p>
<p>덕분에 우리가 setState(3)처럼 값만 넘겨도, React의 내부적으로 <strong>어떤 컴포넌트의 어떤 Hook을 업데이트해야 하는지</strong> 정확히 알 수 있습니다.</p>
<h2 id="마치며"><a class="anchor" href="#마치며">마치며</a></h2>
<p>단순해 보이는 <code>const [state,setState] = useState(initialState)</code> 한 줄 뒤에는 Fiber 아키텍처, 연결 리스트, 그리고 클로저의 개념을 활용하고 있습니다.
이러한 내부 원리를 이해하면, 불필요한 렌더링을 막거나 복잡한 상태 관리 이슈를 디버깅할 때 훨씬 더 명확한 시야를 가질 수 있습니다. 앞으로 작성될 내용에서 지금은 가볍게 넘겼던 개념들을 깊게 다뤄보도록 하겠습니다.</p>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>React</category>
        </item>
        <item>
            <title><![CDATA[JavaScript 동기와 비동기 완벽 가이드]]></title>
            <link>https://hooninedev.com/230816</link>
            <guid isPermaLink="false">https://hooninedev.com/230816</guid>
            <pubDate>Wed, 16 Aug 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[What is Single Thread? javascript 는 Single Thread 기반 언어 ⇒ 한 번에 하나의 작업만 수행 가능 하지만! 동시에 병렬처리 해야 될 때가 있다 그럴 때 javascript는 비동기 함수(setTimeout, ajax, Promise 객체 등)를 이용 자바스크립트 엔진 자체는 Single Thread 지만 특정 API ...]]></description>
            <content:encoded><![CDATA[<p><img src="/content/230816/%EB%A9%94%EC%9D%B8%EC%9D%B4%EB%AF%B8%EC%A7%80.png" alt="메인이미지" loading="lazy" decoding="async"></p>
<h2 id="what-is-single-thread"><a class="anchor" href="#what-is-single-thread">What is Single Thread?</a></h2>
<p>javascript 는 Single Thread 기반 언어 ⇒ 한 번에 하나의 작업만 수행 가능</p>
<p>하지만! 동시에 병렬처리 해야 될 때가 있다</p>
<p>그럴 때 javascript는 비동기 함수(setTimeout, ajax, Promise 객체 등)를 이용</p>
<br/>
<aside>
💡 자바스크립트 엔진 자체는 Single Thread 지만 특정 API 들은 브라우저/런타임을 통해 Multi Thread 로 작업 가능
</aside>
<br/>
<h2 id="동기와-비동기"><a class="anchor" href="#동기와-비동기">동기와 비동기</a></h2>
<p>동기(Synchronous) : 먼저 시작된 작업이 끝날 때까지 기다리다가 새로 시작(한번에 여러작업이 아닌 하나씩)</p>
<p>비동기(Asynchronous): 작업이 끝날때까지 기다리지 않고 다음 작업 수행(한번에 여러작업을 처리함)</p>
<p><img src="/content/230816/%EB%B3%B8%EB%AC%B8%EC%9D%B4%EB%AF%B8%EC%A7%801.png" alt="본문이미지1" loading="lazy" decoding="async"></p>
<h2 id="자바스크립트의-비동기-내장함수"><a class="anchor" href="#자바스크립트의-비동기-내장함수">자바스크립트의 비동기 내장함수</a></h2>
<p>자바스크립트는 비동기 내장함수를 제공함 ⇒ setTimeout, XMLHttpRequest, fetch()</p>
<h3 id="xmlhttprequest"><a class="anchor" href="#xmlhttprequest">XMLHttpRequest</a></h3>
<ul>
<li>현재는 잘 사용하지 않음</li>
<li>관련 링크 : <a href="https://7942yongdae.tistory.com/67">https://7942yongdae.tistory.com/67</a></li>
</ul>
<h3 id="settimeout--cleartimeout"><a class="anchor" href="#settimeout--cleartimeout"><strong>setTimeout / clearTimeout</strong></a></h3>
<ul>
<li>setTimeout() 함수는 특정 코드를 바로 실행하지 않고 일정 시간동안 지연시킨 후 실행</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 사용벙 : setTimeout(function() { 코드 or 콜백함수 }, 지연시간);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"2초 후에 실행됨"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 지연 시간은 밀리초 단위로 기입. 1초 -> 1000</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 2초 후에 콘솔에 "2초 후에 실행됨" 출력</span></span></code></pre></figure>
<ul>
<li>clearTimeout() 함수는 setTimeout()을 취소, 중지시킨다.</li>
<li>디바운싱(debouncing) : 연이어 호출되는 함수들 중 마지막 함수(또는 제일 처음)만 호출하도록 하는 것</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 사용법 : clearTimeout( [식별자] );</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">var</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> timer;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">document.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">querySelector</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"#input"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">addEventListener</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"input"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">e</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  if</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (timer) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // input 될 때 마다 이벤트가 발생하지 않도록 n-1 번째 이벤트 취소</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    clearTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(timer);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // n번째 이벤트만 남겨서 마지막 이벤트만 실행되게</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  timer </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> () {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"여기에 ajax 요청"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, e.target.value);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">200</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span></code></pre></figure>
<h2 id="promise"><a class="anchor" href="#promise">Promise</a></h2>
<h3 id="what-is-promise"><a class="anchor" href="#what-is-promise">What is Promise?</a></h3>
<ul>
<li>Promise의 상태는 프로세스가 기능을 수행하고 있는 중인지, 기능 수행이 완료되어 성공했는지 실패했는지에 대한 상태를 의미</li>
<li>promise의 객체는 new Promise()로 생성</li>
<li>콜백 함수를 선언할 수 있고, 콜백 함수의 인자는 resolve, reject</li>
</ul>
<table>
<thead>
<tr>
<th>대기(pending)</th>
<th>진행 상태, Promise 객체가 생성되어 사용될 준비가 된 상태</th>
</tr>
</thead>
<tbody>
<tr>
<td>이행(Fulfilled)</td>
<td>성공 상태, 비동기 처리에 의해 원하는 올바른 결과를 얻어와 그 결과를 정상적으로 처리하고자 resolve가 호출된 상태</td>
</tr>
<tr>
<td>거부(Rejected)</td>
<td>실패 상태, 무언가 잘못되어 예외로 처리하고자 reject가 호출된 상태</td>
</tr>
</tbody>
</table>
<h3 id="promise-메서드-체인-thencatchfinally"><a class="anchor" href="#promise-메서드-체인-thencatchfinally">promise 메서드 체인 (then/catch/finally)</a></h3>
<ul>
<li>체이닝(chaining) : 동일한 객체에 메서드를 연결할 수 있는 것</li>
<li>then() - 성공(resolve) 시에는 then 메서드에 실행할 콜백 함수를 인자로 넘긴다.</li>
<li>catch() - 실패(reject) 시에는 catch 메서드에 실행할 콜백 함수를 인자로 넘긴다.</li>
<li>finally() - 성공/실패 여부와 상관없이 모두 실행 시에는 finally 메서드에 실행할 콜백 함수를 인자로 넘긴다.</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">promise</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(data))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">err</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(err))</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">finally</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"always run"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 위와 같이 메서드를 체이닝하면 함수를 호출한 주체가 실행을 완료한 뒤 자기 자신을 리턴한다.</span></span></code></pre></figure>
<h3 id="promise-처리-흐름-보기"><a class="anchor" href="#promise-처리-흐름-보기">promise 처리 흐름 보기</a></h3>
<p><img src="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/promises.png" alt="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/promises.png" loading="lazy" decoding="async"></p>
<p>new Promise() 메서드를 호출하면 대기(Pending) 상태가 된다.</p>
<p>이 때, 콜백 함수를 선언할 수 있고 인자는 resolve, reject이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// new Promise() 메서드 호출, 대기 상태</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 콜백 함수 선언</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // ...</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span></code></pre></figure>
<p>콜백 함수의 인자 resolve를 실행하면 이행(Fulfilled) 상태가 된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 이행 상태</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span></code></pre></figure>
<p>또한, 이행 상태가 되면 then()을 이용하여 처리 결과 값을 받을 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">    var</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62"> "resolve"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">;</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(data);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">().</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolvedData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(resolvedData); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// "resolve"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span></code></pre></figure>
<p>콜백 함수의 인자 reject를 실행하면 거부(Rejected) 상태가 된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">  reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span></code></pre></figure>
<p>실패 상태가 되면 실패한 이유(실패 처리의 결과 값)를 catch()로 받을 수 있다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> new</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5"> Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">    reject</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">new</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> Error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Request is failed"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">));</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">getData</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">()</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">function</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">err</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(err); </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// Error: Request is failed</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span></code></pre></figure>
<h3 id="promiseall--promiserace"><a class="anchor" href="#promiseall--promiserace">Promise.all / Promise.race</a></h3>
<ul>
<li><code>Promise.all</code>은 여러 작업을 동시에 처리하고, 작업이 모두 다 실행 완료되면 모두 다 반환한다.</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">all</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">timer</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">timer</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">timer</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">3000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)]).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// [1000, 2000, 3000] → 3000까지 모두 다 실행이 끝난 후에 다 반환</span></span></code></pre></figure>
<ul>
<li><code>Promise.race</code>은 실행이 가장 빠른 결과를 반환한다.</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">race</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">([</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">timer</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">timer</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">2000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">), </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">timer</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">3000</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)]).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">value</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(value);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">});</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 1000 → 가장 빨리 실행된 1000 반환</span></span></code></pre></figure>
<h2 id="fetch-api"><a class="anchor" href="#fetch-api">Fetch API</a></h2>
<p>Fetch 함수는 자바스크립트에서 서버로 네트워크 요청을 보내고 응답을 받을 수 있도록 도와줌</p>
<p>Promise 기반으로 구성되어 있어 비동기 처리에 편리</p>
<p>Promise의 후속 처리 메서드인 then이나 catch와 같은 체이닝으로 작성 가능</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// fetch 기본 구조</span></span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">fetch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(url, options) </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// url, options</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">response</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> response.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">json</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">())</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 첫 번째 then에서는 데이터 타입을 결정한다.</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(data);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  })</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 두 번째 then에서는 데이터를 전달 받는다.</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">error</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"error"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  });</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// catch에서 에러 요청이 발생했을 때, 에러를 받는다.</span></span></code></pre></figure>
<h2 id="http-요청-method"><a class="anchor" href="#http-요청-method">HTTP 요청 (Method)</a></h2>
<ul>
<li>Create -> <code>POST</code> (해당 URI를 요청하면 리소스를 생성)</li>
<li>Read -> <code>GET</code> (해당 리소스를 조회)</li>
<li>Update -> <code>PUT</code> (해당 리소스를 수정)</li>
<li>Delete -> <code>DELETE</code> (리소스를 삭제)</li>
</ul>
<h3 id="get---존재하는-자원을-요청-데이터-가져올-때-사용"><a class="anchor" href="#get---존재하는-자원을-요청-데이터-가져올-때-사용">GET - 존재하는 자원을 요청, 데이터 가져올 때 사용</a></h3>
<p>default 동작이기 때문에 option을 작성 안해도 된다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">fetch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"https://jsonplaceholder.typicode.com/posts/1"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">response</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> response.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">json</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">())</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(data))</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// json 형태로 파싱했기 때문에 json 형태의 데이터가 반환</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">  "userId"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">  "id"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">  "title"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"sunt aut facere repellat provident occaecati excepturi optio reprehenderit"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">  "body"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"quia et suscipit↵suscipit recusandae consequuntur …strum rerum est autem sunt rem eveniet architecto"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<h3 id="post---새로운-자원-생성-요청-데이터-전달할-때-사용"><a class="anchor" href="#post---새로운-자원-생성-요청-데이터-전달할-때-사용">POST - 새로운 자원 생성 요청, 데이터 전달할 때 사용</a></h3>
<p>method 옵션을 POST로 지정해주고, headers 옵션으로 JSON 포맷 사용한다고 알려줘야 하며, body 옵션에는 요청 데이터를 JSON 포맷으로 넣어준다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">fetch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"https://jsonplaceholder.typicode.com/posts"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  method: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"POST"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  headers: {</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    "Content-Type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"application/json"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  body: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">JSON</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">stringify</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    title: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Test"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    body: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"I am testing!"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    userId: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">})</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">response</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> response.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">json</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">())</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(data))</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// JSON.stringify() 메서드는 JavaScript 값이나 객체를 JSON 문자열로 변환한다.</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">title</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Test"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">body</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"I am testing!"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">userId</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">id</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">101</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<h3 id="put---존재하는-자원-변경-요청-데이터-수정할-때-사용"><a class="anchor" href="#put---존재하는-자원-변경-요청-데이터-수정할-때-사용">PUT - 존재하는 자원 변경 요청, 데이터 수정할 때 사용</a></h3>
<p>method 옵션만 PUT으로 설정한다는 점 외에는 POST 방식과 비슷</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">fetch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"https://jsonplaceholder.typicode.com/posts"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  method: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"PUT"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  headers: {</span></span>
<span data-line=""><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">    "Content-Type"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"application/json"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  body: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">JSON</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">stringify</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">({</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    title: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Test"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    body: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"I am testing!"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    userId: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }),</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">})</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">response</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> response.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">json</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">())</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(data));</span></span></code></pre></figure>
<h3 id="delete---존재하는-자원-삭제-요청"><a class="anchor" href="#delete---존재하는-자원-삭제-요청">DELETE - 존재하는 자원 삭제 요청</a></h3>
<p>보내는 데이터가 없기 때문에 headers, body 옵션이 필요 없음</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">fetch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"https://jsonplaceholder.typicode.com/posts/1"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  method: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"DELETE"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">})</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">response</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> response.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">json</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">())</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  .</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">data</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(data));</span></span></code></pre></figure>
<h2 id="asyncawait"><a class="anchor" href="#asyncawait">async/await</a></h2>
<p>then()을 연쇄적으로 체이닝하다보면 콜백지옥마냥 혼란에 빠짐 ⇒ async/await 으로 해결</p>
<p>async/await는 비동기 코드를 동기적인 코드인 것처럼 직관적으로 바꿔주는 역할을 함 → 비동기 코드에 순서를 부여</p>
<p>await는 반드시 async 함수 안에서 실행되며, async는 항상 Promise 객체를 반환</p>
<p>await 키워드는, then을 대신해 then을 체이닝한 것처럼 순서대로 동작한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">async</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> asyncFunc</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> response </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> fetch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"#"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> response.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">json</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<p>try-catch 구문을 사용하여 에러 처리를 할 수 있다는 것도 장점이다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">async</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> function</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> asyncFunc</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">() {</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">  try</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {  </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// try 안에 실행될 코드를 넣어주면 됨</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">	 	let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> response </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1"> fetch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">'#'</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">		let</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> await</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> response.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">json</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">();</span></span>
<span data-line=""><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">	    return</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> data;</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  } </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">catch</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> (e) {  </span><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// catch에는 에러시 실행될 코드</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"error : "</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, e)</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span></code></pre></figure>
<h2 id="ajax"><a class="anchor" href="#ajax">AJAX</a></h2>
<p><strong>A</strong>synchronous <strong>J</strong>avaScript <strong>A</strong>nd <strong>X</strong>ML의 약자</p>
<p>서버와 비동기적으로 통신할 때 사용하는 API</p>
<p>AJAX를 사용하면 백그라운드 영역에서 비동기적으로 서버와 통신하여, 그 결과를 웹 페이지의 일부분에만 표시</p>
<p>AJAX를 사용함으로써 웹 페이지 전체를 다시 로딩하지 않고도, 웹 페이지의 일부분만을 갱신할 수 있음</p>
<h2 id="json"><a class="anchor" href="#json">JSON</a></h2>
<p>JavaScript Object Notation의 약자</p>
<p>데이터를 저장하거나 전송할 때 많이 사용되는 경량의 DATA 교환 형식</p>
<p>JSON은 사람과 기계 모두 이해하기 쉬우며 용량이 작아서, 최근에는 JSON이 XML을 대체해서 데이터 전송 등에 많이 사용</p>
<p>JSON 문서 형식은 자바스크립트 객체의 형식을 기반으로 만들어져서 자바스크립트 객체 표기법과 유사</p>
<p>특정 언어에 종속되지 않기 때문에 다른 프로그래밍 언어를 이용해서도 쉽게 만들 수 있음</p>
<h2 id="자바스크립트-엔진v8"><a class="anchor" href="#자바스크립트-엔진v8">자바스크립트 엔진(V8)</a></h2>
<p><img src="https://user-images.githubusercontent.com/15958325/194826292-d2789365-1ef8-4819-bcc5-e1978e478d70.gif" alt="https://user-images.githubusercontent.com/15958325/194826292-d2789365-1ef8-4819-bcc5-e1978e478d70.gif" loading="lazy" decoding="async"></p>
<h3 id="call-stack"><a class="anchor" href="#call-stack">Call Stack</a></h3>
<p>함수 호출을 스택방식으로 기록하는 자료 구조</p>
<p>들어온 요청이 가장 밑에 쌓이고, 그 위에 스택이 쌓이는 구조</p>
<p>⇒ LIFO(Last In First Out) : 가장 나중에 들어온 작업 먼저 처리</p>
<p>javascript 는 Single Thread 기반 언어라서 하나의 Call Stack만을 가지고 한번에 하나의 Task만 처리</p>
<br/>
<h3 id="memory-heap"><a class="anchor" href="#memory-heap">Memory Heap</a></h3>
<p>객체가 저장되는 메모리 공간</p>
<p>콜 스택의 요소인 실행 컨텍스트는 힙에 저장된 객체를 참조</p>
<br/>
<h2 id="실행-컨텍스트"><a class="anchor" href="#실행-컨텍스트">실행 컨텍스트</a></h2>
<p>식별자를 등록하고 관리하는 스코프</p>
<p>코드 실행 순서를 관리</p>
<p>모든 코드는 실행 컨텍스트를 통해 실행/관리</p>
<h3 id="실행-컨택스트-동작순서"><a class="anchor" href="#실행-컨택스트-동작순서">실행 컨택스트 동작순서</a></h3>
<ol>
<li>
<p>전역 코드 평가 : 변수/함수 선언문을 실행 ⇒ 전역변수/함수를 전역 스코프에 등록시킴</p>
</li>
<li>
<p>전역 코드 실행 : 런타임 시작되어 코드 순차실행 ⇒ 전역 변수에 값 할당, 함수 호출 ⇒ 함수 호출되어 함수 내부로 들어감</p>
</li>
<li>
<p>함수 코드 평가: 함수 내부 매개변수와 지역 변수 선언문 실행 ⇒ 매개변수, 지역변수가 실행 컨텍스트가 관리하는 지역 스코프에 등록 ⇒ arguments 객체 생성 및 지역 스코프에 등록, this 바인딩 결정</p>
</li>
<li>
<p>함수 코드 실행 : 런타임 시작되어 함수 코드 순차 실행 ⇒ 매개변수, 지역변수에 값 할당, 내부 매서드 실행</p>
</li>
</ol>
<h2 id="브라우저-환경"><a class="anchor" href="#브라우저-환경">브라우저 환경</a></h2>
<h3 id="task-queuemacrotask-queue-event-queue"><a class="anchor" href="#task-queuemacrotask-queue-event-queue">Task Queue(Macrotask Queue, Event Queue)</a></h3>
<p>비동기 함수의 콜백 함수, 이벤트 핸들러가 일시적으로 보관</p>
<p>setTimeout(), setInterval(), setImmediate() 와 같은 task 를 넘겨받음</p>
<h3 id="microtask-queue-job-queue"><a class="anchor" href="#microtask-queue-job-queue">Microtask Queue, Job Queue</a></h3>
<p>Promise, async/await, process.nextTick, Object.observe, MutationObserver 과 같은 비동기 호출을 넘겨받음</p>
<p>우선순위가 Task Queue 보다 높다</p>
<p>FIFO(First In First Out) 형태로 실행된다</p>
<p>await 을 만나면 작업을 중지하고 Microtask Queue 에 적재</p>
<h3 id="animation-frames"><a class="anchor" href="#animation-frames">Animation Frames</a></h3>
<p>requestAnimationFrame 와 같이 브라우저 렌더링과 관련된 task 를 넘겨받는 Queue</p>
<p>Microtask 보다 우선순위가 낮고, Macrotask 보다는 높다.</p>
<h3 id="event-loop"><a class="anchor" href="#event-loop">Event Loop</a></h3>
<p>콜 스택에 현재 실행 중인 실행 컨텍스트가 있는지, task Queue 에 대기중인 함수가 있는지 반복해서 확인</p>
<p>콜 스택이 비어있고 task Queue에 대기중인 함수가 있다면 이벤트 루프는 순차적으로 테스크 큐에 대기 중인 함수를 call stack 으로 이동</p>
<p>call stack 으로 이동한 함수는 실행됨</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Start!"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">setTimeout</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(() </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Timeout!"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}, </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">0</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">Promise</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">resolve</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"Promise!"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">).</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">then</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">((</span><span style="--shiki-dark:#FFAB70;--shiki-light:#E36209">res</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">) </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(res));</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">console.</span><span style="--shiki-dark:#B392F0;--shiki-light:#6F42C1">log</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">(</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"End!"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">);</span></span></code></pre></figure>
<p><img src="https://res.cloudinary.com/practicaldev/image/fetch/s---Bt6DKsn--/c_limit,f_auto,fl_progressive,q_66,w_880/https://dev-to-uploads.s3.amazonaws.com/i/6cbjuexvy6z9ltk0bi18.gif" alt="https://res.cloudinary.com/practicaldev/image/fetch/s---Bt6DKsn--/c_limit,f_auto,fl_progressive,q_66,w_880/https://dev-to-uploads.s3.amazonaws.com/i/6cbjuexvy6z9ltk0bi18.gif" loading="lazy" decoding="async"></p>
<p><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6NSYq-nO--/c_limit,f_auto,fl_progressive,q_66,w_880/https://dev-to-uploads.s3.amazonaws.com/i/yqoemb6f32lvovge8yrp.gif" alt="https://res.cloudinary.com/practicaldev/image/fetch/s--6NSYq-nO--/c_limit,f_auto,fl_progressive,q_66,w_880/https://dev-to-uploads.s3.amazonaws.com/i/yqoemb6f32lvovge8yrp.gif" loading="lazy" decoding="async"></p>
<p><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--us8FF30N--/c_limit,f_auto,fl_progressive,q_66,w_880/https://dev-to-uploads.s3.amazonaws.com/i/6wxjxduh62fqt531e2rc.gif" alt="https://res.cloudinary.com/practicaldev/image/fetch/s--us8FF30N--/c_limit,f_auto,fl_progressive,q_66,w_880/https://dev-to-uploads.s3.amazonaws.com/i/6wxjxduh62fqt531e2rc.gif" loading="lazy" decoding="async"></p>
<p><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oOS_-CiG--/c_limit,f_auto,fl_progressive,q_66,w_880/https://dev-to-uploads.s3.amazonaws.com/i/a6jk0exl137yka3oq9k4.gif" alt="https://res.cloudinary.com/practicaldev/image/fetch/s--oOS_-CiG--/c_limit,f_auto,fl_progressive,q_66,w_880/https://dev-to-uploads.s3.amazonaws.com/i/a6jk0exl137yka3oq9k4.gif" loading="lazy" decoding="async"></p>
<p><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hPFPTZp2--/c_limit,f_auto,fl_progressive,q_66,w_880/https://dev-to-uploads.s3.amazonaws.com/i/p54casaaz9oq0g8ztpi5.gif" alt="https://res.cloudinary.com/practicaldev/image/fetch/s--hPFPTZp2--/c_limit,f_auto,fl_progressive,q_66,w_880/https://dev-to-uploads.s3.amazonaws.com/i/p54casaaz9oq0g8ztpi5.gif" loading="lazy" decoding="async"></p>
<h2 id="응용문제-풀어보기-자바스크립트-엔진-v8---chrome"><a class="anchor" href="#응용문제-풀어보기-자바스크립트-엔진-v8---chrome">응용문제 풀어보기 (자바스크립트 엔진 V8 - Chrome)</a></h2>
<p><img src="/content/230816/%EC%9D%91%EC%9A%A9%EB%AC%B8%EC%A0%9C.png" alt="응용문제" loading="lazy" decoding="async"></p>
<h3 id="실행-순서"><a class="anchor" href="#실행-순서">실행 순서</a></h3>
<ol>
<li>a() 함수 호출</li>
<li>a() 함수 ⇒ call stack 에 추가</li>
<li><strong>a1 출력</strong></li>
<li>b() 함수 호출</li>
<li>b() 함수 ⇒ call stack 에 추가</li>
<li><strong>b1 출력</strong></li>
<li>c() 함수 호출</li>
<li>c() 함수 ⇒ call stack에 추가</li>
<li><strong>c1 출력</strong></li>
<li>setTimeout 이 실행되어 task queue 로 이동</li>
<li>await d() 실행 ⇒ d () 함수로 이동</li>
<li>d() 함수 호출</li>
<li>d() 함수 ⇒ call stack 에 추가</li>
<li><strong>d1 출력</strong></li>
<li>Promise 생성 ⇒ then() 핸들러가 micro task queue 로 이동</li>
<li><strong>d2() 출력</strong></li>
<li>d() 함수가 call stack 에서 제거</li>
<li>then() 핸들러가 micro task queue 에서 call stack 으로 이동</li>
<li><strong>then! 출력</strong></li>
<li>c() 함수 종료</li>
<li><strong>c2 출력</strong></li>
<li>c() 함수 call stack 에서 제거</li>
<li>b() 함수 종료</li>
<li><strong>b2 출력</strong></li>
<li>b() 함수 call stack 에서 제거</li>
<li>a() 함수 종료</li>
<li><strong>a2 출력</strong></li>
<li>a() 함수 call stack 에서 제거</li>
<li>타이머 만료되어 task queue 에서 call stack 으로 이동</li>
<li><strong>setTimeout 출력</strong></li>
</ol>
<h2 id="정리"><a class="anchor" href="#정리">정리</a></h2>
<ul>
<li>이벤트 루프가 비동기 작업을 처리하는 우선순위는 <strong>Microtask Queue -> Animation Frames -> Task Queue</strong> 순이다.</li>
<li>이벤트 루프는 Microtask Queue나 Animation Frames를 방문할 때는, 큐 안에 있는 <strong>모든 작업들을 수행</strong></li>
<li>Task Queue를 방문할 때는 <strong>한 번에 하나의 작업</strong>만 call stack으로 전달하고 다른 Queue를 순회</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>자바스크립트</category>
        </item>
        <item>
            <title><![CDATA[Convention List]]></title>
            <link>https://hooninedev.com/230707</link>
            <guid isPermaLink="false">https://hooninedev.com/230707</guid>
            <pubDate>Fri, 07 Jul 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Branch 구조와 전략을 세워보자! 코드를 작성하다보면 엄청나게 많은 Branch를 만들어야한다. 어떻게 구조를 잡아야할지 알아보자! Branch Structure(Gitflow 전략) master - 제품의 안정된 버전을 유지하는 브랜치, 배포 가능한 코드만을 허용하며, 직접적인 작업은 X develop - 개발의 중심이 되는 브랜치, 다른 개발자들과의...]]></description>
            <content:encoded><![CDATA[<h2 id="branch-구조와-전략을-세워보자"><a class="anchor" href="#branch-구조와-전략을-세워보자">Branch 구조와 전략을 세워보자!</a></h2>
<p>코드를 작성하다보면 엄청나게 많은 Branch를 만들어야한다. 어떻게 구조를 잡아야할지 알아보자!</p>
<p><img src="/content/230707/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<br>
<h3 id="branch-structuregitflow-전략"><a class="anchor" href="#branch-structuregitflow-전략">Branch Structure(Gitflow 전략)</a></h3>
<ul>
<li><strong><code>master</code></strong>
<ul>
<li>제품의 안정된 버전을 유지하는 브랜치, 배포 가능한 코드만을 허용하며, 직접적인 작업은 X</li>
</ul>
</li>
<li><strong><code>develop</code></strong>
<ul>
<li>개발의 중심이 되는 브랜치, 다른 개발자들과의 협업 및 새로운 기능 개발을 위해 사용</li>
</ul>
</li>
<li><strong><code>feature</code></strong>
<ul>
<li>새로운 기능 개발을 위한 branch, 개별 기능 또는 작업 단위로 생성 되며 develop 에서 분기됨</li>
<li>개발이 완료되어 pr 이 merge 되면 해당 feature branch 는 삭제(줄기관리)</li>
<li>feature 은 작은 단위의 issue 가 된다.</li>
<li>올바른 branch 관리를 하기위해 각 feature 은 해당하는 이슈의 문제만 해결한다!</li>
</ul>
</li>
<li><strong><code>release</code></strong>
<ul>
<li>배포를 준비하기 위한 branch, 배포 전 마지막 테스트와 버그 수정 진행</li>
<li>develop 에서 분기되며, 완료되면 develop과 master 로 병합.</li>
<li>해당 작업을 진행할때는 문서화(Docs) 정리를 해서 이전 버전에대한 관리를 해야함</li>
</ul>
</li>
<li><strong><code>hotfix</code></strong>
<ul>
<li>긴급한 버그 수정을 위한 브랜치. master 에서 분기되어 수정 작업을 진행하고, 완료되면 develop과 master 로 병합.</li>
</ul>
</li>
</ul>
<br>
<h3 id="work-flow"><a class="anchor" href="#work-flow">Work Flow</a></h3>
<ul>
<li>새로운 기능 개발 시: <strong><code>develop</code></strong> 에서 <strong><code>feature</code></strong> 를 생성하여 개발, 기능이 완료되면 <strong><code>develop</code></strong> 으로 병합</li>
<li>버그 수정 시: <strong><code>develop</code></strong> 에서 <strong><code>bugfix</code>,<code>fix</code></strong> 를 생성하여 수정 작업을 진행. 완료되면 <strong><code>develop</code></strong> 로 병합합니다.</li>
<li>배포 시: <strong><code>develop</code></strong> 의 안정된 상태에서 <strong><code>master</code></strong> 로 병합. 버전 태그를 추가하여 배포 버전을 명시</li>
</ul>
<br>
<h3 id="branch-naming-rule"><a class="anchor" href="#branch-naming-rule">Branch Naming Rule</a></h3>
<ul>
<li>master, develop 고정</li>
<li>feature/{issue-number}-{feature-name}
<ul>
<li>칸반으로 이슈에 따른 작업을 진행하기로 했던 것같아서 위 방법을 사용하는게 어떨까</li>
<li>ex) feature/12-love-deajeon / feature/1-init-project 등과 같이</li>
<li><code>-</code> 를 이용해서 연결, 대문자 사용 x</li>
</ul>
</li>
<li>release 는 release/1.3 과 같이 / 다음 버전 정보 등록</li>
</ul>
<br>
<h2 id="커밋-메세지를-명확하게-작성하는-방법은"><a class="anchor" href="#커밋-메세지를-명확하게-작성하는-방법은">커밋 메세지를 명확하게 작성하는 방법은!?</a></h2>
<p><img src="/content/230707/2.jpeg" alt="2.jpeg" loading="lazy" decoding="async"></p>
<p>커밋 메세지는 다른 개발자에게도 보여진다!</p>
<p>너무 자세하게 적어도 문제이고, 대충 적어도 문제인 커밋 메세지를 어떻게 명확하게 작성할까!? 찾아보자!!</p>
<br>
<h3 id="commit-message-convention"><a class="anchor" href="#commit-message-convention">Commit Message Convention</a></h3>
<ul>
<li>Commit Message 작성은 다음 한칸만 띄우고 작성하자 ⇒ <strong>Ex) Feat: 하이루</strong></li>
</ul>
<br>
<table>
<thead>
<tr>
<th>단어</th>
<th>사용법</th>
</tr>
</thead>
<tbody>
<tr>
<td>Feat</td>
<td>새로운 기능을 추가</td>
</tr>
<tr>
<td>Fix</td>
<td>버그 수정</td>
</tr>
<tr>
<td>Design</td>
<td>CSS 등 사용자 UI 디자인 변경</td>
</tr>
<tr>
<td>!BREAKING CHANGE</td>
<td>커다란 API 변경의 경우</td>
</tr>
<tr>
<td>!HOTFIX</td>
<td>급하게 치명적인 버그를 고쳐야하는 경우</td>
</tr>
<tr>
<td>Style</td>
<td>코드 포맷 변경, 세미 콜론 누락, 코드 수정이 없는 경우</td>
</tr>
<tr>
<td>Refactor</td>
<td>프로덕션 코드 리팩토링</td>
</tr>
<tr>
<td>Comment</td>
<td>필요한 주석 추가 및 변경</td>
</tr>
<tr>
<td>Docs</td>
<td>문서 수정</td>
</tr>
<tr>
<td>Test</td>
<td>테스트 코드, 리펙토링 테스트 코드 추가, Production Code(실제로 사용하는 코드) 변경 없음</td>
</tr>
<tr>
<td>Chore</td>
<td>빌드 업무 수정, 패키지 매니저 수정, 패키지 관리자 구성 등 업데이트, Production Code 변경 없음</td>
</tr>
<tr>
<td>Rename</td>
<td>파일 혹은 폴더명을 수정하거나 옮기는 작업만인 경우</td>
</tr>
<tr>
<td>Remove</td>
<td>파일을 삭제하는 작업만 수행한 경우</td>
</tr>
</tbody>
</table>
<br>
<h3 id="commit-message-자세하게-알아보자"><a class="anchor" href="#commit-message-자세하게-알아보자">Commit Message 자세하게 알아보자</a></h3>
<ol>
<li>
<p>Commit message 는 영문으로, 한글로 작성 가능(단, 영문으로 작성시에는 동사를 사용)</p>
</li>
<li>
<p>특수문자는 사용하지않는다.</p>
</li>
<li>
<p>첫글자는 대문자로 작성한다.</p>
</li>
<li>
<p>최대한 자세하고 직관적인 Commit message 를 작성하자</p>
</li>
<li>
<p>이슈 번호는 # 으로 구분짓는다.</p>
<ul>
<li>Ex) 내가 develop branch 에서 이슈번호 13번의 button 의 props 를 함수화하도록 리팩토링 요청을 받으면 ⇒ <strong>feature/13—implement-button-props-function</strong> 이라는 branch 를 만들어서 commit message 는 <strong>Feat: #13 Implement button props as a function</strong></li>
</ul>
</li>
<li>
<p>귀여움을 위해 이모지를 써보는것도..!? (사용하는 회사도 있다고합니다!?)</p>
<table>
<thead>
<tr>
<th>이모지</th>
<th>설명</th>
</tr>
</thead>
<tbody>
<tr>
<td>🎨</td>
<td>코드의 형식 / 구조를 개선 할 때</td>
</tr>
<tr>
<td>📰</td>
<td>새 파일을 만들 때</td>
</tr>
<tr>
<td>📝</td>
<td>사소한 코드 또는 언어를 변경할 때</td>
</tr>
<tr>
<td>🐎</td>
<td>성능을 향상시킬 때</td>
</tr>
<tr>
<td>📚</td>
<td>문서를 쓸 때</td>
</tr>
<tr>
<td>🐛</td>
<td>버그 reporting할 때, @FIXME 주석 태그 삽입</td>
</tr>
<tr>
<td>🚑</td>
<td>버그를 고칠 때</td>
</tr>
<tr>
<td>🐧</td>
<td>리눅스에서 무언가를 고칠 때</td>
</tr>
<tr>
<td>🍎</td>
<td>Mac OS에서 무언가를 고칠 때</td>
</tr>
<tr>
<td>🏁</td>
<td>Windows에서 무언가를 고칠 때</td>
</tr>
<tr>
<td>🔥</td>
<td>코드 또는 파일 제거할 때 , @CHANGED주석 태그와 함께</td>
</tr>
<tr>
<td>🚜</td>
<td>파일 구조를 변경할 때 . 🎨과 함께 사용</td>
</tr>
<tr>
<td>🔨</td>
<td>코드를 리팩토링 할 때</td>
</tr>
<tr>
<td>☔️</td>
<td>테스트를 추가 할 때</td>
</tr>
<tr>
<td>🔬</td>
<td>코드 범위를 추가 할 때</td>
</tr>
<tr>
<td>💚</td>
<td>CI 빌드를 고칠 때</td>
</tr>
<tr>
<td>🔒</td>
<td>보안을 다룰 때</td>
</tr>
<tr>
<td>⬆️</td>
<td>종속성을 업그레이드 할 때</td>
</tr>
<tr>
<td>⬇️</td>
<td>종속성을 다운 그레이드 할 때</td>
</tr>
<tr>
<td>⏩</td>
<td>이전 버전 / 지점에서 기능을 전달할 때</td>
</tr>
<tr>
<td>⏪</td>
<td>최신 버전 / 지점에서 기능을 백 포트 할 때</td>
</tr>
<tr>
<td>👕</td>
<td>linter / strict / deprecation 경고를 제거 할 때</td>
</tr>
<tr>
<td>💄</td>
<td>UI / style 개선시</td>
</tr>
<tr>
<td>♿️</td>
<td>접근성을 향상시킬 때</td>
</tr>
<tr>
<td>🚧</td>
<td>WIP (진행중인 작업)에 커밋, @REVIEW주석 태그와 함께 사용</td>
</tr>
<tr>
<td>💎</td>
<td>New Release</td>
</tr>
<tr>
<td>🔖</td>
<td>버전 태그</td>
</tr>
<tr>
<td>🎉</td>
<td>Initial Commit</td>
</tr>
<tr>
<td>🔈</td>
<td>로깅을 추가 할 때</td>
</tr>
<tr>
<td>🔇</td>
<td>로깅을 줄일 때</td>
</tr>
<tr>
<td>✨</td>
<td>새로운 기능을 소개 할 때</td>
</tr>
<tr>
<td>⚡️</td>
<td>도입 할 때 이전 버전과 호환되지 않는 특징, @CHANGED주석 태그 사용</td>
</tr>
<tr>
<td>💡</td>
<td>새로운 아이디어, @IDEA주석 태그</td>
</tr>
<tr>
<td>🚀</td>
<td>배포 / 개발 작업 과 관련된 모든 것</td>
</tr>
<tr>
<td>🐘</td>
<td>PostgreSQL 데이터베이스 별 (마이그레이션, 스크립트, 확장 등)</td>
</tr>
<tr>
<td>🐬</td>
<td>MySQL 데이터베이스 특정 (마이그레이션, 스크립트, 확장 등)</td>
</tr>
<tr>
<td>🍃</td>
<td>MongoDB 데이터베이스 특정 (마이그레이션, 스크립트, 확장 등)</td>
</tr>
<tr>
<td>🏦</td>
<td>일반 데이터베이스 별 (마이그레이션, 스크립트, 확장명 등)</td>
</tr>
<tr>
<td>🐳</td>
<td>도커 구성</td>
</tr>
<tr>
<td>🤝</td>
<td>파일을 병합 할 때</td>
</tr>
</tbody>
</table>
</li>
</ol>
<br>
<h2 id="코드를-규칙을-가지고-이쁘게-작성하는-방법"><a class="anchor" href="#코드를-규칙을-가지고-이쁘게-작성하는-방법">코드를 규칙을 가지고 이쁘게 작성하는 방법!</a></h2>
<p>이쁜 코드를 작성하기 위해서 사람들과 양식을 정해야한다!! 그래야 여러사람이 쓸모없는 시간을 코드 수정하는데 사용하지 않기 때문에!!</p>
<p><del>똑바로 안하면, 이렇게 비둘기가 돌아버린다!!</del></p>
<p><img src="/content/230707/3.gif" alt="3.gif" loading="lazy" decoding="async"></p>
<br>
<h3 id="code-convention"><a class="anchor" href="#code-convention">Code Convention</a></h3>
<ol>
<li>
<p>Naming conventions</p>
<ul>
<li>
<p>Components 는 Pascal Case(첫 단어 대문자)</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">Component.tsx Footer.tsx Mainbox.tsx</span></span></code></pre></figure>
</li>
<li>
<p>Non-components 는 Camel Case(띄어쓰기대신 대문자로 구분)</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">loveDeajeon.ts iWentGoingJeju.ts hungryReact.ts</span></span></code></pre></figure>
</li>
<li>
<p>Unit Test(단위테스트) 는 파일명과 동일하게 동작</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">MainItem.ts </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">=></span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> MainItem.test.ts</span></span></code></pre></figure>
</li>
</ul>
</li>
<li>
<p>Bug Avoidance(버그피하기)</p>
<ul>
<li>null, undefined 일 조건은 optional chaning(?.) 연산자 사용</li>
<li>console.log 남기지 말기</li>
<li>side-effects 방지를 위해 외부데이터 props 로 받아서 사용</li>
<li>props 는 읽기전용으로 직접수정 x</li>
<li>불필요한 주석은 남기지 않기</li>
<li>React 의 return 내부에 직접적인 함수처리 자제하기</li>
<li>각 function , import 사이에 1줄 공백으로 구분하기</li>
</ul>
</li>
<li>
<p>ES6</p>
<ul>
<li>spread 연산자 사용</li>
<li>구조분해할당 사용</li>
<li>let, const 사용 (var 은 사용금지)</li>
<li>화살표함수 적극사용</li>
<li>직접적인 null 을 체크해주는 코드 만들지말고 optional chaining 사용</li>
</ul>
</li>
<li>
<p>TypeScript</p>
<ul>
<li>클래스, 타입, 인터페이스, 변수, 함수, 메서드에 Camel Case 사용(띄어쓰기 대신 대문자)</li>
<li>상수는 Snake Case 사용(언더바), 그리고 상수이름 명확하게 할 것</li>
<li>공백 2개또는 4개 설정하기</li>
<li>변수, 함수, 매개변수, 함수 반환에 명시적 타입선언</li>
<li>줄바꿈과 공백을 함수사이에 적극 사용해 구분, 연관된 코드끼리 붙어있도록 신경쓰기</li>
</ul>
</li>
</ol>
<br>
<h3 id="prettier-eslint-airbnb-js-style"><a class="anchor" href="#prettier-eslint-airbnb-js-style">Prettier, ESlint, airbnb Js style</a></h3>
<ul>
<li>
<p>prettier , eslint , airbnb js style 설치</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">yarn add prettier </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">--</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">save</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">dev </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">--</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">save</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">exact</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">yarn add eslint </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">--</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">save</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">dev</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">yarn add </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">peerdeps </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">--</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">dev eslint</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">config</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">airbnb</span></span></code></pre></figure>
</li>
<li>
<p>eslint, prettier 충돌방지 설정</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="tsx" data-theme="github-dark github-light"><code data-language="tsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">yarn add eslint</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">plugin</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">prettier eslint</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">config</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">prettier </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">--</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">save</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">-</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">dev</span></span></code></pre></figure>
</li>
<li>
<p>eslint 설정파일 생성
.eslintrc.json 파일 생성 시 아래와 같은 내용을 추가한다.</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "env"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "browser"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "es6"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "node"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "extends"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"airbnb"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"plugin:prettier/recommended"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">],</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "rules"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "react/jsx-filename-extension"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">1</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, { </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">"extensions"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">".js"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">, </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">".jsx"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">] }]</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  }</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<ul>
<li>env 부분 ( document나 setTimeout과 같은 변수는 browser에서 사용되는 변수인데 설정하지 않는다면 ESLint에러로 인식하기 때문, 마찬가지로 node, es6에서 쓰이는 것들도 에러나지 않게하기 위해서 설정 )</li>
<li>extends 부분
<ul>
<li>위에서 설치한 airbnb JS style 적용하기 위해 <code>airbnb</code> 추가한다.</li>
<li><code>plugin:prettier/recommended</code>를 추가함으로써 prettier 규칙을 eslint 규칙에 추가하고 prettier와 충돌하는 eslint 규칙을 끄는 역할을 한다.
결국 <code>eslint-config-prettier</code>와 <code>eslint-plugin-prettier</code>를 적용하기 위해서 작성한 것</li>
</ul>
</li>
<li>rules 부분
<ul>
<li><code>"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }]</code>
(Component.js와 같은 js파일에서 jsx문법을 사용했을 때 오류가 발생하는 문제를 해결할 수 있다.)</li>
<li>아래의 내용을 참고하여 추가하고 싶은 rule이 있다면 추가적으로 작성하면 된다.</li>
</ul>
</li>
</ul>
</li>
<li>
<p>prettier 생성파일 생성</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="jsx" data-theme="github-dark github-light"><code data-language="jsx" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// .prettierrc.js</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 일부 규칙을 추가했다.</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">// 그리고 주석을 적기 위해 json이 아닌 js파일로 생성하였음.</span></span>
<span data-line=""> </span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">module</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">.</span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">exports</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49"> =</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E"> {</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  singleQuote: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 문자열은 singleQuote로 ("" -> '')</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  semi: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  //코드 마지막에 세미콜론이 있게 formatting</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  tabWidth: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">4</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 들여쓰기 너비는 4칸</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  trailingComma: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"all"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 배열 키:값 뒤에 항상 콤마를 붙히도록 formatting</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  printWidth: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">80</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 코드 한줄이 maximum 80칸</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  arrowParens: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"avoid"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // 화살표 함수가 하나의 매개변수를 받을 때 괄호를 생략하게 formatting</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  endOfLine: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"auto"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // windows에 뜨는 'Delete cr' 에러 해결</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">};</span></span></code></pre></figure>
<ul>
<li>prettier는 기본적으로 프로젝트의 root에 있는 .prettierrc 파일에 적힌 규칙에 의해서 동작.</li>
<li>프로젝트에 이 파일이 없으면 기본값으로 세팅된다.</li>
</ul>
</li>
<li>
<p>setting.json (ctrl + , 또는 cmd +,)</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="json" data-theme="github-dark github-light"><code data-language="json" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">{</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // Set the default</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "editor.formatOnSave"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">  // Enable per-language</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "[javascript]"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "editor.formatOnSave"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">false</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "editor.codeActionsOnSave"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: {</span></span>
<span data-line=""><span style="--shiki-dark:#6A737D;--shiki-light:#6A737D">    // For ESLint</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">    "source.fixAll.eslint"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">  },</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "eslint.alwaysShowStatus"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">true</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">,</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "prettier.disableLanguages"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: [</span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"js"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">],</span></span>
<span data-line=""><span style="--shiki-dark:#79B8FF;--shiki-light:#005CC5">  "files.autoSave"</span><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">: </span><span style="--shiki-dark:#9ECBFF;--shiki-light:#032F62">"onFocusChange"</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">}</span></span></code></pre></figure>
<ul>
<li><code>"[javascript]": { "editor.formatOnSave": false },</code></li>
<li>VSCode에 내장되어 있는 자바스크립트 포맷팅 기능을 사용하지 않고 Prettier 익스텐션을 사용하기 위해서 설정.</li>
<li><code>"editor.codeActionsOnSave": { // For ESLint "source.fixAll.eslint": true },</code>ESLint에 의한 자동 수정 기능을 활성화하기 위해서 설정</li>
<li><code>"eslint.alwaysShowStatus": true,</code>Vscode 하단 바에 ESLint도 표시되기 위해서 설정</li>
<li><code>"prettier.disableLanguages": ["js"],</code>ESLint와 연동된 prettier를 사용하기 위해서 사용한다고 한다.</li>
<li><code>"files.autoSave": "onFocusChange",</code>에디터 바깥으로 포커스가 이동 시 파일을 자동으로 저장하기 위해서 설정</li>
</ul>
</li>
</ul>
<br>
<h2 id="folder-structure"><a class="anchor" href="#folder-structure">Folder Structure</a></h2>
<p>프론트엔드 스택은 React(vite), Typescript, Jotai , Styled-components, React Query, Axios, react-router-dom 를 예시로 했습니다!</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="javascript" data-theme="github-dark github-light"><code data-language="javascript" data-theme="github-dark github-light" style="display: grid;"><span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">src</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">├── components</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   ├── common</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   │   ├── Button.tsx</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   │   └── Input.tsx</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   ├── layout</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   │   ├── Header.tsx</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   │   └── Footer.tsx</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   └── features</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│       ├── Feature1</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│       │   ├── Feature1.tsx</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│       │   ├── Feature1.styles.ts</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│       │   └── Feature1.test.tsx</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│       └── Feature2</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│           ├── Feature2.tsx</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│           ├── Feature2.styles.ts</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│           └── Feature2.test.tsx</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">├── hooks</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   ├── useAuth.ts</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   ├── useData.ts</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   └── </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">├── pages</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   ├── Home.tsx</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   ├── About.tsx</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   ├── Contact.tsx</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   └── </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">├── services</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   ├── api.ts</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   └── </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">├── store</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   ├── store.ts</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   └── </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">├── styles</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   ├── globalStyles.ts</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">│   └── theme.ts</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">└── utils</span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">/</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    ├── helpers.ts</span></span>
<span data-line=""><span style="--shiki-dark:#E1E4E8;--shiki-light:#24292E">    └── </span><span style="--shiki-dark:#F97583;--shiki-light:#D73A49">...</span></span></code></pre></figure>
<ul>
<li><strong><code>components/</code></strong> 폴더는 애플리케이션의 컴포넌트들을 저장하는 곳
<ul>
<li><strong><code>common/</code></strong> 폴더는 여러 곳에서 사용되는 공통 컴포넌트를 저장</li>
<li><strong><code>layout/</code></strong> 폴더는 애플리케이션의 레이아웃을 구성하는 컴포넌트들을 저장</li>
<li><strong><code>features/</code></strong> 폴더는 각각의 기능에 대한 컴포넌트들을 저장</li>
<li><strong><code>features/Feature1/</code></strong> 폴더에는 <strong><code>Feature1.tsx</code></strong> 컴포넌트와 해당 컴포넌트에 대한 스타일 및 테스트 파일을 저장</li>
</ul>
</li>
<li><strong><code>hooks/</code></strong> 폴더는 사용자 정의 훅을 저장
<ul>
<li><strong><code>useAuth.ts</code></strong> 파일은 인증과 관련된 커스텀 훅을 포함</li>
<li>코드의 재사용성을 높이고 로직을 분리하는 데 도움</li>
</ul>
</li>
<li><strong><code>pages/</code></strong> 폴더는 각각의 페이지 컴포넌트를 저장
<ul>
<li><strong><code>Home.tsx</code></strong>, <strong><code>About.tsx</code></strong>, <strong><code>Sign.tsx</code></strong> 등의 파일을 의미</li>
</ul>
</li>
<li><strong><code>services/</code></strong> 폴더는 데이터 통신과 관련된 파일 저장
<ul>
<li><strong><code>api.ts</code></strong> 파일은 API 호출을 추상화하는 함수들을 포함</li>
</ul>
</li>
<li><strong><code>store/</code></strong> 폴더는 전역 상태 관리를 위한 파일들을 저장 (jotai)
<ul>
<li><strong><code>store.ts</code></strong> 파일은 상태 관리를 설정하고 초기화하는 데 사용</li>
</ul>
</li>
<li><strong><code>styles/</code></strong> 폴더는 글로벌 스타일과 테마 설정과 관련된 파일 저장
<ul>
<li><strong><code>globalStyles.ts</code></strong> 파일은 글로벌 CSS 스타일을 설정</li>
<li><strong><code>theme.ts</code></strong> 파일은 애플리케이션의 테마 관련 상수나 스타일을 정의</li>
<li>styled-components 는 여기서 관리 x</li>
</ul>
</li>
<li><strong><code>utils/</code></strong> 폴더는 유틸리티 함수나 도우미 함수를 저장
<ul>
<li>이러한 함수들은 애플리케이션 전반에서 사용</li>
</ul>
</li>
</ul>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>프론트엔드</category>
            <category>소박한궁금증</category>
        </item>
        <item>
            <title><![CDATA[AWS Summit Seoul 2023 - Day2에 다녀와서]]></title>
            <link>https://hooninedev.com/230504</link>
            <guid isPermaLink="false">https://hooninedev.com/230504</guid>
            <pubDate>Thu, 04 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[AWS Summit 2023 2일차입니다. 1일차 행사에서는 AWS 의 기술들을 각 기업들이 어떻게 사용되고 발전되었는지가 주제였다면 이번 2일차에서는 난이도 별로 AWS에 대해서 설명해주고, 발전된 기술을 보여주는 시간을 가졋습니다. 저는 2일차에 참석해서 전문가들의 강연을 듣고 많은 것을 배우며 본 내용을 공유하고자 합니다. AWS Summit 2023...]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>AWS Summit 2023 2일차입니다.
1일차 행사에서는 AWS 의 기술들을 각 기업들이 어떻게 사용되고 발전되었는지가 주제였다면
이번 2일차에서는 난이도 별로 AWS에 대해서 설명해주고, 발전된 기술을 보여주는 시간을 가졋습니다.
저는 2일차에 참석해서 전문가들의 강연을 듣고 많은 것을 배우며 본 내용을 공유하고자 합니다.</p>
</blockquote>
<hr>
<aside>
🔖 AWS Summit 2023 Day2 - TimeLine
</aside>
<hr>
<h2 id="기조연설"><a class="anchor" href="#기조연설">기조연설</a></h2>
<blockquote>
<p>윤석찬 AWS 수석 테크 에반젤리스트, 강동한 Flitto CTO,
송주영 LG U+ 연구위원, Holly Mescrobian AWS 서버리스 컴퓨팅 부사장</p>
</blockquote>
<p><img src="/content/230504/1.png" alt="1.png" loading="lazy" decoding="async"></p>
<p>기조연설에서 윤석찬님과 송주영님, 강동한님의 인터뷰 형식 그리고 AWS Holly Mescrobian 님과 깜짝 출현하신 Werner Vogels AWS CTO 님의 사전 녹화영상으로 이루어졌습니다.</p>
<p>많은 흥미로운 주제중 저는 <strong>입문자들이 AWS 를 어떻게 이용해보고 구성해볼지 에 대해서 생각해보자</strong>라는 주제가 가장 흥미로웠습니다.</p>
<p>그것에 대해서 말씀하신 강연은 <strong>AWS Application Composer</strong>를 사용해 시각적으로 구성해보기와 <strong>AWS Well-Architected</strong> 활용해보기 입니다.</p>
<p>위 주제는 하단에 상세하게 기록해두었으니 한번 확인해보시면 될 것 같습니다.</p>
<h3 id="보안과-데브옵스"><a class="anchor" href="#보안과-데브옵스">보안과 데브옵스</a></h3>
<p><strong>a. 보안</strong></p>
<ul>
<li>보안이라는 것은 접근통제, 암호화, 마스킹, 감시, 추적, 적합한 인증과 권한 관리를 포함한 내용입니다.</li>
<li>되도록 접근 통제로 모든 것을 해결하려 하지 않고 다른 것도 잘 활용 해야한다.</li>
<li>보안그룹 ID, DB 패스워드를 평문으로 박지 않도록 주의 → AWS Secrets Manager, Parameter 등 활용 가능</li>
<li>보안과 암호화에 평소 관심이 많이 있어서 오후에 있는 Cisco 기업의 강연이 있는것을 알게되고 자세한 내용은 후반에 다시 다루겠습니다!</li>
</ul>
<p><strong>b. 데브옵스</strong></p>
<ul>
<li>송주영님께서 데브옵스의 5단게의 중요성에 대해서 말씀해주셧습니다</li>
<li>Security > Reliability > > Automation > Organization. Standard. Governance</li>
<li>만약 거꾸로 정책부터 적용하려하다면 한계점이 생기게 된다 → <strong>엔지니어를 묶어두게 되는 것</strong></li>
</ul>
<hr>
<h2 id="가격은-절반-성능은-두-배-확-달라진-amazon-ec2-알아보기"><a class="anchor" href="#가격은-절반-성능은-두-배-확-달라진-amazon-ec2-알아보기">가격은 절반, 성능은 두 배 확 달라진 Amazon EC2 알아보기</a></h2>
<blockquote>
<p>박연경 AWS 솔루션즈 아케텍트, 김현철 삼성전자 DevOps Engineer</p>
</blockquote>
<p><img src="/content/230504/2.png" alt="2.png" loading="lazy" decoding="async"></p>
<h3 id="1-두번째-강연은-ec2-가-무엇인지-ec2-가-어떻게-바뀌었는지에-대한-박연경님의-강연이었습니다"><a class="anchor" href="#1-두번째-강연은-ec2-가-무엇인지-ec2-가-어떻게-바뀌었는지에-대한-박연경님의-강연이었습니다">1. 두번째 강연은 EC2 가 무엇인지, EC2 가 어떻게 바뀌었는지에 대한 박연경님의 강연이었습니다.</a></h3>
<ol>
<li>EC2 는 클라우드에서 가상 컴퓨팅 리소스를 제공하는 서비스로서 사용자들은 EC2 를 사용하여 AWS 클라우드에서 가상 서버를 설정하고 구성하여 필요에 따라 늘이고 축소할 수있습니다.</li>
<li>AWS 는 EC2 의 가용성과 성능을 더욱 향상해 가장 광범위하고 심층적인 인스턴스로 선택받을 수 있도록 발전하고 있다고 합니다</li>
<li>다음은 AWS Nitro 시스템 아키텍처에 대해서 설명했는데 Nitro 는 고성능, 저비용, 보안 최적화의 가장 좋은 EC2의 독점 가상화 인프라인으로 소개했습니다.
<ul>
<li>주요기능에는 Nitro Cards, Nitro 보안 칩으로 보안기능을 통해 가장 안전한 클라우드 플랫폼을 지원하고, Nitro 하이퍼바이저로 메모리 및 CPU 할당을 관리하고 베어 메탈 수준의 성능을 제공하는 경량 하이퍼바이저 입니다.</li>
</ul>
</li>
<li>AWS 의 Graviton 을 사용해 최고성능, 전력 소비 절감, 비용절감, 가성비 향상을 할수 있다
고합니다.</li>
</ol>
<h3 id="2-요약하면-성능-개선된-ec2-는-아래와-같은-기능이-발전되었습니다"><a class="anchor" href="#2-요약하면-성능-개선된-ec2-는-아래와-같은-기능이-발전되었습니다">2. 요약하면 성능 개선된 EC2 는 아래와 같은 기능이 발전되었습니다.</a></h3>
<p>a. 네트워크 특화(200Gbps) 6세대 인스턴스</p>
<ul>
<li>M, R, C 타입, 인텔 아이스레이크 기반, 최대 200Glops 네트워크 대역폭</li>
<li>EBS 속도의 획기적 개선(대역폭 최대 80Gbps), PPS 2배 개선</li>
</ul>
<p>b. Nitro v5 신규 런치</p>
<ul>
<li>전 세대 대비, 60% 높은 PPS, 30% 낮아진 딜레이</li>
<li>와트당 성능 40% 향상 , c7 gn(미리보기)</li>
</ul>
<p>c. AWs Graviton 3세대 출시</p>
<ul>
<li>부동소수점 및 벡터 연산에 특화된 Graviton 3 아키텍처</li>
<li>c7g, c7gn(미리보기)</li>
</ul>
<p>d. HPC 고성능 컴퓨팅을 위한 신규 인스턴스 출시</p>
<ul>
<li>Hpc7g(Graviton 기반의 최초의 HPC 전용 인스턴스)</li>
<li>Hocsid(인델 Xeon Scalable 기반)</li>
</ul>
<p>e. 기계학습/추론을 위한 인스턴스 출시</p>
<ul>
<li>TRN1/TRNIn</li>
<li>INF2</li>
</ul>
<h3 id="3-박연경님-다음에는-삼성전자-김현철님께서-삼성개발팀에서-aws-ec2-를-사용한-이유에-대해서-설명해주셧습니다"><a class="anchor" href="#3-박연경님-다음에는-삼성전자-김현철님께서-삼성개발팀에서-aws-ec2-를-사용한-이유에-대해서-설명해주셧습니다">3. 박연경님 다음에는 삼성전자 김현철님께서 삼성개발팀에서 AWS EC2 를 사용한 이유에 대해서 설명해주셧습니다.</a></h3>
<p><img src="/content/230504/3.png" alt="3.png" loading="lazy" decoding="async"></p>
<ol>
<li>삼성에서는 비용절감, ARM 기반 프로세서 약진, 저전력 탄소배출, 성능향상등의 이유로 AWS의 최신EC2 사용을 권장하고있습니다. 그럼으로서 3세대 Graviton 인스턴트를 사용 하는 것에 대해 강연해주셧습니다.</li>
<li>그럼으로써 삼성은 API 서버의 응답속도를 개선하고, 인스턴스 비용을 15% 절감하는 효과
를 얻었습니다</li>
</ol>
<hr>
<h2 id="aws의-개발자를-위한-신규-서비스-소개"><a class="anchor" href="#aws의-개발자를-위한-신규-서비스-소개">AWS의 개발자를 위한 신규 서비스 소개</a></h2>
<p>(CodeCatalyst, CodeWhisperer)</p>
<blockquote>
<p>한정호 AWS 솔루션즈 아키텍트</p>
</blockquote>
<h3 id="1-codewhisperer"><a class="anchor" href="#1-codewhisperer">1. CodeWhisperer</a></h3>
<p><a href="https://aws.amazon.com/ko/codewhisperer/">ML 기반 코딩 도우미 - Amazon CodeWhisperer - Amazon Web Services</a></p>
<p>두번째 강연은 AWS 의 강연에서 가장 흥미로운 부분이었다. 팀장님께서 일하다가 링크로 보여주셧을때부터 살펴보앗던 CodeWhisperer 이었다. 요즘 ChatGPT 부터 AI가 만들어주는 코드에 대해서 많은 이야기들이 많은데, 그런 것들을 경험해 보면서 AWS 에서는 어떻게 처리할까에 대해, CodeWhisperer 은 개발자에 AI 기술이 집중되어 있다는 생각이 들었다.
데모와 강연을 보는동안에 구체적인 아이디어에 감탄하고, 더 나아가 개발자의 습성, 습관을 파악하는 기술력에 놀람을 감추지못하엿다. 데모영상을 보고 다른 사람들도 CodeWhisperer 을 사용해보면 좋겠다.</p>
<h3 id="2-codecatalyst"><a class="anchor" href="#2-codecatalyst">2. CodeCatalyst</a></h3>
<p><a href="https://aws.amazon.com/ko/codecatalyst/">통합형 개발 서비스 – Amazon CodeCatalyst – Amazon Web Services</a></p>
<p>그 다음 보여준 기술은 AWS CodeCatalyst 입니다. CodeCatalyst 의 주요 기능에는 4가지가 있습니다.</p>
<ol>
<li>프로젝트 블루프린트 : 다양한 애플리케이션 워크로드를 빠르게 시작</li>
</ol>
<p><img src="/content/230504/4.png" alt="4.png" loading="lazy" decoding="async"></p>
<p><img src="/content/230504/5.png" alt="5.png" loading="lazy" decoding="async"></p>
<p>b. 관리형 CI/CD pipeline : Flexsible 한 관리형 인프라에서 실행되는 자동화된 workflow</p>
<p><img src="/content/230504/6.png" alt="6.png" loading="lazy" decoding="async"></p>
<p>c. 개발/배포 환경 : 손쉬운 개발 및 배포환경 구성 및 자동화</p>
<p><img src="/content/230504/7.png" alt="7.png" loading="lazy" decoding="async"></p>
<p>d. 원활한 협업 : 쉽게 동료들 프로젝트에 초대하고 협업 도구와 프로젝트 리소스에 즉시 엑세스</p>
<p><img src="/content/230504/8.png" alt="8.png" loading="lazy" decoding="async"></p>
<p><img src="/content/230504/9.png" alt="9.png" loading="lazy" decoding="async"></p>
<p>간단히 말하면 개발을 위한 작업을 할때 FrontEnd, BackEnd, Build, Deplooy, CI/CD 등 개발에 필요한 모든 기능을 AWS CodeCatalyst 를 통해 한번에 설정, 운영, 관리할수 있다. 어쩌면 개발을 할 때 가장 많이 사용하는 시간이 설정과 개발환경에 대한 개발자들의 조정이라고 생각한다.</p>
<p>이런 부분을 AWS 가 간략화했다는 부분과 발상으로만 했던 것이 행위로 이루어진것에 대해서 감탄하게 되었다.이 글을 보는 분들도 바로 사용은 해보지 않아도 경험해 보았으면 좋겠다!</p>
<hr>
<h2 id="기업-소개---cisco"><a class="anchor" href="#기업-소개---cisco">기업 소개 - Cisco</a></h2>
<blockquote>
<p>Cisco</p>
</blockquote>
<p><img src="/content/230504/10.png" alt="10.png" loading="lazy" decoding="async"></p>
<p>AWS 기술관련 강연을 들을까 아니면 기업에 대한 설명강연을 들을까 계획을 짜던 도중, 암호화에 대해서 평소 관심이 많아 보안관련 회사이자 우리회사 모기업인 인성정보가 후원사로 있는 Cisco 강연을 듣게되었습니다.</p>
<p>Cisco 에 대한 기업은 전날 EXPO 부스에서 살짝 본적은 있지만, 그때는 정확히 알지못했는데 회사기획서와 보안관련으로 찾아보고 나서 관심을 가져 기업에 대해 찾아보게 되었습니다. 제가 아는 Cisco 는 유무선 프로토콜 및 통신장비를 개발하는 HW 업체라고 알고있었습니다. 하지만 추후 Duo 를 통해 보안관련 플랫폼을 개발하게 되었다고 알게되었습니다.</p>
<p>강연에서는 제로 트러스트 보안 플랫폼인 Cisco 에 대해서 설명하고, 어떠한 인증방법으로 사용자의 존재 유무를 파악하고, 어떤 경우에 악의적 사용자임을 판별해서 결과를 반환하는지에 대해 알게되었습니다.</p>
<h3 id="zero-trust"><a class="anchor" href="#zero-trust">Zero-Trust</a></h3>
<ul>
<li>애플리케이션이 클라우드로 이동함에 따라, 더 쉽게 접근이 가능</li>
<li>취약한 비밀번호, 비밀번호의 재사용, 계정 탈취 공격에 따른 공격자의 애플리케이션 접근 가능성 증가</li>
<li>MFA 우회, 디바이스 보안상, 사용자 권한 및 비정상적인 행위 탐지 부재로 인한 보안 정책 우회</li>
</ul>
<p><img src="/content/230504/11.png" alt="11.png" loading="lazy" decoding="async"></p>
<p>개인정보에 대한 보호와 함께 기업들의 IT 보안도 화두로 올라온 만큼, Cisco 의 Duo 그리고, AWS를 통한 행보가 엄청나다고 느껴지는 강연이었습니다. 이날 마지막 강연이 끝나고 Cisco 부스에 방문해 더 설명을 듣게 되었고 6월 27일 Cisco Connect Korea 2023 에 대해서 전달받게 되었습니다!</p>
<p>현재에는 알고있는 지식이 부족하지만 많은 정보들을 들으며 IT 인으로서 발전해야겠다는 생각을 하
게되었습니다.</p>
<p><a href="https://www.cisco.com/c/m/ko_kr/ciscoconnect.html">[시스코] Cisco Connect Korea 2022</a></p>
<hr>
<h2 id="비즈니스-전쟁에서-승리하기위한-신무기-aws-aiml-서비스"><a class="anchor" href="#비즈니스-전쟁에서-승리하기위한-신무기-aws-aiml-서비스">비즈니스 전쟁에서 승리하기위한 신무기 AWS AI/ML 서비스</a></h2>
<blockquote>
<p>구제훈 AWS 이그제큐티브 테크놀로지 파트너</p>
</blockquote>
<p><img src="/content/230504/12.png" alt="12.png" loading="lazy" decoding="async"></p>
<p>요즘 ChatGPT, LaMDA, 바드, New Bing 과 같이 AI/ML 서비스들이 넘쳐나고 있다. 이럴때 우리와 기업들은 어떻게 선점하고, 적용할 수 있을까라는 생각을 하고있을거같다. 나도 비슷한 생각이 들어서 이번 강연을 선택해서 듣게되었다.</p>
<p>구제훈님께서 강연을 하며 말씀하신 6가지 AWS 서비스에 대해서 정리해서 전달해보겠다.</p>
<h3 id="1-개인화-서비스---고객-경험-맞춤화"><a class="anchor" href="#1-개인화-서비스---고객-경험-맞춤화">1. 개인화 서비스 - 고객 경험 맞춤화</a></h3>
<p>a. 고객참여유도
b. 검색가능성
c. 효율성 및 수익</p>
<h3 id="2-aiccai-enabled-contact-center---지능형-콜센터"><a class="anchor" href="#2-aiccai-enabled-contact-center---지능형-콜센터">2. AICC(AI-Enabled Contact Center) - 지능형 콜센터</a></h3>
<p>a. 셀프 서비스 가상 상담원
b. 실시간 통화 분석 및 상담원 지원
c. 통화 후 분석
⇒ 구제훈님께서는 AI/ML 부분에서 가장 강점을 보일 분야가 콜센터라고 하셧는데, 저도 이야기를 들어보니 AI/ML 이 추구하는 방향성에 제일 적합하다는 생각이 들었습니다.</p>
<h3 id="3-시계열-예측"><a class="anchor" href="#3-시계열-예측">3. 시계열 예측</a></h3>
<h3 id="4-사기-탐지"><a class="anchor" href="#4-사기-탐지">4. 사기 탐지</a></h3>
<p>a. 신규 계정 가입
b. 실시간 결제
c. 온라인 체크아웃
d. 로열티 및 프로모션
e. 그래프 신경망(GNN : Graph Neural Networks) 기반 사기탐지</p>
<ul>
<li>GNN 은 그래프 구조의 정보를 사용자 및 트랜잭션의 속성과 결합하여 악성 사용자와 이벤트를 정상 사용자의 이벤트와 구별하는 학습 활용</li>
</ul>
<h3 id="5-지능형-검색---aws-korea"><a class="anchor" href="#5-지능형-검색---aws-korea">5. 지능형 검색 - AWS Korea</a></h3>
<p>a. 자연어 및 키워드 쿼리
b. 비정형 문서 내장검색 결과</p>
<h3 id="6-산업-장비-모니터링--스마트-팩토리"><a class="anchor" href="#6-산업-장비-모니터링--스마트-팩토리">6. 산업 장비 모니터링- 스마트 팩토리</a></h3>
<p>a. 설비 예지 보전
b. 시각적 품질 검사 자동화
c. 엣지 운영 효율성 향상
d. 수요 예측 및 재고 관리</p>
<p>아직 AI/ML 부분에 대해서 많이 낯설다. 아무래도 나는 기술을 발전시키는 역할보다는 아직 사용하는 역할이여서 그런 것 같다. 내가 사용을 하면서 느낀부분에서 틀린 부분이 있겠지만, 점차 AI/ML 이 삶에 필수적인 생각이 들지
만 아직 대체적인 것은 아닌것 같다. (상호보완적?)</p>
<p>앞으로 수많은 기업에서 AI/ML 을 어떻게 발전시킬지에 대해서 더 관심을 가지게 되는 시간이었다!</p>
<hr>
<h2 id="mongodb-atlas와-함께하는-developer-data-platform"><a class="anchor" href="#mongodb-atlas와-함께하는-developer-data-platform">MongoDB Atlas와 함께하는 Developer Data Platform</a></h2>
<blockquote>
<p>김준 MongoDB 솔루션 아케텍트 매니저</p>
</blockquote>
<p><img src="/content/230504/13.png" alt="13.png" loading="lazy" decoding="async"></p>
<p>AWS Summit 에 참가해서, 아마 내가 그나마 제일많이 써본 DB 인 것 같다. 그래서 제일먼저 강연을 들어봐야겠다는 생각을 하게되었다.</p>
<p>Mongo DB 의 김준님께서는 2019년에 참가하고 그 이후 2023 년에 행사에 참여했다고 했다. 아무래도 많은 시간동안 Mongo DB 도 많은 기술적 발전을 했다고 생각한다. 개발자 입장에서 생산성을 증가시키고, 광범위하게 사용하기 위한 쿼리 기능과 트랜잭션 보장, 대규모 성능 및 고 가용성 제공을 위한 사당과 복제, 암호화와 풍부한 보안 기능으로 강력한 데이터 보완등 2023년의 Mongo DB 를 보면서 2022년 사이드프로젝트를 진행하면서 내가 MongoDB 를 사용해본 이유지 않을까? 라는 생각이 들었다.</p>
<p>Mongo DB 에서는 총 6가지의 키워드를 가지고 강연을 진행했다.</p>
<h3 id="1-atlas-search"><a class="anchor" href="#1-atlas-search">1. Atlas Search</a></h3>
<p>a. 간단하고 단순한 사용
b. 통합된 관리형 서비스
c. 풍부함 Search 쿼리</p>
<h3 id="2-online-arching--automated-data-tiering"><a class="anchor" href="#2-online-arching--automated-data-tiering">2. Online Arching : Automated Data Tiering</a></h3>
<p>a. 자동화 된 데이터 Tiering
b. 쉬운 쿼리 기능
c. 완전 관리형</p>
<h3 id="3-atlas-data-federation"><a class="anchor" href="#3-atlas-data-federation">3. Atlas Data Federation</a></h3>
<p>a. Simplify Data Workflows</p>
<ul>
<li>복잡한 데이터 파이프라인 없이 원활하게 데이터를 집계하고 선호하는 스토리지 위치에 결과를 저장합니다.</li>
</ul>
<p>b. Power Real-Time Apps</p>
<ul>
<li>데이터 이동 또는 변환 없이 AtLas 및 AWS S3상에서 Federation 쿼리 지원</li>
</ul>
<p>c. Get Faster Insights</p>
<ul>
<li>MongoDB 데이터를 열(Columnar) 형식 파일 형식으로 변환 후, 다운스트림 팀에서 사용할 수 있도록 S3에 저장</li>
</ul>
<h3 id="4-atlas-charts--powerful--effective-visualization"><a class="anchor" href="#4-atlas-charts--powerful--effective-visualization">4. Atlas Charts : Powerful &#x26; Effective Visualization</a></h3>
<h3 id="5-atlas-app-service"><a class="anchor" href="#5-atlas-app-service">5. Atlas App Service</a></h3>
<h3 id="6-future-proof-기능-제공"><a class="anchor" href="#6-future-proof-기능-제공">6. Future-Proof 기능 제공</a></h3>
<p><img src="/content/230504/14.png" alt="14.png" loading="lazy" decoding="async"></p>
<p>MongoDB 의 2019 년과 2023 의 차이는 정확히 인지하지 못했다. 아무래도 2019년에 나는 뭘했을
까… 그렇기에 2023년 이후의 MongoDB 에 어떠한 발전이 있을지 지켜보면서 DB 에 관심을 가져
봐야겠다는 생각이 들었다!</p>
<hr>
<h2 id="2023-aws-summit-행사-참여-후기"><a class="anchor" href="#2023-aws-summit-행사-참여-후기">2023 AWS Summit 행사 참여 후기</a></h2>
<p>짧다면 짧을 길다면 길을 행사가 마무리가 되었다. 먼저 2일동안 행사에 참여할 기회를 주신 회사 선임분들께 감사합니다! 이번행사를 통해 AWS 에 대한 공부를 해야겠다는 필요성과 많은 사람들의 의견, 그리고 후원사들에 대해서 알게되었다. 경험을 해보면서 좋은 점도 있지만 아쉬운점은 내가 AWS 에 대한 지식이 더 많았으면 이해가 잘 되었을텐데 아쉬움이 있다.</p>
<p>올해 2023년의 행사는 끝났지만 내년에는 직접 AWS 기술들을 사용해보고 다뤄보면서 2024 행사에는 중급,고급, 더 나아가 EXPO 부스에서 많은 체험을 더 적극적으로 해봐야겠다는 생각이 들었다.</p>
<hr>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>대외활동</category>
        </item>
        <item>
            <title><![CDATA[AWS Summit Seoul 2023 - Day1에 다녀와서]]></title>
            <link>https://hooninedev.com/230503</link>
            <guid isPermaLink="false">https://hooninedev.com/230503</guid>
            <pubDate>Wed, 03 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[안녕하세요! 2023 AWS Summit 행사 1일차입니다 1일차는 산업 업종별 강연이 주제로서 각 기업들이 어떻게 AWS 를 이용해서 업무를 진행하는지 강연을 진행하고있고, 1층 Hall B에서 EXPO 를 통해 각 기업들의 AWS 적용사례를 실제로 볼 수 있는 부스들로 이루어져 있습니다. 저는 첫째날에 3개의 강연과 EXPO 에서 AWS 의 Cloud ...]]></description>
            <content:encoded><![CDATA[<p><img src="/content/230503/1.jpeg" alt="1.jpeg" loading="lazy" decoding="async"></p>
<p>안녕하세요!</p>
<p>2023 AWS Summit 행사 1일차입니다</p>
<p>1일차는 산업 업종별 강연이 주제로서 각 기업들이 어떻게 AWS 를 이용해서 업무를 진행하는지 강연을 진행하고있고, 1층 Hall B에서 EXPO 를 통해 각 기업들의 AWS 적용사례를 실제로 볼 수 있는 부스들로 이루어져 있습니다.</p>
<p>저는 첫째날에 3개의 강연과 EXPO 에서 AWS 의 Cloud 를 다루는 기업들을 살펴보았습니다.</p>
<p><img src="/content/230503/2.png" alt="강연 시간표" loading="lazy" decoding="async"></p>
<p>강연 시간표</p>
<p><img src="/content/230503/3.jpeg" alt="3.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/4.jpeg" alt="4.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/5.jpeg" alt="5.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/6.jpeg" alt="6.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/7.jpeg" alt="7.jpeg" loading="lazy" decoding="async"></p>
<hr>
<hr>
<h2 id="강연-내용"><a class="anchor" href="#강연-내용">강연 내용</a></h2>
<h3 id="기조연설---aws-모니터링-담당-nandini-ramani-부사장님-aws-korea-함기호-대표이사님-오순영-금융ai센터장님-야놀자-엔지니어링-수석-이준영-부대표"><a class="anchor" href="#기조연설---aws-모니터링-담당-nandini-ramani-부사장님-aws-korea-함기호-대표이사님-오순영-금융ai센터장님-야놀자-엔지니어링-수석-이준영-부대표"><strong>기조연설 -</strong> AWS 모니터링 담당 Nandini Ramani 부사장님, AWS Korea 함기호 대표이사님, 오순영 금융AI센터장님, 야놀자 엔지니어링 수석 이준영 부대표</a></h3>
<p>다양한 AWS의 기술들을 소개했는데 가장 기억에 남는 것은 아무래도 Bedrock 이었다. Bedrock은 AWS에서 제공하는 파운데이션모델이다.</p>
<p>파운데이션모델은 쉽게말해 요즘 핫한 대규모 Generative AI, 즉 ChatGPT와 같은 모델을 뜻하는 듯하다. 요즘 하루가 다르게 오픈소스로 대규모 AI가 공개되고 있는데 이를 이용한 기능를 만들때 사용할 수 있는 서비스가 아닐까 싶다.</p>
<p>첫날부터 많은 사람들이 현장에 있어서 신기했고 AWS 행사에 예상외로 많은 사람들이 있어 놀래고 웅장해지는 시간이었다. 그래서 정신없이 흘러가고, 기조연설 중간에는 다음 트랙에 대한 일정을 짜는시간도 가졌다.</p>
<p><img src="/content/230503/8.heic" alt="IMG_1506.HEIC" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/9.jpeg" alt="9.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/10.jpeg" alt="10.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/11.jpeg" alt="11.jpeg" loading="lazy" decoding="async"></p>
<hr>
<h3 id="중앙-집중식-observability-구축"><a class="anchor" href="#중앙-집중식-observability-구축"><strong>중앙 집중식 Observability 구축</strong></a></h3>
<p>첫번째 강연은 오디토리움에서 메가트렌드라는 분야로 강연으로 시작하였습니다.</p>
<p>해당 강연은 정영민 (솔루션즈 아키텍트, AWS) , 정찬훈 (Software Engineer, SendBird) ,김아린 프로 (삼성전자) 총 3분께서 Global Scale Service의 중앙 집중식 Observability 구축하기 라는 주제로 이야기를 시작하셧습니다.</p>
<h3 id="정영민--aws-솔류션즈-아키텍트"><a class="anchor" href="#정영민--aws-솔류션즈-아키텍트"><strong>정영민- AWS 솔류션즈 아키텍트</strong></a></h3>
<p><img src="/content/230503/12.jpeg" alt="12.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/13.jpeg" alt="13.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/14.jpeg" alt="14.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>우리는 어떤시대를 살고있는가?
<ol>
<li>글로벌 스케일시대
<ol>
<li>수백만 명 이상의 사용자</li>
<li>데이터 처리량 : TB , PB, EB</li>
<li>시스템 요구성능 : Micro-second latency</li>
<li>전 세계적 사용</li>
<li>초당 수백만건의 요청건수</li>
<li>사용기기: Mobile, IoT 기기</li>
</ol>
</li>
</ol>
</li>
<li>Observability
<ol>
<li>Monitoring 이상의 개념</li>
<li>시스템 내부의 현재 상태를 잘 이해할 수 있도록 시스템 외부로 효과적인 정보를 전달 하는 것</li>
<li>Monitoring 은 어디에 문제가 생겼는지 알수있지만 / Observability 는 무엇이 문제를 일으켯는지 파악하고, 시스템 상태가 최종 사용자에게 어떤 영향을 주는지 알고싶은것</li>
<li>구성요소
<ol>
<li>log ; 사용자 정의 데이터, issue 에 대한 context 파악</li>
<li>metric: 수치화 데이터 graph 로 표현하여 데이터 트랜드이해</li>
<li>trace : 사용자 중심도구, 각 모듈간의 상관관게 파악</li>
</ol>
</li>
<li>구성의 어려움을 AWS 와 함께해 어려움을 해소
<ol>
<li>연동이 뛰어남</li>
<li>비용 해소</li>
</ol>
</li>
</ol>
</li>
<li>key takeways
<ol>
<li>복잡한 아키텍처를 효율적으로 관측하는 aws managed observability 도구</li>
<li>multi region / multi account 를 한곳에서 관측 가능</li>
<li>비즈니스 의사결정에 적합한 데이터를 제공하는 observability</li>
</ol>
</li>
</ol>
<h3 id="full-stack-observability-를-향한-여정---삼성전자-김아린-프로"><a class="anchor" href="#full-stack-observability-를-향한-여정---삼성전자-김아린-프로"><strong>Full-stack Observability 를 향한 여정 - 삼성전자 김아린 프로</strong></a></h3>
<p><img src="/content/230503/15.jpeg" alt="15.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>prometheus 도입의 한계점 (2019)
<ol>
<li>장기보관의 어려움</li>
<li>read 가 많을 경우 cpu 사용량 증가</li>
<li>clustering을 지원하지 않아 ha 구서의 어려움이 있다.</li>
</ol>
</li>
</ol>
<p><img src="/content/230503/16.jpeg" alt="16.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>Thanos &#x26; prometheus 도입 2020~2022
<ol>
<li>aws s3 에 저장</li>
<li>자동으로 sacle-out 되지않음</li>
<li>지속적인 관리필요</li>
</ol>
</li>
</ol>
<p><img src="/content/230503/17.jpeg" alt="17.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>amazon managed service for prometheus 도입
<ol>
<li>prometheus &#x26; apm 아키텍처 2023</li>
<li>전환효과
<ol>
<li>운영 효율성 편의성증가
<ul>
<li>리소스 scale-out/in 에 따라 운영지표의 수집,저장,알림,쿼리 자동확장축소가능</li>
<li>관리할 리소스 감소</li>
</ul>
</li>
<li>운영 안정성 강화
<ul>
<li>aws 보안 서비스와 통합되어 데이터에 빠르고 안전하게 접근</li>
<li>alerting system이 독립적인 서버로 분리되어 업데이트 시 안정적</li>
</ul>
</li>
</ol>
</li>
</ol>
</li>
</ol>
<p><img src="/content/230503/18.jpeg" alt="18.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>향후 central managed observability 으로 발전하게 된다면 (adot &#x26; amp)
<ol>
<li>분산된 데이터를 한곳으로 중앙화해 효율적인 관리 및 운영 비용감소</li>
<li>aws 보안기술로 안전하게 보호할수있엇 보안성 강화</li>
<li>다양한 aws 서비스와 통합가능</li>
</ol>
</li>
</ol>
<p><img src="/content/230503/19.jpeg" alt="19.jpeg" loading="lazy" decoding="async"></p>
<h3 id="multi-accounts-를-관찰하는-단-하나의-account---정찬훈-software-engineer-sendbird"><a class="anchor" href="#multi-accounts-를-관찰하는-단-하나의-account---정찬훈-software-engineer-sendbird"><strong>Multi Accounts 를 관찰하는 단 하나의 Account - 정찬훈 (Software Engineer, SendBird)</strong></a></h3>
<p><img src="/content/230503/20.jpeg" alt="20.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/21.jpeg" alt="21.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/22.jpeg" alt="22.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/23.jpeg" alt="23.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>
<p>sendbird</p>
<ol>
<li>디지털 커뮤니케이션 플랫폼</li>
<li>채팅관련 api provider</li>
</ol>
</li>
<li>
<p>상용 솔루션을 사용의 한게점</p>
<ol>
<li>heavy cost 을 위해서 ⇒ less Metric , less Logs 을 통해 일어날 일 ⇒ less centralization, more context, engineering fragmentation</li>
<li>vendor 의존성</li>
</ol>
</li>
<li>
<p>위 문제점을 해소하기위한 생각할 부분</p>
<ol>
<li>less cost</li>
<li>easy to learn</li>
<li>less work</li>
<li>granular control</li>
<li>등등</li>
</ol>
</li>
</ol>
<p><img src="/content/230503/24.jpeg" alt="24.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/25.jpeg" alt="25.jpeg" loading="lazy" decoding="async"></p>
<hr>
<p>점심식사는 AWS 에서 제공해주셧습니다</p>
<p><img src="/content/230503/26.jpeg" alt="26.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/27.jpeg" alt="27.jpeg" loading="lazy" decoding="async"></p>
<hr>
<h3 id="클라우드-네이티브-데이터베이스"><a class="anchor" href="#클라우드-네이티브-데이터베이스"><strong>클라우드 네이티브 데이터베이스</strong></a></h3>
<p>이커머스 기업 쿠팡은 폭발적인 성장에 대응하기 위하여 Amazon Aurora 기반의 선택과 집중을 통해 DBA가 보다 의미 있는 일에 투자할 수 있도록 하고 있습니다.</p>
<p>삼성전자의 채팅플러스는 높은 수준의 가용성을 요구하는 통신 서비스의 특성에 맞게 적절한 AWS 데이터베이스를 활용하고 있습니다.</p>
<p>이 세션에서는 쿠팡이 Amazon Aurora를 통하여 얻은 경험 기반의 혁신 사례를 소개하며, 삼성전자에서 수 천만 명의 트래픽을 다루기 위해 Amazon DynamoDB, Amazon ElastiCache for Redis를 활용했던 경험을 공유합니다.</p>
<p>해당 강연은 김영진 (솔루션즈 아키텍트, AWS),김티나 (DBA, 쿠팡) , 권용석 프로 (삼성전자) 총 3분께서 삼성전자/쿠팡의 대규모 트래픽 처리를 위한 클라우드 네이티브 데이터베이스 활용 라는 주제로 이야기를 시작하셧습니다.</p>
<h3 id="김영진---aws-솔루션즈-아키텍트"><a class="anchor" href="#김영진---aws-솔루션즈-아키텍트"><strong>김영진 - AWS 솔루션즈 아키텍트</strong></a></h3>
<p><img src="/content/230503/28.jpeg" alt="28.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/29.jpeg" alt="29.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>자체 db 관리 및 분석의 어려움
<ol>
<li>hw , sw 설치 구성 패치 백업</li>
<li>성능 고가용성 문제</li>
<li>스토리지를 위한 용량 계획및 확장 클러스터</li>
<li>보안 및 규정 준수</li>
</ol>
</li>
<li>개발자는 이제 특별히 구축된 다양한 db 를 사용하여 고도로 분산된 애플리케이션을 구축</li>
<li>고려사항
<ol>
<li>어플리케이션 워크로드</li>
<li>데이터의 형태</li>
<li>어플리케이션 성능 요구사항</li>
<li>운영부담</li>
</ol>
</li>
</ol>
<h3 id="shared-repository-architectrue-style-design-strategy---권용석-프로-삼성전자"><a class="anchor" href="#shared-repository-architectrue-style-design-strategy---권용석-프로-삼성전자"><strong>shared repository architectrue style design strategy - 권용석 프로 (삼성전자)</strong></a></h3>
<p><img src="/content/230503/30.jpeg" alt="30.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>2019 RCS 1.0 migration
<ol>
<li>모놀리식 아키텍처</li>
<li>클러스터당 1,000,000 명의 사용자</li>
<li>각 사업자에 대해 10~20개의 클러스터</li>
<li>lift and shift 전환</li>
</ol>
</li>
</ol>
<p><img src="/content/230503/31.jpeg" alt="31.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>rcs 2.0
<ol>
<li>재설계/구현</li>
<li>마이크로 서비스 아키텍처</li>
<li>하위 도메인으로 분리</li>
<li>issue) 데이터 의존성에서 분리 ⇒ stateless 를 위해</li>
<li><strong>정합성 &#x26; 성능</strong> 이 중요시</li>
<li>효과
<ol>
<li>성능 처리량 5~10배향상</li>
<li>비용 30% 절감</li>
<li>장애 rcs1.0 에 비해 13% 감소</li>
</ol>
</li>
</ol>
</li>
</ol>
<p><img src="/content/230503/32.jpeg" alt="32.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/33.jpeg" alt="33.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/34.jpeg" alt="34.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/35.jpeg" alt="35.jpeg" loading="lazy" decoding="async"></p>
<p>권용석 프로님의 마지막 말씀이 인상 깊어서 가져왔습니다.</p>
<p><strong>목적에 맞는 managed service와 도메인특성을 반영한 전략이 결합되어 견고한 서비스를 만든다.</strong></p>
<h3 id="대용량-트래픽-쿠팡의-db-엔지니어가-클라우드를-사용하는-방법---김티나-dba-쿠팡"><a class="anchor" href="#대용량-트래픽-쿠팡의-db-엔지니어가-클라우드를-사용하는-방법---김티나-dba-쿠팡"><strong>대용량 트래픽, 쿠팡의 db 엔지니어가 클라우드를 사용하는 방법 - 김티나 DBA (쿠팡)</strong></a></h3>
<p><img src="/content/230503/36.jpeg" alt="36.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>쿠팡은 msa 를 이용해서 서비스간의 트패픽을 분리시킴</li>
<li>서비스내에서 서비스를 세부화 해 트래픽증가에 대한 문제 해결</li>
<li>dml(데이터수정) ddl(테이블구조변경) acl(db접근권한요청) 작업이 계속적으로 증가</li>
</ol>
<p><strong>어떻게 반복되는 작업을줄일수있을가?</strong></p>
<p>스마트 지원서비스</p>
<ol>
<li>반복되어 요청되는 작업을 자동으로 수행해주는것으로 함</li>
<li>관리자와 사용자 모두에게 편해야 한다</li>
<li>신뢰 가능한 서비스여야한다.</li>
</ol>
<p>스마트 지원서비스 개발하기 위한 조건</p>
<ol>
<li>신뢰가능한 메타정보 제공 - 최신 서버 구성 정보 제공</li>
<li>장애에 대한 즉각적인 대응
<ol>
<li>예측가능한 장애에 대한 방어 프로세스</li>
<li>모니터링,에러로그</li>
</ol>
</li>
<li>커뮤티케이션 최소화 (쿠팡에서 스마트지원서비스 분야에서 가장많은 요청을 받음)
<ol>
<li>데이터베이스 표준화</li>
<li>쉬운 서비스 가이드 문서 및 Q&#x26;A</li>
<li>에러패턴정리</li>
</ol>
</li>
</ol>
<p>스마트지원서비스- dml 작업 수행</p>
<p><img src="/content/230503/37.jpeg" alt="37.jpeg" loading="lazy" decoding="async"></p>
<ul>
<li>사용자가 직접적으로 관리하지못하고 스마트 지원 서비스를 통해서 작업 수행</li>
</ul>
<p>스마트 지원서비스 -ddl 작업 수행</p>
<ul>
<li>작업에 대한 오류도 많고 많은 모니터링을 요구함</li>
<li>많은 작업을 요구해 한번에 한건만 처리가능하도록 되어있음</li>
<li>스케줄러를 통해 ddl 작업을 할수있도록 함</li>
</ul>
<p>서비스 안정화를 위해서</p>
<ol>
<li>
<p>세션관리</p>
</li>
<li>
<p>데이터 사이즈 관리</p>
</li>
<li>
<p>쿠팡의 블루/그린 서비스</p>
<p><img src="/content/230503/38.jpeg" alt="38.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/39.jpeg" alt="39.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>목적: db 가용성 확보</li>
<li>사용성
<ol>
<li>서버 버전 패치</li>
<li>db migration</li>
<li>대용량 테이블 변경</li>
<li>유지/보수</li>
</ol>
</li>
</ol>
</li>
</ol>
<p>쿠팡의 김티나님의 마지막 한말씀도 의미가 있어서 기록에 남겨봤습니다.</p>
<p><strong>우리는 고객의 삶을 더 좋게 변화시키기 위해 존재한다. 고객은 언제나 우리가 내리는 모든 결정의 시작과 끝이다.</strong></p>
<hr>
<h3 id="expo-참여---첫번째-시간"><a class="anchor" href="#expo-참여---첫번째-시간"><strong>EXPO 참여 - 첫번째 시간</strong></a></h3>
<p>두번째 강연이 끝나고 EXPO 에서 평소에 관심이 있는 기업들의 박스에 방문해, 기술들을 살펴보고 체험해보는 시간을 가졌습니다.</p>
<p><img src="/content/230503/40.jpeg" alt="40.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/41.jpeg" alt="41.jpeg" loading="lazy" decoding="async"></p>
<h3 id="snowflake"><a class="anchor" href="#snowflake"><strong>SNOWFLAKE</strong></a></h3>
<p><a href="https://www.snowflake.com/ko/">데이터 클라우드 | Snowflake KO</a></p>
<p>snowflake 는 클라우드 컴퓨팅 기반 데이터 웨어하우징 회사입니다.</p>
<p>서비스로서의 데이터 웨어하우스(Saas) 를 제공해주며 복잡하게 구성된 웨어하우스를 안전관리 해주고 있습니다.</p>
<p>빠르고 사용하기 쉬운 데이터 스토리지, 처리, 분석 솔루션을 제공하고있습니다</p>
<p><img src="/content/230503/43.png" alt="43.png" loading="lazy" decoding="async"></p>
<p>snowflake 를 이용해 아래의 기능을 처리할 수 잇습니다.</p>
<ul>
<li>더 간단하고 빠른 데이터 탐색을 위해 noSQL 데이터 레이크 교체</li>
<li>빠르고 확장 가능한 BI 및 분석을 제공하기 위해 데이터웨어 하우스 현대화</li>
<li>데이터 사일로를 통합하여 비용 및 복잡성 감소</li>
</ul>
<p>Cloud Data Warehouse 로서 계속된 발전을 이끌고 있는 기업으로서 기술을 살펴볼수있는 좋은 기회였습니다.</p>
<h3 id="aws-자격증-취득-센터"><a class="anchor" href="#aws-자격증-취득-센터">AWS 자격증 취득 센터</a></h3>
<ol>
<li>
<p>aws 는 총 3종류 12개의 자격증으로 이루어져있습니다.</p>
<p><img src="/content/230503/44.png" alt="44.png" loading="lazy" decoding="async"></p>
<ol>
<li>프렉티셔널 자격증 (기초)</li>
<li>Associate 자격증 (일반)</li>
<li>Pro 자격증(프로)</li>
<li>스페셜 (특정분야 전문 자격증), Pro 와 같은 레벨</li>
</ol>
</li>
<li>
<p>현재 FE개발자로서 취득에 적합한 자격증으로 Solutions Architect- Associate 를 권장받앗습니다</p>
</li>
</ol>
<hr>
<p>EXPO 를 방문한 이후에 3번째 강연에 참여했습니다.</p>
<h3 id="스타트업의-빠른-성장-안정적인-서비스-운영-노하우는"><a class="anchor" href="#스타트업의-빠른-성장-안정적인-서비스-운영-노하우는">스타트업의 빠른 성장, 안정적인 서비스 운영 노하우는?</a></h3>
<p>빠른 성장과 안정적인 운영이라는 두 마리 토끼를 잡기 위해, Cloud Native를 통해 글로벌 서비스를 제공하는 두 스타트업의 이야기를 살펴봅니다.</p>
<p>기계학습 (ML)을 위한 대규모 데이터의 효율적인 관리와 AI 서비스 개발을 도와주는 Superb AI의 클라우드 네이티브 노하우, 그리고 EKS Multi tenancy 기반으로 170개국 팬들에게 커뮤니티 및 이커머스 서비스를 안정적으로 제공하는 비마이프렌즈의 경험담을 전달드립니다.</p>
<p>해당 강연은 강철 (어카운트 매니저, AWS) ,이정권 (CTO, Superb AI) ,하명진 (DevOps 팀장, Bemyfriends)총 3분께서 스타트업의 빠른 성장, 안정적인 서비스 운영 노하우는? 라는 주제로 이야기를 시작하셧습니다.</p>
<h3 id="강철-어카운트-매니저-aws---managed-service"><a class="anchor" href="#강철-어카운트-매니저-aws---managed-service"><strong>강철 (어카운트 매니저, AWS) - Managed Service</strong></a></h3>
<p>스타트업이 managed service 를 써야하는 이유?</p>
<ul>
<li>반복적인 운영업무 감소</li>
<li>민첩성 &#x26; 효율성 향상</li>
<li>서비스의 가용성 &#x26; 신뢰성 향상</li>
</ul>
<p>스타트업의 어려운 점</p>
<ul>
<li>money</li>
<li>Resource</li>
<li>time</li>
</ul>
<p><strong>advantage of cloud native</strong></p>
<p><strong>초기 구축 비용 뿐만 아니라 인프라 운영 및 유지관리에 들어가는 시간들도 비용.</strong></p>
<p><strong>생산성, 본질에 집중해 제품의 가치를 늘려보자</strong></p>
<p><img src="/content/230503/45.jpeg" alt="45.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/46.jpeg" alt="46.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/47.jpeg" alt="47.jpeg" loading="lazy" decoding="async"></p>
<h3 id="saas-로-팬덤-비즈니스를-글로벌-팬덤-비즈니스-스타트업-비마이프렌즈---하명진"><a class="anchor" href="#saas-로-팬덤-비즈니스를-글로벌-팬덤-비즈니스-스타트업-비마이프렌즈---하명진"><strong>Saas 로 팬덤 비즈니스를? 글로벌 팬덤 비즈니스 스타트업 비마이프렌즈 - 하명진</strong></a></h3>
<ol>
<li>비마이프렌즈
<ol>
<li>
<p>b.stage ⇒ 맞춤형 글로벌 팬덤 비즈니스를 위한 올인원 saas 솔루션 비스테이지</p>
</li>
<li>
<p>Saas Multi-Tenancy 아키텍처</p>
<p><img src="/content/230503/48.jpeg" alt="48.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>비용절감</li>
<li>확장성</li>
<li>보안</li>
<li>관리용의성</li>
</ol>
</li>
</ol>
</li>
</ol>
<p><strong>saas 는 Multi-Tenancy 를 사용하는 것이 효과적</strong></p>
<p><strong>Multi-Tenancy에서 eks 는 필수</strong></p>
<p><strong>spike trafffic 에 맞는 각 워크로드에 맞는 오픈소스를 적절히 활용</strong></p>
<p><img src="/content/230503/49.jpeg" alt="49.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/50.jpeg" alt="50.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/51.jpeg" alt="51.jpeg" loading="lazy" decoding="async"></p>
<hr>
<h3 id="나에게-지금-필요한-데이터는-무엇일까-더-똑똑하게-ai-개발하는-방법---이정권-superb-ai"><a class="anchor" href="#나에게-지금-필요한-데이터는-무엇일까-더-똑똑하게-ai-개발하는-방법---이정권-superb-ai"><strong>나에게 지금 필요한 데이터는 무엇일까? 더 똑똑하게 ai 개발하는 방법 - 이정권 superb ai</strong></a></h3>
<p><img src="/content/230503/52.jpeg" alt="52.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/53.jpeg" alt="53.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>SuperbAi
<ol>
<li>모든 단계의 ai 단계 조직을 지원해 ai 기술이 널리 보급될수있도록 기여하는것</li>
<li>데이터문제를 집중적으로 해결하는 데이터플랫폼을 만드는것을 목표</li>
<li>Superb label
<ol>
<li>다양한 데이터셋 가공기능</li>
<li>다양한 참여자들의 협업</li>
<li>작업과정제어와 모니터링</li>
<li>ai 도구를 통한 자동화</li>
</ol>
</li>
<li>Superb Cura
<ol>
<li>data label metadata 관리</li>
<li>강력한 qeury 기능</li>
<li>embedding 을 통한 분석</li>
<li>ai 도구분석</li>
</ol>
</li>
</ol>
</li>
</ol>
<p><img src="/content/230503/54.jpeg" alt="54.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/55.jpeg" alt="55.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>Clould Native 로 문제해결방법
<ol>
<li>
<p>대규모 데이터 처리의 문제점</p>
<ol>
<li>다양한 데이터 처리 파이프라인</li>
<li>강력한 모니터링 기능의 요구</li>
<li>안정적인 데이터 서빙</li>
<li>전 세계 어디에서나 낮은 지연 시간 보장</li>
<li>지속적으로 쌓이는 이미지들의 저장 비용</li>
</ol>
</li>
<li>
<p>해결</p>
<ol>
<li>다양한 크기와 종류의 데이터 처리 파이프라인 ⇒ aws step functions, aws lambda, fargate,serverless workflow orchestration</li>
<li>강력한 모니터링 기능의 요구 ⇒ aws cloudwatch 를 통한 로그 수집/쿼리</li>
<li>안정적인 데이터 서빙 , 전 세계 어디에서나 낮은 지연 시간 보장, 지속적으로 쌓이는 이미지들의 저장 비용 ⇒ s3 intelligent-tiering 을 이용한 비용 최적화</li>
</ol>
</li>
<li>
<p>ai 모델 문제점과 해결방안</p>
<ol>
<li>ai 학습 및 추론과정에서 요구되는 높은 gpu 사용량⇒ aws sagemaker 을 통한 gpu 활용(training, inference)</li>
</ol>
</li>
<li>
<p>유연한 워크플로우를 위한 Event-driven 구조</p>
<ol>
<li>실험적인 ai 기술을 보다 적극적으로 도입할 필요성</li>
<li>다양하고 전문적인 알고리즘과 구현 방식의 요구</li>
<li>유연성과 안정성, 부하의 분산</li>
</ol>
<p>⇒ aws eks 를 통한 containerized app serving , managed db 활용, cdc 를 통한 event-driven 구조</p>
</li>
<li>
<p>빠른 배포주기</p>
<ol>
<li>빠르게 변하는 ai/ml 도메인</li>
</ol>
</li>
</ol>
</li>
</ol>
<p>이정권님의 마지막 말이 인상깊어 적어두었습니다.</p>
<p><strong>ai 를 위한 현대적인 데이터 플랫폼을 만드는 기업, 다양한 데이터, 지역, 목적을 가진 사용자를 대상으로 다양한 알고리즘을 통해 제공하는 일을 하고있다.</strong></p>
<hr>
<h3 id="expo-참여---두번째-시간"><a class="anchor" href="#expo-참여---두번째-시간"><strong>EXPO 참여 - 두번째 시간</strong></a></h3>
<p>많은 기업들의 부스가 있었지만 시간이 부족해서 몇개의 기업들을 선택적으로 참여할 수 밖에 없어 아쉬웠습니다.</p>
<h3 id="유데미"><a class="anchor" href="#유데미"><strong>유데미</strong></a></h3>
<p><a href="https://www.udemy.com/ko/">온라인 강의 - 자신의 일정에 맞춰 뭐든지 배워 보세요 | Udemy</a></p>
<p><img src="/content/230503/56.png" alt="56.png" loading="lazy" decoding="async"></p>
<p>유데미는 미국 온라인 아카데미 기업체인데 현재 웅진싱크빅을 통해 접근할수있습니다.</p>
<p>프로그래밍, 디자인, 그래픽 등 다양한 분야를 접근 할 수 있는 유데미에서 행사부스를 통해 자신들이 AWS 를 어떻게 이용하는지와 유데미를 이용하는데 필요한 정보를 얻을수 있는 시간이었습니다.</p>
<p><img src="/content/230503/57.jpeg" alt="57.jpeg" loading="lazy" decoding="async"></p>
<hr>
<h3 id="datadog"><a class="anchor" href="#datadog"><strong>DATADOG</strong></a></h3>
<p><a href="https://www.datadoghq.com/">Cloud Monitoring as a Service | Datadog</a></p>
<p><img src="/content/230503/58.jpeg" alt="58.jpeg" loading="lazy" decoding="async"></p>
<ol>
<li>Datadog이란?
<ol>
<li>Datadog는 hybrid-cloud 환경에서의 서버, 컨테이너, 애플리케이션, 서비스 등에 대해 end-to-end 가시성을 제공하는 DevOps 환경을 위한 모니터링 및 분석 서비스 하는 기업입니다</li>
</ol>
</li>
<li>Datadog 를 이용해 어떻게 문제 해결을 할 수 있는가?
<ol>
<li>주요 성능 메트릭 및 이벤트 모니터링. All in One Place</li>
</ol>
<ul>
<li>여러 소스의 데이터를 실시간으로 확인하여 host, device 또는 추가 메타데이터를 통해 태그 형태로 호스트 뷰를 분할 및 축소 가능</li>
<li>조식 전체와 외부에서 접근 제어가 가능하고 공유가 가능한 고해상도 대형 그래프 및 대시보드 생성</li>
</ul>
<ol start="2">
<li>모니터링 자동화 기능을 통해 인프라 관리 효율성 향상</li>
</ol>
<ul>
<li>긴밀하게 통합된 구성 관리 툴과 NAT의 강력한 API를 활용하여 Datadog 모니터링 자동화</li>
<li>이상 징후 탐지 기능을 통해 잠재적인 이슈를 자동으로 탐지</li>
<li>강력하고 사용자 지정 가능한 태그를 사용하여 동적 인프라 내에서 장치 및 서비스 자동으로 그룹화</li>
</ul>
<ol start="3">
<li>메트릭 및 시스템 이벤트를 오버레이하여 문제를 즉시 식별</li>
</ol>
<ul>
<li>상이한 IT 시스템 및 구성 요소 간에 메트릭과 이벤트를 상호 연계하여 시스템 변경 사항이 다른 시스템에 영향을 미치는지 평가 가능</li>
<li>올바른 코드 변경, 관련 구성 업데이트 관련 문제 식별</li>
<li>프로덕션 데이터와 함께 팀원들과 컨텍스트 내에서 문제 논의</li>
</ul>
<ol start="4">
<li>운영 서비스 관련 팀 및 팀원에게 중요한 문제에 대해 알림(Alert) 기능 사용</li>
</ol>
<ul>
<li>통합 서비스 또는 인프라의 모든 시스템에서 발생한 개별 장치에서 메트릭 및 이벤트에 대한 세밀한 경고 생성</li>
</ul>
</li>
<li>datadog 는 350 가지 이상의 integration 을 지원합니다</li>
</ol>
<p>Datadog 의 cto 님께서 AWS 을 이용한 회사의 기술을 보여주셧습니다.</p>
<p>아직 기술력이 부족해 말씀하시는것을 다 이해하지는 못했지만, 직접 화면에 입출력을 통해 산출물을 보니 모니터링 분석 서비스에 대해서 새로운 경험을 해보는 시간이었습니다.</p>
<h3 id="mongodb"><a class="anchor" href="#mongodb"><strong>MongoDB</strong></a></h3>
<p><img src="/content/230503/59.jpeg" alt="59.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230503/60.jpeg" alt="60.jpeg" loading="lazy" decoding="async"></p>
<p><a href="https://www.mongodb.com/">MongoDB: The Developer Data Platform</a></p>
<p>해당 인프라는 FE 개발자로 DB를 공부하는 과정에서 사용해본적 있어 흥미로운 감정을 가지고 부스에 참여했습니다.</p>
<p>여러가지 정보를 살펴보며 제가 찾아보지 못한 부분을 볼 수 있어서 색다른 경험이었습니다.</p>
<p>Day2 에 MongoDB 에서 강연이 있다고해서 해당 내용은 참여후에 따로 기록해둘 예정입니다</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>대외활동</category>
        </item>
        <item>
            <title><![CDATA[점핏에서 주최한 Front-end 개취콘을 다녀와서]]></title>
            <link>https://hooninedev.com/230430</link>
            <guid isPermaLink="false">https://hooninedev.com/230430</guid>
            <pubDate>Sun, 30 Apr 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[점핏에서 진행하는 FE 북콘테스트가 강남역 모나코스페이스에서 개최되었습니다. 4월 30일에 13:00~17:00 까지 200명의 오프라인 참여자와 2000여명의 온라인 참가자가 참가해 최신 FE 에 대해서 훑어보고, 방향성에 대해서 이야기 Q/A 하는 의미있는 시간이었습니다. 오프라인 참가자에게는 도서와 문구류를 선물로 점핏에서 제공하셧습니다! 강연 목록 ...]]></description>
            <content:encoded><![CDATA[<p><img src="/content/230430/%EC%83%81%EB%8B%A81.jpeg" alt="상단1.jpeg" loading="lazy" decoding="async"></p>
<p><img src="/content/230430/%EC%83%81%EB%8B%A82.jpeg" alt="상단2.jpeg" loading="lazy" decoding="async"></p>
<p>점핏에서 진행하는 FE 북콘테스트가 강남역 모나코스페이스에서 개최되었습니다.</p>
<p>4월 30일에 13<div></div>~17<div></div> 까지 200명의 오프라인 참여자와 2000여명의 온라인 참가자가 참가해 최신 FE 에 대해서 훑어보고, 방향성에 대해서 이야기 Q/A 하는 의미있는 시간이었습니다.</p>
<p>오프라인 참가자에게는 도서와 문구류를 선물로 점핏에서 제공하셧습니다!</p>
<h2 id="강연-목록"><a class="anchor" href="#강연-목록">강연 목록</a></h2>
<ol>
<li>13<div></div> ~ 15<div></div> #1. 센스있게 일하는 FE 개발자 되기
<ol>
<li>FE 개발자의 소프트 스킬과 하드 스킬 - 김태곤</li>
<li>협업?! 이렇게 한번해봐(FE 개발자가 타 직군과 대화를 해야하는 이유) - 유동균</li>
</ol>
</li>
<li>15<div></div> ~ 17<div></div> #2 Fit한 정보로 성장 부스트업
<ol>
<li>FE 개발 트렌드(리엑트 개발 생태계와 Next.js까지) - 이인제[소플]</li>
<li>타입스크립트로 FE 개발 레벨업 - 장기효[캡틴판교]</li>
</ol>
</li>
</ol>
<h2 id="강연-요약"><a class="anchor" href="#강연-요약">강연 요약</a></h2>
<ol>
<li>
<p>FE 개발자의 소프트 스킬과 하드 스킬 - 김태곤</p>
<ol>
<li>
<p>코드는늙는다 ⇒ 좋은코드를 만든다? 유지보수를 잘한다? 기준이 어떻게 될까</p>
<ol>
<li>테스트가 용이해야한다
<ul>
<li>어떻게 테스트를 용이하게 할까? ⇒ 기능별, 동작방법으로 코드를 구분화</li>
</ul>
</li>
<li>읽기 쉬울 것
<ul>
<li>함수, 변수의 이름을 명확하게 하는 것이 중요하다</li>
<li>회사에서 프로젝트 내부에서 필요로 한다면 한국어로 하는것도 괜찮다. 어짜피 코드를 빌드하고 배포하는 과정에서는 변경되기 때문</li>
</ul>
</li>
<li>일관성</li>
</ol>
</li>
<li>
<p>테스트 코드는 가능한 부분부터 작성하다.</p>
<p><a href="https://www.google.com/search?q=%EB%A7%88%ED%8B%B4%ED%8C%8C%EC%9A%B8%EB%9F%AC+%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81&#x26;rlz=1C5CHFA_enKR1055KR1055&#x26;oq=%EB%A7%88%ED%8B%B4%ED%8C%8C%EC%9A%B8%EB%9F%AC+%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81&#x26;aqs=chrome.0.0i355i512j46i512j0i512l2j0i30j0i30i625.3373j0j4&#x26;sourceid=chrome&#x26;ie=UTF-8#imgrc=J5-kGgONxIZFjM%3A">마틴파울러 리팩토링 - Google Search</a></p>
</li>
<li>
<p>한 커밋에는 한 가지 문제만.</p>
<ol>
<li>추적 가능하게 유지해야함!</li>
<li>revert 할 때 돌아가야되기때문에</li>
</ol>
</li>
<li>
<p>실험은 한 번에 하나씩만.</p>
<ol>
<li>고생= (학습+프로젝트) x 2</li>
<li>배우면서 진행하는 것은 목표를 뚜렷하게 하고 실험은 한번에 하나씩만 하는게 의미있다.</li>
</ol>
</li>
<li>
<p>나만의 학습 루틴을 만들어두자. (공부는 단기적 방향성이 아닌 장기적 방향성이기때문)</p>
</li>
<li>
<p>가장 좋은 방법은 교육 ( 누군가에게 배운것, 알게된것을 전달하는 것이 필요하다)</p>
</li>
<li>
<p>대체로, 옳은 기술은 없다. 상황에 따른 선택이 있을 뿐.</p>
<ol>
<li>명확한 원칙이라는 것은 없다.</li>
<li>대체적으로 그 선택에 대한 있을뿐이라는 것을 생각해야함.</li>
</ol>
</li>
<li>
<p>FE 개발자는 절반쯤은 UX 전문가가 되어야 한다.</p>
<ol>
<li>사용자의 불만, 요구사항에 대해서 알고 있어야됨.</li>
<li>미리 알기위해서는 사용자의 마음에서 UX/UI 적으로 생각을 해야된다.</li>
<li>그렇게 해야 추후에 작업에 대해서 2차, 3차적 반복적 행위가 생기지 않을 수 있다.</li>
</ol>
</li>
<li>
<p>풀스택 엔지니어링 지식은 익히되 풀스택 엔지니어를 지향하지는 말자.</p>
<ol>
<li>모든것을 포용적으로 잘할수없다.</li>
<li>그렇기때문에 해당직무에 전문성을 키우자.</li>
<li>FE 개발에도 전문성이 점점 필요해져 가고있기때문에.</li>
</ol>
</li>
<li>
<p>‘안 된다’ 는 말은 그만</p>
</li>
<li>
<p>거절의 3단계</p>
<ol>
<li>숙고하고 대안을 제시하고 이득을 보자</li>
</ol>
</li>
<li>
<p>이직은 늘 준비하는 것</p>
<ol>
<li>결과보다는 과정에서 배우는 것, 얻는 것이 있기때문에 항상 준비하고 대처하자</li>
</ol>
</li>
<li>
<p>이직할 때 볼 것 : 커리어, 연봉, 워라벨</p>
<ol>
<li>아직 신입의 단계에서는 워라벨 보다는 진취적인 미래를 생각해보자.</li>
</ol>
</li>
<li>
<p>커리어 = 시간 + 스토리</p>
<ol>
<li>연차보다는 내가 그 회사에서 어떤 일을 했고, 배웠는지 가 더중요하다</li>
<li>의미없는 연차보다는 배울려고하고, 나만의 이야기를 기록하고 쌓아가자</li>
</ol>
</li>
<li>
<p>성과급/복지보다는 연봉</p>
</li>
<li>
<p>가능하다면, 자신의 기술이 메인으로 사용되는 회사로 가는 게 좋다.</p>
</li>
<li>
<p>뱀의 머리보다는 용의 꼬리가 낫다.</p>
<ol>
<li>배움이 될 수 있는 곳으로 가야한다.</li>
<li>현실에 안도하지말고 계속 배울려고 하자.</li>
</ol>
</li>
<li>
<p>호인과 호구는 다르다.</p>
</li>
<li>
<p>실무능력 = 프로그래밍 스킬(구현) + 도메인 지식(문제 정의) + 커뮤니케이션(협업)</p>
</li>
<li>
<p>독서하듯 코드를 읽자.</p>
<ol>
<li>코드에 관한 지식이 많을 수록 더 많이 더 오래 기억할 수 있다.</li>
<li>입력과 인출은 사용할수록 강화된다.</li>
</ol>
</li>
<li>
<p>문제는 시스템으로 예방하자.</p>
<ol>
<li>Linter, Formatter, Typescript, SCM 등 쓰면서 시스템으로 문제를 쉽고 빠르게 파악하자.</li>
</ol>
</li>
<li>
<p>가능한 작게 시작하다.</p>
<ol>
<li><strong><em>YAGNI</em></strong>(You aren't gonna need it)는 프로그래머가 필요하다고 간주할 때까지 기능을 추가하지 않는 것이 좋다는 익스트림 프로그래밍(XP)의 원칙</li>
<li><strong><em>Kiss</em></strong>(Keep It Simple Stupid!)는 SW 설계는 되도록 간단, 단순하게 만드는게 좋다는 원칙</li>
</ol>
</li>
<li>
<p>Repeat Yourself (좋은 반복도 있다)</p>
</li>
<li>
<p>Divide and Conquer(작게 나누어 정복)</p>
</li>
<li>
<p>질문에도 기술이 있다.</p>
</li>
<li>
<p>시간은 금이다</p>
</li>
<li>
<p>너무 열심히 안 살아도 된다</p>
<ol>
<li>너무빠르게 발전하기를 원하면 번아웃이 오기쉽다</li>
<li>어제보다 하나라도 발전한 사람이 되어보자</li>
</ol>
</li>
<li>
<p>전문가 얘기 너무 많이 듣지 말자.</p>
<ol>
<li>시간이 지나면 전문가의 말을 자연스럽게 파악할 수 있다.</li>
<li>내문제는 내가 제일 잘 알기 때문에 나에게 집중하고 고쳐보자.</li>
</ol>
</li>
</ol>
</li>
<li>
<p>협업?! 이렇게 한번해봐(FE 개발자가 타 직군과 대화를 해야하는 이유) - 유동균</p>
<ol>
<li>
<p>소통의 중요성</p>
<ol>
<li>
<p>프론트엔드의 역할은 중간다리 역할을 하고있다</p>
<p><img src="/content/230430/%EB%B3%B8%EB%AC%B82.png" alt="본문2" loading="lazy" decoding="async"></p>
</li>
</ol>
</li>
<li>
<p>소통을 잘 할려면??</p>
<ol>
<li>맥락(소통의 중점 주제) 과 의도(소통을 통해 얻고싶은 부분)를 알아야한다</li>
<li>맥락+의도 = 불필요한 커뮤니케이션 비용이 절감됨</li>
</ol>
</li>
<li>
<p>맥락과 의도를 챙기는 방법은?</p>
<ol>
<li>목적지향적인 태도로 대화해보자(대화에서 이루고자하는 목적에 집중하자)</li>
<li>광범위한 이해도, 지식을 가지자</li>
</ol>
</li>
</ol>
</li>
<li>
<p>FE 개발 트렌드(리엑트 개발 생태계와 Next.js까지) - 이인제[소플]</p>
<p><img src="/content/230430/%EB%B3%B8%EB%AC%B81.png" alt="본문1" loading="lazy" decoding="async"></p>
<ol>
<li>Next js
<ol>
<li>Vercel 에서 만든 SSR(Server Side Rendering) FrameWork</li>
<li>react 로 작업을 할때의 복잡한 설정을 쉽고 간편하게 사용할 수 있다.</li>
<li>FE 와 BE 에서 하나의 프로젝트로 개발 할 수 있다.</li>
</ol>
</li>
<li>Q/A
<ol>
<li>개발자로써 능력을 키우는 좋은 방법은 어떤걸까요?
<ul>
<li>나만의 프로젝트를 만들어서, 새로운 스킬을 연습해보자.</li>
</ul>
</li>
<li>다양한기술 vs 한가지 기술 중 어떤 방법으로 공부를 하는게 좋을까?
<ul>
<li>깊이있게 배워보는 것이 좋다는 것은 상태관리, 언어 등이 될 것 같다.</li>
<li>라이브러리는 종류도 많고 발전속도가 빠르니 필요 상황에 따라서 배워보는 것을 추천</li>
<li>결론적으로는 상황에 맞춰서 효율적으로 배우는게 좋아보임</li>
</ul>
</li>
<li>Next js 를 공부할려고 하는데 어떤 방향으로 공부하면 될까요?
<ul>
<li>Nextjs 를 공부해도 되는데 react 와 언어의 개념을 잘 구축한 다음에 넘어오는 것이 이해하고, 습득, 응용하는데 도움이 될것같다.</li>
</ul>
</li>
<li>Nextjs 의 13 버전이 나왔는데 바로 사용해봐야되는지?
<ul>
<li>새로운 버전도 중요하지만 유행을 따라만 가다보면 혼동이 올수있으니, 프로젝트랑 회사의 상황을 보고 공부해야될 것같다.</li>
</ul>
</li>
</ol>
</li>
<li>마지막 말
<ol>
<li>지금은 어떤 도움이 있을까 생각을 하고 공부를 하겠지만, 그런 공부들이 누적이 되면 큰 값어치가 되어 돌아오니 꾸준히 오랫동안 해보는게 좋을거같다.</li>
</ol>
</li>
</ol>
</li>
<li>
<p>타입스크립트로 FE 개발 레벨업 - 장기효[캡틴판교]</p>
<ol>
<li>
<p>타입스크립트의 장점</p>
<ol>
<li>사용자를 보호해준다, 사용자의 경험을 향상시켜준다.</li>
</ol>
</li>
<li>
<p>타입스크립트 쉽게 접근하는 방법</p>
<ol>
<li>JSDoc 을 사용
<ul>
<li>일정한 형식으로 코드에 설명을 추가하는 주석</li>
<li>// @ts-check (JS 의 code level 에서 타입지정가능)</li>
</ul>
</li>
<li>TS 를 당장 사용하기 어려우면 JSDoc 으로 시작해보는 걸 추천</li>
</ol>
</li>
<li>
<p>TS 관련 핸드북</p>
<p><a href="https://joshua1988.github.io/ts/intro.html">Introduction | 타입스크립트 핸드북</a></p>
</li>
<li>
<p>Q/A</p>
<ol>
<li>업무에서 방대한 기술스택을 요구하고 하면 어떻게 해야될까?
<ul>
<li>모든것을 한번에 할려고하면 혼동이 오니 필요순위에 따져서 배우는것을 추천</li>
</ul>
</li>
<li>FE 개발자로서 ChatGPT 와 Compiler 를 사용하는 걸 어떻게 생각하나? 주니어 개발자로서
<ul>
<li>검증하는 작업이 필요하다.</li>
<li>documents, mdn 등의 사이트로 옳바른 내용인지 확인해봐야함</li>
</ul>
</li>
<li>FE 개발자의 학습 방향성?
<ul>
<li>재미있게 알려주는 학습, 나에게 적합하게 하는 방법에 대해서 생각해 스토리를 만들어라</li>
</ul>
</li>
<li>ChatGPT 에게 옳바른 질문을 하는법
<ul>
<li>우리는 개발자이니 구체적으로 어떤 기술스택과 어떤 내용인지 등등을 명확히해서 요구해야함.</li>
</ul>
</li>
<li>TS 의 새로운 버전을 공부해야 될까?
<ul>
<li>현재 내가 사용하는것과의 차이점을 파악</li>
<li>파악한 후에 적용해야되는 범위부터 시작해서 후에 공부하는 방법으로 해야할듯함</li>
</ul>
</li>
</ol>
</li>
</ol>
</li>
</ol>
<h2 id="결론"><a class="anchor" href="#결론">결론</a></h2>
<p>FE 개발자로서 옳바른 방향성에 대해서 항상 궁금하고 물음표였는데 이번 행사를 참가하면서 다른 사람의 이야기를 들어볼 수 있어서 의미있는 시간이었습니다.</p>
<p>앞으로 FE 개발자뿐 아닌 개발 컨퍼런스가 자주 열려서 의견을 공유하는 시간을 가지면 좋겠습니다</p>
<figure data-rehype-pretty-code-figure=""><pre tabindex="0" data-language="toc" data-theme="github-dark github-light"><code data-language="toc" data-theme="github-dark github-light" style="display: grid;"><span data-line=""> </span></code></pre></figure>]]></content:encoded>
            <author>jihoon7705@gmail.com (이지훈)</author>
            <category>대외활동</category>
        </item>
    </channel>
</rss>