<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dong_eon_.log</title>
        <link>https://velog.io/</link>
        <description>개발 중에 마주한 문제와 해결 과정, 새롭게 배운 지식, 그리고 알고리즘 문제 해결에 대한 다양한 인사이트를 공유하는 기술 블로그입니다</description>
        <lastBuildDate>Wed, 17 Sep 2025 15:29:33 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dong_eon_.log</title>
            <url>https://velog.velcdn.com/images/dong_eon_/profile/8fc02441-dfaf-465c-adc4-e83a9cc18cf6/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dong_eon_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dong_eon_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[잃어버린 내 코드 찾기(feat. github)]]></title>
            <link>https://velog.io/@dong_eon_/%EC%9E%83%EC%96%B4%EB%B2%84%EB%A6%B0-%EB%82%B4-%EC%BD%94%EB%93%9C-%EC%B0%BE%EA%B8%B0feat.-github</link>
            <guid>https://velog.io/@dong_eon_/%EC%9E%83%EC%96%B4%EB%B2%84%EB%A6%B0-%EB%82%B4-%EC%BD%94%EB%93%9C-%EC%B0%BE%EA%B8%B0feat.-github</guid>
            <pubDate>Wed, 17 Sep 2025 15:29:33 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ol>
<li><a href="#%EC%82%AC%EA%B1%B4%EC%9D%98-%EB%B0%9C%EB%8B%A8">사건의 발단</a></li>
<li><a href="#%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EA%B3%BC%EC%A0%95%EA%B3%BC-%ED%95%99%EC%8A%B5-%EA%B3%84%EA%B8%B0">문제 해결 과정과 학습 계기</a></li>
<li><a href="#%EC%A3%BC%EC%9A%94-%EA%B0%9C%EB%85%90">주요 개념</a><ul>
<li><a href="#github-permalink">GitHub Permalink</a></li>
<li><a href="#git-blame">Git Blame</a></li>
<li><a href="#git-log%EC%99%80-github-history">Git Log와 GitHub History</a></li>
</ul>
</li>
<li><a href="#%EC%84%B8-%EA%B0%80%EC%A7%80-%EA%B0%9C%EB%85%90-%EB%B9%84%EA%B5%90">세 가지 개념 비교</a></li>
<li>[간단한 그림으로 전체 요약](#간단한 그림으로 전체 요약)</li>
<li><a href="#%EA%B2%B0%EB%A1%A0">결론</a></li>
</ol>
<h3 id="사건의-발단">사건의 발단</h3>
<blockquote>
</blockquote>
<p>회사에서 리팩토링을 진행하면서 수많은 커밋과 수차례의 merge가 진행되었었다.
리팩토링의 종점에 거의 도달했을때, 리팩토링 초창기에 있었던 코드가 필요하다는 것을 알게 되었다.
해당 코드의 line 의 위치는 기억하지만 이미 최신 브랜치에서는 해당라인이 다른 코드로 덮여 있는 이 상황...</p>
<h3 id="문제-해결-과정과-학습-계기">문제 해결 과정과 학습 계기</h3>
<p>옆자리 사수분 (shout out to ** calvin **) 께 내 상황을 공유하고 도움을 요청하니 Github permalink와 git blame 을 통해 타임머신으로 이동해서 쉽게 찾으셨습니다.
그때 들은 설명과 해당 키워드를 통해 내가 개인적으로 공부한 내용을 기록하려고 합니다.</p>
<h3 id="주요-개념">주요 개념</h3>
<ul>
<li>** GitHub Permalink **</li>
<li>** Git Blame ** </li>
<li>** Git log **</li>
</ul>
<h3 id="github-permalink">GitHub Permalink</h3>
<ol>
<li><p>개념
<code>Permalink</code> 는 브랜치명 대신 특정 커밋 해시를 URL에 넣어, <strong>그 커밋 시점의 파일(그리고 선택한 라인 범위)</strong>을 가리킨다. 브랜치가 앞으로 업데이트되더라도 Permalink는 과거 시점에 “고정”된다.</p>
<ul>
<li>브랜치 링크(시간에 따라 변함)<pre><code>https://github.com/org/repo/blob/main/src/utils/date.ts#L10-L20</code></pre></li>
<li>Permalink(커밋 고정, 내용 불변)<pre><code>https://github.com/org/repo/blob/3c7e92b5a5/src/utils/date.ts#L10-L20</code></pre>여기서 3c7e92b5a5는 커밋 해시이며, #L10-L20은 라인 앵커다. 이 링크는 그 커밋의 트리 스냅샷 안에서 해당 경로와 라인을 표시한다.</li>
</ul>
</li>
<li><p>생성 방법</p>
</li>
</ol>
<ul>
<li>GitHub 웹에서 파일을 열고 줄 번호를 클릭 → “Copy permalink” 또는 상단 메뉴의 “…” → Copy permalink” 를 사용한다.</li>
<li>이미 브랜치 링크를 보고 있다면, 키보드 단축키 Y 를 누르면 URL이 자동으로 커밋 해시 기반의 Permalink로 변환된다.</li>
<li>라인 범위는 #L10 또는 #L10-L20 형태로 지정한다.</li>
</ul>
<ol start="3">
<li>간단한 도식<pre><code>main:     A ---- B ---- C (HEAD)
           \
Permalink →    B  ← 고정된 스냅샷</code></pre></li>
</ol>
<h3 id="git-blame">Git Blame</h3>
<ol>
<li>개념
Blame은 파일의 각 라인이 현재 모습으로 자리 잡게 된 가장 최신 커밋을 표시한다.
GitHub 파일 화면의 Blame 버튼으로 접근할 수 있고, CLI에서는 git blame을 쓴다.</li>
</ol>
<ul>
<li>본질: “이 줄은 누가 언제 어떤 커밋에서 마지막으로 손댔는가?”</li>
<li>GitHub Blame에는 “View blame prior to this change” 기능이 있다. 이는 해당 줄을 바꾼 그 커밋의 직전 상태로 점프하여 라인의 족보를 거슬러 올라가게 한다.</li>
</ul>
<ol start="2">
<li>“prior to”가 이동하는 기준</li>
</ol>
<ul>
<li>git log 처럼 파일의 최신 커밋 순서로 이동하는 것이 아니라, 그 줄을 실제로 수정한 커밋 체인만 따라간다.</li>
<li>즉, 줄을 수정하지 않은 커밋은 건너뛰고, e -&gt; c -&gt; a 와 같이 라인을 바꾼 커밋들만 역순으로 밟는다.</li>
</ul>
<p>도식</p>
<pre><code>커밋 전체:  a ---- b ---- c ---- d ---- e (HEAD)
라인 수정:  *           *           *

blame prior-to 체인:  e  →  c  →  a
log(파일) 체인:        e  →  d  →  c  →  b  →  a</code></pre><h3 id="git-log와-github-history">Git Log와 GitHub History</h3>
<ol>
<li>개념
Log, History는 파일단위의 모든 커밋을 시간 역순으로 나열한다. github 파일 화면의 history 버튼이 이에 해당된다.</li>
</ol>
<ul>
<li>핵심: &quot;이 파일은 어떤 커밋들을 거쳐 바뀌어 왔는가?&quot;</li>
<li>라인을 건드리지 않은 변경(ex 파일 헤더 주석, import 정리, 포맷팅 등)도 모두 표에 잡힌다.</li>
</ul>
<h3 id="세-가지-개념-비교">세 가지 개념 비교</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>Permalink</th>
<th>Blame</th>
<th>Log/History</th>
</tr>
</thead>
<tbody><tr>
<td>기준</td>
<td>특정 커밋 + 파일 + 라인(범위)</td>
<td>특정 라인의 마지막 수정 커밋(라인 계보)</td>
<td>파일 전체 커밋 이력</td>
</tr>
<tr>
<td>보여주는 것</td>
<td>고정 스냅샷(재현가능)</td>
<td>라인의 최신 수정자와 커밋, prior-to로 과거 추적</td>
<td>파일에 영향을 준 모든 커밋(라인 비특이적)</td>
</tr>
<tr>
<td>이동/탐색</td>
<td>고정(변하지 않음)</td>
<td>라인을 수정한 커밋 체인만 역추적</td>
<td>시간 역순으로 커밋 나열</td>
</tr>
<tr>
<td>장점</td>
<td>문서화/리뷰에 안정적 인용</td>
<td>“그 줄”의 역사와 맥락을 정확히 추적</td>
<td>큰 흐름, 중요한 전환점 파악</td>
</tr>
<tr>
<td>한계</td>
<td>최신과 동기화되지 않음</td>
<td>rename/이동 감지에 제약(웹)</td>
<td>라인 기원 파악에는 부정확</td>
</tr>
<tr>
<td>GitHub 위치</td>
<td>Code 뷰 → Copy permalink 또는 <code>Y</code></td>
<td>Code 뷰 → Blame → prior to</td>
<td>Code 뷰 → History, PR, Network</td>
</tr>
</tbody></table>
<h3 id="간단한-그림으로-전체-요약">간단한 그림으로 전체 요약</h3>
<pre><code>[브랜치 타임라인]
A ---- B ---- C ---- D ---- E (HEAD)

- Permalink: 특정 시점(B)의 스냅샷을 영구 지칭
- Blame: 특정 라인만 추적 → E → C → A
- Log/History: 파일 단위의 전체 흐름 → E, D, C, B, A

[브랜치/병합 개략]
        feature-x ---o
                     \
A --- B --- C --------M --- E
            \        /
             hotfix ----o

- History/PR: M에서 어떤 브랜치가 병합되었는지 맥락 확인
- Network: 분기/병합의 노선도 파악</code></pre><h3 id="결론">결론</h3>
<ul>
<li>Permalink 는 스냅샷의 정밀한 인용 도구다. 브랜치가 움직여도 링크는 움직이지 않는다.</li>
<li>Blame 은 라인 단위의 족보다. prior -to 체인으로 &quot;그 줄&quot;의 진화를 거슬러 올라간다.</li>
<li>Log, History는 파일의 연대기이다. 큰 변곡점과 흐름을 파악하는데 유리하다.</li>
</ul>
<p><del>왜 dev 브랜치가서 코드 확인 안했냐는 나쁜 말은 ㄴㄴ</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트에서 자주 쓰이는 타입 레벨 트릭들]]></title>
            <link>https://velog.io/@dong_eon_/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%97%90%EC%84%9C-%EC%9E%90%EC%A3%BC-%EC%93%B0%EC%9D%B4%EB%8A%94-%ED%83%80%EC%9E%85-%EB%A0%88%EB%B2%A8-%ED%8A%B8%EB%A6%AD%EB%93%A4</link>
            <guid>https://velog.io/@dong_eon_/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%97%90%EC%84%9C-%EC%9E%90%EC%A3%BC-%EC%93%B0%EC%9D%B4%EB%8A%94-%ED%83%80%EC%9E%85-%EB%A0%88%EB%B2%A8-%ED%8A%B8%EB%A6%AD%EB%93%A4</guid>
            <pubDate>Wed, 27 Aug 2025 07:27:17 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>타입스크립트를 공부하다 보면 단순한 문법을 넘어서 <strong>트릭</strong>처럼 느껴지는 문법 패턴들을 만나게 됩니다.<br>이 글에서는 그중에서도 <strong>유니온 추출 / 매핑 후 값 유니온화 / 튜플 원소 타입 뽑기</strong> 같은 핵심 패턴을 정리합니다.  </p>
</blockquote>
<hr>
<h2 id="1-tnumber-→-배열튜플-원소-타입-유니온">1. <code>T[number]</code> → 배열/튜플 원소 타입 유니온</h2>
<h3 id="개념">개념</h3>
<ul>
<li>배열 타입 <code>T</code>에 <code>[number]</code>를 붙이면, 해당 배열의 <strong>모든 원소 타입을 유니온으로 추출</strong>합니다.</li>
</ul>
<h3 id="예시">예시</h3>
<pre><code>type Roles = [&quot;guest&quot;, &quot;user&quot;, &quot;admin&quot;];
type Union = Roles[number];
// &quot;guest&quot; | &quot;user&quot; | &quot;admin&quot;</code></pre><p>👉 튜플이든 배열이든, <code>[number]</code>는 “이 타입에서 나올 수 있는 모든 값들의 합집합”을 의미합니다.<br>즉, 런타임 반복이 아니라 타입 레벨 유니온 추출이에요.</p>
<hr>
<h2 id="2-objkeyof-obj-→-객체-value-타입-유니온">2. <code>Obj[keyof Obj]</code> → 객체 value 타입 유니온</h2>
<h3 id="개념-1">개념</h3>
<ul>
<li>객체 타입에서 <code>keyof Obj</code>는 키들의 유니온.</li>
<li>다시 <code>Obj[keyof Obj]</code>를 하면, 그 키들에 해당하는 <strong>모든 value 타입을 유니온으로</strong> 가져옵니다.</li>
</ul>
<h3 id="예시-1">예시</h3>
<pre><code>type Obj = {
  a: number;
  b: string;
};

type Values = Obj[keyof Obj];
// number | string</code></pre><p>👉 이 패턴은 “객체의 모든 value 타입을 유니온으로 만들고 싶을 때” 사용합니다.</p>
<hr>
<h2 id="3--p-in-keyof-t--keyof-t-→-매핑-후-값-유니온-뽑기">3. <code>{ [P in keyof T]: ... }[keyof T]</code> → 매핑 후 값 유니온 뽑기</h2>
<h3 id="개념-2">개념</h3>
<ul>
<li>매핑 타입으로 키를 순회하면서 조건을 걸어 값을 생성.</li>
<li>마지막에 <code>[keyof T]</code>로 인덱싱하면, 생성된 객체의 <strong>value들을 유니온으로 모아 추출</strong>합니다.</li>
</ul>
<h3 id="예시-2">예시</h3>
<pre><code>type Obj = { a: 1; b: 2; c: 3 };

type PickEven&lt;T&gt; = {
  [P in keyof T]: T[P] extends 2 ? P : never
}[keyof T];

type Result = PickEven&lt;Obj&gt;;
// &quot;b&quot;</code></pre><p>👉 <code>...}[keyof T]</code> 트릭은 매핑 타입 결과를 다시 유니온으로 만드는 핵심 패턴입니다.</p>
<hr>
<h2 id="4-트릭-조합-실전-problem-03--auth-guards">4. 트릭 조합 실전: Problem 03 – auth-guards</h2>
<p>이제 위의 트릭을 합쳐서, 특정 역할(Role)에 따라 접근 가능한 경로를 타입으로 구하는 문제를 풀어보겠습니다.</p>
<h3 id="요구사항">요구사항</h3>
<ul>
<li>Role: <code>&quot;guest&quot; | &quot;user&quot; | &quot;admin&quot;</code></li>
<li>RouteMap: 각 path에 접근 가능한 role 리스트를 정의</li>
<li>AccessibleRoutes<R>: 역할 R이 접근 가능한 path들의 유니온</li>
</ul>
<h3 id="풀이">풀이</h3>
<pre><code>export type Role = &quot;guest&quot; | &quot;user&quot; | &quot;admin&quot;;

export type RouteMap = {
  &quot;/&quot;: { roles: [&quot;guest&quot;, &quot;user&quot;, &quot;admin&quot;] };
  &quot;/login&quot;: { roles: [&quot;guest&quot;] };
  &quot;/dashboard&quot;: { roles: [&quot;user&quot;, &quot;admin&quot;] };
  &quot;/settings&quot;: { roles: [&quot;admin&quot;] };
};

export type AccessibleRoutes&lt;R extends Role&gt; = never // 초기 상태

// AccessibleRoutes&lt;R&gt; 기대값
// ----------------------------
type G = AccessibleRoutes&lt;&quot;guest&quot;&gt;;
type G_Expected = &quot;/&quot; | &quot;/login&quot;;
type _g = Expect&lt;Equal&lt;G, G_Expected&gt;&gt;;

type U = AccessibleRoutes&lt;&quot;user&quot;&gt;;
type U_Expected = &quot;/&quot; | &quot;/dashboard&quot;;
type _u = Expect&lt;Equal&lt;U, U_Expected&gt;&gt;;

type A = AccessibleRoutes&lt;&quot;admin&quot;&gt;;
type A_Expected = &quot;/&quot; | &quot;/dashboard&quot; | &quot;/settings&quot; | &quot;/login&quot;;
type _a = Expect&lt;Equal&lt;A, A_Expected&gt;&gt;;


정답
// 1. 특정 path의 roles 유니온 추출
type RouteRoles&lt;P extends keyof RouteMap&gt; = RouteMap[P][&quot;roles&quot;][number];

// 2. 매핑 타입 + 조건부 타입 → 접근 가능한 path 필터링
export type AccessibleRoutes&lt;R extends Role&gt; = {
  [P in keyof RouteMap]: R extends RouteRoles&lt;P&gt; ? P : never
}[keyof RouteMap];</code></pre><h3 id="결과">결과</h3>
<ul>
<li><code>AccessibleRoutes&lt;&quot;guest&quot;&gt;</code> → <code>&quot;/&quot; | &quot;/login&quot;</code>  </li>
<li><code>AccessibleRoutes&lt;&quot;user&quot;&gt;</code> → <code>&quot;/&quot; | &quot;/dashboard&quot;</code>  </li>
<li><code>AccessibleRoutes&lt;&quot;admin&quot;&gt;</code> → <code>&quot;/&quot; | &quot;/dashboard&quot; | &quot;/settings&quot;</code></li>
</ul>
<hr>
<h2 id="✅-결론">✅ 결론</h2>
<ul>
<li><code>T[number]</code>: 배열/튜플 원소 타입을 유니온으로 추출하는 패턴  </li>
<li><code>Obj[keyof Obj]</code>: 객체의 value 타입들을 유니온으로 추출하는 패턴  </li>
<li><code>{ [P in keyof T]: ... }[keyof T]</code>: 매핑 타입 후 값을 다시 유니온으로 모으는 패턴  </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[제네릭에서의 extends: 제약 vs 조건부]]></title>
            <link>https://velog.io/@dong_eon_/%EC%A0%9C%EB%84%A4%EB%A6%AD%EC%97%90%EC%84%9C%EC%9D%98-extends-%EC%A0%9C%EC%95%BD-vs-%EC%A1%B0%EA%B1%B4%EB%B6%80</link>
            <guid>https://velog.io/@dong_eon_/%EC%A0%9C%EB%84%A4%EB%A6%AD%EC%97%90%EC%84%9C%EC%9D%98-extends-%EC%A0%9C%EC%95%BD-vs-%EC%A1%B0%EA%B1%B4%EB%B6%80</guid>
            <pubDate>Wed, 27 Aug 2025 06:28:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>TypeScript에서 <code>extends</code>는 두 가지 문맥에서 사용됩니다.  </p>
</blockquote>
<p>1) 제네릭 제약(Constraint)<br>2) 조건부 타입(Conditional Type)<br>겉보기엔 똑같아 보여도, 의미와 쓰임새는 전혀 다릅니다. 이번 글에서는 이 둘을 확실히 구분해보고, 마지막에 실전 예제까지 살펴봅니다.</p>
<hr>
<h2 id="1-제네릭-제약-constraint">1. 제네릭 제약 (Constraint)</h2>
<h3 id="정의">정의</h3>
<p>제네릭 파라미터 T가 어떤 타입 U의 <strong>부분집합</strong>이어야 함을 선언하는 것.<br>형태: <code>&lt;T extends U&gt;</code><br>→ “T는 반드시 U에 호환 가능한 타입이어야 한다.”</p>
<h3 id="예시">예시</h3>
<pre><code>function lengthOf&lt;T extends { length: number }&gt;(arg: T): number {
  return arg.length;
}

lengthOf(&quot;hello&quot;);        // string → length 있음 → OK
lengthOf([1, 2, 3]);      // number[] → length 있음 → OK
lengthOf({ length: 10 }); // 객체도 OK
// lengthOf(123);         // ❌ number에는 length 없음</code></pre><p>👉 <code>T extends { length: number }</code> 제약이 걸렸기 때문에, <code>length</code> 속성이 없는 타입은 아예 함수 호출이 불가능합니다.  </p>
<hr>
<h2 id="2-조건부-타입-conditional-type">2. 조건부 타입 (Conditional Type)</h2>
<h3 id="정의-1">정의</h3>
<p>조건문처럼 타입을 분기합니다.<br>형태: <code>T extends U ? X : Y</code><br>→ “T가 U에 할당 가능하면 X, 아니면 Y”</p>
<h3 id="예시-1">예시</h3>
<pre><code>type IsString&lt;T&gt; = T extends string ? &quot;yes&quot; : &quot;no&quot;;

type A = IsString&lt;string&gt;; // &quot;yes&quot;
type B = IsString&lt;number&gt;; // &quot;no&quot;</code></pre><h3 id="제네릭과-함께">제네릭과 함께</h3>
<pre><code>type ElementType&lt;T&gt; = T extends (infer U)[] ? U : T;

type A = ElementType&lt;string[]&gt;; // string
type B = ElementType&lt;number&gt;;   // number</code></pre><p>👉 infer 키워드를 함께 써서, 조건이 맞으면 내부 타입을 변수처럼 추출해내는 것도 가능합니다.</p>
<hr>
<h2 id="3-제약과-조건부를-함께-쓰는-경우">3. 제약과 조건부를 함께 쓰는 경우</h2>
<p>실무에서 자주 쓰는 패턴은 두 가지가 결합된 형태입니다.</p>
<pre><code>function getProp&lt;T extends object, K extends keyof T&gt;(obj: T, key: K): T[K] {
  return obj[key];
}</code></pre><ul>
<li>T extends object → T는 반드시 객체여야 함  </li>
<li>K extends keyof T → K는 반드시 T의 키 중 하나여야 함  </li>
<li>T[K] → K가 유효 키라는 것이 보장되므로 안전하게 값의 타입을 추출  </li>
</ul>
<hr>
<h2 id="4-차이-정리">4. 차이 정리</h2>
<table>
<thead>
<tr>
<th>문맥</th>
<th>문법</th>
<th>의미</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>제약(Constraint)</strong></td>
<td><code>&lt;T extends U&gt;</code></td>
<td>제네릭 선언 시 제한. T는 반드시 U 안에 있어야 한다.</td>
<td><code>&lt;T extends object&gt;</code></td>
</tr>
<tr>
<td><strong>조건부 타입(Conditional Type)</strong></td>
<td><code>T extends U ? X : Y</code></td>
<td>타입 계산 시 분기. T가 U에 할당 가능하면 X, 아니면 Y.</td>
<td><code>T extends string ? &quot;ok&quot; : &quot;no&quot;</code></td>
</tr>
</tbody></table>
<hr>
<h2 id="5-실무-활용-패턴">5. 실무 활용 패턴</h2>
<ul>
<li><p><strong>제약으로 안전성 확보</strong></p>
<pre><code>  function pick&lt;T, K extends keyof T&gt;(obj: T, key: K): T[K] {
    return obj[key];
  }</code></pre><p>  → K가 반드시 T의 키라는 제약이 있으므로 안전합니다.</p>
</li>
<li><p><strong>조건부 타입으로 타입 변환</strong></p>
<pre><code>  type NonNullable&lt;T&gt; = T extends null | undefined ? never : T;

  type A = NonNullable&lt;string | null&gt;; // string
  type B = NonNullable&lt;number | undefined&gt;; // number</code></pre></li>
</ul>
<hr>
<h2 id="qa-정리">Q&amp;A 정리</h2>
<h3 id="q1-제네릭-제약에서의-extends는-어떤-느낌인가요">Q1. 제네릭 제약에서의 extends는 어떤 느낌인가요?</h3>
<ul>
<li>“이 타입은 반드시 저 타입의 <strong>하위 집합</strong>이어야 한다”라는 규칙을 미리 거는 것.  </li>
<li>런타임에서 if 체크를 하는 게 아니라, <strong>애초에 호출/사용 자체가 금지</strong>됩니다.</li>
</ul>
<h3 id="q2-조건부-타입에서의-extends는-어떻게-다른가요">Q2. 조건부 타입에서의 extends는 어떻게 다른가요?</h3>
<ul>
<li>타입 레벨의 <strong>삼항 연산자</strong> 같은 것.  </li>
<li>타입 T를 실제로 넣으면 결과가 X 또는 Y로 “계산”됩니다.  </li>
<li>즉, <strong>타입을 실행해서 결과를 얻는 로직</strong>이라고 이해하면 편해요.</li>
</ul>
<hr>
<h2 id="6-실전-예시-requestof--responseof">6. 실전 예시: RequestOf / ResponseOf</h2>
<p>Day4 문제인 api-endpoints를 예로 들어보겠습니다.</p>
<pre><code>export type ApiMap = {
  &quot;/login&quot;: {
    req: { id: string; pw: string };
    res: { token: string };
  };
  &quot;/me&quot;: {
    res: { id: string; name: string };
  };
  &quot;/items&quot;: {
    req: { page: number };
    res: { items: Array&lt;{ id: string; name: string }&gt; };
  };
};

export type RequestOf&lt;M, K extends keyof M&gt; =
  &quot;req&quot; extends keyof M[K] ? M[K][&quot;req&quot;] : never;

export type ResponseOf&lt;M, K extends keyof M&gt; =
  M[K][&quot;res&quot;];</code></pre><p>여기서 쓰이는 extends 두 가지 문맥:</p>
<ol>
<li><p><strong>제약</strong>  </p>
<ul>
<li><code>&lt;K extends keyof M&gt;</code>  </li>
<li>K는 반드시 M 객체의 키 중 하나여야 한다.  </li>
<li>즉, &quot;/login&quot;, &quot;/me&quot;, &quot;/items&quot; 중 하나여야 함.</li>
</ul>
</li>
<li><p><strong>조건부 타입</strong>  </p>
<ul>
<li><code>&quot;req&quot; extends keyof M[K] ? ... : ...</code>  </li>
<li>M[K]에 &quot;req&quot; 키가 존재하면 M[K][&quot;req&quot;],<br>아니면 never.  </li>
<li>즉, <strong>조건부 분기</strong>로 req가 없는 엔드포인트(&quot;/me&quot;)에서는 never 처리.</li>
</ul>
</li>
</ol>
<p>👉 이 조합 덕분에, &quot;/login&quot; 같은 엔드포인트에서는 요청 타입이 나오고, &quot;/me&quot; 같은 요청 없는 엔드포인트에서는 자동으로 never가 됩니다.</p>
<hr>
<h2 id="✅-결론">✅ 결론</h2>
<ul>
<li><strong>제약(Constraint)</strong>:제약 조건입니다. 규칙이라고 생각하세요. 제공하는 유형 K는 반드시 M의 키여야 한다. 이는 허용되는 유형을 제한하는 것입니다.</li>
<li><strong>조건부 타입(Conditional Type)</strong>: 타입 레벨에서 if/else 분기  </li>
<li>실무에서는 두 가지가 함께 쓰이며, RequestOf 같은 유틸 타입에서 <strong>“키 제약 + 조건부 분기”</strong> 패턴이 핵심적으로 활용됩니다.  </li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Typescript day3]]></title>
            <link>https://velog.io/@dong_eon_/Typescript-day3</link>
            <guid>https://velog.io/@dong_eon_/Typescript-day3</guid>
            <pubDate>Tue, 26 Aug 2025 07:19:49 GMT</pubDate>
            <description><![CDATA[<p>Day 3에서는 <strong>실무 스타일 패턴 학습</strong>을 주제로, 단순 문법 암기에서 벗어나 실제 프론트엔드 개발에서 자주 맞닥뜨리는 타입 문제를 연습했습니다.<br>이 글은 <strong>문제 풀이 과정</strong>과, 풀면서 생긴 궁금증을 정리한 <strong>Q&amp;A 심화 정리</strong>까지 모두 담고 있습니다.  </p>
<hr>
<h3 id="problem-1--filterstring">Problem 1 – FilterString</h3>
<h4 id="문제-요구">문제 요구</h4>
<p>주어진 타입 T가 string이면 그대로 남기고, 그렇지 않으면 never가 되도록 타입을 정의하라.  </p>
<h4 id="문제-code">문제 code</h4>
<pre><code>// 초기 상태  
export type FilterString&lt;T&gt; = never;  

type A = FilterString&lt;string&gt;;      // string  
type B = FilterString&lt;number&gt;;      // never  
type C = FilterString&lt;&quot;hello&quot;&gt;;     // &quot;hello&quot;  
type D = FilterString&lt;boolean&gt;;     // never  
type E = FilterString&lt;string | 1&gt;;  // string  </code></pre><h4 id="test-code">test code</h4>
<details>  
<summary>테스트 코드 보기</summary>  

<pre><code>import type { Expect, Equal } from &quot;../../tests/type-assert.ts&quot;;  
import type { FilterString } from &quot;./01-filter-string.ts&quot;;  

type _cases = [  
  Expect&lt;Equal&lt;FilterString&lt;string&gt;, string&gt;&gt;,  
  Expect&lt;Equal&lt;FilterString&lt;number&gt;, never&gt;&gt;,  
  Expect&lt;Equal&lt;FilterString&lt;&quot;hello&quot;&gt;, &quot;hello&quot;&gt;&gt;,  
  Expect&lt;Equal&lt;FilterString&lt;boolean&gt;, never&gt;&gt;,  
  Expect&lt;Equal&lt;FilterString&lt;string | 1&gt;, string&gt;&gt;,  
];  </code></pre></details>  

<h4 id="풀이-과정">풀이 과정</h4>
<p>조건부 타입을 떠올렸고, <code>T extends string ? T : never</code> 형태로 구현하면 정확히 요구사항을 만족했습니다.<br>유니언 타입 입력 시 분배 조건부 타입이 동작해 자동으로 필터링된 결과가 나왔습니다.  </p>
<h4 id="적용된-개념">적용된 개념</h4>
<ul>
<li>조건부 타입 (T extends U ? X : Y)  </li>
<li>분배 조건부 타입  </li>
</ul>
<h4 id="최종-코드">최종 코드</h4>
<pre><code>export type FilterString&lt;T&gt; = T extends string ? T : never;  </code></pre><hr>
<h3 id="problem-2--publicuser">Problem 2 – PublicUser</h3>
<h4 id="문제-요구-1">문제 요구</h4>
<p>User 타입에서 password와 ssn만 제거한 PublicUser 타입을 만들어라.  </p>
<h4 id="문제-code-1">문제 code</h4>
<pre><code>export type User = {  
  id: string;  
  email: string;  
  name: string;  
  phone?: string;  
  address?: string;  
  password: string;  
  ssn: string;  
};  

// 초기 상태  
export type PublicUser = never;  </code></pre><h4 id="test-code-1">test code</h4>
<details>  
<summary>테스트 코드 보기</summary>  

<pre><code>import type { Expect, Equal } from &quot;../../tests/type-assert.ts&quot;;  
import type { PublicUser } from &quot;./02-public-user.ts&quot;;  

type _cases = [  
  Expect&lt;Equal&lt;PublicUser,  
    { id: string; email: string; name: string; phone?: string; address?: string }  
  &gt;&gt;,  
];  </code></pre></details>  

<h4 id="풀이-과정-1">풀이 과정</h4>
<p>민감 정보를 제거한다는 요구는 <code>Omit</code> 유틸리티 타입으로 쉽게 해결 가능했습니다.<br><code>Omit&lt;User, &quot;password&quot; | &quot;ssn&quot;&gt;</code>로 정의하면 의도대로 동작합니다.  </p>
<h4 id="적용된-개념-1">적용된 개념</h4>
<ul>
<li>Omit&lt;T, K&gt; = 특정 키 제거  </li>
<li>내부적으로 Pick + Exclude 조합으로 동작  </li>
</ul>
<h4 id="최종-코드-1">최종 코드</h4>
<pre><code>export type PublicUser = Omit&lt;User, &quot;password&quot; | &quot;ssn&quot;&gt;;  </code></pre><hr>
<h3 id="problem-3--statuscounters">Problem 3 – StatusCounters</h3>
<h4 id="문제-요구-2">문제 요구</h4>
<p>네트워크 요청 상태를 &quot;idle&quot; | &quot;loading&quot; | &quot;success&quot; | &quot;error&quot;로 정의하고,<br>각 상태별 카운터를 number 값으로 갖는 객체 타입을 정의하라.  </p>
<h4 id="문제-code-2">문제 code</h4>
<pre><code>// 초기 상태  
export type Status = string;  
export type Counters = Record&lt;string, number&gt;;  </code></pre><h4 id="test-code-2">test code</h4>
<details>  
<summary>테스트 코드 보기</summary>  

<pre><code>import type { Expect, Equal } from &quot;../../tests/type-assert.ts&quot;;  
import type { Status, Counters } from &quot;./03-status-counters.ts&quot;;  

type _cases = [  
  Expect&lt;Equal&lt;Status, &quot;idle&quot; | &quot;loading&quot; | &quot;success&quot; | &quot;error&quot;&gt;&gt;,  
  Expect&lt;Equal&lt;Counters, { idle: number; loading: number; success: number; error: number }&gt;&gt;,  
];  </code></pre></details>  

<h4 id="풀이-과정-2">풀이 과정</h4>
<p>매핑 타입으로 유니언을 순회하며 모든 상태를 key로 정의했습니다.<br>Record&lt;Status, number&gt;로 간단히 해결할 수 있었습니다.  </p>
<h4 id="적용된-개념-2">적용된 개념</h4>
<ul>
<li>매핑 타입 { [K in U]: ... }  </li>
<li>Record&lt;K, V&gt; = { [P in K]: V }  </li>
</ul>
<h4 id="최종-코드-2">최종 코드</h4>
<pre><code>export type Status = &quot;idle&quot; | &quot;loading&quot; | &quot;success&quot; | &quot;error&quot;;  
export type Counters = Record&lt;Status, number&gt;;  </code></pre><hr>
<h3 id="problem-4--apivalues">Problem 4 – ApiValues</h3>
<h4 id="문제-요구-3">문제 요구</h4>
<p>ApiMap 구조에서 각 엔드포인트의 data 타입만 추출하여 유니언으로 모으라.  </p>
<h4 id="문제-code-3">문제 code</h4>
<pre><code>export type ApiMap = {  
  &quot;/me&quot;: { data: { id: string; name: string } };  
  &quot;/items&quot;: { data: Array&lt;{ id: string; price: number }&gt; };  
  &quot;/health&quot;: { data: { ok: true } };  
};  

// 초기 상태  
export type ValueOf&lt;T&gt; = never;  
export type ApiValues&lt;T&gt; = never;  </code></pre><h4 id="test-code-3">test code</h4>
<details>  
<summary>테스트 코드 보기</summary>  

<pre><code>import type { Expect, Equal } from &quot;../../tests/type-assert.ts&quot;;  
import type { ApiMap, ApiValues } from &quot;./04-api-values.ts&quot;;  

type _cases = [  
  Expect&lt;Equal&lt;  
    ApiValues&lt;ApiMap&gt;,  
    { id: string; name: string }  
    | Array&lt;{ id: string; price: number }&gt;  
    | { ok: true }  
  &gt;&gt;,  
];  </code></pre></details>  

<h4 id="풀이-과정-3">풀이 과정</h4>
<p>객체 value 타입 전체를 뽑는 T[keyof T]를 ValueOf<T>로 정의했습니다.<br>그 후 ValueOf<T>[&quot;data&quot;]로 각 엔드포인트의 data 속성 타입만 추출했습니다.  </p>
<h4 id="적용된-개념-3">적용된 개념</h4>
<ul>
<li>인덱스드 액세스 타입 T[K]  </li>
<li>T[keyof T] = 객체 value 전체 유니언  </li>
<li>ValueOf<T>[&quot;data&quot;]로 data 속성 추출  </li>
</ul>
<h4 id="최종-코드-3">최종 코드</h4>
<pre><code>export type ValueOf&lt;T&gt; = T[keyof T];  
export type ApiValues&lt;T&gt; = ValueOf&lt;T&gt;[&quot;data&quot;];  </code></pre><hr>
<h3 id="problem-5--partialby">Problem 5 – PartialBy</h3>
<h4 id="문제-요구-4">문제 요구</h4>
<p>PartialBy&lt;T, K&gt;를 구현해, T에서 K 키만 optional로 만들고 나머지는 그대로 유지하라.  </p>
<h4 id="문제-code-4">문제 code</h4>
<pre><code>type User = { id: string; name: string; email: string; age?: number };  

// 초기 상태  
export type PartialBy&lt;T, K extends keyof T&gt; = never;  </code></pre><h4 id="test-code-4">test code</h4>
<details>  
<summary>테스트 코드 보기</summary>  

<pre><code>import type { Expect, Equal } from &quot;../../tests/type-assert.ts&quot;;  
import type { PartialBy } from &quot;./05-partial-by.ts&quot;;  

type User = { id: string; name: string; email: string; age?: number };  

type _cases = [  
  Expect&lt;Equal&lt;  
    PartialBy&lt;User, &quot;email&quot;&gt;,  
    { id: string; name: string; email?: string; age?: number }  
  &gt;&gt;,  
  Expect&lt;Equal&lt;  
    PartialBy&lt;User, &quot;name&quot; | &quot;age&quot;&gt;,  
    { id: string; name?: string; email: string; age?: number }  
  &gt;&gt;,  
];  </code></pre></details>  

<h4 id="풀이-과정-4">풀이 과정</h4>
<p>처음엔 조건부 타입으로 optional을 구현했지만 이는 단순히 undefined 허용으로만 처리되었습니다.<br>그래서 Omit + Pick + Partial을 교차(&amp;)시켜 특정 키만 optional로 만들었습니다.  </p>
<h4 id="적용된-개념-4">적용된 개념</h4>
<ul>
<li>Omit&lt;T, K&gt;: 특정 키 제외  </li>
<li>Pick&lt;T, K&gt;: 특정 키만 선택  </li>
<li>Partial&lt;Pick&lt;T, K&gt;&gt;: 해당 키만 optional 처리  </li>
<li>&amp; (교차 타입): 두 타입을 합쳐 최종 타입 생성  </li>
</ul>
<h4 id="최종-코드-4">최종 코드</h4>
<pre><code>export type PartialBy&lt;T, K extends keyof T&gt; =  
  Omit&lt;T, K&gt; &amp; Partial&lt;Pick&lt;T, K&gt;&gt;;  </code></pre><hr>
<h2 id="🔎-qa-심화-정리">🔎 Q&amp;A 심화 정리</h2>
<hr>
<h3 id="1-k-in-status-vs-keyof-status">1. <code>K in Status</code> vs <code>keyof Status</code></h3>
<h4 id="1-k-in-status">(1) <code>K in Status</code></h4>
<ul>
<li><p><code>in</code> 키워드는 <strong>매핑 타입 문맥</strong>에서 사용된다.  </p>
</li>
<li><p><code>Status</code>라는 유니언 타입의 원소들을 하나씩 순회하며, 그 값을 객체의 키로 만든다.  </p>
<pre><code>  type Status = &quot;idle&quot; | &quot;loading&quot; | &quot;success&quot;;  
  type Counters = { [K in Status]: number };  

  // 결과  
  type Counters = {  
    idle: number;  
    loading: number;  
    success: number;  
  }  </code></pre></li>
</ul>
<p>👉 즉, <code>K in Status</code>는 <strong>유니언을 그대로 key 집합으로 사용하는 방법</strong>이다.  </p>
<h4 id="2-keyof-status">(2) <code>keyof Status</code></h4>
<ul>
<li><code>keyof</code>는 <strong>객체 타입의 키를 뽑아내는 연산자</strong>다.  </li>
<li>하지만 <code>Status</code>는 유니언 리터럴 타입이지 객체가 아니다.  </li>
<li>따라서 <code>keyof Status</code>를 쓰면 문자열 리터럴의 내부 프로퍼티가 뽑혀서 전혀 다른 결과가 된다.  </li>
</ul>
<pre><code>type Status = &quot;idle&quot; | &quot;loading&quot;;  
type Wrong = keyof Status;  
// 결과: string | number | symbol  </code></pre><p>👉 이유: 문자열 리터럴 타입은 사실상 string의 하위 타입이고, 자바스크립트에서 문자열의 키는 string | number | symbol이 될 수 있기 때문이다.  </p>
<h4 id="3-정리">(3) 정리</h4>
<ul>
<li><strong>유니언 타입을 그대로 키로 쓰고 싶을 때</strong> → <code>K in Status</code>  </li>
<li><strong>객체의 키를 추출하고 싶을 때</strong> → <code>keyof User</code>  </li>
</ul>
<hr>
<h3 id="2-keyof-객체-타입-결과는">2. <code>keyof 객체 타입</code> 결과는?</h3>
<h4 id="1-기본-객체">(1) 기본 객체</h4>
<pre><code>type User = { id: string; name: string; email?: string };  
type Keys = keyof User;  
// &quot;id&quot; | &quot;name&quot; | &quot;email&quot;  </code></pre><p>👉 옵셔널 여부와 관계없이 키는 모두 포함된다.  </p>
<h4 id="2-인덱스-시그니처">(2) 인덱스 시그니처</h4>
<pre><code>type Dictionary = { [key: string]: number };  
type Keys = keyof Dictionary;  
// string | number  </code></pre><p>👉 자바스크립트 객체 키는 사실상 string | number | symbol이 될 수 있다.  </p>
<h4 id="3-배열">(3) 배열</h4>
<pre><code>type Arr = string[];  
type Keys = keyof Arr;  
// number | &quot;length&quot; | &quot;push&quot; | &quot;pop&quot; | ...  </code></pre><p>👉 배열도 사실상 객체이므로, 숫자 인덱스와 배열 메서드들이 키로 잡힌다.  </p>
<h4 id="4-실무-응용">(4) 실무 응용</h4>
<ol>
<li><p>안전한 프로퍼티 접근  </p>
<pre><code> function getValue&lt;T, K extends keyof T&gt;(obj: T, key: K): T[K] {  
   return obj[key];  
 }  </code></pre></li>
<li><p>자동 필드 검사기  </p>
<pre><code> type UserFields = keyof User;  
 // &quot;id&quot; | &quot;name&quot; | &quot;email&quot;  
</code></pre></li>
</ol>
<hr>
<h3 id="3--교차-타입-intersection-type">3. <code>&amp;</code> (교차 타입, Intersection Type)</h3>
<h4 id="1-기본-개념">(1) 기본 개념</h4>
<ul>
<li><code>A &amp; B</code>는 A와 B를 모두 만족하는 타입이다.  </li>
<li>객체 타입끼리 교차하면 속성이 병합된다.  <pre><code>  type A = { id: number };  
  type B = { name: string };  
  type AB = A &amp; B;  
  // { id: number; name: string }  </code></pre><h4 id="2-겹치는-키가-다르면">(2) 겹치는 키가 다르면?</h4>
  type A = { value: string };<br>  type B = { value: number };<br>  type AB = A &amp; B;<br>  // { value: string &amp; number } → never  </li>
</ul>
<p>👉 교차 타입에서 겹치는 키의 타입이 호환되지 않으면 never가 된다.  </p>
<h4 id="3-실무-응용">(3) 실무 응용</h4>
<ul>
<li>유틸리티 타입 합성  <pre><code>  type PartialBy&lt;T, K extends keyof T&gt; =  
    Omit&lt;T, K&gt; &amp; Partial&lt;Pick&lt;T, K&gt;&gt;;  </code></pre></li>
<li>API 응답 타입 확장  <pre><code>  type Base = { id: string };  
  type WithTimestamp = { createdAt: Date };  
  type ApiResponse = Base &amp; WithTimestamp;  
  // { id: string; createdAt: Date }  </code></pre></li>
</ul>
<hr>
<h3 id="4-인덱스드-액세스-타입-vs-매핑-타입-문맥">4. 인덱스드 액세스 타입 vs 매핑 타입 문맥</h3>
<h4 id="1-인덱스드-액세스-타입">(1) 인덱스드 액세스 타입</h4>
<ul>
<li>문법: T[K]  </li>
<li>의미: 타입 T에서 키 K의 값 타입을 가져온다.  <pre><code>  type User = { id: number; name: string };  
  type IdType = User[&quot;id&quot;]; // number  
  type Values = User[keyof User]; // number | string  </code></pre>👉 핵심: <strong>값을 꺼내는 문법</strong>이다.  </li>
</ul>
<h4 id="2-매핑-타입-문맥">(2) 매핑 타입 문맥</h4>
<ul>
<li>문법: { [K in U]: ... }  </li>
<li>의미: 유니언 U를 순회해서 새로운 객체 타입을 만든다.  <pre><code>  type Status = &quot;idle&quot; | &quot;loading&quot;;  
  type Counters = { [K in Status]: number };  
  // { idle: number; loading: number }  </code></pre>👉 핵심: <strong>키를 새로 만드는 문법</strong>이다.  </li>
</ul>
<h4 id="3-키-리매핑as">(3) 키 리매핑(as)</h4>
<ul>
<li>매핑 타입 문맥에서만 사용할 수 있다.  <pre><code>  type Rename&lt;T&gt; = {  
    [K in keyof T as `new_${string &amp; K}`]: T[K];  
  };  </code></pre>👉 인덱스드 액세스(T[keyof T])에서는 as를 쓸 수 없다.  </li>
</ul>
<h4 id="4-정리">(4) 정리</h4>
<ul>
<li>T[K] → 값 꺼내기  </li>
<li>{ [K in U]: ... } → 키 순회하여 객체 만들기  </li>
<li>as (키 리매핑) → 매핑 타입에서만 사용 가능  </li>
</ul>
<hr>
<h1 id="✨-day-3-전체-정리">✨ Day 3 전체 정리</h1>
<ul>
<li>조건부 타입으로 타입 필터링  </li>
<li>Omit/Pick/Partial 조합으로 일부 키 optional 처리  </li>
<li>매핑 타입과 Record로 상태 매핑  </li>
<li>Indexed Access + ValueOf로 API 응답 타입 추출  </li>
<li>교차 타입(&amp;)으로 유틸리티 합성  </li>
<li><code>K in</code> vs <code>keyof</code>, 인덱스드 액세스 vs 매핑 타입 문맥, <code>?:</code> vs <code>| undefined</code> 차이  </li>
</ul>
<p>👉 Day3 학습은 단순 문법을 넘어서, <strong>실무 타입 설계 감각</strong>을 키우는 훈련이었다.  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JS Promise 개념 정리]]></title>
            <link>https://velog.io/@dong_eon_/JS-Promise-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@dong_eon_/JS-Promise-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 21 Aug 2025 08:00:44 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ol>
<li><a href="#1-%EC%BD%9C%EB%B0%B1-%ED%95%A8%EC%88%98%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80">콜백 함수란 무엇인가?</a></li>
<li><a href="#2-%EC%BD%9C%EB%B0%B1-%EA%B8%B0%EB%B0%98-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC%EC%9D%98-%ED%95%9C%EA%B3%84">콜백 기반 비동기 처리의 한계</a></li>
<li><a href="#3-promise%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80">Promise란 무엇인가?</a></li>
<li><a href="#4-promise%EC%9D%98-%EC%83%81%ED%83%9C%EC%99%80-%ED%9D%90%EB%A6%84">Promise의 상태와 흐름</a></li>
<li><a href="#5-promise%EC%9D%98-%EC%83%9D%EC%84%B1-%EB%B0%8F-%EA%B8%B0%EB%B3%B8-%EC%82%AC%EC%9A%A9%EB%B2%95">Promise의 생성 및 기본 사용법</a></li>
<li><a href="#6-promise-%EC%B2%B4%EC%9D%B4%EB%8B%9D-then-catch-finally">Promise 체이닝: then, catch, finally</a></li>
<li><a href="#7-%EC%A3%BC%EC%9A%94-promise-%EC%9C%A0%ED%8B%B8-%EB%A9%94%EC%84%9C%EB%93%9C%EB%93%A4">주요 Promise 유틸 메서드들</a></li>
<li><a href="#8-asyncawait-%EB%AC%B8%EB%B2%95">async/await 문법</a></li>
<li><a href="#9-%EC%BD%9C%EB%B0%B1-%EA%B8%B0%EB%B0%98-%ED%95%A8%EC%88%98%EB%A5%BC-promise%EB%A1%9C-%EB%B3%80%ED%99%98%ED%95%98%EA%B8%B0">콜백 기반 함수를 Promise로 변환하기</a></li>
<li><a href="#10-%EC%8B%A4%EC%A0%84-%EC%98%88%EC%A0%9C-%EB%B0%8F-%EC%A0%95%EB%A6%AC">실전 예제 및 정리</a></li>
</ol>
<hr>
<h2 id="1-콜백-함수란-무엇인가">1. 콜백 함수란 무엇인가?</h2>
<h3 id="📌-개념-설명">📌 개념 설명</h3>
<p>콜백 함수(Callback Function)란 <strong>다른 함수의 인자로 전달되어 실행되는 함수</strong>를 말합니다. 자바스크립트에서는 일급 객체(First-class Object)로서 함수를 값처럼 전달할 수 있기 때문에, 콜백 패턴은 아주 일반적으로 사용됩니다.</p>
<h3 id="✍️-예시">✍️ 예시</h3>
<pre><code class="language-js">function greet(name, callback) {
  console.log(&quot;안녕하세요, &quot; + name);
  callback();
}

function sayBye() {
  console.log(&quot;안녕히 가세요!&quot;);
}

greet(&quot;동언&quot;, sayBye);</code></pre>
<p>위 코드에서 <code>sayBye</code> 함수는 <code>greet</code> 함수의 콜백으로 전달되어 나중에 실행됩니다.</p>
<h3 id="🔍-역할">🔍 역할</h3>
<ul>
<li>로직의 흐름을 나중으로 미루고 싶을 때 (지연 실행)</li>
<li>이벤트 기반 프로그래밍에 필수적 (예: 클릭 이벤트 후 실행)</li>
<li>비동기 작업을 처리할 때 (파일 읽기, 서버 응답 처리 등)</li>
</ul>
<h3 id="🧠-주의할-점">🧠 주의할 점</h3>
<ul>
<li>콜백 지옥(Callback Hell)로 인해 코드 가독성이 떨어질 수 있음</li>
<li>에러 처리가 어렵고 중첩이 깊어짐</li>
</ul>
<hr>
<h2 id="2-콜백-기반-비동기-처리의-한계">2. 콜백 기반 비동기 처리의 한계</h2>
<h3 id="📌-문제점-정리">📌 문제점 정리</h3>
<p>콜백 함수는 간단한 비동기 흐름에서는 유용하지만, 여러 비동기 작업을 <strong>중첩해서 순차적으로 처리</strong>해야 할 경우 다음과 같은 문제들이 발생합니다.</p>
<h3 id="😵-콜백-지옥-callback-hell">😵 콜백 지옥 (Callback Hell)</h3>
<pre><code class="language-js">readFile(&quot;user.json&quot;, (err, data) =&gt; {
  if (err) throw err;
  parseData(data, (err, result) =&gt; {
    if (err) throw err;
    fetchUser(result.id, (err, user) =&gt; {
      if (err) throw err;
      console.log(user);
    });
  });
});</code></pre>
<ul>
<li>중첩 구조로 인해 <strong>가독성↓ 유지보수↓</strong></li>
<li>에러 처리 로직이 <strong>복잡하고 중복됨</strong></li>
<li>흐름 제어(순차, 병렬 처리)가 어려움</li>
</ul>
<blockquote>
<p>💡 이러한 단점을 해결하기 위해 등장한 것이 바로 <strong>Promise</strong>입니다.</p>
</blockquote>
<hr>
<h2 id="3-promise란-무엇인가">3. Promise란 무엇인가?</h2>
<h3 id="📌-개념-설명-1">📌 개념 설명</h3>
<p><strong>Promise</strong>는 자바스크립트의 <strong>비동기 처리를 위한 객체</strong>입니다. 비동기 작업의 최종 성공 또는 실패를 나타내는 값을 <strong>미래 시점에 전달</strong>하겠다는 약속을 의미합니다.</p>
<h3 id="✅-promise-상태">✅ Promise 상태</h3>
<ul>
<li><code>pending</code>: 대기 중 (아직 결과가 없음)</li>
<li><code>fulfilled</code>: 작업 성공</li>
<li><code>rejected</code>: 작업 실패</li>
</ul>
<pre><code class="language-js">const promise = new Promise((resolve, reject) =&gt; {
  // 비동기 로직
});</code></pre>
<h3 id="🤔-등장-배경">🤔 등장 배경</h3>
<ul>
<li>콜백 지옥 → 가독성 문제</li>
<li>순차적 흐름 처리 어려움</li>
<li>에러 처리 로직의 분산</li>
</ul>
<h3 id="🧪-예시-코드">🧪 예시 코드</h3>
<pre><code class="language-ts">function fetchData(): Promise&lt;string&gt; {
  return new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; {
      const success = true;
      if (success) {
        resolve(&quot;🎉 데이터 로드 성공&quot;);
      } else {
        reject(new Error(&quot;❌ 데이터 로드 실패&quot;));
      }
    }, 2000);
  });
}</code></pre>
<h3 id="📈-실전-활용">📈 실전 활용</h3>
<ul>
<li>서버 API 통신: <code>fetch()</code>, <code>axios</code></li>
<li><code>setTimeout</code>/<code>setInterval</code> 대체</li>
<li>Node.js 파일 처리 등</li>
</ul>
<hr>
<h2 id="4-promise의-상태와-흐름">4. Promise의 상태와 흐름</h2>
<h3 id="🧱-세-가지-상태--설명">🧱 세 가지 상태 &amp; 설명</h3>
<table>
<thead>
<tr>
<th>상태</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>pending</code></td>
<td>초기 상태, 아직 결과가 없음</td>
</tr>
<tr>
<td><code>fulfilled</code></td>
<td>비동기 작업이 성공적으로 완료됨</td>
</tr>
<tr>
<td><code>rejected</code></td>
<td>비동기 작업이 실패하여 오류 발생</td>
</tr>
</tbody></table>
<h3 id="📈-상태-전이transition">📈 상태 전이(transition)</h3>
<ul>
<li><code>resolve(value)</code> → <code>fulfilled</code> 상태로 전이</li>
<li><code>reject(error)</code> → <code>rejected</code> 상태로 전이</li>
<li>한 번 상태가 정해지면 절대 바뀌지 않음 (immutable)</li>
</ul>
<pre><code class="language-js">const promise = new Promise((resolve, reject) =&gt; {
  resolve(&quot;성공&quot;);
  reject(&quot;실패&quot;); // 무시됨
});</code></pre>
<hr>
<h2 id="5-promise의-생성-및-기본-사용법">5. Promise의 생성 및 기본 사용법</h2>
<pre><code class="language-js">const promise = new Promise((resolve, reject) =&gt; {
  // 비동기 작업
  setTimeout(() =&gt; {
    const ok = true;
    if (ok) resolve(&quot;작업 완료&quot;);
    else reject(new Error(&quot;에러 발생&quot;));
  }, 1000);
});</code></pre>
<ul>
<li>📌 <code>resolve(value)</code> → 정상 결과 전달  </li>
<li>📌 <code>reject(error)</code> → 에러 객체 전달</li>
</ul>
<hr>
<h2 id="6-promise-체이닝-then-catch-finally">6. Promise 체이닝: then, catch, finally</h2>
<p>Promise의 가장 큰 장점 중 하나는 <strong>체이닝(Chaining)</strong> 을 통해 여러 비동기 작업을 순차적으로 연결할 수 있다는 점입니다.
<code>then</code>, <code>catch</code>
<code>then()</code>메서드는 성공 결과를, <code>catch()</code>메서드는 실패 결과를 처리합니다. 이 메서드들은 새로운 Promise를 반환하므로 계속해서 연결할 수 있습니다.</p>
<pre><code class="language-js">fetchData()
  .then((data) =&gt; {
    console.log(&quot;성공&quot;, data);
  })
  .catch((err) =&gt; {
    console.error(&quot;실패&quot;, err.message);
  });</code></pre>
<h3 id="🛠-finally">🛠 finally</h3>
<p><code>finally()</code>메서드는 Promise의 성공/실패 여부와 상관없이 무조건 한 번 실행됩니다. 주로 로딩 스피너 제거 등 마무리 작업에 사용됩니다.</p>
<pre><code class="language-js">fetchData()
  .finally(() =&gt; {
    console.log(&quot;작업 종료&quot;);
  });</code></pre>
<hr>
<h2 id="7-주요-promise-유틸-메서드들">7. 주요 Promise 유틸 메서드들</h2>
<h3 id="71-promiseall">7.1 Promise.all</h3>
<ul>
<li>모든 Promise가 성공해야 결과 반환</li>
<li>하나라도 실패하면 전체가 reject</li>
</ul>
<pre><code class="language-js">Promise.all([fetchA(), fetchB(), fetchC()])
  .then(([a, b, c]) =&gt; console.log(a, b, c))
  .catch(console.error);</code></pre>
<h3 id="72-promiserace">7.2 Promise.race</h3>
<ul>
<li>가장 먼저 처리된 Promise의 결과 반환</li>
</ul>
<pre><code class="language-js">Promise.race([fetchSlow(), fetchFast()])
  .then(console.log)
  .catch(console.error);</code></pre>
<h3 id="73-promiseallsettled">7.3 Promise.allSettled</h3>
<ul>
<li>성공/실패 상관없이 각 결과 개별 확인</li>
</ul>
<pre><code class="language-js">Promise.allSettled([fetch1(), fetch2()])
  .then((results) =&gt;
    results.forEach((res) =&gt;
      res.status === &quot;fulfilled&quot;
        ? console.log(&quot;성공:&quot;, res.value)
        : console.error(&quot;실패:&quot;, res.reason)
    )
  );</code></pre>
<h3 id="74-promiseany">7.4 Promise.any</h3>
<ul>
<li>가장 먼저 성공한 결과만 반환</li>
<li>전부 실패하면 AggregateError 반환</li>
</ul>
<pre><code class="language-js">Promise.any([fail1(), fail2(), success()])
  .then(console.log)
  .catch((e) =&gt; console.error(&quot;모두 실패&quot;, e));</code></pre>
<hr>
<h2 id="8-asyncawait">8. async/await</h2>
<h3 id="📌-개념-설명-2">📌 개념 설명</h3>
<p><code>async/await</code>는 Promise를 <strong>동기 코드처럼</strong> 사용할 수 있게 해주는 문법입니다.</p>
<ul>
<li><code>async</code> 함수는 항상 Promise를 반환합니다.</li>
<li><code>await</code>은 해당 Promise의 결과가 나올 때까지 기다립니다.</li>
</ul>
<h3 id="🧪-예시-코드-1">🧪 예시 코드</h3>
<pre><code class="language-ts">async function loadUserProfile() {
  try {
    const name = await fetchName(); // Promise&lt;string&gt;
    const age = await fetchAge();   // Promise&lt;number&gt;
    const job = await fetchJob();   // Promise&lt;string&gt;

    return { name, age, job };
  } catch (err) {
    console.error(&quot;에러:&quot;, err);
    throw err;
  }
}</code></pre>
<h3 id="✅-장점">✅ 장점</h3>
<table>
<thead>
<tr>
<th>장점</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>가독성 ↑</td>
<td>중첩 없는 직관적 코드 작성 가능</td>
</tr>
<tr>
<td>에러 처리 용이</td>
<td><code>try/catch</code> 문법으로 일괄 처리 가능</td>
</tr>
<tr>
<td>디버깅 쉬움</td>
<td>호출 스택 간결</td>
</tr>
</tbody></table>
<h3 id="⚠️-주의사항">⚠️ 주의사항</h3>
<ul>
<li><code>await</code>는 반드시 <code>async</code> 함수 안에서 사용</li>
<li>병렬 호출 시에는 <code>Promise.all()</code> 사용이 더 효율적</li>
</ul>
<hr>
<h2 id="9-콜백-기반-함수를-promise로-변환하기">9. 콜백 기반 함수를 Promise로 변환하기</h2>
<p>기존 콜백 패턴을 Promise로 감싸는 예제:</p>
<pre><code class="language-ts">function legacy(callback: (value: string) =&gt; void) {
  setTimeout(() =&gt; {
    callback(&quot;✅ 완료&quot;);
  }, 1000);
}

function wrapped(): Promise&lt;string&gt; {
  return new Promise((resolve) =&gt; {
    legacy((result) =&gt; {
      resolve(result);
    });
  });
}</code></pre>
<blockquote>
<p>🧠 <code>util.promisify()</code> (Node.js 내장 유틸)도 유사한 기능을 제공합니다.</p>
</blockquote>
<hr>
<h2 id="10-실전-예제-및-정리">10. 실전 예제 및 정리</h2>
<pre><code class="language-ts">async function getWeatherInfo() {
  try {
    const [temp, wind] = await Promise.all([
      fetchTemperature(),
      fetchWindSpeed(),
    ]);
    return { temp, wind };
  } catch (err) {
    console.error(&quot;날씨 정보를 불러오는데 실패했습니다.&quot;);
    throw err;
  }
}</code></pre>
<p><strong>정리</strong></p>
<ul>
<li>콜백 → Promise → async/await으로의 진화</li>
<li>각각의 방식은 상황에 따라 적절히 선택</li>
<li>병렬/순차 처리 유틸 메서드 적극 활용</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript day2]]></title>
            <link>https://velog.io/@dong_eon_/TypeScript-%EC%9C%A0%ED%8B%B8%EB%A6%AC%ED%8B%B0-%ED%83%80%EC%9E%85-%EC%8B%A4%EC%A0%84-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@dong_eon_/TypeScript-%EC%9C%A0%ED%8B%B8%EB%A6%AC%ED%8B%B0-%ED%83%80%EC%9E%85-%EC%8B%A4%EC%A0%84-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 19 Aug 2025 11:24:44 GMT</pubDate>
            <description><![CDATA[<p>TypeScript의 강력한 타입 시스템을 이해하고 활용하기 위한 첫 걸음으로, Day 1에서는 <code>keyof</code>, <code>typeof</code>, <code>Record</code>, <code>Omit</code>, <code>Exclude</code>, <code>Extract</code>, 조건부 타입, <code>infer</code>, 템플릿 리터럴 타입 등 실무에서도 매우 자주 사용하는 핵심 문법들을 학습했습니다.</p>
<hr>
<h2 id="keyof와-제네릭-제약">keyof와 제네릭 제약</h2>
<h3 id="개념-설명">개념 설명</h3>
<p><code>keyof T</code>는 객체 타입 <code>T</code>의 모든 키를 유니언 타입으로 반환합니다. 예를 들어 <code>keyof { name: string; age: number }</code>는 <code>&quot;name&quot; | &quot;age&quot;</code>가 됩니다. 또한 제네릭을 사용할 때 <code>T extends object</code>처럼 타입의 범위를 제한해주면 타입 오류를 방지할 수 있습니다.</p>
<pre><code class="language-ts">function getValue&lt;T extends object, K extends keyof T&gt;(obj: T, key: K): T[K] {
  return obj[key];
}</code></pre>
<h3 id="부가-설명">부가 설명</h3>
<ul>
<li><code>keyof</code>는 <code>Record</code>, <code>Pick</code>, <code>Omit</code> 등 다른 유틸리티 타입들과 자주 결합됩니다.</li>
<li>제네릭 제약을 통해 <code>keyof</code>의 사용 대상을 명확히 하여 예기치 않은 타입 추론을 방지할 수 있습니다.</li>
</ul>
<hr>
<h2 id="recordk-v">Record&lt;K, V&gt;</h2>
<h3 id="개념-설명-1">개념 설명</h3>
<p><code>Record&lt;K, V&gt;</code>는 K를 키로, V를 값으로 갖는 객체 타입을 생성합니다.</p>
<pre><code class="language-ts">type Role = &quot;admin&quot; | &quot;user&quot;;
type RolePermissions = Record&lt;Role, string[]&gt;;</code></pre>
<h3 id="부가-설명-1">부가 설명</h3>
<ul>
<li>매핑 가능한 딕셔너리 형태를 만들 때 매우 유용합니다.</li>
<li>문자열 조합이 타입으로 제한되어야 할 때 활용하면 타입 안정성이 높아집니다.</li>
<li>Tailwind 클래스, API 경로, 파일명 등에서 실무적으로 자주 쓰입니다.</li>
</ul>
<hr>
<h2 id="omitt-k">Omit&lt;T, K&gt;</h2>
<h3 id="개념-설명-2">개념 설명</h3>
<p><code>Omit&lt;T, K&gt;</code>는 객체 타입 T에서 K에 해당하는 속성을 제거한 타입을 생성합니다.</p>
<pre><code class="language-ts">type User = { id: number; password: string };
type PublicUser = Omit&lt;User, &quot;password&quot;&gt;; // { id: number }</code></pre>
<h3 id="부가-설명-2">부가 설명</h3>
<ul>
<li>민감 정보를 제외한 타입 생성에 유용합니다.</li>
<li>내부적으로 <code>Pick&lt;T, Exclude&lt;keyof T, K&gt;&gt;</code>처럼 동작합니다.</li>
<li>따라서 T는 반드시 객체 타입이어야 하며, 유니언 타입에서는 동작하지 않습니다.</li>
</ul>
<hr>
<h2 id="excludet-u">Exclude&lt;T, U&gt;</h2>
<h3 id="개념-설명-3">개념 설명</h3>
<p><code>Exclude&lt;T, U&gt;</code>는 유니언 타입 T에서 U에 해당하는 타입을 제외합니다.</p>
<pre><code class="language-ts">type Role = &quot;admin&quot; | &quot;user&quot; | &quot;guest&quot;;
type PublicRole = Exclude&lt;Role, &quot;admin&quot;&gt;; // &quot;user&quot; | &quot;guest&quot;</code></pre>
<h3 id="부가-설명-3">부가 설명</h3>
<ul>
<li>유니언 타입 중 제외하고 싶은 타입이 있을 때 매우 유용합니다.</li>
<li>객체가 아닌 타입에서도 사용 가능하며, 유연하게 타입을 좁힐 수 있습니다.</li>
</ul>
<hr>
<h2 id="extractt-u">Extract&lt;T, U&gt;</h2>
<h3 id="개념-설명-4">개념 설명</h3>
<p><code>Extract&lt;T, U&gt;</code>는 유니언 타입 T에서 U에 해당하는 타입만 추출합니다.</p>
<pre><code class="language-ts">type Role = &quot;admin&quot; | &quot;user&quot; | &quot;guest&quot;;
type Staff = Extract&lt;Role, &quot;admin&quot; | &quot;user&quot;&gt;; // &quot;admin&quot; | &quot;user&quot;</code></pre>
<h3 id="부가-설명-4">부가 설명</h3>
<ul>
<li>유니언 타입에서 특정 값만 남기고 싶을 때 사용합니다.</li>
<li>조건 필터링에 가까운 역할을 하며, <code>Exclude</code>와 반대입니다.</li>
</ul>
<hr>
<h2 id="조건부-타입과-infer">조건부 타입과 infer</h2>
<h3 id="개념-설명-5">개념 설명</h3>
<p>조건부 타입은 <code>A extends B ? X : Y</code> 형태로 타입 간 관계에 따라 결과를 분기시킵니다. <code>infer</code> 키워드를 사용하면 타입을 추론할 수 있습니다.</p>
<pre><code class="language-ts">type ElementType&lt;T&gt; = T extends Array&lt;infer U&gt; ? U : T;

type A = ElementType&lt;string[]&gt;;  // string
type B = ElementType&lt;boolean&gt;;   // boolean</code></pre>
<h3 id="부가-설명-5">부가 설명</h3>
<ul>
<li><code>infer</code>는 <code>ReturnType</code>, <code>Parameters</code> 등 여러 내장 유틸리티 타입에서도 사용됩니다.</li>
<li>타입 연산 중간 결과를 임시로 추론해 다음 단계의 조건 분기 등에 활용할 수 있게 해줍니다.</li>
</ul>
<hr>
<h2 id="템플릿-리터럴-타입">템플릿 리터럴 타입</h2>
<h3 id="개념-설명-6">개념 설명</h3>
<p>템플릿 리터럴 타입은 문자열 리터럴과 유니언 타입을 결합해 새로운 문자열 타입 조합을 생성할 수 있습니다.</p>
<pre><code class="language-ts">type Size = &quot;sm&quot; | &quot;md&quot;;
type Variant = &quot;primary&quot; | &quot;secondary&quot;;
type ButtonClass = `btn-${Size}-${Variant}`;</code></pre>
<h3 id="부가-설명-6">부가 설명</h3>
<ul>
<li>허용된 문자열 조합만 가능하게 하여 런타임 오류를 줄입니다.</li>
<li>실무에서는 클래스명 제약, API 경로 제약, 자동 완성에 도움을 주는 패턴으로 자주 사용됩니다.</li>
</ul>
<hr>
<h2 id="마무리">마무리</h2>
<p>이번 학습을 통해 타입스크립트의 핵심 유틸리티 타입들과 그 동작 원리에 대해 이해하게 되었습니다. 단순히 문법을 외우는 것을 넘어, 타입 설계에 있어 어떤 유틸리티를 언제 사용해야 하는지에 대한 감각을 익힐 수 있었습니다.</p>
<p>특히 객체 타입과 유니언 타입을 다룰 때 사용하는 <code>Pick</code>, <code>Omit</code>, <code>Extract</code>, <code>Exclude</code>의 차이와 활용 방식, 조건부 타입과 <code>infer</code>를 통해 타입을 유연하게 조작하는 방법, 템플릿 리터럴 타입을 통한 문자열 패턴 제한은 앞으로 타입 안정성을 높이는 데 큰 역할을 할 것입니다.</p>
<blockquote>
<p>타입은 문법이 아니라 설계입니다. 그리고 설계는 연습과 시행착오를 통해 단단해집니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Typescript day1]]></title>
            <link>https://velog.io/@dong_eon_/Typescript1</link>
            <guid>https://velog.io/@dong_eon_/Typescript1</guid>
            <pubDate>Mon, 18 Aug 2025 06:04:26 GMT</pubDate>
            <description><![CDATA[<p>타입스크립트의 기본 문법 너머에는 실무에서 빛을 발하는 &#39;타입 설계&#39;의 영역이 있다는 것을 깨달았습니다. 이 문서는 AI라는 페어 프로그래머와 함께, 수박 겉핥기 수준에 머물러 있던 저의 타입스크립트 지식을 실무에 바로 적용할 수 있는 단단한 역량으로 발전시키는 여정을 기록한 것입니다.</p>
<hr>
<h2 id="📌-keyof와-typeof를-이용한-안전한-경로-추출">📌 keyof와 typeof를 이용한 안전한 경로 추출</h2>
<h3 id="문제">문제</h3>
<pre><code class="language-typescript">const routes = {
  home: &quot;/&quot;,
  users: &quot;/users&quot;,
  settings: &quot;/settings&quot;,
} as const;

type RouteName = keyof typeof routes;

function toPath(name: RouteName) {
  return routes[name];
}

// ✅ 통과
toPath(&quot;home&quot;);
toPath(&quot;users&quot;);

// ❌ 컴파일 에러 발생해야 함
toPath(&quot;login&quot;);
toPath(&quot;logout&quot;);</code></pre>
<h3 id="사용된-문법-설명">사용된 문법 설명</h3>
<ul>
<li><code>typeof</code>: 변수의 타입을 추론해 타입 시스템에서 사용 가능하게 만듦</li>
<li><code>keyof</code>: 객체 타입의 키를 유니온 타입으로 추출</li>
</ul>
<hr>
<h2 id="📌-record를-사용한-안전한-필드-매핑">📌 Record를 사용한 안전한 필드 매핑</h2>
<h3 id="문제-1">문제</h3>
<pre><code class="language-typescript">interface User {
  id: number;
  name: string;
  email: string;
  createdAt: string;
}

const userFieldLabels: Record&lt;keyof User, string&gt; = {
  id: &#39;User ID&#39;,
  name: &#39;Name&#39;,
  email: &#39;Email&#39;,
  createdAt: &#39;Created&#39;,
};</code></pre>
<h3 id="사용된-문법-설명-1">사용된 문법 설명</h3>
<ul>
<li><code>Record&lt;K, V&gt;</code>: K를 키로, V를 값으로 갖는 객체 타입 생성</li>
<li><code>keyof T</code>: 타입 T의 모든 키를 유니온 타입으로 추출</li>
</ul>
<hr>
<h2 id="📌-omit을-활용한-민감-정보-제거">📌 Omit을 활용한 민감 정보 제거</h2>
<h3 id="문제-2">문제</h3>
<pre><code class="language-typescript">interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  token: string;
}

type PublicUser = Omit&lt;User, &#39;password&#39; | &#39;token&#39;&gt;;

const safeUsers: PublicUser[] = [
  {
    id: 1,
    name: &#39;Alice&#39;,
    email: &#39;alice@email.com&#39;
  },
  {
    id: 2,
    name: &#39;Bob&#39;,
    email: &#39;bob@email.com&#39;
  },
];</code></pre>
<h3 id="사용된-문법-설명-2">사용된 문법 설명</h3>
<ul>
<li><code>Omit&lt;T, K&gt;</code>: 타입 T에서 속성 K를 제거한 타입 생성</li>
</ul>
<hr>
<h2 id="📌-유니언-타입-분기와-never를-활용한-안전한-분기-처리">📌 유니언 타입 분기와 never를 활용한 안전한 분기 처리</h2>
<h3 id="문제-3">문제</h3>
<pre><code class="language-typescript">type LinkProps = {
  variant: &#39;link&#39;;
  label: string;
  href: string;
  onClick?: never;
};

type ButtonProps = {
  variant: &#39;button&#39;;
  label: string;
  onClick: () =&gt; void;
  href?: never;
};

type Props = LinkProps | ButtonProps;

function Render(p: Props) {
  if (p.variant === &#39;link&#39;) {
    return `&lt;a href=&quot;${p.href}&quot;&gt;${p.label}&lt;/a&gt;`;
  } else {
    return `&lt;button&gt;${p.label}&lt;/button&gt;`;
  }
}</code></pre>
<h3 id="사용된-문법-설명-3">사용된 문법 설명</h3>
<ul>
<li><strong>유니언 타입(Union Type)</strong>: 여러 타입 중 하나의 타입을 가질 수 있음</li>
<li><strong>never</strong>: 절대 사용되어서는 안 되는 값 또는 타입을 나타냄</li>
<li><strong>타입 좁히기(Narrowing)</strong>: 조건문 등을 통해 유니언 타입을 구체적으로 좁히는 타입 보호 기법</li>
</ul>
<hr>
<h2 id="📌-조건부-타입으로-api-응답-형태-제어하기">📌 조건부 타입으로 API 응답 형태 제어하기</h2>
<h3 id="문제-4">문제</h3>
<pre><code class="language-typescript">interface ApiResponse&lt;T&gt; {
  data: T;
  meta: { total: number };
}

type FetchResult&lt;T, Raw extends boolean&gt; = Raw extends true ? ApiResponse&lt;T&gt; : T;

async function fetchData&lt;T, Raw extends boolean&gt;(
  url: string,
  opts: { raw: Raw }
): Promise&lt;FetchResult&lt;T, Raw&gt;&gt; {
  const response = {
    data: null as unknown as T,
    meta: { total: 0 },
  };

  return opts.raw ? response as FetchResult&lt;T, Raw&gt; : response.data as FetchResult&lt;T, Raw&gt;;
}</code></pre>
<h3 id="사용된-문법-설명-4">사용된 문법 설명</h3>
<ul>
<li><strong>조건부 타입(Conditional Type)</strong>: <code>A extends B ? X : Y</code> 형태로 타입 분기</li>
<li><strong>제네릭 제약(Generic Constraint)</strong>: <code>Raw extends boolean</code> 처럼 타입 조건을 제한</li>
</ul>
<hr>
<h2 id="✅-마무리">✅ 마무리</h2>
<p>이번 학습에서는 단순히 문법을 외우는 것이 아니라, 문제를 풀고 타입 에러를 해결하는 과정을 통해 자연스럽게 고급 타입 문법에 익숙해질 수 있었습니다.</p>
<p>아직 배울 것이 많고 미숙한 점도 많지만, 조금씩 성장하고 있다는 감각을 놓지 않고 계속해서 기록을 이어가보려 합니다.</p>
<blockquote>
<p>타입은 문법이 아니라 설계입니다.<br>그리고 설계는 연습과 시행착오를 통해 단단해집니다.</p>
</blockquote>
<p>다음 학습 기록도 기대해주세요 🙌</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vercel Serverless function을 사용한 CORS 해결기]]></title>
            <link>https://velog.io/@dong_eon_/Vercel-Serverless-function%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-CORS-%ED%95%B4%EA%B2%B0%EA%B8%B0</link>
            <guid>https://velog.io/@dong_eon_/Vercel-Serverless-function%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-CORS-%ED%95%B4%EA%B2%B0%EA%B8%B0</guid>
            <pubDate>Sun, 02 Feb 2025 13:05:51 GMT</pubDate>
            <description><![CDATA[<h2 id="1-cors-에러를-만난-이유">1. CORS 에러를 만난 이유</h2>
<p>저는 Myfit이라는 네이버 클라우드 해커톤 프로젝트를 진행하면서, 서버 없이 프론트엔드만 개발하는 환경이였습니다.
이 과정에서 네이버 클라우드의 클로바 AI API를 호출해야 했는데, CORS 에러가 발생했습니다.</p>
<h2 id="2-cors란-무엇인가">2. CORS란 무엇인가</h2>
<p><strong>CORS</strong> 는 다른 출처에서 리소스를 공유할 때 발생하는 브라우저의 보안 메커니즘입니다. 
여기서 <strong>출처</strong>는 아래의 세 가지 요소를 기준으로 정의됩니다.</p>
<ul>
<li>프로토콜 (예: http, https)</li>
<li>도메인 (예: example.com)</li>
<li>포트 번호 (예: 3000)</li>
</ul>
<p><strong>동일 출처 정책(Same-Origin0Policy)</strong> 에 따라, 브라우저는 기본적으로 동일한 출처에서만 데이터를 요청할 수 있도록 제한합니다. 예를들어,
<code>http://localhost:3000</code>에서 실행 중인데, API 요청을 <code>https://api.example.com</code> 으로 보낸다면, 이 요청은 <strong>다를 출처(Cross-Origin)</strong> 에 해당하므로 브라우저가 차단합니다.</p>
<p>CORS는 서버가 클라이언트 요청을 허용하도록 명시적으로 승인하는 방식으로 이 제한을 완화할 수 있도록 설계되었습니다. 이를 위해서 서버는 <strong>CORS 헤더</strong>를 추가하여 요청을 허용할 출처를 명시해야 합니다.</p>
<h4 id="corsd의-기본-동작-방식">CORSD의 기본 동작 방식</h4>
<p>CORS는 다음과 같은 방식으로 동작합니다.</p>
<ol>
<li><strong>간단 요청</strong>: 요청 헤더가 단순한 경우, 서버는 Access-Control-Allow-Origin헤더를 통해 요청 출처를 허용할지 여부를 결정합니다.</li>
<li><strong>Preflight 요청</strong> : 특정 조건을 마족하는 요청은 서버와 미리 협상(Preflight) 과정을 거칩니다.</li>
</ol>
<ul>
<li>클라이언트는 OPTIONS 메서드로 서버에 요청을 보내 허용 가능한 메서드와 헤더를 확인합니다.</li>
<li>서버가 허용 응답을 반환하면 클라이언트가 실제 요청을 보냅니다.</li>
</ul>
<h2 id="3-cors가-발생하는-이유">3. CORS가 발생하는 이유</h2>
<p>웹 브라우저는 사용자의 데이터를 보호하기 위해 동일 출처 정책을 적용합니다. 이 정책이 없다면 악의적인 사이트가 사용자의 세션 쿠키를 이용해 민감한 데이터를 탈취하거나, 권한이 없는 요청을 보낼 수 있습니다.</p>
<p>예를들어 현재 내가 실행하고 있는 React앱은 <code>http://localhost:3000</code>에서 실행중이지만
API는 <code>https://api.example.com</code>에서 호스팅되기 때문에 브라우저는 React 앱의 요청을 <strong>다른 출처</strong>로 간주하여 차단합니다.</p>
<h2 id="4-vercel-serverless-functions로-cors-문제-해결하기">4. Vercel Serverless Functions로 CORS 문제 해결하기</h2>
<p>Vercel Serverless Functions는 클라이언트(React 앱)와 외부 API 서버 사이에서 프록시(proxy) 역할을 수행합니다.
    •    브라우저의 동일 출처 정책은 브라우저와 서버 간의 통신에만 적용됩니다.
    •    브라우저가 동일한 출처라고 판단하는 Vercel Serverless Functions에 요청을 보내면, 브라우저는 이를 차단하지 않습니다.
    •    Serverless Function이 외부 API와 통신하고 결과를 React 앱으로 전달하는 방식으로 CORS 문제를 해결합니다.</p>
<p><strong>프록시 서버의 흐름 예시</strong></p>
<blockquote>
<pre><code>1.    React 앱은 https://my-app.vercel.app/api/data로 요청을 보냅니다.
2.    Vercel Serverless Function(/api/data)는 외부 API(https://api.example.com/data)로 요청을 보냅니다.
3.    외부 API에서 데이터를 받아온 후 Vercel Serverless Function이 이를 React 앱에 응답으로 전달합니다.</code></pre></blockquote>
<p>React 앱은 Vercel Serverless Functions를 동일 출처로 간주하므로, 브라우저는 CORS 문제 없이 요청과 응답을 처리할 수 있습니다.</p>
<p><strong>추가로</strong> Vercel Serverless Functions을 사용하면 같은 출처일텐데, 왜 <code>Access-Control-Allow-Origin</code> 설정을 하는지 gpt에게 물어봤는데 </p>
<blockquote>
<p>Vercel에 배포된 상태에서 같은 도메인에서 Serverless Function을 호출하면 CORS 설정이 필요 없지만
개발 서버에서 호출하는 경우  localhost:3000 에서 실행돼서 CORS 정책이 적용되기 때문이라고 한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[설문조사 화면 만들기]]></title>
            <link>https://velog.io/@dong_eon_/%EC%84%A4%EB%AC%B8%EC%A1%B0%EC%82%AC-%ED%99%94%EB%A9%B4-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@dong_eon_/%EC%84%A4%EB%AC%B8%EC%A1%B0%EC%82%AC-%ED%99%94%EB%A9%B4-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sat, 28 Dec 2024 08:45:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dong_eon_/post/e7948240-0162-420c-a82d-0d42ddec3819/image.png" alt=""></p>
<blockquote>
<p>myfit 프로젝트를 진행하며, 사용자 설문조사 화면을 제작했습니다.
 예상보다 어려운 부분이 많았던 만큼, 그 과정을 기록으로 남기고자 이 글을 작성하게 되었습니다.</p>
</blockquote>
<h2 id="시작">시작</h2>
<blockquote>
<p>가장 먼저 진행한 작업은 컴포넌트 분리였습니다.
 ProgressBar와 하나의 질문을 담는 Container를 각각 컴포넌트로 분리한 뒤, 질문 Container를 map 함수로 렌더링하고 ProgressBar와 함께 하나의 화면에 구성했습니다.</p>
</blockquote>
<h2 id="유의할-점">유의할 점</h2>
<p>먼저 설문조사를 만들기 전에 고려해야 할 점들이 있었습니다.
    •    첫 번째 질문은 첫 질문과 그 다음 질문만 보여야 하고, 마지막 질문은 이전 질문과 마지막 질문만 보여야 합니다. (그 외의 질문들은 이전 질문과 현재 질문, 그리고 다음 질문이 보여야 함)
    •    각 질문 간의 이동은 별도의 버튼 없이, 질문 컨테이너를 클릭하여 이동하도록 해야 합니다.
    •    사용자의 점수를 totalScore 배열에 저장해야 합니다.
    •    현재 질문에서 점수를 체크하면 자동으로 다음 질문으로 넘어가야 합니다.</p>
<h3 id="질문-container-만들기">질문 Container 만들기</h3>
<p><code>[1, 2, 3, 4, 5]</code>로 구성된 배열을 map 함수로 렌더링하고, 각 숫자가 선택될 때 상위 컴포넌트로 선택된 값을 전달하도록 구현했습니다.
그리고 selectedValue라는 상태(state)를 생성하여, 각 숫자(value)가 선택되었을 때 해당 값이 selectedValue와 일치하면 UI에 파란색으로 표시되도록 구현했습니다.</p>
<h3 id="질문-container-렌더링">질문 Container 렌더링</h3>
<p>이 부분은 몇 가지 제약사항 때문에 다소 복잡했습니다. 
우선, 사전에 정의된 유의사항에 따라 첫 번째 질문과 마지막 질문은 각각 두 개씩 표시해야 했습니다. 
이를 해결하기 위해 visibleItems라는 변수를 만들어 현재 렌더링할 질문 Container를 제어하도록 했습니다.
또한, 현재 질문이 몇 번째인지 확인하기 위해 currentIndex라는 상태(state)를 생성하여 상태 관리를 했으며, visibleItems는 아래의 함수로 결정되도록 구현했습니다.</p>
<p><img src="https://velog.velcdn.com/images/dong_eon_/post/df332f94-385a-4a4b-92aa-20c07e50eac5/image.png" alt=""></p>
<h4 id="질문간-이동">질문간 이동</h4>
<p>현재 질문에서 점수를 체크한 후 다음 질문으로 넘어가는 함수와, 질문 Container를 클릭하여 이동하는 함수를 분리했습니다.
질문 Container 클릭 시 이동하는 함수는 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/dong_eon_/post/36d5caae-3b03-4096-b259-0042d9fddddb/image.png" alt=""></p>
<h4 id="이슈">이슈</h4>
<p>하지만 여기서 또 다른 문제가 발생했습니다.
기존에는 질문 Container에서 선택된 value 값을 상위 컴포넌트로 전달하여 totalScore 배열의 currentIndex에 추가하는 방식으로 구현했습니다.
그러나 질문 Container의 selectedValue 상태(state)를 초기값으로 null로 설정한 탓에 문제가 발생했습니다.
선택 당시에는 문제가 없었지만, 다다음 질문으로 이동했다가 다시 이전 질문으로 돌아올 경우 상태가 초기화되어 null 값이 적용되었고, 이로 인해 UI 상에서 선택된 항목이 표시되지 않는 현상이 나타났습니다.</p>
<p>이를 해결하기 위해 totalScore 배열을 처음부터 null로 초기화된 배열로 설정했습니다.
이후 currentScore라는 prop을 통해 totalScore[index] 값을 질문 Container로 내려주고, 이를 selectedValue 상태의 초기값으로 설정하여 문제를 해결했습니다.</p>
<h4 id="이슈-1">이슈</h4>
<p>또 다른 이슈는 설문조사 화면에서 질문 간의 이동 방식이 버튼 없이, 다음 질문 또는 이전 질문을 클릭하면 이동하도록 구현된 점이었습니다.
그런데 이동 중에 currentIndex와 다른 질문 Container의 숫자를 클릭하면 해당 값이 변경되는 문제가 발생했습니다.</p>
<p>이 문제를 해결하기 위해 disabled 속성을 활용했습니다. currentIndex와 질문 Container의 index가 일치하지 않는 경우, 해당 질문 Container를 비활성화(disabled) 상태로 설정하여 잘못된 값 변경을 방지했습니다.
<img src="https://velog.velcdn.com/images/dong_eon_/post/24197907-8586-4dcf-9bfe-47224c060a23/image.png" alt=""></p>
<h3 id="진행도-측정">진행도 측정</h3>
<p>ProgressBar에서 진행도를 측정할 때, totalScore 배열에서 null 값의 개수를 파악하여 진행도를 계산했습니다.</p>
<p><img src="https://velog.velcdn.com/images/dong_eon_/post/475fac92-ee2e-4bcc-9337-27ff5a318c35/image.png" alt=""></p>
<h3 id="끝으로">끝으로</h3>
<p>설문조사 화면 구현 과정에서 겪었던 여러 가지 문제와 그 해결 방안을 다뤄보았습니다. 각 질문의 이동 방식과 점수 선택, 그리고 UI 상태 관리에서 발생한 여러 제약을 고려하여 기능을 구현하는 과정이었습니다.</p>
<p>특히, 질문 간 이동과 점수 체크의 흐름을 유연하게 처리하기 위해 여러 가지 조건을 추가하고, 상태 관리를 통해 일관성 있는 사용자 경험을 제공하려 노력했습니다.</p>
<p>앞으로도 더 나은 코드를 작성하기 위해 계속해서 고민하고 개선해 나가겠습니다.</p>
<p>읽어주셔서 감사합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[트러블슈팅]실시간 달리기 응원 화면 개선]]></title>
            <link>https://velog.io/@dong_eon_/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%8B%AC%EB%A6%AC%EA%B8%B0-%EC%9D%91%EC%9B%90-%ED%99%94%EB%A9%B4-%EA%B0%9C%EC%84%A0</link>
            <guid>https://velog.io/@dong_eon_/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%8B%AC%EB%A6%AC%EA%B8%B0-%EC%9D%91%EC%9B%90-%ED%99%94%EB%A9%B4-%EA%B0%9C%EC%84%A0</guid>
            <pubDate>Fri, 22 Nov 2024 12:32:00 GMT</pubDate>
            <description><![CDATA[<h2 id="이슈-배경">이슈 배경</h2>
<p>기존 달리기 응원의 경우 이모지 모달이 늦게 렌더링 되어 어색한 모습이였다.
<img src="https://velog.velcdn.com/images/dong_eon_/post/2632e41f-5acb-49e9-887f-5d755f7a5c88/image.gif" alt=""></p>
<h2 id="해결-과정">해결 과정</h2>
<p>해당 이슈의 원인은 이모지 모달 높이 값을 빨리 구하지 못해서 였다.
이모지 모달의 높이는 <code>기기 높이 - 키보드 높이 - textinput의 높이</code>로 이루어져있는데 이모지 모달의 높이를 구하는 과정에서 키보드 높이를 좀 더 빨리 구한다면 해결할 수 있다고 생각했다.</p>
<pre><code>Keyboard.addListener(
      &#39;keyboardWillShow&#39;,
      onKeyboardDidShow,
    );</code></pre><p>기존 <code>keyboardDidShow</code> 이벤트에서 <code>keyboardWillShow</code>로 변경 하니 한층 애니메이션이 빨라졌다.</p>
<p><img src="https://velog.velcdn.com/images/dong_eon_/post/0c221369-c8a0-4309-9c04-6e50c564ce4b/image.gif" alt=""></p>
<h3 id="추가-문제"><strong>추가 문제</strong></h3>
<p>이모지 모달을 빨리 생성하는데에는 성공했지만, 모달의 검은 배경이 같이 올라가 보이는 게 상당히 어색해보였다. 
해당 부분을 해결하기 위해 이모지와 모달 뒷 배경을 animation 으로 생성해서 어색함을 제거 하려고 했다.</p>
<pre><code>useEffect(() =&gt; {
    Animated.timing(opacityAnim, { //모달 뒷 배경 애니메이션
      toValue: 0.4,
      duration: 600,
      useNativeDriver: true,
    }).start();

    Animated.timing(emojiScaleAnim, { //이모지 애니메이션
      toValue: 1,
      duration: 400,
      useNativeDriver: true,
    }).start();

    return () =&gt; {
      Animated.timing(opacityAnim, {
        toValue: 0,
        duration: 400,
        useNativeDriver: true,
      }).start();

      Animated.timing(emojiScaleAnim, {
        toValue: 1,
        duration: 500,
        useNativeDriver: true,
      }).start();
    };
  }, [opacityAnim, emojiScaleAnim]);</code></pre><p><strong>결과물</strong>
<img src="https://velog.velcdn.com/images/dong_eon_/post/a0c1b031-f7e2-460c-a91a-64a2d3212e01/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[트러블슈팅]한국어 입력이벤트 이슈]]></title>
            <link>https://velog.io/@dong_eon_/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85%ED%95%9C%EA%B5%AD%EC%96%B4-%EC%9E%85%EB%A0%A5%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@dong_eon_/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85%ED%95%9C%EA%B5%AD%EC%96%B4-%EC%9E%85%EB%A0%A5%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Thu, 21 Nov 2024 16:58:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이전에 진행했던 프로젝트를 회고하면서, 발생했던 트러블슈팅을 뒤늦게나마 정리하는 글입니다.</p>
</blockquote>
<h2 id="이슈-배경">이슈 배경</h2>
<p><img src="https://velog.velcdn.com/images/dong_eon_/post/2cc8ae63-714c-4c9f-ae66-e2641111592b/image.png" alt=""></p>
<p>Input 컴포넌트에서 검색어를 입력하면 InputResult 컴포넌트에서 받아온 data를 자동완성 해서 보여줍니다.
이때 Input 컴포넌트에서 방향키를 통해 InputResult를 선택할 수 있습니다.
방향키를 통해 선택하는 과정에서 한국어를 입력하고 방향키를 누르면 이벤트가 2번 발생하는 에러가 발생합니다.
&quot;테&quot;를 검색하면 [개발 필터링 테스트, 쇼핑 필터링 테스트, 코딩 필터링 테스트...]가 나옵니다.
이때 아래 방향키를 누르면 &quot;코딩 필터링 테스트&quot;가 선택되고 콘솔에는 ArrowDown이 2번 찍혔습니다.</p>
<h2 id="이슈-원인-찾기">이슈 원인 찾기</h2>
<p>영어를 입력했을 때는 위와 같은 에러가 발생하지 않았고, 한국어를 입력했을 때만 발생하는 걸 발견하여 korean input keydown devent twice 검색 키워드를 통해 검색하였습니다.
저와 같은 에러가 발생하는 깃헙 이슈를 발견하였고, 해당 이슈를 해결하는 방법을 찾아 적용하였습니다.
요약하면 IME는 다양한 언어를 브라우저에서 지원하도록 변환시켜주는 어플리케이션인데, IME 과정에서 keydown 이벤트가 발생하면 OS와 브라우저 둘 다 keydown 이벤트를 처리하기 때문에 중복 이벤트가 발생하는 것이었습니다.</p>
<h2 id="해결과정">해결과정</h2>
<h3 id="첫-번째-시도">첫 번째 시도</h3>
<ul>
<li>깃헙에 있는 예시 코드를 적용했습니다.<ul>
<li>keyCode는 더 이상 사용되지 않기 때문에 isComposing만 사용했습니다.</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">const handleKeyDown = (e: React.KeyboardEvent&lt;HTMLInputElement&gt;) =&gt; {
    if (e.isComposing) {
      return;
    }
        /* ... */
};</code></pre>
<ul>
<li>하지만 React에는 iSComposing을 제공하지 않기 때문에 에러가 발생합니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/dong_eon_/post/e25d74fb-b316-441f-b4d4-2dd53b810591/image.png" alt=""></p>
<h3 id="두-번째-시도">두 번째 시도</h3>
<ul>
<li>현재 프로젝트에선 Next.js와 TypeScript를 사용하고 있기 때문에 저와 같은 환경인 <a href="https://velog.io/@dosomething/React-%ED%95%9C%EA%B8%80-%EC%9E%85%EB%A0%A5%EC%8B%9C-keydown-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%A4%91%EB%B3%B5-%EB%B0%9C%EC%83%9D-%ED%98%84%EC%83%81">블로그</a>를 참고하여 코드를 수정했습니다.</li>
<li>하지만 <code>KeyboardEvent</code>를 import하는 과정에서 에러가 발생하여 레거시 코드인 keyCode를 사용하여 임시로 해결하였습니다.<ul>
<li>composition 중일 때는 keyCode로 229를 반환합니다.</li>
</ul>
</li>
</ul>
<pre><code class="language-tsx">const handleKeyDown = (e: React.KeyboardEvent&lt;HTMLInputElement&gt;) =&gt; {
    if (e.keyCode === 229) {
      return;
    }
        /* ... */
};</code></pre>
<ul>
<li>참고<ul>
<li>깃헙 이슈: <a href="https://github.com/vuejs/vue/issues/10277">https://github.com/vuejs/vue/issues/10277</a></li>
<li>블로그: <a href="https://velog.io/@dosomething/React-%ED%95%9C%EA%B8%80-%EC%9E%85%EB%A0%A5%EC%8B%9C-keydown-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%A4%91%EB%B3%B5-%EB%B0%9C%EC%83%9D-%ED%98%84%EC%83%81">https://velog.io/@dosomething/React-한글-입력시-keydown-이벤트-중복-발생-현상</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js Parallel Routes 와 Intercepting Routes 를 사용해 Modal 만들기]]></title>
            <link>https://velog.io/@dong_eon_/Next.js-Parallel-Routes-%EC%99%80-Intercepting-Routes-%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4-Modal-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@dong_eon_/Next.js-Parallel-Routes-%EC%99%80-Intercepting-Routes-%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4-Modal-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Fri, 12 Jul 2024 06:19:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dong_eon_/post/2187b189-a230-4a6f-a4cc-bc050752ab5c/image.png" alt=""></p>
<h3 id="parallel-routes-와-intercepting-routes-를-사용해-modal-만들기">Parallel Routes 와 Intercepting Routes 를 사용해 Modal 만들기</h3>
<p>이전에 학습한 <u><a href="https://velog.io/@dong_eon_/Next.js-%ED%95%99%EC%8A%B5%EA%B8%B0-Parallel-Routes">Parallel Routes</a></u> 와 <u><a href="https://velog.io/@dong_eon_/Next.js-%ED%95%99%EC%8A%B5%EA%B8%B0-intercepting-routes">Intercepting Routes</a></u> 를 사용해서 Modal을 만들수 있다고 문서에 작성돼있어서 직접 한번 만들어 보았다.</p>
<p>시작하기에 앞서, 간단히 요약하자면 Parallel Routes 는 한 화면에 두개의 페이지를 띄우는 것이고, Intercepting Routes 는 말 그대로 route를 인터셉트 하는 것 이다.</p>
<p><em>참고로 <u>Intercepting Routes</u> 는 클라이언트 컴포넌트에서만 적용이 된다</em></p>
<p><img src="https://velog.velcdn.com/images/dong_eon_/post/f0cebeed-d580-45b6-be1b-64af96b82e79/image.png" alt="">
<img src="https://velog.velcdn.com/images/dong_eon_/post/9598a814-57c4-4e8d-9bc6-d349de8534d6/image.png" alt=""></p>
<p>(설명의 편의를 위해 <code>/authflow</code>를 홈 화면이라 표현하겠습니다.)</p>
<ol>
<li><p><code>/authflow</code>에서 버튼 클릭을 통해 <code>/authflow/login</code> 으로 접속하면
<code>/authflow/login/page.tsx</code> 가 실행되지 않고
<code>authflow/(.)login/page.tsx</code>가 실행을 가로채 간다.</p>
</li>
<li><p>그런데 실행을 가로챈 화면은 홈 화면에서 Parallel Routes가 구현되어 있으므로 layout의 children 이 아닌 modal에서 실행되며 그 결과 브라우저의 주소가 바뀌지만 홈 화면과 동시에 표시된다.</p>
</li>
<li><p>가로채기 당한 <code>authflow/login/page.tsx</code>는 새로고침 하거나 주소 창에 <code>authflow/login</code> 을 직접 입력하여 접근하면 실행된다.
즉, 새로고침이나 주소를 타이핑하여 직접 접근 하는 경우에는 Intercepting Routes가 발생하지 않는다. 따라서</p>
</li>
</ol>
<p><code>authflow/(.)login/page.tsx</code> 와 동일하게 작업하면 된다.</p>
<p>실행본</p>
<p><img src="https://velog.velcdn.com/images/dong_eon_/post/5988ac81-ffbb-4385-8787-9c6ce7e94470/image.gif" alt="">
더불어 이렇게 만든 모달은 Next.js에서는 다음과 같은 문제를 해결할 수 있다고 한다.
•    URL을 통해 모달 콘텐츠를 공유 가능하게 한다.
•    페이지를 새로 고침해도 모달을 닫지 않고 컨텍스트를 유지한다.
•    이전으로 이동 시 이전 라우트로 가지 않고 모달을 닫는다.
•    앞으로 이동 시 모달을 다시 연다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 학습기 intercepting routes]]></title>
            <link>https://velog.io/@dong_eon_/Next.js-%ED%95%99%EC%8A%B5%EA%B8%B0-intercepting-routes</link>
            <guid>https://velog.io/@dong_eon_/Next.js-%ED%95%99%EC%8A%B5%EA%B8%B0-intercepting-routes</guid>
            <pubDate>Thu, 11 Jul 2024 05:17:11 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dong_eon_/post/cf86b1ca-43da-4f94-9a36-52e98c6ab0fd/image.png" alt=""></p>
<h4 id="nextjs-공식문서를-번역하여-기록해둔-내용입니다">Next.js 공식문서를 번역하여 기록해둔 내용입니다.</h4>
<h2 id="intercepting-routes">Intercepting routes</h2>
<p>Intercepting routes는 현재 레이아웃 내에서 애플리케이션의 다른 부분의 라우트를 로드할 수 있게 한다.
이러한 라우팅 패러다임은 사용자가 다른 컨텍스트로 전환하지 않고도 라우트의 내용을 표시하고자 할 때 유용하다.</p>
<p>예를 들어, 피드에서 사진을 클릭할 때, 사진을 모달로 표시하여 피드 위에 오버레이할 수 있다. 이 경우, Next.js는 /photo/123 라우트를 가로채서 URL을 마스킹하고 /feed 위에 오버레이한다.
<img src="https://velog.velcdn.com/images/dong_eon_/post/b999e356-5b85-436e-9c4b-d683a3e47689/image.png" alt=""></p>
<p>그러나 공유 가능한 URL을 클릭하거나 페이지를 새로 고침하여 사진으로 이동할 때는 전체 사진 페이지가 렌더링되어야 한다. 이 경우에는 Intercepting routes가 발생하지 않는다.
<img src="https://velog.velcdn.com/images/dong_eon_/post/46b6d3de-d722-423b-83e1-4323708ae1c6/image.png" alt=""></p>
<p>-&gt; 그래서 같은 경로의 페이지가 하나 더 있어야 한다.</p>
<h3 id="convention">Convention</h3>
<p>Intercepting routes는 세그먼트를 위한 상대 경로 규칙과 비슷한 (..) 규칙으로 정의할 수 있다.</p>
<p>다음과 같이 사용할 수 있다.</p>
<pre><code>•    (.)는 같은 레벨의 세그먼트를 매칭한다.
•    (..)는 한 레벨 위의 세그먼트를 매칭한다.
•    (..)(..)는 두 레벨 위의 세그먼트를 매칭한다.
•    (…)는 루트 앱 디렉토리에서 세그먼트를 매칭한다.</code></pre><p>예를 들어, feed 세그먼트 내에서 photo 세그먼트를 가로채려면 (..)photo 디렉토리를 생성할 수 있다.
<img src="https://velog.velcdn.com/images/dong_eon_/post/d6a5d3f6-5d22-431e-a476-cb1545441417/image.png" alt=""></p>
<p>(..) 규칙은 파일 시스템이 아니라 라우트 세그먼트를 기반으로 한다는 점에 유의해야 한다.</p>
<p>-&gt; 디렉토리 구조가 아닌 브라우저 주소창 ../../ 단위로(세그먼트) 레벨을 측정한다는 것을 유의</p>
<h3 id="사용예제-모달">사용예제: 모달</h3>
<p>Intercepting Routes는 Parallel Routes와 함께 사용하여 모달을 만들 수 있다. 이를 통해 모달을 구축할 때 흔히 발생하는 문제를 해결할 수 있다. 예를 들어:</p>
<pre><code>•    URL을 통해 모달 콘텐츠를 공유 가능하게 한다.
•    페이지를 새로 고침해도 모달을 닫지 않고 컨텍스트를 유지한다.
•    이전으로 이동 시 이전 라우트로 가지 않고 모달을 닫는다.
•    앞으로 이동 시 모달을 다시 연다.</code></pre><p>다음과 같은 UI 패턴을 고려할 수 있다. 사용자가 클라이언트 사이드 네비게이션을 통해 갤러리에서 사진 모달을 열거나, 공유 가능한 URL을 통해 직접 사진 페이지로 이동할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/dong_eon_/post/7bd7e2c9-16ff-48fa-a042-d410ae0a836e/image.png" alt=""></p>
<p>위 예시에서, @modal이 세그먼트가 아니라 슬롯이므로 photo 세그먼트로의 경로는 (..) 매처를 사용할 수 있다. 
이는 파일 시스템 상으로는 두 레벨 위에 있더라도, photo 라우트는 한 세그먼트 레벨 위에만 있는 것을 의미한다.</p>
<h4 id="사용하기-좋은-예제">사용하기 좋은 예제</h4>
<ul>
<li>상단 네비게이션 바에서 로그인 모달을 열면서 전용 /login 페이지도 가질때</li>
<li>측면 모달에서 쇼핑 카트를 여는 경우 등</li>
</ul>
<p><a href="https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes">출처: Next.js 공식문서</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 학습기 Route Groups]]></title>
            <link>https://velog.io/@dong_eon_/Next.js-%ED%95%99%EC%8A%B5%EA%B8%B0-Route-Groups</link>
            <guid>https://velog.io/@dong_eon_/Next.js-%ED%95%99%EC%8A%B5%EA%B8%B0-Route-Groups</guid>
            <pubDate>Wed, 10 Jul 2024 03:51:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dong_eon_/post/11b52a1f-e733-437b-bfe3-a1a97814dda2/image.png" alt=""></p>
<h4 id="nextjs-공식문서를-번역하여-기록해둔-내용입니다">Next.js 공식문서를 번역하여 기록해둔 내용입니다.</h4>
<h3 id="route-groups">Route Groups</h3>
<p>앱 디렉토리에서는 중첩된 폴더가 일반적으로 URL 경로에 매핑된다.
하지만 폴더를 Route Group으로 표시하면 해당 폴더가 경로의 URL 경로에 포함되지 않도록 할 수 있다.</p>
<p>이를 통해 URL 경로 구조에 영향을 주지 않으면서 경로 세그먼트와 프로젝트 파일을 논리적으로 그룹화할 수 있다.</p>
<p>Route Groups는 다음과 같은 경우에 유용하다.</p>
<h4 id="사이트-섹션-의도-팀별로-경로를-그룹화할-때">사이트 섹션, 의도, 팀별로 경로를 그룹화할 때</h4>
<p>예시: 사이트의 마케팅 페이지와 쇼핑 페이지를 별도로 그룹화하기</p>
<pre><code>app/
|-- (marketing)/
|   |-- layout.js
|   |-- home/
|   |   |-- page.js
|   |-- about/
|       |-- page.js
|-- (shop)/
    |-- layout.js
    |-- products/
    |   |-- page.js
    |-- cart/
        |-- page.js</code></pre><pre><code>(marketing) 그룹에는 마케팅 관련 페이지 (home, about)가 포함된다.
(shop) 그룹에는 쇼핑 관련 페이지 (products, cart)가 포함된다.
이렇게 그룹화하면 관련된 파일들을 논리적으로 그룹화하여 관리할 수 있다.</code></pre><h4 id="동일한-경로-세그먼트-레벨에서-중첩된-레이아웃을-활성화할-때">동일한 경로 세그먼트 레벨에서 중첩된 레이아웃을 활성화할 때</h4>
<p>예시: 인증 관련 페이지와 애플리케이션 관련 페이지를 분리된 레이아웃으로 관리하기</p>
<pre><code>app/
|-- (auth)/
|   |-- layout.js
|   |-- login/
|   |   |-- page.js
|   |-- register/
|       |-- page.js
|-- (app)/
    |-- layout.js
    |-- dashboard/
    |   |-- page.js
    |-- settings/
        |-- page.js</code></pre><pre><code>(auth) 그룹은 인증 관련 경로 (login, register)를 포함하며, 인증 레이아웃 (layout.js)을 사용한다.
(app) 그룹은 애플리케이션 관련 경로 (dashboard, settings)를 포함하며, 애플리케이션 레이아웃 (layout.js)을 사용한다.
동일한 경로 세그먼트 레벨에서 각기 다른 레이아웃을 사용하여, 서로 다른 사용자 경험을 제공할 수 있다.</code></pre><h4 id="동일한-세그먼트에서-여러-중첩된-레이아웃을-생성할-때-여러-루트-레이아웃-포함">동일한 세그먼트에서 여러 중첩된 레이아웃을 생성할 때, 여러 루트 레이아웃 포함</h4>
<p>예시: 블로그와 쇼핑 섹션을 독립적으로 관리하기</p>
<pre><code>app/
|-- (blog)/
|   |-- layout.js
|   |-- posts/
|   |   |-- page.js
|   |-- authors/
|       |-- page.js
|-- (shop)/
    |-- layout.js
    |-- products/
    |   |-- page.js
    |-- cart/
        |-- page.js</code></pre><pre><code>(blog) 그룹은 블로그 관련 경로 (posts, authors)를 포함하며, 블로그 레이아웃 (layout.js)을 사용한다.
(shop) 그룹은 쇼핑 관련 경로 (products, cart)를 포함하며, 쇼핑 레이아웃 (layout.js)을 사용한다.
각각의 루트 레이아웃이 독립적으로 존재하며, 각 섹션에 대해 완전히 다른 UI나 사용자 경험을 제공한다.</code></pre><h4 id="공통-세그먼트의-일부-경로에-레이아웃을-추가할-때">공통 세그먼트의 일부 경로에 레이아웃을 추가할 때</h4>
<p>예시: 특정 경로에만 공통 레이아웃을 적용하기</p>
<pre><code>app/
|-- (common)/
|   |-- layout.js
|   |-- account/
|   |   |-- page.js
|   |-- cart/
|       |-- page.js
|-- checkout/
    |-- page.js</code></pre><pre><code>(common) 그룹에는 공통 레이아웃을 사용하는 경로 (account, cart)가 포함된다.
checkout 경로는 공통 레이아웃을 사용하지 않으며, 별도의 레이아웃을 사용할 수 있다.
이를 통해 특정 경로에만 공통 레이아웃을 적용하고, 다른 경로는 독립적으로 관리할 수 있다.</code></pre><h3 id="convention">Convention</h3>
<p>폴더 이름을 괄호로 감싸서 Route Group을 생성할 수 있습니다: (folderName)</p>
<h3 id="examples">Examples</h3>
<p>URL 경로에 영향을 주지 않고 경로를 조직화하기
URL에 영향을 주지 않고 경로를 조직화하려면 관련 경로를 함께 유지하기 위해 그룹을 만들수 있다.
괄호 안의 폴더는 URL에서 생략됩니다. (예(marketing))</p>
<p><img src="https://velog.velcdn.com/images/dong_eon_/post/ce22a54a-5997-411e-937b-cd3e14140280/image.png" alt=""></p>
<p>(marketing) 및 (shop) 내부의 경로가 동일한 URL 계층 구조를 공유하더라도 해당 폴더에 layout.js 파일을 추가하여 각 그룹에 대해 다른 레이아웃을 만들 수 있다.
<img src="https://velog.velcdn.com/images/dong_eon_/post/dc3e8acc-8003-4d83-b670-ed80fe9f18f7/image.png" alt=""></p>
<h4 id="특정-세그먼트를-레이아웃에-선택하기">특정 세그먼트를 레이아웃에 선택하기</h4>
<p>특정 경로를 레이아웃에 선택하려면 새 경로 그룹을 만들고 (예: (shop)) 동일한 레이아웃을 공유하는 경로를 그룹으로 이동하십시오 (예: account와 cart). 그룹 외부의 경로는 레이아웃을 공유하지 않는다 (예: checkout).
<img src="https://velog.velcdn.com/images/dong_eon_/post/7dbed230-c648-46f7-8993-073d4e1df9c8/image.png" alt=""></p>
<h4 id="여러-루트-레이아웃-생성하기">여러 루트 레이아웃 생성하기</h4>
<p>여러 루트 레이아웃을 생성하려면 최상위 layout.js 파일을 제거하고 각 경로 그룹 내에 layout.js 파일을 추가해야한다. 
완전히 다른 UI나 경험을 가진 섹션으로 애플리케이션을 분할할 때 유용하다. 각 루트 레이아웃에는 <code>&lt;html&gt;</code> 및 <code>&lt;body&gt;</code> 태그가 추가되어야 한다.
<img src="https://velog.velcdn.com/images/dong_eon_/post/57242250-0c85-4d7c-9d07-3f5b5be8f76f/image.png" alt=""></p>
<p>💡 알아두면 좋은 점</p>
<ul>
<li>경로 그룹의 이름은 조직화를 위한 것 외에는 특별한 의미가 없다. URL 경로에 영향을 주지 않는다.</li>
<li>경로 그룹을 포함하는 경로는 다른 경로와 동일한 URL 경로로 해결되어서는 안된다. 
예를 들어, 경로 그룹이 URL 구조에 영향을 주지 않기 때문에 (marketing)/about/page.js와 (shop)/about/page.js는 모두 /about으로 해석되어 오류를 발생시킬 수 있다.</li>
<li>최상위 layout.js 파일 없이 여러 루트 레이아웃을 사용하는 경우, 홈 page.js 파일은 라우트 그룹 중 하나에 정의되어야 한다. 예를 들어: app/(marketing)/page.js.</li>
<li>여러 루트 레이아웃 간의 탐색은 클라이언트 측 네비게이션과 달리 전체 페이지 로드를 발생시킨다. 예를 들어, app/(shop)/layout.js를 사용하는 /cart에서 app/(marketing)/layout.js를 사용하는 /blog로 이동하면 전체 페이지 로드가 발생한다. 이는 여러 루트 레이아웃에만 적용된다.</li>
</ul>
<p><a href="https://nextjs.org/docs/app/building-your-application/routing/route-groups#opting-specific-segments-into-a-layout">출처: Next.js 공식문서</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 학습기 Parallel Routes]]></title>
            <link>https://velog.io/@dong_eon_/Next.js-%ED%95%99%EC%8A%B5%EA%B8%B0-Parallel-Routes</link>
            <guid>https://velog.io/@dong_eon_/Next.js-%ED%95%99%EC%8A%B5%EA%B8%B0-Parallel-Routes</guid>
            <pubDate>Wed, 10 Jul 2024 02:37:12 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dong_eon_/post/61ed9def-5634-47a7-b299-ba5c7cc91a93/image.png" alt=""></p>
<h4 id="nextjs-공식문서를-번역하여-기록해둔-내용입니다">Next.js 공식문서를 번역하여 기록해둔 내용입니다.</h4>
<h3 id="parallel-routes">Parallel Routes</h3>
<p>패러렐 라우트는 동시에 또는 조건부로 동일한 레이아웃 내에서 하나 이상의 페이지를 렌더링할 수 있게 한다.
이는 대시보드나 소셜 사이트의 피드와 같이 매우 동적인 앱 섹션에 유용하다.</p>
<p><img src="https://velog.velcdn.com/images/dong_eon_/post/486d55f3-562c-4543-aaf8-62c5a5ce60a2/image.png" alt=""></p>
<h3 id="slots">Slots</h3>
<p>패러렐 라우트는 이름이 지정된 슬롯(named slots)을 사용하여 생성된다. 
슬롯은 @폴더 형식으로 정의된다. 예를 들어, 다음 파일 구조는 @analytics와 @team 두 개의 슬롯을 정의한다.
<img src="https://velog.velcdn.com/images/dong_eon_/post/9c182b8d-d781-4a98-984a-7dd2968d42e1/image.png" alt=""></p>
<p>슬롯은 공유된 부모 레이아웃에 props로 전달된다. 
위 예시에서는 app/layout.js 파일의 컴포넌트가 이제 @analytics와 @team 슬롯 props를 받아들이고, children props와 함께 병렬로 렌더링할 수 있다.</p>
<pre><code>export default function Layout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode
  analytics: React.ReactNode
  team: React.ReactNode
}) {
  return (
    &lt;&gt;
      {children}
      {team}
      {analytics}
    &lt;/&gt;
  )
}</code></pre><p>그러나 슬롯은 라우트 세그먼트가 아니며 URL 구조에 영향을 미치지 않는다. 
예를 들어, /@analytics/views 경로의 경우, URL은 /views가 됩니다. 왜냐하면 @analytics는 슬롯이기 때문이다.</p>
<p>💡 알아 두면 좋은 점.</p>
<ul>
<li>children prop은 명시적으로 슬롯이며 폴더에 매핑될 필요가 없다.
이는 app/page.js가 app/@children/page.js와 동등하다는 것을 의미한다.</li>
</ul>
<h3 id="defaultjs">default.js</h3>
<p>기본적으로, 매칭되지 않은 슬롯에 대한 폴백을 렌더링하려면 default.js 파일을 정의할 수 있다.
이 경우 초기 로드 또는 전체 페이지 새로 고침 시 유용하다.</p>
<p><img src="https://velog.velcdn.com/images/dong_eon_/post/26899dd2-fa3e-4136-bba2-bde2ae43917f/image.png" alt="">
위와 같이  @team 슬롯에는 /settings 페이지가 있지만, @analytics 슬롯에는 없는 예시에서</p>
<p>/settings로 이동할 때, @team 슬롯은 /settings 페이지를 렌더링하며 @analytics 슬롯의 현재 활성 페이지를 유지한다.</p>
<p>새로 고침 시, Next.js는 @analytics에 대해 default.js를 렌더링한다.
만약 default.js가 존재하지 않으면 404 페이지가 대신 렌더링된다.</p>
<p>추가로, children은 암시적인 슬롯이기 때문에 Next.js가 부모 페이지의 활성 상태를 복구할 수 없을 때 children에 대한 폴백을 렌더링하기 위해 default.js 파일을 생성해야 한다.</p>
<p>정리하자면, 패러렐 라우트를 사용할 때, <code>@</code>폴더에서 기본적으로 보여줄 것이 없을 때 page.tsx 대신 사용한다. page.tsx를 사용해도 무방하지만, 특별히 보여줄 것이 없다면 default.tsx를 사용하여 null을 return한다.</p>
<h3 id="useselectedlayoutsement">useSelectedLayoutSement</h3>
<p>useSelectedLayoutSegment와 useSelectedLayoutSegments는 모두 parallelRoutesKey 매개변수를 받아 슬롯 내에서 활성 라우트 세그먼트를 읽을 수 있게 한다.</p>
<pre><code>&#39;use client&#39;

import { useSelectedLayoutSegment } from &#39;next/navigation&#39;

export default function Layout({ auth }: { auth: React.ReactNode }) {
  const loginSegment = useSelectedLayoutSegment(&#39;auth&#39;)
  // ...
}</code></pre><p>사용자가 app/@auth/login (또는 URL 바에서 /login)으로 이동할 때, loginSegment는 문자열 “login”과 같아진다.</p>
<p><a href="https://nextjs.org/docs/app/building-your-application/routing/parallel-routes">출처: Next.js 공식문서</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 학습기 Layout]]></title>
            <link>https://velog.io/@dong_eon_/Next.js-%ED%95%99%EC%8A%B5%EA%B8%B0-Layout</link>
            <guid>https://velog.io/@dong_eon_/Next.js-%ED%95%99%EC%8A%B5%EA%B8%B0-Layout</guid>
            <pubDate>Wed, 10 Jul 2024 02:25:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dong_eon_/post/61ed9def-5634-47a7-b299-ba5c7cc91a93/image.png" alt=""></p>
<h4 id="nextjs-공식문서를-번역하여-기록해둔-내용입니다">Next.js 공식문서를 번역하여 기록해둔 내용입니다.</h4>
<h3 id="layout">Layout</h3>
<p>레이아웃은 여러 경로 사이에서 공유되는 UI이다. 레이아웃은 내비게이션 시 상태를 보존하고 상호작용이 가능하며, <strong>다시 렌더링되지 않는다</strong>. 또한, 레이아웃은 중첩될 수도 있다.</p>
<p>레이아웃을 정의하려면 layout.js 파일에서 React 컴포넌트를 기본으로 내보내면 된다. 이 컴포넌트는 렌더링 시 자식 레이아웃(존재하는 경우) 또는 페이지로 채워질 children prop을 받아야 한다.</p>
<p>예를 들어, 레이아웃은 /dashboard 및 /dashboard/settings 페이지와 공유된다.</p>
<pre><code>app/
└── dashboard/
    ├── layout.js
    ├── page.js
    └── settings/
        └── page.js</code></pre><p>이렇게 하면 /dashboard와 /dashboard/settings 페이지가 이 레이아웃을 공유하게 된다.</p>
<h3 id="root-layout">Root Layout</h3>
<p>루트 레이아웃은 앱 디렉토리의 최상위 수준에서 정의되며 모든 경로에 적용된다. 이 레이아웃은 필수적이며, 서버에 반환되는 초기 HTML을 수정할 수 있도록 <strong><code>&lt;html&gt;</code> 과 <code>&lt;body&gt;</code> 태그를 포함해야 한다.</strong></p>
<h3 id="nested-layout">Nested Layout</h3>
<p>기본적으로 폴더 계층 구조 내의 레이아웃은 중첩된다. 이는 레이아웃이 자식 레이아웃을 자신의 children prop을 통해 감싸는 것을 의미한다. 특정 경로 세그먼트(폴더) 내에 layout.js 파일을 추가함으로써 레이아웃을 중첩할 수 있다.</p>
<pre><code>app/
├── layout.js
└── dashboard/
    ├── layout.js
    └── page.js</code></pre><h4 id="세그먼트-란">세그먼트 란?</h4>
<p>세그먼트(segment)란 URL 경로에서 경로를 구성하는 각 부분을 말한다. 일반적으로 경로는 여러 세그먼트로 나뉘어지며, 각 세그먼트는 슬래시(/)로 구분된다.</p>
<p>예를 들어, 다음 URL에서 세그먼트는 다음과 같다
<code>https://example.com/products/electronics/laptops</code></p>
<pre><code>•    첫 번째 세그먼트: /products
•    두 번째 세그먼트: /electronics
•    세 번째 세그먼트: /laptops</code></pre><p>💡 알아두면 좋은 정보.</p>
<ul>
<li>레이아웃으로는 .js, .jsx 또는 .tsx 파일 확장자를 사용할 수 있다.</li>
<li>루트 레이아웃에만 <code>&lt;html&gt;</code> 및 <code>&lt;body&gt;</code> 태그를 포함할 수 있다.</li>
<li>같은 폴더에 layout.js와 page.js 파일이 정의되어 있으면, 레이아웃은 페이지를 감싼다.</li>
<li>기본적으로 레이아웃은 서버 컴포넌트입니다만, 클라이언트 컴포넌트로 설정할 수도 있다.</li>
<li>부모 레이아웃과 자식 사이에서 데이터를 전달하는 것은 불가능하다. 그러나 같은 데이터를 경로에서 여러 번 가져오면 React가 자동으로 중복 요청을 제거하며 성능에 영향을 미치지 않는다.</li>
<li>레이아웃은 자신 아래의 경로 세그먼트에는 접근할 수 없다. 모든 경로 세그먼트에 접근하려면 클라이언트 컴포넌트 내에서 useSelectedLayoutSegment 또는 useSelectedLayoutSegments를 사용할 수 있다.</li>
<li>Route Groups를 사용하여 특정 경로 세그먼트를 공유된 레이아웃에서 선택적으로 제외하거나 포함할 수 있다.</li>
</ul>
<p>출처: <a href="https://nextjs.org/docs">Next.js 공식문서</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[curl 이란?]]></title>
            <link>https://velog.io/@dong_eon_/curl-%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@dong_eon_/curl-%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Wed, 03 Jul 2024 07:59:05 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dong_eon_/post/13204fdc-58d8-44e0-a870-8436d48409ea/image.png" alt=""></p>
<h1 id="curl-이란">curl 이란?</h1>
<h2 id="학습-배경">학습 배경</h2>
<p>프로젝트를 진행하던 중 API가 정상적으로 작동하지 않는 문제를 발견했습니다. 이 상황을 백엔드 담당자에게 전달하자, 담당자는 재현 가능한 요청을 보내달라고 요청했습니다. 당시에는 &#39;재현 가능한 요청&#39;이 무엇을 의미하는지 몰랐기 때문에 단순히 실패하는 요청을 다시 보냈습니다.</p>
<p>백엔드 담당자는 curl 형식으로 재현 가능한 요청을 보내달라고 했고, 이때 처음으로 curl이라는 도구에 대해 알게 되었습니다.</p>
<p>이슈를 해결한 후, curl에 대해 더 깊이 학습해야겠다는 필요성을 느끼게 되었습니다.</p>
<h2 id="그래서-curl이-뭔데">그래서 curl이 뭔데?</h2>
<p><strong>curl</strong> 이란 <strong>client URL</strong>의 줄임말로 다양한 통신 프로토콜을 이용하여 데이터를 전송하기 위한 라이브러리와 명령 줄 도구를 제공하는 컴퓨터 소프트웨어 프로젝트이다.
주로 웹에서 데이터를 가져오거나 업로드할 때 사용됩니다.</p>
<p><strong>curl</strong>은 다양한 프로토콜(<em>HTTP, HTTPS, FTP, FTPS, SCP, SFTP, SMTP, POP3, IMAP</em>)을 지원하며, 스크립트나 명령줄에서 HTTP 요청을 보내고 응답을 확인하는 데 매우 유용합니다.</p>
<h2 id="기본-사용법">기본 사용법</h2>
<p>curl의 기본 구문은 다음과 같습니다.</p>
<pre><code class="language-bash">curl [options] [URL]</code></pre>
<p><strong>주요 옵션들</strong></p>
<ul>
<li>-X : HTTP 메서드를 지정합니다. 예: GET, POST, PUT, DELETE.</li>
<li>-H : 요청 헤더를 설정합니다.</li>
<li>-d : 요청 본문을 설정합니다. 주로 POST 요청에 사용됩니다.</li>
<li>-i : 응답의 헤더를 포함합니다.</li>
<li>-o : 응답을 파일로 저장합니다.</li>
<li>-u : 사용자 인증을 설정합니다. 예: username:password.</li>
</ul>
<p><strong>HTTP,HTTPS 에서의 사용 예제</strong></p>
<ol>
<li>GET 요청<pre><code class="language-bash">curl https://api.example.com/data</code></pre>
</li>
<li>POST 요청<pre><code class="language-bash">curl -X POST https://api.example.com/data -d &#39;{&quot;key&quot;:&quot;value&quot;}&#39; -H &quot;Content-Type: application/json&quot;</code></pre>
</li>
<li>헤더 포함<pre><code class="language-bash">curl https://api.example.com/data -H &quot;Authorization: Bearer your_token_here&quot;</code></pre>
</li>
<li>파일 다운로드<pre><code class="language-bash">curl -o filename.zip https://example.com/file.zip</code></pre>
</li>
<li>응답 헤더 포함<pre><code class="language-bash">curl -i https://api.example.com/data</code></pre>
</li>
<li>사용자 인증<pre><code class="language-bash">curl -u username:password https://api.example.com/protected</code></pre>
</li>
</ol>
<p>예를 들어 로그인 요청을 재현하려면</p>
<pre><code class="language-bash">curl -X POST https://api.example.com/login \
  -H &quot;Content-Type: application/json&quot; \
  -H &quot;Authorization: Bearer exampletoken123&quot; \
  -d &#39;{&quot;username&quot;:&quot;user@example.com&quot;,&quot;password&quot;:&quot;password123&quot;}&#39;</code></pre>
<p>이 명령은 다음을 포함합니다</p>
<ul>
<li>HTTP 메서드: POST</li>
<li>URL: <a href="https://api.example.com/login">https://api.example.com/login</a></li>
<li>헤더: Content-Type과 Authorization 헤더</li>
<li>본문 데이터: JSON 형식의 사용자명과 비밀번호</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[트러블슈팅] 타이머 정확성 이슈]]></title>
            <link>https://velog.io/@dong_eon_/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-%ED%83%80%EC%9D%B4%EB%A8%B8-%EC%A0%95%ED%99%95%EC%84%B1-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@dong_eon_/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-%ED%83%80%EC%9D%B4%EB%A8%B8-%EC%A0%95%ED%99%95%EC%84%B1-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Tue, 25 Jun 2024 07:06:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dong_eon_/post/c0af95fc-d57e-4bac-971c-8589756f7c90/image.png" alt=""></p>
<h2 id="이슈-배경">이슈 배경</h2>
<p>달리기 앱에서 타이머를 구현하고 사용하던 중 장시간 타이머를 사용시 실제시간과 타이머 표시시간 사이에 오차가 발생하는 것을 알게 되었습니다.</p>
<h2 id="이슈-원인-찾기">이슈 원인 찾기</h2>
<p>타이머 정확성 관련해서 구글링을 했고
javascript의 이벤트 루프와 taskqueue 그리고 promise와 같은 비동기 처리 과정에서 발생하는 지연 문제임을 파악했습니다.</p>
<h3 id="🔍-이벤트-루프와-마이크로-태스크-큐-매크로-태스크-큐-란">🔍 이벤트 루프와 마이크로 태스크 큐, 매크로 태스크 큐 란?</h3>
<p><img src="https://velog.velcdn.com/images/dong_eon_/post/9edd15d3-5ebc-4621-8ebf-13c787e8bbba/image.gif" alt="">
<strong>이벤트 루프(Event Loop)</strong></p>
<ul>
<li>이벤트 루프는 자바스크립트의 비동기 처리 메커니즘의 핵심 개념입니다.</li>
<li>이벤트 루프는 콜 스택, 웹 API, 태스크 큐(microtask queue, macrotask queue)를 관리하며, 이들 간의 상호작용을 통해 자바스크립트 코드를 실행합니다.</li>
<li>이벤트 루프는 끊임없이 실행 중이며, 콜 스택이 비어있을 때마다 태스크 큐에서 다음 작업을 가져와 실행합니다.</li>
</ul>
<p><strong>microtask queue</strong></p>
<ul>
<li>microtask queue는 Promise의 then/catch/finally 콜백, MutationObserver 등의 마이크로태스크를 관리하는 큐입니다.</li>
<li>마이크로태스크는 매우 빠르게 실행되며, 콜 스택이 비어있을 때마다 microtask queue에서 작업을 가져와 실행합니다.</li>
<li>microtask queue의 작업은 macrotask queue보다 우선순위가 높습니다.</li>
</ul>
<p><strong>macrotask queue</strong></p>
<ul>
<li>macrotask queue는 setTimeout, setInterval, I/O 작업, 이벤트 핸들러 등의 매크로태스크를 관리하는 큐입니다.</li>
<li>매크로태스크는 상대적으로 오래 실행되며, 콜 스택이 비어있고 microtask queue가 비어있을 때 macrotask queue에서 작업을 가져와 실행합니다.</li>
</ul>
<p><strong>작동 순서</strong></p>
<ol>
<li>콜 스택에 함수가 쌓입니다.</li>
<li>함수 내에서 비동기 작업(setTimeout, Promise 등)이 발생하면 해당 작업은 웹 API로 전달됩니다.</li>
<li>웹 API에서 작업이 완료되면 해당 작업은 태스크 큐(microtask queue 또는 macrotask queue)로 전달됩니다.</li>
<li>콜 스택이 비어있을 때, 이벤트 루프는 태스크 큐를 확인합니다.</li>
<li>먼저 microtask queue에서 작업을 가져와 실행합니다.</li>
<li>microtask queue가 비어있으면 macrotask queue에서 작업을 가져와 실행합니다.</li>
<li>이 과정을 반복하며 자바스크립트 코드를 실행합니다.</li>
</ol>
<h2 id="이슈-해결-과정">이슈 해결 과정</h2>
<p>(before)
<img src="https://velog.velcdn.com/images/dong_eon_/post/3e3e5582-1717-4440-8e49-75757b3a01ff/image.png" alt="">
(after)
<img src="https://velog.velcdn.com/images/dong_eon_/post/742dab1a-4f53-448e-ae79-52a8a04a29fd/image.png" alt=""></p>
<p>문제 해결을 위해 setInterval의 각 호출 시 setTime을 통한 증가 방식에서 벗어나, 각 호출마다 시작 시간과 현재 시간을 비교하여 시간을 설정하는 방식으로 변경했고, 이를 통해 시간의 정확성을 향상 시켰습니다.</p>
<p>또한 더욱 정확한 시간 계산을 위해 호출 간격을 500ms로 줄였습니다. 하지만, 이 과정에서 <code>useState</code> 로 상태를 관리하게 되면 렌더링이 과도하게 발생하여 성능 저하의 우려가 있었습니다.</p>
<p>따라서 변수를 생성하여 실제 시간 값은 렌더링에 영향을 주지 않는 방식으로 하되, 1초이상 차이 날 경우에만 <code>useState</code> 를 통해 상태를 업데이트하고 화면에 반영하도록 하여 성능을 고려했습니다.</p>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://velog.io/@yejineee/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84%EC%99%80-%ED%83%9C%EC%8A%A4%ED%81%AC-%ED%81%90-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C-%ED%83%9C%EC%8A%A4%ED%81%AC-%EB%A7%A4%ED%81%AC%EB%A1%9C-%ED%83%9C%EC%8A%A4%ED%81%AC-g6f0joxx">이벤트 루프와 매크로,마이크로 태스크 큐</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[백준] 29792번 규칙적인 보스돌이 (파이썬)]]></title>
            <link>https://velog.io/@dong_eon_/%EB%B0%B1%EC%A4%80-29792%EB%B2%88-%EA%B7%9C%EC%B9%99%EC%A0%81%EC%9D%B8-%EB%B3%B4%EC%8A%A4%EB%8F%8C%EC%9D%B4-%ED%8C%8C%EC%9D%B4%EC%8D%AC</link>
            <guid>https://velog.io/@dong_eon_/%EB%B0%B1%EC%A4%80-29792%EB%B2%88-%EA%B7%9C%EC%B9%99%EC%A0%81%EC%9D%B8-%EB%B3%B4%EC%8A%A4%EB%8F%8C%EC%9D%B4-%ED%8C%8C%EC%9D%B4%EC%8D%AC</guid>
            <pubDate>Fri, 21 Jun 2024 10:23:32 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>문제링크: <a href="https://www.acmicpc.net/problem/29792">https://www.acmicpc.net/problem/29792</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dong_eon_/post/9e908503-cc58-4d1a-b756-f5daa349ff72/image.png" alt=""></p>
<h3 id="문제해결-아이디어">문제해결 아이디어</h3>
<ul>
<li>각각의 캐릭터당 최대 보상으로 보스를 잡는 dp를 생성하고 최대 보상값을 res에 저장</li>
<li>res를 내림차순으로 정렬해서 m번째 캐릭터까지 합하여 정답출력</li>
<li>각각의 캐릭터 별로 보스를 잡는 데 걸리는 시간을 구한다.</li>
<li>dp는 보스 종류, 15분(900초)로 구성한다.</li>
</ul>
<h3 id="소스코드">소스코드</h3>
<pre><code>import sys
input = sys.stdin.readline

n, m, k = map(int, input().split())
# 캐릭터 개수, 하루에 사용할 캐릭터 수, 보스 종류

damage = [] # 캐릭터 데미지

for _ in range(n):
    damage.append(int(input()))

boss = [] # 체력, 보상

res = []

for _ in range(k):
    boss.append(list(map(int, input().split())))

for i in range(n):
    time = [0]
    for hp, worth in boss:
        h = hp // damage[i]
        if hp % damage[i]:
            h += 1

        time.append(h)

    dp = [[0] * (901) for _ in range(k+1)]

    for a in range(1, k+1):
        for b in range(1, 901):
            if b &gt;= time[a]:
                dp[a][b] = max(dp[a-1][b], boss[a-1][1] + dp[a-1][b-time[a]]) # 현재보스를 잡지 않을 경우, 현재 보스를 잡을 경우(현재 보스 보상 + 걸리는 시간을 뺀 이전 보상 최대값)
            else:
                dp[a][b] = dp[a-1][b]

    res.append(dp[k][-1])

res.sort(reverse=True)

print(sum(res[:m]))</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[git 대소문자 구분하여 적용하기]]></title>
            <link>https://velog.io/@dong_eon_/git-%EB%8C%80%EC%86%8C%EB%AC%B8%EC%9E%90-%EA%B5%AC%EB%B6%84%ED%95%98%EC%97%AC-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dong_eon_/git-%EB%8C%80%EC%86%8C%EB%AC%B8%EC%9E%90-%EA%B5%AC%EB%B6%84%ED%95%98%EC%97%AC-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 13 Jun 2024 06:18:44 GMT</pubDate>
            <description><![CDATA[<h1 id="git-대소문자-구분-이슈">git 대소문자 구분 이슈</h1>
<hr>
<h3 id="문제상황">문제상황</h3>
<p>vscode로 파일명을 대소문자만 변경해서 push를 했는 데 왠걸 원격 브랜치의 파일명이 그대로 유지되는 일이 있었습니다.
해당 문제를 파악하지 못한채 pr을 올려버렸고 merge 된 main을 실행해보면 파일명과 import 한 파일의 대소문자가 달라 에러가 발생하는 이슈가 생겼습니다.</p>
<h3 id="어떻게-해결했는가">어떻게 해결했는가?</h3>
<p>신속하게 구글링을 했고 해당 이슈는 여러 사람들도 많이 겪은 이슈라 어렵지 않게 해결할 수 있었습니다.
해결 방법은 크게 두가지가 있었는 데 <code>git mv</code> 명령어와 <code>git config core.ingnorecase false</code> 방법이 있었습니다.</p>
<h4 id="git-mv-명령어란">git mv 명령어란</h4>
<p><code>git mv</code> 명령어는 git에서 파일을 이동하거나 이름을 변경하는 데 사용됩니다.</p>
<ol>
<li>파일 이름 변경<pre><code class="language-sh">git mv old_filename.txt new_filename.txt</code></pre>
</li>
</ol>
<ul>
<li><code>old_filename.txt -&gt; new_filename.txt</code> 으로 변경</li>
</ul>
<ol start="2">
<li>파일 이동<pre><code class="language-sh">git mv filename_txt new_directory/</code></pre>
</li>
</ol>
<ul>
<li><code>filename.txt</code> 파일을 <code>new_directory</code> 디렉토리로 이동합니다.</li>
</ul>
<ol start="3">
<li>파일 이동 및 이름 변경<pre><code class="language-sh">git mv old_filename.txt new_directory/new_filename.txt</code></pre>
</li>
</ol>
<ul>
<li><code>old_filename.txt</code> 파일을 <code>new_directory</code> 디렉토리로 이동하면서 이름을 <code>new_filename.txt</code>로 변경합니다.</li>
</ul>
<pre><code class="language-sh">git mv old_filename.txt OLD_FILENAME.txt</code></pre>
<p>해당 방법을 통해 문제를 해결했습니다.</p>
<h4 id="git-config-coreingnorecase-명령어란">git config core.ingnorecase 명령어란</h4>
<p><code>git config core.ignorecase</code>는 git 설정에서 파일 이름의 대소문자 구분을 활성화하는 옵션입니다.</p>
<p>window나 macos의 경우 파일 시스템을 대소문자를 구별하지 않지만 Linux의 경우 대소문자를 구분하므로 크로스 플랫폼 개발시 유용한 설정입니다.</p>
<p><code>git config core.ignorecase</code>설정을 true로 할 경우 git이 파일 이름의 대소문자를 무시합니다.
예를들어 <code>README.md</code>와 <code>readme.md</code>를 같은 파일로 인식합니다.</p>
<p>false로 설정 할 경우 git이 파일 이름의 대소문자를 구분합니다. <code>README.md</code>와 <code>readme.md</code>를 다른 파일로 인식합니다.</p>
<h3 id="결론">결론</h3>
<p>저는 <code>git mv</code> 명령어를 이용하여 문제를 해결했습니다만. <code>git config core.ignorecase</code> 명령어는 어떤 건지 궁금해서 더 찾아보았고 각각의 해결방법을 정리해서 작성하게 되었습니다.</p>
]]></description>
        </item>
    </channel>
</rss>