<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jungo_0</title>
        <link>https://velog.io/</link>
        <description>열심히 기록할 예정🙃</description>
        <lastBuildDate>Sat, 06 Jun 2026 08:38:42 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jungo_0</title>
            <url>https://velog.velcdn.com/images/jungo_0/profile/7ec3a889-5948-40c6-b3d3-3af69edf771d/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jungo_0. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jungo_0" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[React]Framer Motion 등 애니메이션(Exit Animation)과 Key의 상호작용]]></title>
            <link>https://velog.io/@jungo_0/React-Framer-Motion%EC%9D%98-Exit-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98%EC%9D%80-%EC%99%9C-key-%EC%97%86%EC%9D%B4%EB%8A%94-%EC%9E%91%EB%8F%99%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%84%EA%B9%8C-AnimatePresence-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@jungo_0/React-Framer-Motion%EC%9D%98-Exit-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98%EC%9D%80-%EC%99%9C-key-%EC%97%86%EC%9D%B4%EB%8A%94-%EC%9E%91%EB%8F%99%ED%95%98%EC%A7%80-%EC%95%8A%EC%9D%84%EA%B9%8C-AnimatePresence-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Sat, 06 Jun 2026 08:38:42 GMT</pubDate>
            <description><![CDATA[<h1 id="react-framer-motion의-exit-애니메이션은-왜-key-없이는-작동하지-않을까-animatepresence-동작-원리">[React] Framer Motion의 Exit 애니메이션은 왜 <code>key</code> 없이는 작동하지 않을까? (AnimatePresence 동작 원리)</h1>
<blockquote>
<p><strong>&quot;리액트 엔진은 데이터가 삭제되면 DOM 노드를 즉시 파괴(Unmount)하려 하지만, 애니메이션 라이브러리는 <span style="background-color:#5A5AFF; color:#ffffff; padding:2px 6px; border-radius:4px;"><code>key</code>를 앵커로 삼아 &#39;언마운트를 지연(Defer Unmounting)&#39;</span>시키고 애니메이션이 끝난 뒤에 비로소 DOM을 정리합니다.&quot;</strong></p>
</blockquote>
<p>리스트에서 아이템을 삭제하거나 팝업 모달을 닫을 때 스르륵 사라지는 퇴장 효과(Exit Animation)를 구현하기 위해 <strong>Framer Motion</strong>의 <code>&lt;AnimatePresence&gt;</code>를 많이 사용합니다.</p>
<p>그런데 <code>&lt;motion.div&gt;</code>에 <code>exit={{ opacity: 0 }}</code> 속성을 아무리 완벽하게 정의해 두어도, <strong>고유한 <code>key</code>를 부여하지 않으면 퇴장 애니메이션이 완전히 씹히고 화면에서 툭 꺼지듯이 사라지는 현상</strong>을 겪게 됩니다.</p>
<p>도대체 리액트의 렌더링 파이프라인과 Framer Motion 내부에서 어떤 일이 일어나기에 <code>key</code>가 없으면 Exit 애니메이션이 불가능한 것인지, 그 상호작용 원리를 심층 분석합니다.</p>
<hr>
<h3 id="1-근본적인-딜레마-리액트의-즉각-삭제-vs-애니메이션의-시간차latency">1. 근본적인 딜레마: 리액트의 &quot;즉각 삭제&quot; vs 애니메이션의 &quot;시간차(Latency)&quot;</h3>
<p>Exit 애니메이션을 구현할 때 가장 큰 기술적 장벽은 <strong>리액트 가상 DOM과 브라우저 DOM의 라이프사이클 차이</strong>입니다.</p>
<h4 id="일반적인-리액트-조건부-렌더링">[일반적인 리액트 조건부 렌더링]</h4>
<ol>
<li><p>상태 변경: isVisible = false (또는 리스트에서 item 삭제)</p>
</li>
<li><p>재조정(Reconciliation): &quot;이 노드는 트리에서 제외되었음&quot; 판정</p>
</li>
<li><p>커밋(Commit) 단계: 브라우저 DOM에서 해당 노드를 &#39;즉시 물리적으로 제거(Unmount)&#39;
➔ ⚠️ 결과: 0.3초 동안 서서히 투명해질 시간적 여유가 전혀 없음!</p>
</li>
</ol>
<p>사용자가 보고 즐길 수 있는 페이드아웃(Fade-out) 효과가 발생하려면, 상태는 이미 <code>false</code>가 되었더라도 <strong><span style="color:#2ECC71; font-weight:bold;">실제 DOM 노드는 화면에 0.3초~0.5초 동안 살아남아 있어야</span></strong> 합니다.</p>
<p><span style="background-color:#5A5AFF; color:#ffffff; padding:2px 6px; border-radius:4px;"><code>&lt;AnimatePresence&gt;</code>는 리액트의 &#39;즉각 언마운트&#39; 동작을 중간에서 가로채(Intercept) 퇴장 애니메이션이 완료될 때까지 컴포넌트의 수명을 인위적으로 연장합니다.</span></p>
<hr>
<h3 id="2-animatepresence의-핵심-메커니즘-언마운트-지연deferred-unmount">2. <code>AnimatePresence</code>의 핵심 메커니즘: 언마운트 지연(Deferred Unmount)</h3>
<p>Framer Motion의 <code>&lt;AnimatePresence&gt;</code>는 내부적으로 직전 렌더링의 자식 요소(<code>prevChildren</code>)와 현재 렌더링의 자식 요소(<code>currentChildren</code>)를 <strong><span style="color:#2ECC71; font-weight:bold;"><code>key</code>를 기준으로 비교·추적</span></strong>합니다.</p>
<h4 id="animatepresence의-내부-처리-흐름">[AnimatePresence의 내부 처리 흐름]</h4>
<ol>
<li><p>이전 렌더링: Children 목록 ➔ [ Key: &#39;item-1&#39;, Key: &#39;item-2&#39; ]</p>
</li>
<li><p>삭제 발생:   Children 목록 ➔ [ Key: &#39;item-2&#39; ]</p>
</li>
<li><p>AnimatePresence의 비교:</p>
<ul>
<li><p>&quot;어? &#39;item-1&#39;이라는 Key를 가진 자식이 이번 렌더 트리에서 사라졌네?&quot;</p>
</li>
<li><p>&quot;리액트가 바로 DOM을 지우지 못하도록 &#39;item-1&#39; 노드를 메모리(PresenceContext)에 복제·유지하자!&quot;</p>
</li>
</ul>
</li>
<li><p>Exit 애니메이션 실행:</p>
<ul>
<li>&#39;item-1&#39; 컴포넌트에 isPresent: false 플래그 전달 ➔ exit={{ opacity: 0 }} 실행 (0.3초 소요)</li>
</ul>
</li>
<li><p>최종 언마운트:</p>
<ul>
<li>애니메이션 완료 이벤트(onAnimationComplete) 수신 ➔ 비로소 실제 DOM에서 노드 완전 제거</li>
</ul>
</li>
</ol>
<p>❖ <strong><code>key</code>가 반드시 필요한 이유:</strong></p>
<ul>
<li><strong><span style="color:#2ECC71; font-weight:bold;">사라진 엔터티 식별</span></strong>: 부모의 렌더 함수가 실행되었을 때, <span style="color:#2ECC71; font-weight:bold;">&quot;어떤 구체적인 컴포넌트가 사라지는 중인가?&quot;</span>를 식별할 수 있는 유일한 기준이 바로 <code>key</code>입니다.</li>
<li><strong><span style="color:#2ECC71; font-weight:bold;">인스턴스 캐싱</span></strong>: <code>AnimatePresence</code>는 사라진 <code>key</code>의 DOM 노드와 스타일 상태를 캐싱하여 화면에 일시적으로 렌더링을 지속시킵니다.</li>
</ul>
<p><span style="background-color:#5A5AFF; color:#ffffff; padding:2px 6px; border-radius:4px;"><code>key</code>가 없으면 AnimatePresence는 어떤 요소가 삭제 대기 상태(Exiting)인지 추적할 수 없으므로 지연 메커니즘 자체가 발동하지 않습니다.</span></p>
<hr>
<h3 id="3-코드-검증-key-유무에-따른-동작-차이">3. 코드 검증: <code>key</code> 유무에 따른 동작 차이</h3>
<h4 id="①-모달--토글-컴포넌트의-조건부-렌더링">① 모달 / 토글 컴포넌트의 조건부 렌더링</h4>
<pre><code class="language-jsx">import { motion, AnimatePresence } from &#39;framer-motion&#39;;

// ✅ 올바른 패턴: AnimatePresence 직계 자식에 고유 key 할당
function AlertModal({ isOpen, message }) {
  return (
    &lt;AnimatePresence&gt;
      {isOpen &amp;&amp; (
        &lt;motion.div
          key=&quot;alert-modal&quot; // 💡 고유 Key 필수!
          initial={{ opacity: 0, y: -20 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, y: -20 }}
          transition={{ duration: 0.3 }}
        &gt;
          &lt;p&gt;{message}&lt;/p&gt;
        &lt;/motion.div&gt;
      )}
    &lt;/AnimatePresence&gt;
  );
}
</code></pre>
<ul>
<li><p><code>key=&quot;alert-modal&quot;</code>이 있는 경우: <code>isOpen</code>이 <code>false</code>가 되어도 <code>AnimatePresence</code>가 해당 키를 감지하고 0.3초간 위로 올라가며 투명해진 뒤 사라집니다.</p>
</li>
<li><p><code>key</code>를 생략한 경우: <code>isOpen</code>이 <code>false</code>가 되는 즉시 리액트가 DOM을 날려버려 퇴장 애니메이션이 0초 만에 끊기고 즉시 사라집니다.</p>
</li>
</ul>
<p>② 동적 리스트 아이템 삭제 (Todo List)</p>
<pre><code class="language-jsx">// ❌ 안티 패턴: key={index}를 사용한 리스트 애니메이션
&lt;AnimatePresence&gt;
  {todos.map((todo, index) =&gt; (
    &lt;motion.li
      key={index} // ⚠️ 인덱스를 키로 사용
      initial={{ opacity: 0, x: -50 }}
      animate={{ opacity: 1, x: 0 }}
      exit={{ opacity: 0, x: 50 }}
    &gt;
      {todo.text}
    &lt;/motion.li&gt;
  ))}
&lt;/AnimatePresence&gt;</code></pre>
<h3 id="keyindex-사용-시-발생하는-치명적-시각-결함"><code>key={index}</code> 사용 시 발생하는 치명적 시각 결함</h3>
<ol>
<li><p>사용자가 3개 항목<code>(A: 0, B: 1, C: 2)</code> 중 첫 번째 &#39;A&#39;를 삭제함.</p>
</li>
<li><p>배열이 재정렬되면서 <code>B</code>가 인덱스 0번, <code>C</code>가 인덱스 1번이 됨.</p>
</li>
<li><p><code>AnimatePresence</code>는 <code>key=0</code>, <code>key=1</code>은 여전히 존재하고, &quot;마지막 <code>key=2</code>가 삭제되었다&quot;고 오판함.</p>
</li>
<li><p><strong>[참사 발생</strong>]: 사용자는 A를 지웠는데, 화면에서는 맨 밑의 C가 페이드아웃되고 A는 순식간에 사라진 뒤 B가 A 자리로 튕겨 올라가는 끔찍한 UI 깨짐이 발생합니다.</p>
</li>
</ol>
<pre><code class="language-jsx">  // ✅ 올바른 패턴: 불변 고유 ID(todo.id)를 Key로 바인딩
&lt;AnimatePresence&gt;
  {todos.map((todo) =&gt; (
    &lt;motion.li
      key={todo.id} // 💡 불변하는 고유 ID 바인딩
      initial={{ opacity: 0, x: -50 }}
      animate={{ opacity: 1, x: 0 }}
      exit={{ opacity: 0, x: 50 }}
    &gt;
      {todo.text}
    &lt;/motion.li&gt;
  ))}
&lt;/AnimatePresence&gt;</code></pre>
<p>불변 고유 ID를 부여해야만 정확히 삭제된 <code>&#39;A&#39;</code> 노드에만 exit 트랜지션이 적용되고, 나머지 아이템들은 자연스럽게 layout 애니메이션으로 빈자리를 채웁니다.</p>
<pre><code class="language-jsx">  // ✅ React Router 페이지 전환 애니메이션 패턴
function AnimatedRoutes() {
  const location = useLocation();

  return (
    &lt;AnimatePresence mode=&quot;wait&quot;&gt;
      {/* 💡 pathname을 key로 주어 라우트가 바뀔 때마다 이전 페이지 언마운트를 지연시킴 */}
      &lt;Routes key=&quot;{location.pathname}&quot; location=&quot;{location}&quot;&gt;
        &lt;Route element=&quot;{&lt;Home&quot; path=&quot;/&quot;/&gt;} /&gt;
        &lt;Route element=&quot;{&lt;About&quot; path=&quot;/about&quot;/&gt;} /&gt;
      &lt;/Routes&gt;
    &lt;/AnimatePresence&gt;
  );
}</code></pre>
<ul>
<li><p>동작 원리: URL 경로(<code>location.pathname</code>)가 바뀌면 <code>Routes</code>의 <code>key</code>가 변경됩니다.</p>
</li>
<li><p><code>AnimatePresence</code>는 이전 <code>pathname</code> 컴포넌트의 <code>exit</code> 애니메이션이 끝날 때까지 화면에 유지하고, <code>mode=&quot;wait&quot;</code> 옵션에 따라 이전 페이지가 완전히 퇴장한 뒤 새 페이지를 마운트합니다.</p>
</li>
</ul>
<h3 id="애니메이션-라이브러리-관점의-key-비교-요약">애니메이션 라이브러리 관점의 Key 비교 요약</h3>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left"><code>key</code> 누락 (No Key)</th>
<th align="left"><code>key={index}</code></th>
<th align="left"><code>key={item.id}</code> (불변 고유값)</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>퇴장 노드 식별</strong></td>
<td align="left">❌ 식별 불가</td>
<td align="left">⚠️ 엉뚱한 노드(맨 끝 노드)로 오인</td>
<td align="left"><span style="color:#2ECC71; font-weight:bold;">✅ 삭제된 노드 정확히 추적</span></td>
</tr>
<tr>
<td align="left"><strong>Exit 애니메이션</strong></td>
<td align="left"><strong>완전히 동작 안 함 (즉시 증발)</strong></td>
<td align="left"><strong>엉뚱한 아이템이 Fade-out 됨</strong></td>
<td align="left"><span style="color:#2ECC71; font-weight:bold;">✅ 지정된 요소만 부드럽게 퇴장</span></td>
</tr>
<tr>
<td align="left"><strong>언마운트 지연</strong></td>
<td align="left">리액트 기본 즉시 삭제 발동</td>
<td align="left">잘못된 인덱스 노드 지연 삭제</td>
<td align="left"><span style="color:#2ECC71; font-weight:bold;">정확한 트랜지션 완료 후 DOM 제거</span></td>
</tr>
<tr>
<td align="left"><strong>레이아웃 안정성</strong></td>
<td align="left">화면 깜빡임 및 레이아웃 튐</td>
<td align="left">아이템 위치 꼬임 발생</td>
<td align="left"><span style="background-color:#5A5AFF; color:#ffffff; padding:2px 6px; border-radius:4px;">완벽하고 매끄러운 UX 제공</span></td>
</tr>
</tbody></table>
<hr>
<h3 id="📚-references">📚 References</h3>
<ul>
<li><a href="https://www.framer.com/motion/animate-presence/">Framer Motion Official Docs - AnimatePresence &amp; Exit Animations</a></li>
<li><a href="https://react.dev/learn/preserving-and-resetting-state">React Official Documentation - Preserving and Resetting State</a></li>
<li><a href="https://www.framer.com/motion/animate-presence/#keys">Framer Motion Guide - Component Keys and Presence Context</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AI 용어 정리]]]></title>
            <link>https://velog.io/@jungo_0/AI-%EC%9A%A9%EC%96%B4-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jungo_0/AI-%EC%9A%A9%EC%96%B4-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 21 May 2026 04:20:54 GMT</pubDate>
            <description><![CDATA[<h1 id="ai-용어-정리-힘든-ai-핵심-용어-12가지-총정리">[AI 용어 정리] 힘든 AI 핵심 용어 12가지 총정리</h1>
<blockquote>
<p><strong>&quot;AI 시대를 완벽하게 활용하기 위해 반드시 알아야 할 <span style="color:#2ECC71; font-weight:bold;">12가지 핵심 개념</span>을 한눈에 정리합니다. 복잡한 기술 용어도 비유와 예시를 통해 한 번에 이해할 수 있습니다.&quot;</strong></p>
</blockquote>
<p>유튜브 채널 &#39;혼잡스&#39;의 <strong><a href="http://www.youtube.com/watch?v=N3st1ZrB_zc">모르면 따라가기 힘든 Ai 용어정리 12개 딱 한장으로 끝! (바이브코딩 지도 2부)</a></strong> 영상을 바탕으로, 바이브 코딩과 AI 서비스 구축 시 필수로 알아야 하는 12가지 AI 핵심 용어를 체계적으로 정리한 블로그 포스팅입니다.</p>
<hr>
<h3 id="1-ai의-기본-두뇌와-감각">1. AI의 기본 두뇌와 감각</h3>
<ul>
<li><strong>1. LLM (Large Language Model, 대형 언어 모델)</strong><ul>
<li>무수한 문장과 데이터를 읽고 학습한 <strong><span style="color:#2ECC71; font-weight:bold;">기본 두뇌</span></strong> 역할을 합니다. (예: ChatGPT, Claude, Gemini)</li>
</ul>
</li>
<li><strong>2. 멀티모달 (Multimodal)</strong><ul>
<li>글로만 소통하던 LLM에 <strong><span style="color:#2ECC71; font-weight:bold;">눈과 귀(시각·청각 감각)</span></strong>를 달아준 것입니다.</li>
<li>이미지, 소리, 영상 등 다양한 형태의 입력을 이해할 수 있게 도와줍니다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="2-ai-예절-교육과-맞춤형-학습">2. AI 예절 교육과 맞춤형 학습</h3>
<ul>
<li><strong>3. RLHF (Reinforcement Learning from Human Feedback, 인간 피드백 기반 강화학습)</strong><ul>
<li>사람이 직접 점수를 매기고 평가하여 AI가 친절하고 안전하게 답하도록 세팅하는 <strong><span style="color:#2ECC71; font-weight:bold;">AI 예절 교육</span></strong> 과정입니다.</li>
</ul>
</li>
<li><strong>4. 파인튜닝 (Fine-Tuning)</strong><ul>
<li>범용적으로 일 잘하는 AI 신입 사원에게 우리 회사의 내부 자료를 잔뜩 안겨주어 <strong><span style="color:#2ECC71; font-weight:bold;">우리 팀 맞춤형 전문가로 재학습</span></strong>시키는 과정입니다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="3-명령-기술과-문서-창고-시스템">3. 명령 기술과 문서 창고 시스템</h3>
<ul>
<li><strong>5. 프롬프트 엔지니어링 (Prompt Engineering)</strong><ul>
<li>AI에게 원하는 최상의 결과물이 나오도록 <strong><span style="color:#2ECC71; font-weight:bold;">지시문(Prompt)을 효과적으로 설계하고 작성하는 기술</span></strong>입니다.</li>
</ul>
</li>
<li><strong>6. 지식 베이스 (Knowledge Base)</strong><ul>
<li>AI가 거짓말(할루시네이션)을 하지 않도록 회사 규정, 매뉴얼, 회의록을 모아둔 <strong><span style="color:#2ECC71; font-weight:bold;">문서 데이터 창고</span></strong>입니다.</li>
</ul>
</li>
<li><strong>7. RAG (Retrieval-Augmented Generation, 검색 증강 생성)</strong><ul>
<li>질문을 받았을 때 무작정 답을 지어내는 것이 아니라, <span style="color:#2ECC71; font-weight:bold;">지식 베이스 창고에서 정확한 문서를 찾아서 검증된 근거로 답변</span>하게 만들어 주는 장치입니다.</li>
</ul>
</li>
</ul>
<p><span style="background-color:#5A5AFF; color:#ffffff; padding:2px 6px; border-radius:4px;">파인튜닝이 AI의 &#39;뇌&#39; 자체를 바꾸는 과정이라면, RAG는 AI의 뇌는 그대로 둔 채 &#39;문서 창고&#39;에서 직접 찾아서 답하게 만드는 방식입니다.</span></p>
<hr>
<h3 id="4-도구-연결과-자율적-업무-수행">4. 도구 연결과 자율적 업무 수행</h3>
<ul>
<li><strong>8. MCP (Model Context Protocol)</strong><ul>
<li>AI가 구글 캘린더, 이메일, 데이터베이스 등 다양한 외부 도구와 쉽게 연결될 수 있도록 이어주는 <strong><span style="color:#2ECC71; font-weight:bold;">멀티플러그 어댑터(연결 표준 규격)</span></strong>입니다.</li>
</ul>
</li>
<li><strong>9. 에이전트 (Agent)</strong><ul>
<li>단순 질문에 답하는 수준을 넘어, <span style="color:#2ECC71; font-weight:bold;">목표를 설정해 주면 스스로 추론하고 계획을 세워 외부 도구를 활용해 업무를 완수</span>하는 자율형 AI입니다.</li>
</ul>
</li>
<li><strong>10. 워크플로우 자동화 (Workflow Automation)</strong><ul>
<li>AI 에이전트를 공장의 컨베이어 벨트에 배치하듯, <span style="color:#2ECC71; font-weight:bold;">매일 반복되는 복잡한 업무 프로세스를 자동으로 연쇄 실행</span>되도록 만든 시스템입니다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="5-통제-장치와-미래의-ai">5. 통제 장치와 미래의 AI</h3>
<ul>
<li><strong>11. 하네스 (Harness)</strong><ul>
<li>아무리 똑똑해도 야생마처럼 통제 불능이 될 수 있는 AI를 <strong><span style="color:#2ECC71; font-weight:bold;">원하는 방향으로 부르고 제어하기 위한 마구/통제 시스템</span></strong>입니다.</li>
</ul>
</li>
<li><strong>12. AGI / ASI (범용 인공지능 / 초인공지능)</strong><ul>
<li><strong>AGI</strong>: 영업, 회계, 개발 등 모든 영역에서 사람처럼 일할 수 있는 <strong><span style="color:#2ECC71; font-weight:bold;">만능 범용 인공지능</span></strong> (현재 지향하는 목표 stage)</li>
<li><strong>ASI</strong>: 모든 면에서 인간의 지능을 뛰어넘는 상상 속의 <strong><span style="color:#2ECC71; font-weight:bold;">초인공지능</span></strong></li>
</ul>
</li>
</ul>
<p><span style="background-color:#5A5AFF; color:#ffffff; padding:2px 6px; border-radius:4px;">아무리 뛰어난 AI 모델이라도 하네스(Harness)와 MCP 시스템을 얼마나 정교하게 짜느냐에 따라 실제 비즈니스 결과물의 성패가 갈립니다.</span></p>
<hr>
<h3 id="💡-ai-핵심-용어-12가지-한눈에-보기">💡 AI 핵심 용어 12가지 한눈에 보기</h3>
<table>
<thead>
<tr>
<th align="left">번호</th>
<th align="left">용어</th>
<th align="left">쉽고 직관적인 비유</th>
<th align="left">핵심 역할</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>01</strong></td>
<td align="left"><strong>LLM</strong></td>
<td align="left">엄청난 지식을 읽은 뇌</td>
<td align="left">대화 및 정보 처리의 기본 두뇌</td>
</tr>
<tr>
<td align="left"><strong>02</strong></td>
<td align="left"><strong>멀티모달</strong></td>
<td align="left">뇌에 부착된 눈과 귀</td>
<td align="left">이미지·음성·영상 입출력 처리</td>
</tr>
<tr>
<td align="left"><strong>03</strong></td>
<td align="left"><strong>RLHF</strong></td>
<td align="left">예절 교육</td>
<td align="left">인간의 피드백을 통한 피드백 강화학습</td>
</tr>
<tr>
<td align="left"><strong>04</strong></td>
<td align="left"><strong>파인튜닝</strong></td>
<td align="left">신입사원 회사 교육</td>
<td align="left">회사/팀 특화 데이터로 재학습</td>
</tr>
<tr>
<td align="left"><strong>05</strong></td>
<td align="left"><strong>프롬프트 엔지니어링</strong></td>
<td align="left">지시문 작성 기술</td>
<td align="left">AI가 최선의 답을 내도록 지시하는 방법</td>
</tr>
<tr>
<td align="left"><strong>06</strong></td>
<td align="left"><strong>지식 베이스</strong></td>
<td align="left">회사 문서 창고</td>
<td align="left">규정집, 매뉴얼 등 모아둔 데이터 보관소</td>
</tr>
<tr>
<td align="left"><strong>07</strong></td>
<td align="left"><strong>RAG</strong></td>
<td align="left">창고 정보 검색 장치</td>
<td align="left">문서 창고에서 관련 근거를 찾아 답함</td>
</tr>
<tr>
<td align="left"><strong>08</strong></td>
<td align="left"><strong>MCP</strong></td>
<td align="left">멀티플러그 어댑터</td>
<td align="left">AI와 외부 도구(DB, 메일 등) 연결 표준</td>
</tr>
<tr>
<td align="left"><strong>09</strong></td>
<td align="left"><strong>에이전트</strong></td>
<td align="left">자율 처리 직원</td>
<td align="left">목표 설정 시 스스로 추론·계획·도구 활용</td>
</tr>
<tr>
<td align="left"><strong>10</strong></td>
<td align="left"><strong>워크플로우 자동화</strong></td>
<td align="left">업무 컨베이어 벨트</td>
<td align="left">반복적인 업무 프로세스를 연쇄 자동화</td>
</tr>
<tr>
<td align="left"><strong>11</strong></td>
<td align="left"><strong>하네스</strong></td>
<td align="left">야생마 통제용 마구</td>
<td align="left">AI를 원하는 방향으로 이끄는 제어 프레임워크</td>
</tr>
<tr>
<td align="left"><strong>12</strong></td>
<td align="left"><strong>AGI / ASI</strong></td>
<td align="left">만능 직원 / 초지능</td>
<td align="left">범용 인공지능 및 인간을 뛰어넘는 초지능</td>
</tr>
</tbody></table>
<hr>
<h3 id="📚-references">📚 References</h3>
<ul>
<li><a href="http://www.youtube.com/watch?v=N3st1ZrB_zc">유튜브 영상 - 모르면 따라가기 힘든 Ai 용어정리 12개 딱 한장으로 끝! (바이브코딩 지도 2부)</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[바이브 코딩]  ]]></title>
            <link>https://velog.io/@jungo_0/%EB%B0%94%EC%9D%B4%EB%B8%8C-%EC%BD%94%EB%94%A9</link>
            <guid>https://velog.io/@jungo_0/%EB%B0%94%EC%9D%B4%EB%B8%8C-%EC%BD%94%EB%94%A9</guid>
            <pubDate>Sun, 17 May 2026 11:57:20 GMT</pubDate>
            <description><![CDATA[<h1 id="바이브-코딩-이란">[바이브 코딩] 이란</h1>
<blockquote>
<p><strong>&quot;개발자를 고용하거나 외주를 주지 않고도, <span style="color:#2ECC71; font-weight:bold;">클로드 코드(Claude Code)</span>와 <span style="color:#2ECC71; font-weight:bold;">Supabase</span>, <span style="color:#2ECC71; font-weight:bold;">Vercel</span> 조합만 알면 혼자서 완벽한 웹서비스를 배포할 수 있습니다.&quot;</strong></p>
</blockquote>
<p>AI 시대가 도래하면서 손으로 일일이 코드를 짜지 않고 AI와 대화하며 개발하는 <strong>바이브 코딩(Vibe Coding)</strong>이 큰 주목을 받고 있습니다. 하지만 터미널, VS Code, Git 등 복잡한 초기 환경 세팅 때문에 입문 과정에서 포기하는 경우가 많습니다.</p>
<p>본 글에서는 복잡한 기존 방식을 벗어나, <strong>단 한 장의 지도로 바이브 코딩의 도구 세팅부터 프론트엔드·백엔드 연결, 최종 배포까지의 전 과정을 정리</strong>합니다.</p>
<hr>
<h3 id="1-바이브-코딩-필수-개념--도구-세팅">1. 바이브 코딩 필수 개념 &amp; 도구 세팅</h3>
<p>과거에는 터미널(Terminal) 창을 열어 복잡한 명령어 형태로 프로그램을 설치하고, VS Code 편집기와 Git을 일일이 연동해야 했습니다. 하지만 최근에는 단독 실행이 가능한 현대적인 신식 도구들이 등장하면서 세팅 과정이 대폭 단순화되었습니다.</p>
<ul>
<li><strong>LLM (대형 언어 모델)</strong>: Claude, ChatGPT, Gemini 등 무수한 데이터를 학습한 <strong><span style="color:#2ECC71; font-weight:bold;">두뇌</span></strong> 역할</li>
<li><strong>코딩 에이전트 (Coding Agent)</strong>: LLM과 연결되어 실제로 코드를 작성하고 실행하는 <strong><span style="color:#2ECC71; font-weight:bold;">손과 발</span></strong> 역할 (예: Claude Code PC 버전, Cursor AI)</li>
</ul>
<hr>
<h4 id="🛠️-3분만에-끝내는-기본-환경-세팅">🛠️ 3분만에 끝내는 기본 환경 세팅</h4>
<ol>
<li><strong><span style="color:#2ECC71; font-weight:bold;">Claude Code PC 버전 설치</span></strong><ul>
<li>구글에 <code>Claude Code</code>를 검색하여 공식 다운로드 페이지 접속 후 설치 파일을 실행합니다.</li>
<li>실행 후 로그인 및 좌측 상단의 <strong>[Code] 탭</strong>을 선택합니다. (대화 창 안에서 코드 작성, 실행 과정, 결과물 미리보기까지 한 번에 처리됨)</li>
</ul>
</li>
<li><strong><span style="color:#2ECC71; font-weight:bold;">Git (버전 관리 저장소) 설치</span></strong><ul>
<li>코드가 변경될 때마다 이전으로 되돌리거나 저장하는 <strong><span style="color:#2ECC71; font-weight:bold;">세이브 파일</span></strong> 역할을 합니다.</li>
<li><code>git-scm.com</code>에 접속하여 설치 프로그램을 받되, 모든 옵션은 기본값(Default)으로 두고 완료합니다.</li>
</ul>
</li>
<li><strong><span style="color:#2ECC71; font-weight:bold;">GitHub (클라우드 저장소) 연동</span></strong><ul>
<li>내 컴퓨터에 저장된 Git 기록을 인터넷 참고/창고에 올리는 서비스입니다.</li>
<li>GitHub 가입 후 Claude Code 대화창 옆 <code>+</code> 버튼 ➔ <code>Connect</code> ➔ <code>GitHub</code>를 선택해 계정을 인증합니다.</li>
</ul>
</li>
</ol>
<p><span style="background-color:#5A5AFF; color:#ffffff; padding:2px 6px; border-radius:4px;">Claude Code와 GitHub를 연결해 두면, &quot;GitHub에 올려줘&quot;라는 한 마디로 작업 시점의 코드가 웹 저장소에 자동으로 보관됩니다.</span></p>
<hr>
<h3 id="2-프론트엔드front-end-구축-눈에-보이는-화면-만들기">2. 프론트엔드(Front-end) 구축: 눈에 보이는 화면 만들기</h3>
<p>웹사이트는 크게 눈에 보이는 화면인 <strong><span style="color:#2ECC71; font-weight:bold;">프론트엔드(시계의 판과 바늘)</span></strong>와 뒤에서 데이터를 처리하는 <strong><span style="color:#2ECC71; font-weight:bold;">백엔드(시계 내부 태엽)</span></strong>로 나뉩니다.</p>
<p>프론트엔드 개발 시 HTML, CSS, JavaScript나 React, Flutter 같은 밀키트형 프레임워크를 다 외울 필요가 없습니다. <strong>만들고자 하는 프로젝트의 규모를 Claude Code에게 전달하는 것이 핵심</strong>입니다.</p>
<ul>
<li><strong>규모에 맞는 기술 스택 제안 요청:</strong><ul>
<li>소규모 서비스/단일 페이지인데 과도하게 큰 스택을 쓰거나, 반대로 트래픽이 몰릴 서비스에 너무 가벼운 스택을 쓰면 추후 사이트가 다운될 수 있습니다.</li>
<li><strong>프롬프트 예시:</strong> *&quot;단일 페이지 소개 사이트를 만들어줘. 서비스 규모에 적합한 언어와 기술 스택을 먼저 제안해줘.&quot;*</li>
</ul>
</li>
</ul>
<hr>
<h3 id="3-백엔드back-end-구축-supabase로-매니저-고용하기">3. 백엔드(Back-end) 구축: Supabase로 매니저 고용하기</h3>
<p>회원가입, 게시판, 데이터 저장 등 화면 뒤에서 일어나는 비즈니스 로직을 처리하려면 서버와 데이터베이스(DB)가 필수적입니다. 과거에는 AWS 등에서 서버를 빌리고 DB 프로그램을 직접 설치하는 등 진입장벽이 매우 높았으나, 이를 한 번에 해결해 주는 매니저 서비스가 있습니다.</p>
<ul>
<li><strong><span style="color:#2ECC71; font-weight:bold;">Supabase (서버 &amp; 데이터 담당 매니저)</span></strong>: 서버 구축, 데이터베이스 테이블 생성, 회원가입 인증을 알아서 처리해 주는 BaaS 서비스</li>
</ul>
<hr>
<h4 id="🗄️-supabase-데이터베이스-연결-흐름">🗄️ Supabase 데이터베이스 연결 흐름</h4>
<ol>
<li><code>supabase.com</code>에 접속하여 GitHub 계정으로 가입 후 <strong>New Project</strong>를 생성합니다.</li>
<li>프로젝트 생성 시 발급되는 <code>Project URL</code>과 <code>공개용 API Key(열쇠)</code> 두 가지를 복사합니다.</li>
<li>Claude Code에게 두 정보를 제공하며 요청합니다.<ul>
<li><strong>프롬프트 예시:</strong> *&quot;지금 만든 사이트에 이 Supabase 프로젝트(URL, API Key)를 연결해주고, 필요한 데이터 테이블을 생성하는 SQL 문을 짜줘.&quot;*</li>
</ul>
</li>
<li>Claude Code가 제공한 SQL 문을 Supabase의 <strong>SQL Editor</strong>에 붙여넣고 <code>Run</code>을 누르면 데이터베이스 구축이 완료됩니다.</li>
</ol>
<p><span style="background-color:#5A5AFF; color:#ffffff; padding:2px 6px; border-radius:4px;">Supabase를 연결하면 웹사이트에서 입력한 데이터가 실제 웹 서버 데이터베이스 표에 즉시 기록되는 백엔드 시스템이 완성됩니다.</span></p>
<hr>
<h3 id="4-최종-웹-배포-vercel로-전-세계에-사이트-공개하기">4. 최종 웹 배포: Vercel로 전 세계에 사이트 공개하기</h3>
<p>내 컴퓨터에서만 작동하던 웹사이트를 전 세계 누구나 접속할 수 있도록 인터넷에 올리는 과정을 <strong>배포(Deployment)</strong>라고 합니다.</p>
<ul>
<li><strong><span style="color:#2ECC71; font-weight:bold;">Vercel (배포 담당 매니저)</span></strong>: GitHub 저장소와 연동하여 자동으로 웹에 올리고 도메인을 생성해 주는 무료 배포 플랫폼</li>
</ul>
<hr>
<h4 id="🚀-vercel-자동-배포-3단계">🚀 Vercel 자동 배포 3단계</h4>
<ol>
<li><code>vercel.com</code>에 접속하여 <strong>Continue with GitHub</strong>로 가입 및 로그인합니다.</li>
<li><strong>Add New ➔ Project</strong>를 누른 뒤, 아까 Claude Code로 작업해 올린 <strong>GitHub 저장소(Repository)</strong>를 선택(<code>Import</code>)하고 <code>Deploy</code> 버튼을 클릭합니다.</li>
<li>배포가 완료되면 전 세계 누구나 접속할 수 있는 <strong>인터넷 주소(URL)</strong>가 생성됩니다.</li>
</ol>
<p><span style="background-color:#5A5AFF; color:#ffffff; padding:2px 6px; border-radius:4px;">이후 Claude Code로 코드를 수정해 GitHub에 올릴 때마다, Vercel 배포 매니저가 이를 감지하여 최신 버전으로 자동 업데이트해 줍니다.</span></p>
<hr>
<h3 id="💡-핵심-요약-비교표">💡 핵심 요약 비교표</h3>
<table>
<thead>
<tr>
<th align="left">분류</th>
<th align="left">역할</th>
<th align="left">추천 도구 / 서비스</th>
<th align="left">핵심 특징</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>개발 도구</strong></td>
<td align="left">AI 기반 코드 작성 및 실행</td>
<td align="left"><span style="color:#2ECC71; font-weight:bold;">Claude Code (PC)</span></td>
<td align="left">단독 창에서 지시, 코드 작성, 결과 확인까지 통합 제공</td>
</tr>
<tr>
<td align="left"><strong>코드 저장소</strong></td>
<td align="left">코드 변경 이력 저장 및 인터넷 보관</td>
<td align="left"><span style="color:#2ECC71; font-weight:bold;">Git &amp; GitHub</span></td>
<td align="left">세이브 파일 저장 및 배포 플랫폼과의 실시간 연동</td>
</tr>
<tr>
<td align="left"><strong>백엔드 매니저</strong></td>
<td align="left">DB 관리 및 사용자 데이터 처리</td>
<td align="left"><span style="color:#2ECC71; font-weight:bold;">Supabase</span></td>
<td align="left">복잡한 서버 구축 없이 URL과 API Key로 DB 자동 구성</td>
</tr>
<tr>
<td align="left"><strong>배포 매니저</strong></td>
<td align="left">인터넷상에 글로벌 사이트 공개</td>
<td align="left"><span style="color:#2ECC71; font-weight:bold;">Vercel</span></td>
<td align="left">GitHub 업로드 시 실시간으로 웹사이트 자동 업데이트</td>
</tr>
</tbody></table>
<hr>
<h3 id="🏁-결론">🏁 결론</h3>
<p>바이브 코딩의 전체 구조는 다음 3단계로 요약할 수 있습니다.</p>
<ol>
<li><strong>도구 세팅</strong>: <span style="color:#2ECC71; font-weight:bold;">Claude Code</span> + <span style="color:#2ECC71; font-weight:bold;">Git/GitHub</span> 연동</li>
<li><strong>프론트엔드</strong>: 서비스 규모에 맞게 AI에게 스택 제안을 받아 개발</li>
<li><strong>백엔드 &amp; 배포</strong>: 데이터 관리는 <span style="background-color:#5A5AFF; color:#ffffff; padding:2px 6px; border-radius:4px;">Supabase</span>, 최종 배포 및 관리는 <span style="background-color:#5A5AFF; color:#ffffff; padding:2px 6px; border-radius:4px;">Vercel</span>에 위임</li>
</ol>
<p>이 세 가지 매니저 도구 조합만 이해하면 코딩을 몰라도 혼자서 구동 가능한 완전한 웹 서비스를 빠르게 완성할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] useEffect
없이 컴포넌트 State 강제 리셋하기]]></title>
            <link>https://velog.io/@jungo_0/.-Framer-Motion-%EB%93%B1-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98Exit-Animation%EA%B3%BC-Key%EC%9D%98-%EC%83%81%ED%98%B8%EC%9E%91%EC%9A%A9-%EA%B5%AC%ED%98%84%ED%95%A0-%EB%95%8C-key-%EA%B0%80-%EC%97%86%EC%9C%BC%EB%A9%B4-%EC%99%9C-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%82%98%EA%B1%B0%EB%82%98-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98%EC%9D%B4-%EC%94%B9%ED%9E%88%EB%8A%94%EC%A7%80-%EC%84%A4%EB%AA%85%ED%95%A9%EB%8B%88%EB%8B%A4.-%ED%95%B5%EC%8B%AC-%EC%9D%B8%EC%82%AC%EC%9D%B4%ED%8A%B8-%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%8A%94-DOM%EC%97%90%EC%84%9C-%EB%85%B8%EB%93%9C%EB%A5%BC-%EC%A6%89%EC%8B%9C-%EC%A7%80%EC%9A%B0%EB%A0%A4-%ED%95%98%EC%A7%80%EB%A7%8C-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%8A%94-key-%EB%A5%BC-%EA%B8%B0%EC%A4%80%EC%9C%BC%EB%A1%9C-%EC%82%AD%EC%A0%9C-%EB%8C%80%EA%B8%B0-%EC%A4%91%EC%9D%B8-%EB%85%B8%EB%93%9C%EB%A5%BC-%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%97%90-%EB%B6%99%EC%9E%A1%EC%95%84%EB%91%90%EA%B3%A0-fade-out-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98%EC%9D%B4-%EB%81%9D%EB%82%9C-%EB%92%A4%EC%97%90-DOM%EC%9D%84-%EC%A0%9C%EA%B1%B0%ED%95%A9%EB%8B%88%EB%8B%A4.-O-%EC%A6%89-key-%EA%B0%80-%EC%97%86%EC%9C%BC%EB%A9%B4-%EC%96%B4%EB%96%A4-%EC%9A%94%EC%86%8C%EA%B0%80-%EC%82%AC%EB%9D%BC%EC%A7%80%EB%8A%94-%EC%A4%91%EC%9D%B8%EC%A7%80-</link>
            <guid>https://velog.io/@jungo_0/.-Framer-Motion-%EB%93%B1-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98Exit-Animation%EA%B3%BC-Key%EC%9D%98-%EC%83%81%ED%98%B8%EC%9E%91%EC%9A%A9-%EA%B5%AC%ED%98%84%ED%95%A0-%EB%95%8C-key-%EA%B0%80-%EC%97%86%EC%9C%BC%EB%A9%B4-%EC%99%9C-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%82%98%EA%B1%B0%EB%82%98-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98%EC%9D%B4-%EC%94%B9%ED%9E%88%EB%8A%94%EC%A7%80-%EC%84%A4%EB%AA%85%ED%95%A9%EB%8B%88%EB%8B%A4.-%ED%95%B5%EC%8B%AC-%EC%9D%B8%EC%82%AC%EC%9D%B4%ED%8A%B8-%EB%A6%AC%EC%95%A1%ED%8A%B8%EB%8A%94-DOM%EC%97%90%EC%84%9C-%EB%85%B8%EB%93%9C%EB%A5%BC-%EC%A6%89%EC%8B%9C-%EC%A7%80%EC%9A%B0%EB%A0%A4-%ED%95%98%EC%A7%80%EB%A7%8C-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%8A%94-key-%EB%A5%BC-%EA%B8%B0%EC%A4%80%EC%9C%BC%EB%A1%9C-%EC%82%AD%EC%A0%9C-%EB%8C%80%EA%B8%B0-%EC%A4%91%EC%9D%B8-%EB%85%B8%EB%93%9C%EB%A5%BC-%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%97%90-%EB%B6%99%EC%9E%A1%EC%95%84%EB%91%90%EA%B3%A0-fade-out-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98%EC%9D%B4-%EB%81%9D%EB%82%9C-%EB%92%A4%EC%97%90-DOM%EC%9D%84-%EC%A0%9C%EA%B1%B0%ED%95%A9%EB%8B%88%EB%8B%A4.-O-%EC%A6%89-key-%EA%B0%80-%EC%97%86%EC%9C%BC%EB%A9%B4-%EC%96%B4%EB%96%A4-%EC%9A%94%EC%86%8C%EA%B0%80-%EC%82%AC%EB%9D%BC%EC%A7%80%EB%8A%94-%EC%A4%91%EC%9D%B8%EC%A7%80-</guid>
            <pubDate>Wed, 13 May 2026 09:15:37 GMT</pubDate>
            <description><![CDATA[<h1 id="react---key를-활용한-컴포넌트-state-강제-리셋">[React]   <code>key</code>를 활용한 컴포넌트 State 강제 리셋</h1>
<blockquote>
<p><strong>&quot;<code>key</code>는 배열 렌더링 전용 속성이 아닙니다. 리액트에서 <span style="background-color:#5A5AFF; color:#ffffff; padding:2px 6px; border-radius:4px;">&#39;컴포넌트 인스턴스의 생명주기를 강제로 재시작하고 State를 초기화하는 선언적 트리거&#39;</span>입니다.&quot;</strong></p>
</blockquote>
<p>웹 애플리케이션을 개발하다 보면 사용자 ID가 바뀌거나 선택된 모달 데이터가 변경될 때, 컴포넌트 내부의 입력 폼(State)이나 로컬 상태를 깨끗하게 비워야 하는 상황을 자주 마주합니다.</p>
<p>이때 대부분의 주니어 개발자들은 습관적으로 <code>useEffect</code>를 선언하고 <code>setFormData(initialState)</code>를 호출해 수동으로 상태를 리셋하곤 합니다. 하지만 이는 <strong>불필요한 렌더링 플리커(Flicker)</strong>와 <strong>동기화 버그</strong>를 유발하는 명령형 안티 패턴입니다.</p>
<p>React 공식 문서에서도 적극 권장하는 <strong><code>key</code> 프로퍼티를 활용한 선언적 State 초기화 패턴</strong>의 원리와 실무 적용법을 심층적으로 살펴봅니다.</p>
<hr>
<h3 id="1-흔히-저지르는-안티-패턴-useeffect로-상태-수동-동기화">1. 흔히 저지르는 안티 패턴: <code>useEffect</code>로 상태 수동 동기화</h3>
<p>사용자가 프로필 목록에서 다른 유저를 클릭해 상세 폼(<code>ProfileForm</code>)을 열람하는 상황을 가정해 보겠습니다.</p>
<pre><code class="language-jsx">// ❌ 비효율적이고 위험한 패턴: useEffect를 통한 상태 리셋
function ProfileForm({ user }) {
  const [comment, setComment] = useState(&#39;&#39;);

  // ⚠️ Props(user.id) 변경을 감지하고 상태를 수동으로 비움
  useEffect(() =&gt; {
    setComment(&#39;&#39;);
  }, [user.id]);

  return (
    &lt;div&gt;
      &lt;h3&gt;{user.name}의 한 줄 코멘트&lt;/h3&gt;
      &lt;textarea value={comment} onChange={(e) =&gt; setComment(e.target.value)} /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<h3 id="1-이-방식이-유발하는-3가지-문제점">1 이 방식이 유발하는 3가지 문제점</h3>
<ol>
<li><p><strong><span style="color:#2ECC71; font-weight:bold;">찰나의 화면 깜빡임 (Stale UI Flash)</span></strong>:</p>
<ul>
<li>리액트는 먼저 <strong>이전 유저의 <code>comment</code> 상태를 유지한 채</strong> 새 유저 이름으로 화면을 1회 렌더링합니다.</li>
<li>브라우저에 Paint가 끝난 뒤 <code>useEffect</code>가 실행되고 나서야 <code>setComment(&#39;&#39;)</code>로 인해 2번째 리렌더링이 발생합니다. 사용자 눈에는 이전 데이터가 순간적으로 스쳐 지나가는 깜빡임(Flash)</span>이 노출됩니다.</li>
</ul>
</li>
<li><p><strong><span style="color:#2ECC71; font-weight:bold;">불필요한 렌더 트리 낭비</span></strong>:</p>
<ul>
<li>단 한 번의 데이터 변경에 <span style="color:#2ECC71; font-weight:bold;">컴포넌트가 2번씩 렌더링</span>되므로 불필요한 연산 및 성능 낭비가 발생합니다.</li>
</ul>
</li>
<li><p><strong><span style="color:#2ECC71; font-weight:bold;">상태 누락 위험 (휴먼 에러)</span></strong>:</p>
<ul>
<li>폼 내부에 관리할 State(<code>input</code>, <code>checkbox</code>, <code>file</code>, <code>validation</code> 등)가 늘어날 때마다 <code>useEffect</code> 내부에 리셋 로직을 일일이 수동으로 추가</span>해야 하므로 상태 초기화가 누락되는 버그가 발생하기 쉽습니다.</li>
</ul>
</li>
</ol>
<h3 id="2-해결책-단일-컴포넌트에-key를-부여하는-역발상">2. 해결책: 단일 컴포넌트에 key를 부여하는 역발상</h3>
<p>리액트의 재조정(Reconciliation) 엔진은 형제 노드뿐만 아니라 <strong>단일 컴포넌트</strong>에서도 <code>key</code>를 기준으로 인스턴스의 유지 여부를 판단합니다.</p>
<pre><code class="language-js">// ✅ 선언적이고 완벽한 베스트 프랙티스: 부모에서 key에 식별자를 전달
function ProfilePage({ user }) {
  return (
    &lt;div&gt;
      &lt;h2&gt;프로필 관리 대시보드&lt;/h2&gt;
      {/* 💡 user.id가 변경되는 순간, 기존 인스턴스를 파괴하고 새 인스턴스를 마운트 */}
      &lt;ProfileForm key=&quot;{user.id}&quot; user=&quot;{user}&quot;/&gt;
    &lt;/div&gt;
  );
}

function ProfileForm({ user }) {
  // useEffect가 전혀 필요 없음! 마운트 시 항상 깔끔한 초기값으로 시작
  const [comment, setComment] = useState(&#39;&#39;);

  return (
    &lt;div&gt;
      &lt;h3&gt;{user.name}의 한 줄 코멘트&lt;/h3&gt;
      &lt;textarea value={comment} onChange={(e) =&gt; setComment(e.target.value)} /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><code>key</code> 값을 변경하는 것만으로 리액트는 이전 컴포넌트의 Fiber 노드를 통째로 Unmount하고 새 State를 가진 인스턴스를 즉시 Mount합니다.</p>
<h3 id="3-리액트-내부-엔진-동작-비교-reconciliation--fiber">3. 리액트 내부 엔진 동작 비교 (Reconciliation &amp; Fiber)</h3>
<p><code>key</code>가 변경될 때 리액트 내부에서 일어나는 과정을 도식화하면 다음과 같습니다.</p>
<h4 id="case-1-useeffect-수동-초기화">[Case 1: useEffect 수동 초기화]</h4>
<ol>
<li><code>user.id</code> 변경</li>
<li>이전 State(&#39;이전 댓글&#39;)를 유지한 채 <span style="color:#2ECC71; font-weight:bold;">1차 Render &amp; DOM Commit</span> ➔ ⚠️ 이전 상태 잔류</li>
<li>브라우저 Paint 완료 후 <code>useEffect</code> 실행 ➔ <code>setComment(&#39;&#39;)</code> 트리거</li>
<li><span style="color:#2ECC71; font-weight:bold;">2차 Render &amp; DOM Commit</span> ➔ 총 2회 렌더링 비용 발생</li>
</ol>
<h4 id="case-2-keyuserid-변경-테크닉">[Case 2: key={user.id} 변경 테크닉]</h4>
<ol>
<li><code>user.id</code> 변경 ➔ <code>key</code> 값이 변경됨</li>
<li>리액트 판단: <span style="color:#2ECC71; font-weight:bold;">&quot;Key가 달라졌으니 완전히 다른 컴포넌트다!&quot;</span></li>
<li>기존 Fiber Node 및 DOM 완전 정리(Unmount) + 새 Fiber Node 생성(Mount)</li>
<li>State 초기화가 포함된 상태로 단 1회의 Clean Render &amp; DOM Commit 완료 ($O(1)$)</span></li>
</ol>
<p>❖ <strong><code>key</code> 리셋 패턴의 3대 핵심 이점:</strong></p>
<ul>
<li><strong><span style="color:#2ECC71; font-weight:bold;">완벽한 선언적 코드 (Declarative)</span></strong>: 상태를 어떻게 지울지(<code>setX(&#39;&#39;)</code>) 명령하지 않고, <span style="color:#2ECC71; font-weight:bold;">&quot;유저가 바뀌면 새 폼이다&quot;</span>라고 선언합니다.</li>
<li><strong><span style="color:#2ECC71; font-weight:bold;">플리커 프리 (Zero Flicker)</span></strong>: 단 1회의 렌더 패스 안에서 초기 상태가 결합되어 화면 깜빡임이 완전히 사라집니다.</span></li>
<li><strong><span style="color:#2ECC71; font-weight:bold;">하위 비제어 상태(Uncontrolled State) 및 스크롤까지 일괄 리셋</span></strong>: 컴포넌트가 새로 마운트되므로 DOM의 내부 포커스, 스크롤 위치, 에러 스타일까지 완벽히 초기화</span>됩니다.</li>
</ul>
<h3 id="4-실무에서-쓰이는-3가지-대표-시나리오">4. 실무에서 쓰이는 3가지 대표 시나리오</h3>
<ul>
<li><p><strong>① <span style="color:#2ECC71; font-weight:bold;">상세 보기 모달 / 사이드 드로어 (Detail Drawer)</span></strong>:</p>
<ul>
<li>목록에서 여러 항목을 클릭해 상세 모달을 띄울 때, 이전 항목의 로딩 상태나 수정 중이던 텍스트가 남아있는 문제를 <code>&lt;DetailModal key={selectedItem.id} item={selectedItem} /&gt;</code> 한 줄로 완벽히 해결</span>합니다.</li>
</ul>
</li>
<li><p><strong>② <span style="color:#2ECC71; font-weight:bold;">다단계 설문조사 / 위저드 폼 (Wizard Form Step)</span></strong>:</p>
<ul>
<li>다음 단계로 넘어갈 때 이전 스텝 컴포넌트를 재사용하면서 State가 꼬이는 현상을 <code>&lt;StepForm key={currentStep} step={currentStep} /&gt;</code> <span style="color:#2ECC71; font-weight:bold;">형태로 깔끔하게 격리</span>합니다.</li>
</ul>
</li>
<li><p><strong>③ <span style="color:#2ECC71; font-weight:bold;">캔버스 / 차트 라이브러리 초기화</span></strong>:</p>
<ul>
<li><code>Chart.js</code>나 <code>D3.js</code>처럼 인스턴스 파괴(<code>destroy()</code>) 후 재구성이 까다로운 서드파티 라이브러리를 감싼 컴포넌트는 <code>&lt;ChartCanvas key={chartDataId} /&gt;</code>를 통해 완벽한 라이프사이클 재시작을 유도</span>할 수 있습니다.</li>
</ul>
</li>
</ul>
<h3 id="💡-3가지-key-안티패턴-및-올바른-해결책-비교">💡 3가지 Key 안티패턴 및 올바른 해결책 비교</h3>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left"><code>key={index}</code></th>
<th align="left"><code>key={Math.random()}</code></th>
<th align="left"><code>key={item.id}</code> (불변 고유값)</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>고유성 (Unique)</strong></td>
<td align="left">⚠️ 순서에 의존</td>
<td align="left"><span style="color:#2ECC71; font-weight:bold;">✅ 고유함</span></td>
<td align="left"><span style="color:#2ECC71; font-weight:bold;">✅ 완벽히 고유함</span></td>
</tr>
<tr>
<td align="left"><strong>불변성 (Stable)</strong></td>
<td align="left">❌ 삽입/삭제 시 변경</td>
<td align="left">❌ <strong>매 렌더링마다 변경</strong></td>
<td align="left"><span style="color:#2ECC71; font-weight:bold;">✅ 렌더링 내내 불변</span></td>
</tr>
<tr>
<td align="left"><strong>주요 발생 버그</strong></td>
<td align="left">이전 상태가 엉뚱한 위치로 전이</td>
<td align="left"><strong>DOM 강제 파괴, 포커스 풀림, 깜빡임</strong></td>
<td align="left"><span style="color:#2ECC71; font-weight:bold;">없음 (버그 방어)</span></td>
</tr>
<tr>
<td align="left"><strong>DOM 라이프사이클</strong></td>
<td align="left">기존 DOM 강제 재사용</td>
<td align="left">매번 Unmount ➔ Mount 반복</td>
<td align="left">정상 Update / 필요한 노드만 Mount</td>
</tr>
</tbody></table>
<hr>
<h3 id="📚-references">📚 References</h3>
<ul>
<li><a href="https://react.dev/learn/rendering-lists#why-does-react-need-keys">React Official Documentation - Why does React need keys?</a></li>
<li><a href="https://react.dev/learn/rendering-lists#rules-of-keys">React Official Documentation - Rules of Keys</a></li>
<li><a href="https://react.dev/learn/preserving-and-resetting-state">Dan Abramov - Preserving and Resetting State in React</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] Server Actions, formData, useFormState]]></title>
            <link>https://velog.io/@jungo_0/Next.js-Server-Actions-formData-useFormState</link>
            <guid>https://velog.io/@jungo_0/Next.js-Server-Actions-formData-useFormState</guid>
            <pubDate>Thu, 16 Apr 2026 01:19:23 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jungo_0/post/0b366307-f3d7-4564-81bc-bb7b8b679bed/image.png" alt=""></p>
<h1 id="nextjs-app-router-핵심-정리-server-actions-formdata-useformstate">[Next.js] App Router 핵심 정리: Server Actions, formData, useFormState</h1>
<p>Next.js의 App Router 환경에서 데이터를 주고받는 방식은 기존 리액트 방식과는 완전히 다릅니다. 핵심이 되는 세 가지 개념을 실제 코드 예시와 함께 정리합니다.</p>
<hr>
<h2 id="1-server-actions-서버-액션">1. Server Actions (서버 액션)</h2>
<p><strong>&quot;백엔드 로직을 프론트엔드 함수처럼 직접 호출하는 기술&quot;</strong></p>
<p>기존에는 데이터를 전송하기 위해 API 엔드포인트를 따로 만들었지만, 서버 액션은 함수 하나로 서버측 로직을 실행합니다.</p>
<ul>
<li><strong>구현:</strong> 함수 내부에 <code>&#39;use server&#39;</code> 지시어를 선언합니다.</li>
<li><strong>장점:</strong> * API URL을 일일이 관리할 필요가 없습니다.<ul>
<li>민감한 DB 로직이 클라이언트에 노출되지 않아 보안에 강력합니다.</li>
<li>자바스크립트가 로드되지 않은 환경에서도 HTML Form 표준에 따라 동작합니다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="2-formdata-폼-데이터">2. formData (폼 데이터)</h2>
<p><strong>&quot;폼에 담긴 모든 데이터를 한데 묶은 보따리&quot;</strong></p>
<p>서버 액션이 호출될 때 전달되는 인자로, 폼 내부의 모든 입력값을 가지고 있습니다.</p>
<ul>
<li><strong>자동 수집:</strong> 각 <code>input</code> 태그에 <code>name</code> 속성만 적어주면 제출 시점에 데이터를 자동으로 싹 긁어모읍니다.</li>
<li><strong>데이터 추출:</strong> <code>formData.get(&#39;name값&#39;)</code> 메서드로 원하는 데이터를 꺼낼 수 있습니다.</li>
<li><strong>💡 useRef와 차이점:</strong><ul>
<li><strong>useRef (리모컨):</strong> 특정 입력창에 포커스를 주거나 직접 클릭을 발생시키는 등 &#39;조작&#39;이 목적입니다.</li>
<li><strong>formData (보따리):</strong> 입력된 데이터들을 서버로 &#39;전송&#39;하기 위해 묶어주는 것이 목적입니다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="3-useformstate-폼-상태-관리">3. useFormState (폼 상태 관리)</h2>
<p><strong>&quot;서버의 응답을 UI에 실시간으로 연결하는 통로&quot;</strong></p>
<p>서버 액션이 실행된 후, 서버에서 보내온 결과(성공/실패 메시지 등)를 클라이언트 화면에 보여주기 위해 사용하는 Hook입니다.</p>
<h3 id="실제-구현-예시">실제 구현 예시</h3>
<pre><code class="language-javascript">// 1. 서버 액션 정의 (actions.js)
&#39;use server&#39;;

export async function shareMeal(prevState, formData) {
  const title = formData.get(&#39;title&#39;);

  if (!title) {
    return { message: &#39;제목을 입력해주세요!&#39; }; // 에러 메시지 리턴
  }

  // DB 저장 로직 수행...
  return { message: &#39;성공적으로 저장되었습니다.&#39; };
}

// 2. 클라이언트 컴포넌트 (MealForm.js)
&#39;use client&#39;;
import { useFormState } from &#39;react-dom&#39;;
import { shareMeal } from &#39;./actions&#39;;

export default function MealForm() {
  const [state, formAction] = useFormState(shareMeal, { message: null });

  return (
    &lt;form action={formAction}&gt;
      &lt;input type=&quot;text&quot; name=&quot;title&quot; /&gt;
      {/* 서버에서 보낸 메시지가 state에 담겨 출력됨 */}
      {state.message &amp;&amp; &lt;p&gt;{state.message}&lt;/p&gt;}
      &lt;button type=&quot;submit&quot;&gt;제출&lt;/button&gt;
    &lt;/form&gt;
  );
}</code></pre>
<hr>
<h3 id="4-revalidatepath-데이터-최신화-캐싱">4. revalidatePath: 데이터 최신화 (캐싱)</h3>
<p>Next.js는 성능을 위해 페이지를 캐싱합니다. 데이터가 추가되었는데 화면이 바뀌지 않는다면 캐시 때문입니다.</p>
<ul>
<li><p>역할: revalidatePath(&#39;/meals&#39;)를 호출하면 해당 경로의 기존 캐시를 버리고 새로운 데이터를 다시 가져오도록 명령합니다.</p>
</li>
<li><p>시점: 보통 서버 액션 안에서 DB 업데이트가 성공적으로 끝난 직후에 사용합니다.</p>
</li>
</ul>
<h2 id="5-전체-데이터-흐름-요약">5. 전체 데이터 흐름 요약</h2>
<ol>
<li><p>사용자: 폼 작성 후 제출 클릭.</p>
</li>
<li><p>클라이언트: 모든 데이터가 formData 보따리에 담김.</p>
</li>
<li><p>서버: Server Action 실행, DB 작업 수행.</p>
</li>
<li><p>서버: 작업 결과(메시지 등)를 반환하고 revalidatePath로 캐시 갱신.</p>
</li>
<li><p>클라이언트: useFormState가 리턴받은 결과를 화면에 표시.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] 데이터 로딩 방식]]></title>
            <link>https://velog.io/@jungo_0/Next.js-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%A1%9C%EB%94%A9-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@jungo_0/Next.js-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%A1%9C%EB%94%A9-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Wed, 15 Apr 2026 09:17:29 GMT</pubDate>
            <description><![CDATA[<h2 id="nextjs-서버-컴포넌트에서-데이터베이스-직접-연결하기">[Next.js] 서버 컴포넌트에서 데이터베이스 직접 연결하기</h2>
<blockquote>
<p><code>useEffect</code>와 <code>fetch</code> 없이 서버에서 직접 데이터를 꺼내오는 현대적인 풀스택 로딩 전략을 분석함</p>
</blockquote>
<p><strong>키워드:</strong> Direct DB Access, RSC async/await, lib 폴더 구조, SQLite</p>
<hr>
<h3 id="1-데이터-로딩-방식의-혁신">1. 데이터 로딩 방식의 혁신</h3>
<p>기존 리액트(CSR)는 브라우저가 API 서버를 호출하고 기다리는 복잡한 과정을 거쳤지만, Next.js 서버 컴포넌트는 서버 내부에서 <strong>DB에 직접 접근</strong>합니다.</p>
<table>
<thead>
<tr>
<th align="left">비교 항목</th>
<th align="left">기존 리액트 (CSR)</th>
<th align="left">Next.js 서버 컴포넌트 (RSC)</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>통신 방식</strong></td>
<td align="left">브라우저 → API 서버 → DB</td>
<td align="left"><strong>서버 컴포넌트 → DB (직접)</strong></td>
</tr>
<tr>
<td align="left"><strong>필요한 훅</strong></td>
<td align="left"><code>useState</code>, <code>useEffect</code> 필수</td>
<td align="left"><strong>필요 없음 (async/await 사용)</strong></td>
</tr>
<tr>
<td align="left"><strong>보안</strong></td>
<td align="left">API 엔드포인트 노출 위험</td>
<td align="left"><strong>서버 내부 실행으로 안전함</strong></td>
</tr>
</tbody></table>
<hr>
<h3 id="2-db-로직-분리-libmealsjs">2. DB 로직 분리 (<code>lib/meals.js</code>)</h3>
<p>데이터베이스 연결과 쿼리 로직은 별도의 <code>lib</code> 폴더에서 관리하여 코드의 가독성과 재사용성을 높입니다.</p>
<pre><code class="language-javascript">import sql from &#39;better-sqlite3&#39;;

const db = sql(&#39;meals.db&#39;);

export async function getMeals() {
  // 로딩 상태 확인을 위해 의도적인 지연(Delay) 추가
  await new Promise((resolve) =&gt; setTimeout(resolve, 2000));

  // .all()은 모든 행을 배열로 가져옴
  return db.prepare(&#39;SELECT * FROM meals&#39;).all();
}</code></pre>
<hr>
<h3 id="3-컴포넌트에-asyncawait를-직접-쓰는-이유">3. 컴포넌트에 async/await를 직접 쓰는 이유</h3>
<p>가장 많은 입문자가 헷갈리는 부분입니다. 왜 일반 리액트 컴포넌트는 async가 안 되고, 서버 컴포넌트는 될까요?</p>
<h3 id="①-일반-리액트csr의-한계">① 일반 리액트(CSR)의 한계</h3>
<p>일반적인 클라이언트 컴포넌트는 <strong>&quot;즉시 UI(JSX)를 반환해야 하는 함수&quot;</strong>입니다</p>
<ul>
<li><p>문제점: async를 붙이면 함수가 JSX가 아닌 Promise를 반환합니다.</p>
</li>
<li><p>결과: 리액트 엔진은 화면에 그릴 HTML이 필요한데 &#39;나중에 줄게&#39;라는 약속(Promise)을 받으면 에러를 냅니다. 그래서 예전엔 useEffect 안에서 데이터를 따로 관리했습니다.</p>
</li>
</ul>
<h3 id="②-서버-컴포넌트rsc의-마법">② 서버 컴포넌트(RSC)의 마법</h3>
<p>서버 컴포넌트는 브라우저가 아닌 <strong>서버(백엔드 환경)</strong>에서 실행됩니다.</p>
<ul>
<li>기다림의 미학: 서버는 &quot;데이터 가져오는 데 2초 걸려? 오케이, 다 가져올 때까지 내가 기다렸다가(await) 완성된 HTML을 통째로 구워서 보내줄게&quot;라고 할 수 있는 능력이 있습니다.</li>
</ul>
<h3 id="데이터-로딩-프로세스-변화">데이터 로딩 프로세스 변화</h3>
<ol>
<li>서버 진입: 요청이 오면 서버 컴포넌트 함수가 실행됨.</li>
<li>비동기 대기: await getMeals() 라인에서 DB 응답이 올 때까지 함수 실행을 잠시 멈춤.</li>
<li>HTML 생성: 데이터가 도착하면 그 데이터를 버무려 최종 HTML을 완성함.</li>
<li>전송: 브라우저는 자바스크립트가 실행되기 전부터 이미 데이터가 채워진 HTML을 받게 됨.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] 서버 컴포넌트, 클라이언트 컴포넌트]]></title>
            <link>https://velog.io/@jungo_0/Next.js-%EC%84%9C%EB%B2%84-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@jungo_0/Next.js-%EC%84%9C%EB%B2%84-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Wed, 15 Apr 2026 07:29:27 GMT</pubDate>
            <description><![CDATA[<h2 id="nextjs-레이아웃layoutjs과-메타데이터metadata의-이해">[Next.js] 레이아웃(layout.js)과 메타데이터(Metadata)의 이해</h2>
<blockquote>
<p><code>page.js</code>가 각 페이지의 구체적인 내용이라면, <code>layout.js</code>는 이를 감싸는 공통의 뼈대와 환경 설정을 담당함</p>
</blockquote>
<p><strong>키워드:</strong> Root Layout, Children Props, Nested Layout, Metadata SEO</p>
<hr>
<h3 id="1-layoutjs의-정의와-역할">1. layout.js의 정의와 역할</h3>
<p><code>layout.js</code>는 하나 이상의 페이지를 감싸는 공통의 <strong>껍데기(Shell)</strong>입니다. 헤더, 푸터, 내비게이션 바처럼 모든 페이지에서 반복되는 요소들을 한 곳에서 관리합니다.</p>
<h3 id="2-루트-레이아웃root-layout의-필수성">2. 루트 레이아웃(Root Layout)의 필수성</h3>
<p><code>app</code> 폴더 최상위의 <code>layout.js</code>는 애플리케이션의 근본입니다. 리액트 컴포넌트임에도 불구하고 <code>&lt;html&gt;</code>과 <code>&lt;body&gt;</code> 태그를 직접 렌더링해야 하는 필수 파일입니다.</p>
<h3 id="3-children-속성과-페이지-삽입">3. children 속성과 페이지 삽입</h3>
<p>레이아웃은 리액트의 표준 속성인 <strong><code>children</code></strong>을 통해 내용을 전달받습니다.</p>
<ul>
<li><strong>동작</strong>: 사용자가 접속한 경로의 <code>page.js</code>가 레이아웃의 <code>{children}</code> 자리로 자동 조립됩니다.</li>
</ul>
<hr>
<h3 id="4-중첩-레이아웃-nested-layout">4. 중첩 레이아웃 (Nested Layout)</h3>
<p>특정 하위 폴더에도 레이아웃을 추가할 수 있으며, 이는 해당 섹션(예: 관리자 페이지, 마이페이지)에만 특화된 디자인을 적용할 때 유용합니다.</p>
<h3 id="5-메타데이터metadata와-seo">5. 메타데이터(Metadata)와 SEO</h3>
<p>Next.js에서는 <code>&lt;head&gt;</code> 태그를 직접 쓰지 않고 약속된 <strong><code>metadata</code></strong> 객체를 사용합니다.</p>
<ul>
<li><strong>자동 처리</strong>: <code>export const metadata = { title: &#39;...&#39;, description: &#39;...&#39; }</code>라고 정의하면 Next.js가 이면에서 메타 태그를 생성합니다.</li>
<li><strong>상속</strong>: 상위 레이아웃의 메타데이터는 하위 페이지로 자연스럽게 이어집니다.</li>
</ul>
<hr>
<h1 id="19-nextjs-서버-컴포넌트rsc-vs-클라이언트-컴포넌트rcc">19. [Next.js] 서버 컴포넌트(RSC) vs 클라이언트 컴포넌트(RCC)</h1>
<blockquote>
<p>코드가 서버(Node.js)에서 실행되는지, 브라우저(클라이언트)에서 실행되는지에 따라 성능과 기능이 결정됨</p>
</blockquote>
<hr>
<h3 id="1-리액트-서버-컴포넌트-react-server-components-rsc">1. 리액트 서버 컴포넌트 (React Server Components, RSC)</h3>
<p>Next.js의 모든 컴포넌트는 별도 설정이 없다면 <strong>기본적으로 서버 컴포넌트</strong>입니다.</p>
<ul>
<li><strong>실행 위치</strong>: 브라우저가 아닌 <strong>백엔드 서버</strong>에서 실행됩니다.</li>
<li><strong>장점</strong>: <ul>
<li>자바스크립트 번들 크기가 줄어들어 성능 향상.</li>
<li>완성된 HTML을 보내주므로 <strong>SEO(검색 엔진 최적화)</strong>에 매우 유리.</li>
</ul>
</li>
<li><strong>특징</strong>: <code>console.log</code>가 브라우저가 아닌 <strong>터미널</strong>에 찍힙니다.</li>
</ul>
<hr>
<h3 id="2-클라이언트-컴포넌트-client-components-rcc">2. 클라이언트 컴포넌트 (Client Components, RCC)</h3>
<p>브라우저 전용 기능이나 사용자와의 실시간 상호작용이 필요한 경우 사용합니다.</p>
<ul>
<li><strong>사용법</strong>: 파일 최상단에 <strong><code>&#39;use client&#39;;</code></strong> 지시어를 추가합니다.</li>
<li><strong>언제 사용하나?</strong><ol>
<li><strong>상태 및 생명주기</strong>: <code>useState</code>, <code>useEffect</code> 사용 시.</li>
<li><strong>이벤트</strong>: <code>onClick</code>, <code>onChange</code> 핸들러 사용 시.</li>
<li><strong>브라우저 API</strong>: <code>window</code>, <code>localStorage</code> 접근 시.</li>
</ol>
</li>
</ul>
<hr>
<h3 id="3-왜-기본이-서버-컴포넌트일까-비교">3. 왜 기본이 서버 컴포넌트일까? (비교)</h3>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left">일반 리액트 (CSR)</th>
<th align="left">Next.js 서버 컴포넌트 (RSC)</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>초기 HTML</strong></td>
<td align="left">빈 껍데기 (<code>div id=&quot;root&quot;</code>)</td>
<td align="left"><strong>모든 컨텐츠가 채워진 HTML</strong></td>
</tr>
<tr>
<td align="left"><strong>JS 로드량</strong></td>
<td align="left">전체 로직 다운로드 (무거움)</td>
<td align="left">필요한 최소 코드만 전송 (가벼움)</td>
</tr>
<tr>
<td align="left"><strong>SEO</strong></td>
<td align="left">불리함</td>
<td align="left"><strong>매우 유리함</strong></td>
</tr>
</tbody></table>
<hr>
<h3 id="4-실전-예시-imageslideshow-컴포넌트">4. 실전 예시: ImageSlideshow 컴포넌트</h3>
<p>슬라이드쇼처럼 <code>setInterval</code>이나 <code>useState</code>를 써서 실시간으로 화면을 바꿔야 하는 경우, 서버는 브라우저의 미래 상황을 예측할 수 없으므로 반드시 <strong>클라이언트 컴포넌트</strong>로 작성해야 합니다.</p>
<p>```javascript
&#39;use client&#39;; // 이 컴포넌트는 이제 브라우저에서 생동감 있게 움직입니다.</p>
<p>import { useEffect, useState } from &#39;react&#39;;
// ... 5초마다 이미지를 바꾸는 로직 (브라우저 메모리와 타이머 사용)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] Next.js 기초]]></title>
            <link>https://velog.io/@jungo_0/Next.js-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@jungo_0/Next.js-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Wed, 15 Apr 2026 07:15:52 GMT</pubDate>
            <description><![CDATA[<h2 id="nextjs-풀스택-프레임워크의-정의와-서버-컴포넌트의-동작-원리">[Next.js] 풀스택 프레임워크의 정의와 서버 컴포넌트의 동작 원리</h2>
<blockquote>
<p>리액트를 넘어선 표준 프레임워크 Next.js의 핵심인 &#39;보호된 파일명&#39;과 서버 중심의 렌더링 설계를 분석함</p>
</blockquote>
<p><strong>키워드:</strong> Full-stack Framework, File-based Routing, RSC(React Server Components), Prefetching</p>
<hr>
<h3 id="1-nextjs의-정의와-특징">1. Next.js의 정의와 특징</h3>
<p>Next.js는 리액트(React)를 기반으로 하는 <strong>풀스택 프레임워크</strong>입니다. 리액트가 UI 라이브러리로서 기능을 제공한다면, Next.js는 라우팅, 서버 사이드 렌더링, API 구축 등 웹 서비스에 필요한 모든 도구를 종합적으로 제공합니다.</p>
<p>❖ <strong>핵심 특징:</strong></p>
<ul>
<li><strong>파일 기반 라우팅</strong>: 특정 폴더 구조와 파일명만으로 자동으로 URL 경로를 생성함.</li>
<li><strong>서버 컴포넌트 기본값</strong>: 모든 컴포넌트가 별도 설정 없이도 서버에서 실행되는 구조를 가짐.</li>
<li><strong>성능 자동 최적화</strong>: 이미지, 폰트 최적화 및 코드 분할(Code Splitting)을 자동으로 처리함.</li>
</ul>
<hr>
<h3 id="2-보호된-파일명-pagejs">2. 보호된 파일명: <code>page.js</code></h3>
<p>Next.js 프로젝트에서는 파일명이 곧 기능이 되는 <strong>약속된 규칙</strong>이 존재합니다. 그중 가장 핵심이 되는 것이 <code>page.js</code>입니다.</p>
<ul>
<li><strong>역할</strong>: 해당 경로(URL)에서 실제로 브라우저에 렌더링될 리액트 컴포넌트를 정의함.</li>
<li><strong>규칙</strong>: 반드시 해당 파일 내에서 컴포넌트를 <code>export</code> 해야만 Next.js 엔진이 인지하고 화면을 구성함.</li>
</ul>
<hr>
<h3 id="3-리액트-서버-컴포넌트-react-server-components">3. 리액트 서버 컴포넌트 (React Server Components)</h3>
<p>Next.js 환경의 컴포넌트는 기본적으로 <strong>서버 컴포넌트</strong>로 취급됩니다. 이는 컴포넌트가 브라우저가 아닌 백엔드 서버에서 실행됨을 의미합니다.</p>
<h4 id="-서버-컴포넌트-실행-프로세스"><strong>### 서버 컴포넌트 실행 프로세스</strong></h4>
<blockquote>
<ol>
<li><strong>서버 실행</strong>: 사용자가 페이지를 요청하면 서버에서 컴포넌트 함수를 실행함.</li>
<li><strong>로그 출력</strong>: 컴포넌트 내부의 <code>console.log</code>는 브라우저 콘솔이 아닌 <strong>서버 터미널</strong>에 출력됨.</li>
<li><strong>HTML 전송</strong>: 계산이 끝난 완성된 HTML 구조만 브라우저로 전송함.</li>
<li><strong>클라이언트 부담 감소</strong>: 브라우저가 내려받아야 할 자바스크립트 번들 크기가 획기적으로 줄어듦.</li>
</ol>
</blockquote>
<hr>
<h3 id="4-link-컴포넌트-spa의-부드러움과-seo의-결합">4. Link 컴포넌트: SPA의 부드러움과 SEO의 결합</h3>
<p>Next.js는 초기 방문 시에는 서버에서 완성된 페이지를 보여주고, 이후 이동 시에는 필요한 데이터만 교체하는 하이브리드 방식을 취합니다.</p>
<ul>
<li><strong><code>&lt;a&gt;</code> 태그의 한계</strong>: 전체 페이지를 다시 다운로드하여 새로고침(깜빡임)이 발생하고 SPA의 장점이 사라짐.</li>
<li><strong><code>&lt;Link&gt;</code> 컴포넌트</strong>: 브라우저의 기본 이동 동작을 막고 자바스크립트로 화면 내용만 교체함.</li>
</ul>
<h4 id="-성능의-핵심-프리페칭prefetching"><strong>### 성능의 핵심: 프리페칭(Prefetching)</strong></h4>
<p>Next.js의 <code>&lt;Link&gt;</code>는 단순히 이동만 돕지 않습니다. 링크가 사용자의 화면(뷰포트)에 들어오는 순간, Next.js는 해당 페이지의 데이터를 <strong>미리 로드</strong>해둡니다.</p>
<p><span style="background-color:#5A5AFF">사용자가 링크를 클릭하기도 전에 데이터를 준비해두기 때문에, 클릭하는 순간 즉각적인 전환이 일어나는 압도적인 반응성을 제공합니다.</span></p>
<hr>
<h3 id="요약-및-결론">요약 및 결론</h3>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>서버 컴포넌트</strong></td>
<td align="left">백엔드에서 실행되어 초기 로딩 속도와 보안 향상</td>
</tr>
<tr>
<td align="left"><strong>page.js</strong></td>
<td align="left">경로를 결정짓는 핵심 파일 (보호된 파일명)</td>
</tr>
<tr>
<td align="left"><strong>Link 컴포넌트</strong></td>
<td align="left">새로고침 없는 SPA 이동과 프리페칭을 통한 초고속 전환</td>
</tr>
</tbody></table>
<hr>
<h3 id="결론"><strong>결론</strong></h3>
<p>Next.js를 사용한다는 것은 단순히 리액트를 서버에서 돌리는 것을 넘어, <strong>최적의 사용자 경험을 위한 설계 시스템</strong>을 도입하는 것입니다. 서버 컴포넌트와 Link 컴포넌트의 조화는 현대 웹이 추구하는 속도와 SEO 두 마리 토끼를 모두 잡는 가장 우아한 방법입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 테스팅 심화]]></title>
            <link>https://velog.io/@jungo_0/React-%ED%85%8C%EC%8A%A4%ED%8C%85-%EC%8B%AC%ED%99%94</link>
            <guid>https://velog.io/@jungo_0/React-%ED%85%8C%EC%8A%A4%ED%8C%85-%EC%8B%AC%ED%99%94</guid>
            <pubDate>Tue, 14 Apr 2026 10:58:00 GMT</pubDate>
            <description><![CDATA[<h2 id="리액트-테스팅-완벽-가이드-기초부터-비동기-mocking까지">리액트 테스팅 완벽 가이드: 기초부터 비동기 Mocking까지</h2>
<blockquote>
<p>&quot;사용자가 소프트웨어를 사용하는 방식과 유사하게 테스트하라&quot;는 철학 아래, 컴포넌트의 렌더링부터 복잡한 비동기 로직까지 검증하는 실전 테스팅 기법임</p>
</blockquote>
<p><strong>키워드:</strong> RTL(React Testing Library), AAA 패턴, User Event, Mocking(jest.fn), Async Testing</p>
<hr>
<h3 id="1-리액트-테스팅의-핵심-철학">1. 리액트 테스팅의 핵심 철학</h3>
<p>리액트 테스팅 라이브러리(RTL)의 최우선 가치는 내부 구현(State, Props 명칭 등)이 아닌 <strong>사용자의 관점</strong>을 검증하는 것입니다. </p>
<p>❖ <strong>핵심 원칙:</strong></p>
<ul>
<li>내부 상태값이 무엇인지보다 <strong>화면에 무엇이 보이고, 어떤 상호작용이 일어나는지</strong>가 중요함.</li>
<li>코드를 리팩토링해도 사용자 경험이 같다면 테스트는 통과해야 함.</li>
</ul>
<hr>
<h3 id="2-테스트의-기본-구조-aaa-패턴">2. 테스트의 기본 구조: AAA 패턴</h3>
<p>모든 테스트 코드는 가독성과 유지보수를 위해 3단계 구조를 따릅니다.</p>
<ol>
<li><strong>Arrange (준비)</strong>: 테스트할 컴포넌트를 <code>render()</code>로 가상 DOM에 띄움.</li>
<li><strong>Act (실행)</strong>: 클릭, 타이핑 등 사용자의 행동을 수행 (<code>userEvent</code> 활용).</li>
<li><strong>Assert (단언)</strong>: 결과가 예상과 일치하는지 <code>expect()</code>로 확인.</li>
</ol>
<hr>
<h3 id="3-단계별-실습-및-메서드-분석">3. 단계별 실습 및 메서드 분석</h3>
<h4 id="①-기초-정적-컴포넌트-테스트-getbytext"><strong>① 기초: 정적 컴포넌트 테스트 (<code>getByText</code>)</strong></h4>
<p>가장 기본적인 텍스트 렌더링 확인 단계입니다. <code>describe</code>로 관련 테스트를 그룹화합니다.</p>
<pre><code class="language-javascript">describe(&#39;Greeting component&#39;, () =&gt; {
  test(&#39;renders &quot;Hello World&quot; as a text&#39;, () =&gt; {
    render(&lt;Greeting /&gt;); // Arrange
    const helloWorldElement = screen.getByText(&#39;Hello World!&#39;); // Act &amp; Assert
    expect(helloWorldElement).toBeInTheDocument();
  });
});
</code></pre>
<h4 id="②-중급-이벤트와-상태-변경-getbyrole--userevent"><strong>② 중급: 이벤트와 상태 변경 (<code>getByRole</code> &amp; <code>userEvent</code>)</strong></h4>
<p>버튼 클릭 시 UI가 변하는지 테스트합니다. <code>userEvent</code>는 실제 브라우저의 상호작용(포커스, 클릭 등)을 더 정밀하게 흉내 냅니다.</p>
<pre><code class="language-javascript">test(&#39;버튼 클릭 시 &quot;Changed!&quot; 문구가 나타나는지 확인&#39;, () =&gt; {
  render(&lt;Greeting /&gt;);

  // 1. 역할(Role)로 버튼 찾기
  const buttonElement = screen.getByRole(&#39;button&#39;);

  // 2. 사용자의 실제 클릭 동작 수행
  userEvent.click(buttonElement); 

  // 3. 변경된 UI 검증
  const outputElement = screen.getByText(&#39;Changed!&#39;);
  expect(outputElement).toBeInTheDocument();
});</code></pre>
<h4 id="③-고급-요소의-부재-확인-queryby"><strong>③ 고급: 요소의 부재 확인 (<code>queryBy</code>)</strong></h4>
<p>특정 요소가 <strong>화면에 없어야 함</strong>을 검증할 때는 <code>getBy</code>를 쓰면 안 됩니다. 요소를 찾지 못하는 순간 에러를 던져 테스트가 중단되기 때문입니다.</p>
<ul>
<li><strong><code>queryByText</code></strong>: 요소를 찾지 못하면 에러 대신 <code>null</code>을 반환합니다.</li>
<li><strong>활용</strong>: <code>expect(...).toBeNull()</code>과 함께 사용하여 특정 UI가 사라졌음을 안전하게 확인합니다.</li>
</ul>
<hr>
<h4 id="④-심화-비동기-데이터와-모킹-mocking"><strong>④ 심화: 비동기 데이터와 모킹 (<code>Mocking</code>)</strong></h4>
<p>API 호출처럼 시간이 걸리는 작업은 실제 네트워크를 타지 않도록 <code>jest.fn()</code>으로 가짜 함수를 만들어 대체(Mocking)합니다.</p>
<pre><code class="language-js">test(&#39;성공 시 포스트 목록을 렌더링함&#39;, async () =&gt; {
  // 1. Mocking: 실제 fetch를 가짜 함수로 대체하여 외부 의존성 제거
  window.fetch = jest.fn();
  window.fetch.mockResolvedValueOnce({
    json: async () =&gt; [{ id: &#39;p1&#39;, title: &#39;First post&#39; }],
  });

  render(&lt;Async /&gt;);

  // 2. 비동기 대기: findAllByRole
  // find로 시작하는 메서드는 요소가 나타날 때까지 기다리며 Promise를 반환함
  const listItemElements = await screen.findAllByRole(&#39;listitem&#39;);

  // 3. 결과 검증
  expect(listItemElements).not.toHaveLength(0);
});
</code></pre>
<hr>
<h3 id="-테스팅-메서드-선정-가이드"><strong>### 테스팅 메서드 선정 가이드</strong></h3>
<p>상황에 맞는 쿼리(Query)를 선택하는 것이 테스트의 견고함을 결정합니다. 단순히 요소를 찾는 것을 넘어, 테스트의 의도에 맞는 접두사를 사용하는 것이 중요합니다.</p>
<table>
<thead>
<tr>
<th align="left">메서드 접두사</th>
<th align="left">특징</th>
<th align="left">용도</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>getBy...</strong></td>
<td align="left">요소를 즉시 찾음. 없으면 즉시 에러 발생</td>
<td align="left">일반적인 요소 존재 확인 (가장 기본적이고 많이 쓰임)</td>
</tr>
<tr>
<td align="left"><strong>queryBy...</strong></td>
<td align="left">요소를 찾지 못하면 에러 대신 <code>null</code> 반환</td>
<td align="left">특정 요소가 <strong>존재하지 않음</strong>을 안전하게 확인할 때</td>
</tr>
<tr>
<td align="left"><strong>findBy...</strong></td>
<td align="left">요소를 찾을 때까지 <strong>기다림</strong> (Promise 반환)</td>
<td align="left"><strong>비동기</strong> 데이터 로딩 후 화면에 나타나는 요소를 확인할 때</td>
</tr>
</tbody></table>
<hr>
<h4 id="💡-쿼리-선택의-팁"><strong>💡 쿼리 선택의 팁</strong></h4>
<ol>
<li><strong>우선순위</strong>: 가능하면 <code>getByRole</code>을 최우선으로 사용하세요. 이는 시각 장애인이 스크린 리더를 사용하는 방식과 유사하여 접근성까지 함께 테스트할 수 있습니다.</li>
<li><strong>에러 메시지</strong>: <code>getBy</code>는 요소를 찾지 못할 때 전체 DOM 구조를 에러 메시지로 보여주기 때문에 디버깅에 매우 유리합니다.</li>
<li><strong>비동기 처리</strong>: <code>findBy</code>는 기본적으로 1000ms(1초) 동안 요소를 기다립니다. API 응답 속도에 따라 이 시간은 조절 가능합니다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 리액트 테스팅 ]]></title>
            <link>https://velog.io/@jungo_0/React-%ED%85%8C%EC%8A%A4%ED%8C%85</link>
            <guid>https://velog.io/@jungo_0/React-%ED%85%8C%EC%8A%A4%ED%8C%85</guid>
            <pubDate>Tue, 14 Apr 2026 10:24:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jungo_0/post/701539d0-bf04-4f1b-85f8-eb0eef3c0739/image.png" alt=""></p>
<h2 id="리액트-테스팅-기초-코드가-코드를-검증하는-법">리액트 테스팅 기초: 코드가 코드를 검증하는 법</h2>
<blockquote>
<p>수동 테스트의 한계를 극복하고, 앱이 커져도 안정성을 유지할 수 있도록 테스트 코드를 작성하는 자동화 테스팅의 핵심 원리임</p>
</blockquote>
<p><strong>키워드:</strong> Unit/Integration/E2E Test, Jest, React Testing Library(RTL), AAA 패턴</p>
<hr>
<h3 id="1-수동-테스트의-한계와-자동화의-필요성">1. 수동 테스트의 한계와 자동화의 필요성</h3>
<p>우리는 보통 브라우저에서 직접 클릭하며 기능을 확인하는 <strong>수동 테스트</strong>를 진행합니다. 하지만 서비스가 커지면 다음과 같은 치명적인 문제가 발생합니다.</p>
<p>❖ <strong>수동 테스트의 문제점:</strong></p>
<ul>
<li><strong>회귀 버그(Regression Bug)</strong>: 새 기능을 넣었을 때 기존 기능이 망가지는 것을 놓침.</li>
<li><strong>심리적 부담</strong>: &quot;이거 수정하면 다른 데 터지는 거 아냐?&quot;라는 두려움 발생.</li>
<li><strong>비효율성</strong>: 복잡한 앱을 매번 전수 조사하는 것은 불가능함.</li>
</ul>
<p><span style="background-color:#5A5AFF">자동화 테스팅은 수동 테스트를 대신하는 것이 아니라, 코드가 코드를 검사하게 함으로써 개발자의 확신을 더해주는 도구입니다.</span></p>
<hr>
<h3 id="2-테스팅의-3가지-유형">2. 테스팅의 3가지 유형</h3>
<p>자동화 테스트는 검사하는 범위에 따라 세 단계로 구분됩니다.</p>
<table>
<thead>
<tr>
<th align="left">유형</th>
<th align="left">테스트 범위</th>
<th align="left">특징</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>단위 테스트 (Unit)</strong></td>
<td align="left">함수, 개별 컴포넌트</td>
<td align="left">가장 작고 빠르며, 로직의 정확성을 검증함</td>
</tr>
<tr>
<td align="left"><strong>통합 테스트 (Integration)</strong></td>
<td align="left">여러 컴포넌트의 협력</td>
<td align="left">컴포넌트끼리 데이터를 잘 주고받는지 확인함</td>
</tr>
<tr>
<td align="left"><strong>E2E 테스트</strong></td>
<td align="left">앱 전체 흐름 (End-to-End)</td>
<td align="left">실제 사용자처럼 로그인부터 결제까지 시뮬레이션함</td>
</tr>
</tbody></table>
<hr>
<h3 id="3-테스팅-도구의-역할-jest--rtl">3. 테스팅 도구의 역할: Jest &amp; RTL</h3>
<p>리액트 앱은 보통 두 가지 도구의 조합으로 테스트를 진행합니다.</p>
<ul>
<li><strong>Jest (심판)</strong>: 테스트 코드를 실행하고 결과가 성공인지 실패인지 판단하는 테스트 러너입니다.</li>
<li><strong>React Testing Library (시뮬레이터)</strong>: 가상의 브라우저(JSDOM)에 컴포넌트를 렌더링하고 사용자의 행동을 흉내 냅니다.</li>
</ul>
<hr>
<h3 id="4-실전-코드-분석-및-aaa-패턴">4. 실전 코드 분석 및 AAA 패턴</h3>
<p>테스트 코드는 <strong>Arrange(준비), Act(행동), Assert(단언)</strong>라는 3단계 법칙을 따를 때 가장 명확합니다.</p>
<pre><code class="language-js">import { render, screen } from &#39;@testing-library/react&#39;;
import App from &#39;./App&#39;;

test(&#39;리액트 학습 링크가 렌더링되는지 확인&#39;, () =&gt; {
  // 1. Arrange (준비): 테스트할 컴포넌트를 가상 DOM에 띄움
  render(&lt;App /&gt;);

  // 2. Act (행동): 필요한 요소를 찾음 (/i는 대소문자 무시)
  const linkElement = screen.getByText(/learn react/i);

  // 3. Assert (단언): 결과가 맞는지 확인 (문서에 존재하는가?)
  expect(linkElement).toBeInTheDocument();
});
</code></pre>
<h3 id="테스트-실행-흐름-요약">테스트 실행 흐름 요약</h3>
<p>가상 렌더링: render() 함수가 실제 브라우저 없이도 메모리 상에 HTML 구조를 생성함.</p>
<p>요소 탐색: screen.getByText를 통해 사용자가 눈으로 보는 텍스트를 기준으로 요소를 찾음.</p>
<p>검증 실행: expect(...).toBeInTheDocument()가 심판 역할을 하며 테스트 통과 여부를 결정함.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React]데이터 변경 Action과 데이터 관리  ]]></title>
            <link>https://velog.io/@jungo_0/%EB%B0%B1%EC%97%94%EB%93%9C%EB%A1%9C-%EB%B3%B4%EB%82%B4%EA%B8%B0</link>
            <guid>https://velog.io/@jungo_0/%EB%B0%B1%EC%97%94%EB%93%9C%EB%A1%9C-%EB%B3%B4%EB%82%B4%EA%B8%B0</guid>
            <pubDate>Mon, 13 Apr 2026 07:25:38 GMT</pubDate>
            <description><![CDATA[<h2 id="데이터-변경의-핵심-action과-데이터-관리-전략">데이터 변경의 핵심: Action과 데이터 관리 전략</h2>
<blockquote>
<p><code>loader</code>가 데이터를 가져오는(Read) 역할이었다면, <code>action</code>은 데이터를 보내고 조작하는(Create, Update, Delete) 핵심 도구임</p>
</blockquote>
<p><strong>키워드:</strong> <code>action</code>, <code>useLoaderData</code>, <code>useRouteLoaderData</code>, <code>throw json()</code>, <code>useRouteError</code></p>
<hr>
<h3 id="1-loader-화면이-뜨기-전에-데이터를-가져온다">1. loader(): 화면이 뜨기 전에 데이터를 가져온다</h3>
<p>기본 리액트 방식(<code>useEffect</code>)은 컴포넌트가 렌더링된 후에 데이터를 가져오지만, <code>loader</code>는 페이지 이동이 완료되기 전에 데이터를 미리 가져옵니다.</p>
<p>❖ <strong>장점 및 특징:</strong></p>
<ul>
<li><strong>UX 향상</strong>: 빈 화면이나 로딩 스피너를 보는 시간을 줄이고, 완성된 페이지를 바로 보여줍니다.</li>
<li><strong>자동 처리</strong>: <code>loader</code>가 <code>response</code> 객체를 그대로 리턴하면, 리액트 라우터가 내부적으로 <code>.json()</code> 처리를 수행해줍니다.</li>
</ul>
<hr>
<h3 id="2-useloaderdata--userouteloaderdata">2. useLoaderData &amp; useRouteLoaderData</h3>
<p>가져온 데이터를 컴포넌트에서 꺼내 쓰는 방법입니다.</p>
<ul>
<li><strong><code>useLoaderData</code></strong>: 현재 라우트에 설정된 <code>loader</code> 데이터를 직접 가져옵니다.</li>
<li><strong><code>useRouteLoaderData</code></strong>: 부모 라우트의 데이터를 공유받을 때 사용합니다. 라우트 설정에 <code>id</code>를 부여하고, 그 <code>id</code>를 열쇠 삼아 자식 페이지(상세, 수정 등)들이 데이터를 공유합니다.</li>
</ul>
<hr>
<h3 id="3-동적-라우트와-파라미터-params">3. 동적 라우트와 파라미터 (params)</h3>
<p>URL에 <code>:eventId</code>와 같은 변수가 포함된 경우, <code>loader</code> 함수는 자동으로 <code>params</code> 객체를 인자로 받습니다.</p>
<p>❖ <strong>차이점:</strong></p>
<ul>
<li>컴포넌트 외부에서 실행되므로 <code>useParams</code> 훅을 사용할 수 없습니다.</li>
<li>대신 <code>loader({ params })</code> 형태를 활용하여 어떤 데이터를 가져올지 API 주소를 동적으로 생성합니다.</li>
</ul>
<hr>
<h3 id="4-throw-json을-이용한-우아한-에러-처리">4. throw json()을 이용한 우아한 에러 처리</h3>
<p>데이터 로딩 중 404나 500 에러가 발생하면 리액트 라우터의 방식으로 에러를 &quot;던집니다(throw)&quot;.</p>
<ul>
<li><strong><code>json()</code> 유틸리티</strong>: <code>JSON.stringify</code>를 직접 쓸 필요 없이 객체와 상태 코드를 한 번에 던질 수 있게 해줍니다.</li>
<li><strong><code>useRouteError</code></strong>: 던져진 에러는 공통 <code>ErrorPage</code>에서 이 훅으로 낚아채서 상황에 맞는 메시지(404: 페이지 없음, 500: 서버 에러 등)를 보여줍니다.</li>
</ul>
<hr>
<h3 id="5-데이터-재사용을-위한-중첩-라우트-설계">5. 데이터 재사용을 위한 중첩 라우트 설계</h3>
<p>동일한 데이터가 필요한 여러 페이지(상세 보기, 수정 페이지)를 하나의 부모 라우트로 묶어 관리합니다.</p>
<p>❖ <strong>최적화 원리:</strong></p>
<ul>
<li>부모 라우트에 <code>id</code>와 <code>loader</code>를 설정합니다.</li>
<li>하위의 모든 자식들은 추가적인 API 호출 없이 부모의 데이터를 재사용할 수 있어 성능과 유지보수에 매우 유리합니다.</li>
</ul>
<hr>
<h4 id="-action-작동-프로세스-cud"><strong>### Action 작동 프로세스 (CUD)</strong></h4>
<blockquote>
<ol>
<li><strong>양식 제출</strong>: 사용자가 <code>&lt;Form&gt;</code>을 제출하면 브라우저의 기본 동작을 막고 라우터의 <code>action</code>이 가동됨.</li>
<li><strong>데이터 처리</strong>: <code>action</code> 함수가 <code>request</code> 객체에서 폼 데이터를 뽑아내 서버로 <code>POST/PATCH/DELETE</code> 요청을 보냄.</li>
<li><strong>자동 갱신</strong>: 서버 작업이 끝나면 리액트 라우터가 현재 페이지의 <code>loader</code>를 다시 실행하여 화면을 최신 상태로 업데이트함.</li>
<li><strong>이동</strong>: 작업 완료 후 <code>redirect()</code>를 통해 목록 페이지 등으로 사용자를 안내함.</li>
</ol>
</blockquote>
<hr>
<h3 id="요약-및-결론">요약 및 결론</h3>
<p>리액트 라우터의 <code>loader</code>와 <code>action</code>을 활용하면 컴포넌트 내부 로직이 획기적으로 줄어듭니다.</p>
<p><span style="background-color:#5A5AFF">&quot;읽기는 loader, 쓰기는 action&quot;. 이 두 축을 중심으로 라우트를 설계하면 데이터의 흐름이 명확해지고 앱의 반응 속도가 비약적으로 향상됩니다.</span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 리액트 라우터 loader]]></title>
            <link>https://velog.io/@jungo_0/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%9D%BC%EC%9A%B0%ED%84%B0-loader</link>
            <guid>https://velog.io/@jungo_0/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%9D%BC%EC%9A%B0%ED%84%B0-loader</guid>
            <pubDate>Mon, 13 Apr 2026 05:25:58 GMT</pubDate>
            <description><![CDATA[<h2 id="리액트-라우터의-혁신-loader를-활용한-선-로딩-전략">리액트 라우터의 혁신: Loader를 활용한 선 로딩 전략</h2>
<blockquote>
<p>컴포넌트가 렌더링된 후 데이터를 가져오는 기존 방식(Waterfall)을 탈피하여, 페이지 이동과 동시에 데이터를 준비하는 효율적인 데이터 패칭 기법임</p>
</blockquote>
<p><strong>키워드:</strong> <code>createBrowserRouter</code>, <code>loader</code>, <code>useLoaderData</code>, <code>Outlet</code>, <code>NavLink (end)</code></p>
<hr>
<h3 id="중첩-라우팅과-레이아웃-구성">중첩 라우팅과 레이아웃 구성</h3>
<p>리액트 라우터 6.4 버전부터는 객체 기반의 라우터 설정을 통해 복잡한 계층 구조를 직관적으로 관리할 수 있습니다.</p>
<p>❖ <strong>구조적 특징:</strong></p>
<ul>
<li><strong><code>RootLayout</code></strong>: 전체 앱의 공통 뼈대 (네비게이션 등).</li>
<li><strong><code>EventsRootLayout</code></strong>: <code>/events</code> 하위 페이지들의 공통 레이아웃.</li>
<li><strong><code>Outlet</code></strong>: 부모 라우트 컴포넌트 내에서 자식 라우트 컴포넌트가 렌더링될 위치를 지정함.</li>
<li><strong><code>NavLink (end)</code></strong>: 경로가 정확히 일치할 때만 활성 상태(active)를 표시하도록 제한함.</li>
</ul>
<hr>
<h3 id="loader-선-로딩-후-렌더링의-마법">loader: &quot;선 로딩 후 렌더링&quot;의 마법</h3>
<p>기존의 <code>useEffect</code> 방식은 컴포넌트가 화면에 나타난 뒤에야 데이터를 가져왔지만, <code>loader</code>는 페이지에 발을 들이기 전에 미리 장을 봐오는 것과 같습니다.</p>
<table>
<thead>
<tr>
<th align="left">방식</th>
<th align="left">흐름 (Process)</th>
<th align="left">사용자 경험 (UX)</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>기존 (useEffect)</strong></td>
<td align="left">컴포넌트 렌더링 → fetch 실행 → 상태 업데이트 → 재렌더링</td>
<td align="left">빈 화면이나 로딩 스피너를 먼저 보게 됨</td>
</tr>
<tr>
<td align="left"><strong>혁신 (loader)</strong></td>
<td align="left"><strong>데이터 로딩 완료(await)</strong> → 완성된 데이터와 함께 컴포넌트 등장</td>
<td align="left">데이터가 채워진 완성된 페이지를 즉시 보게 됨</td>
</tr>
</tbody></table>
<p><span style="background-color:#5A5AFF"><code>loader</code>는 라우트 설정 단계에서 함수를 연결하여, 해당 경로로 이동하기 직전에 데이터를 미리 가져오는 &#39;사전 준비 요원&#39;입니다.</span></p>
<hr>
<h3 id="실전-코드-분석-loader-적용하기">실전 코드 분석: loader 적용하기</h3>
<pre><code class="language-js">{
  path: &#39;events&#39;,
  element: &lt;EventsRootLayout /&gt;,
  children: [
    {
      index: true,
      element: &lt;EventsPage /&gt;,
      // 페이지 이동 전 실행되는 사전 로딩 함수
      loader: async () =&gt; {
        const response = await fetch(&#39;http://localhost:8080/events&#39;);

        if (!response.ok) {
          // 에러 처리 로직
        } else {
          const resData = await response.json();
          return resData.events; // 리턴된 데이터는 컴포넌트에서 useLoaderData로 사용 가능
        }
      },
    },
    { path: &#39;:eventId&#39;, element: &lt;EventDetailPage /&gt; },
  ],
}</code></pre>
<hr>
<h4 id="데이터-패칭-프로세스의-변화">데이터 패칭 프로세스의 변화</h4>
<blockquote>
<ol>
<li><strong>사용자 클릭</strong>: 사용자가 <code>/events</code> 링크를 클릭함.</li>
<li><strong>Loader 가동</strong>: 리액트 라우터가 해당 경로의 <code>loader</code> 함수를 감지하고 데이터를 가져오기 시작함.</li>
<li><strong>컴포넌트 렌더링</strong>: 데이터 로딩이 끝날 때까지 이동을 잠시 유보했다가, 데이터가 준비되면 <code>EventsPage</code>를 화면에 뿌림.</li>
<li><strong>데이터 사용</strong>: 컴포넌트 내부에서 <code>useLoaderData()</code> 훅을 통해 <code>useState</code> 없이도 데이터를 바로 사용함.</li>
</ol>
</blockquote>
<hr>
<h3 id="커스텀-레이아웃과-outlet의-조화">커스텀 레이아웃과 Outlet의 조화</h3>
<p>중첩된 라우트 구조에서 레이아웃 컴포넌트는 UI의 일관성을 유지하는 핵심 역할을 합니다.</p>
<p>❖ <strong>레이아웃 관리:</strong></p>
<ul>
<li><code>EventsRootLayout</code> 내부에 <code>&lt;EventsNavigation /&gt;</code>을 배치하고 그 아래에 <code>&lt;Outlet /&gt;</code>을 둡니다.</li>
<li>이렇게 하면 <code>/events</code>, <code>/events/new</code>, <code>/events/1</code> 등 어떤 하위 페이지로 가더라도 서브 네비게이션은 고정된 채 알맹이(<code>Outlet</code>)만 바뀌게 됩니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React]리듀서와 비동기의 분리 Redux ,  비동기]]></title>
            <link>https://velog.io/@jungo_0/React%EB%A6%AC%EB%93%80%EC%84%9C%EC%99%80-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%9D%98-%EB%B6%84%EB%A6%AC-Redux-%EB%B9%84%EB%8F%99%EA%B8%B0</link>
            <guid>https://velog.io/@jungo_0/React%EB%A6%AC%EB%93%80%EC%84%9C%EC%99%80-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%9D%98-%EB%B6%84%EB%A6%AC-Redux-%EB%B9%84%EB%8F%99%EA%B8%B0</guid>
            <pubDate>Sun, 12 Apr 2026 12:20:03 GMT</pubDate>
            <description><![CDATA[<h2 id="리덕스-설계의-황금률---리듀서와-비동기의-분리">리덕스 설계의 황금률:   리듀서와 비동기의 분리</h2>
<blockquote>
<p>리듀서 내부의 금지된 규칙을 이해하고, &quot;멍청한 백엔드&quot;를 보완하는 프론트엔드 설계 전략을 마스터하는 단계임</p>
</blockquote>
<p><strong>키워드:</strong> Reducer Purity(리듀서의 순수성), Side Effects(부수 효과), Fat Reducer vs Lean Component, Thunk(썽크)</p>
<hr>
<h3 id="리듀서-안에서-fetch를-쓰면-안-되는-이유">리듀서 안에서 fetch()를 쓰면 안 되는 이유</h3>
<p>리덕스의 리듀서는 반드시 <strong>순수 함수(Pure Function)</strong>여야 합니다. 즉, 외부 세계를 건드리지 않고 입력값에 대해서만 결과를 내놓아야 합니다.</p>
<p>❖ <strong>설계 원칙 위반:</strong></p>
<ul>
<li><strong>예측 가능성 파괴</strong>: 비동기 요청은 언제 끝날지 알 수 없습니다. 리덕스는 현재 상태를 즉각적으로 파악해야 하는데, 비동기가 끼어들면 상태 추적이 불가능해집니다.</li>
<li><strong>순수성 위반</strong>: 서버 데이터를 수정하는 행위는 &#39;부수 효과&#39;입니다. 리듀서는 오직 &#39;상태 계산&#39;만 해야 하며 외부 환경(서버)을 바꿔서는 안 됩니다.</li>
</ul>
<p><span style="background-color:#5A5AFF">리듀서는 &quot;입력(현재 상태+액션) -&gt; 출력(새 상태)&quot;이 즉각적이고 명확한 &#39;계산기&#39; 역할만 수행해야 합니다.</span></p>
<hr>
<h3 id="전략적-선택-멍청한-백엔드-대응법">전략적 선택: &quot;멍청한 백엔드&quot; 대응법</h3>
<p>파이어베이스와 같이 단순히 저장만 하는 백엔드를 사용할 때는 프론트엔드가 더 똑똑해져야 합니다.</p>
<table>
<thead>
<tr>
<th align="left">시나리오</th>
<th align="left">백엔드 특징</th>
<th align="left">프론트엔드(리덕스) 역할</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>똑똑한 백엔드</strong></td>
<td align="left">자체 로직(수량 계산 등) 있음</td>
<td align="left">상품 ID만 전송 후 결과값 반영</td>
</tr>
<tr>
<td align="left"><strong>멍청한 백엔드</strong></td>
<td align="left">시키는 대로 저장만 함</td>
<td align="left"><strong>리듀서에서 모든 계산</strong> 후 전체 데이터 전송</td>
</tr>
</tbody></table>
<h4 id="-데이터-동기화-프로세스"><strong>### 데이터 동기화 프로세스</strong></h4>
<blockquote>
<ol>
<li><strong>리덕스 선제 업데이트</strong>: 리듀서에서 복잡한 계산(수량, 합계)을 완벽하게 끝냄</li>
<li><strong>상태 감지 (App.js)</strong>: <code>useSelector</code>로 바뀐 상태를 감지함</li>
<li><strong>서버 복사 (PUT)</strong>: <code>useEffect</code>를 통해 최신 상태를 서버에 통째로 덮어쓰기(PUT)하여 동기화함</li>
</ol>
</blockquote>
<hr>
<h3 id="해결책-썽크thunk와-액션-생성자">해결책: 썽크(Thunk)와 액션 생성자</h3>
<p>리듀서에 넣을 수 없는 비동기 로직은 <strong>리덕스 썽크(Thunk)</strong>를 통해 우아하게 해결할 수 있습니다.</p>
<p>❖ <strong>로직의 거처 선정:</strong></p>
<ul>
<li><strong>리듀서(Reducer)</strong>: &quot;데이터 계산의 중심&quot;. 절대 비동기 금지.</li>
<li><strong>썽크(Thunk)</strong>: &quot;비즈니스 로직의 해결사&quot;. HTTP 요청, 에러 처리 등 &#39;지저분한&#39; 일을 전담.</li>
<li><strong>컴포넌트(Component)</strong>: &quot;화면의 전달자&quot;. 복잡한 건 몰라도 됨. 오직 <code>dispatch</code>만 수행 (Lean Component).</li>
</ul>
<pre><code class="language-js">// App.js에서의 자동 동기화 예시
useEffect(() =&gt; {
  if (isInitial) { // 초기 렌더링 시 전송 방지
    isInitial = false;
    return;
  }

  // 리덕스 상태(cart)가 바뀔 때마다 서버에 PUT 요청
  sendCartData(cart); 
}, [cart]);</code></pre>
<h4 id="리덕스-데브툴devtools-활용">리덕스 데브툴(DevTools) 활용</h4>
<p>단순한 디버깅을 넘어, 애플리케이션의 흐름을 지배하는 도구입니다.</p>
<p>❖ 핵심 기능:</p>
<ul>
<li><p>상태 추적: 액션 전후의 상태 변화를 한눈에 비교.</p>
</li>
<li><p>시간 여행(Time Travel): 과거의 특정 액션 시점으로 상태를 되돌려 버그 재현.</p>
</li>
<li><p><span style='color:yellowgreen'>복잡한 상태 변화를 시각화하여 유지보수 시간을 획기적으로 단축함.</span></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Map Set]]></title>
            <link>https://velog.io/@jungo_0/Map-Set</link>
            <guid>https://velog.io/@jungo_0/Map-Set</guid>
            <pubDate>Thu, 12 Feb 2026 08:04:54 GMT</pubDate>
            <description><![CDATA[<h2 id="map--set-완벽-정리">Map &amp; Set 완벽 정리</h2>
<blockquote>
<p>배열(<code>Array</code>)만 쓰다가 특정 상황에서 막힐 때가 있는데, 그때 꺼내 쓰는 게 <code>Map</code>이랑 <code>Set</code>임. 중복 제거랑 탐색 속도 면에서 효율이 압도적이라 정리</p>
</blockquote>
<h3 id="1-set-세트">1. Set (세트)</h3>
<p><code>Set</code>은 <strong>중복을 허용하지 않는</strong> 데이터 집합임. 값만 저장하는데, 똑같은 걸 두 번 넣어도 하나만 남음.</p>
<h4 id="특징">특징</h4>
<ul>
<li>중복된 값은 알아서 걸러짐.</li>
<li>특정 값이 있는지 확인할 때 <code>has()</code>를 쓰는데, 배열의 <code>includes()</code>보다 훨씬 빠름 ($O(1)$).</li>
</ul>
<pre><code class="language-javascript">const mySet = new Set();

mySet.add(1);
mySet.add(2);
mySet.add(2); // 중복이라 안 들어감

console.log(mySet.has(1)); // true
console.log(mySet.has(3)); // false
console.log(mySet.size);   // 2 (개수 확인)

// ✅ 꿀팁: 배열 중복 제거 한 줄 컷
const arr = [1, 2, 2, 3, 3];
const uniqueArr = [...new Set(arr)]; // [1, 2, 3]

2. Map (맵)Map은 Key(키) - Value(값) 쌍으로 저장하는 구조임. 객체({})랑 비슷하지만 코테에선 Map이 훨씬 편함.특징키값으로 숫자, 객체 등 아무거나 다 쓸 수 있음.size로 데이터가 몇 개인지 바로 확인 가능.데이터가 들어온 순서가 유지됨.JavaScriptconst myMap = new Map();

myMap.set(&#39;바키&#39;, &#39;격투가&#39;);
myMap.set(&#39;죠죠&#39;, &#39;스탠드사&#39;);
myMap.set(123, &#39;숫자 키 가능&#39;);

console.log(myMap.get(&#39;바키&#39;)); // &#39;격투가&#39;
console.log(myMap.has(&#39;죠죠&#39;)); // true (키 있는지 확인)
console.log(myMap.size);        // 3
</code></pre>
<p>구분,Array (배열),Set / Map
속도,includes() 쓰면 O(N) (느림),has() 쓰면 O(1) (개빠름)
중복,상관없음 (다 들어감),중복 불가 (특히 Set)
용도,순서대로 저장하거나 인덱스 접근할 때,빠른 탐색이나 빈도수 셀 때</p>
<p>실전 예시: 숫자 개수 세기
배열에 숫자가 각각 몇 개씩 있는지 셀 때 Map 쓰면 깔끔함.</p>
<pre><code class="language-js">const arr = [1, 1, 2, 3, 1, 2];
const counts = new Map();

arr.forEach(num =&gt; {
  // 있으면 가져와서 +1, 없으면 0에서 +1
  counts.set(num, (counts.get(num) || 0) + 1);
});

console.log(counts.get(1)); // 3</code></pre>
<p>✅ 정리: 단순히 들어있는지 체크만 할 거면 Set, 이름표 붙여서 관리하거나 개수 세야 하면 Map 쓰면 됨. 성능 차이 때문에 큰 데이터 다룰 땐 필수임.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React]Redux와 불변성(Immutability)]]></title>
            <link>https://velog.io/@jungo_0/Redux%EC%99%80-%EB%B6%88%EB%B3%80%EC%84%B1Immutability</link>
            <guid>https://velog.io/@jungo_0/Redux%EC%99%80-%EB%B6%88%EB%B3%80%EC%84%B1Immutability</guid>
            <pubDate>Mon, 26 Jan 2026 06:52:38 GMT</pubDate>
            <description><![CDATA[<h2 id="커스텀-훅custom-hooks으로-로직-분리하기">커스텀 훅(Custom Hooks)으로 로직 분리하기</h2>
<blockquote>
<p>복잡한 비동기 통신이나 상태 관리 로직을 하나의 함수(Hook)로 묶어 여러 컴포넌트에서 재사용할 수 있게 만드는 고급 기법임</p>
</blockquote>
<p>리액트의 내장 훅(<code>useState</code>, <code>useEffect</code> 등)을 조합하여 나만의 도구를 만드는 과정이다.</p>
<hr>
<h3 id="커스텀-훅의-핵심-규칙">커스텀 훅의 핵심 규칙</h3>
<p>커스텀 훅을 만들 때는 리액트가 이를 일반 함수와 구분할 수 있도록 약속된 형식을 지켜야 한다.</p>
<p>❖ <strong>작성 가이드:</strong></p>
<ul>
<li><strong><code>use</code> 접두사</strong>: 반드시 함수 이름은 <code>use</code>로 시작해야 함 (예: <code>useFetch</code>, <code>useInput</code>)</li>
<li><strong>상태의 독립성</strong>: 같은 훅을 여러 컴포넌트에서 사용해도 각각의 컴포넌트는 자신만의 독립된 상태를 가짐</li>
<li><strong>로직 공유</strong>: UI가 아닌 <strong>동작(Logic)</strong>을 공유하는 것이 목적임</li>
</ul>
<p><span style="background-color:#5A5AFF">커스텀 훅은 컴포넌트를 가볍게 만들고, 코드의 중복을 획기적으로 줄여주는 &quot;로직 주머니&quot;와 같다.</span></p>
<hr>
<h3 id="실전-예시-usefetch-커스텀-훅">실전 예시: useFetch 커스텀 훅</h3>
<p>데이터를 가져오는 공통 로직을 <code>useFetch.js</code> 파일로 분리한 모습이다.</p>
<pre><code class="language-js">import { useEffect, useState } from &#39;react&#39;;

export function useFetch(fetchFn, initialValue) {
  const [isFetching, setIsFetching] = useState(false);
  const [error, setError] = useState();
  const [fetchedData, setFetchedData] = useState(initialValue);

  useEffect(() =&gt; {
    async function fetchData() {
      setIsFetching(true);
      try {
        const data = await fetchFn();
        setFetchedData(data);
      } catch (error) {
        setError({ message: error.message || &#39;데이터를 불러오지 못했습니다.&#39; });
      }
      setIsFetching(false);
    }
    fetchData();
  }, [fetchFn]);

  // 컴포넌트에서 필요한 값들을 객체 형태로 반환
  return { isFetching, fetchedData, setFetchedData, error };
}</code></pre>
<h4 id="커스텀-훅-작동-프로세스">커스텀 훅 작동 프로세스</h4>
<blockquote>
<ol>
<li><strong>매개변수 전달</strong>: 컴포넌트에서 실행할 비동기 함수(<code>fetchFn</code>)와 초기값(<code>initialValue</code>)을 훅에 넘겨줌</li>
<li><strong>내부 상태 관리</strong>: 훅 내부에서 로딩, 에러, 데이터 상태를 독립적으로 관리함</li>
<li><strong>자동 실행</strong>: <code>useEffect</code>를 통해 컴포넌트가 마운트될 때 데이터를 자동으로 가져옴</li>
<li><strong>UI 연결</strong>: 컴포넌트는 훅이 리턴한 값들을 구조 분해 할당으로 받아 화면을 그리는 데만 집중함</li>
</ol>
</blockquote>
<hr>
<h3 id="커스텀-훅의-이점-관심사의-분리">커스텀 훅의 이점: 관심사의 분리</h3>
<p>컴포넌트 파일 안에 수십 줄의 <code>useEffect</code>와 <code>useState</code>가 섞여 있으면 가독성이 떨어진다.</p>
<p>❖ <strong>최적화 효과:</strong></p>
<ul>
<li><strong>가독성 향상</strong>: 컴포넌트는 &quot;어떤 UI를 보여줄지&quot;에만 집중하고, 훅은 &quot;어떻게 데이터를 가져올지&quot;만 고민함</li>
<li><strong>유지보수 용이</strong>: 데이터 패칭 로직에 수정이 필요할 때, 여러 파일을 고칠 필요 없이 커스텀 훅 파일 하나만 수정하면 됨</li>
<li><span style='color:yellowgreen'>함수 이름(<code>useFetch</code>)만 봐도 이 로직이 무엇을 하는지 한눈에 파악할 수 있음</span></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 효율적인 폼(Form) 관리: FormData API와 preventDefault]]></title>
            <link>https://velog.io/@jungo_0/React-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%ED%8F%BCForm-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@jungo_0/React-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-%ED%8F%BCForm-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Sun, 19 Oct 2025 05:34:13 GMT</pubDate>
            <description><![CDATA[<h2 id="데이터-삭제와-낙관적-업데이트-optimistic-updates">데이터 삭제와 낙관적 업데이트 (Optimistic Updates)</h2>
<blockquote>
<p>사용자가 삭제 버튼을 눌렀을 때, 서버의 응답을 기다리지 않고 화면에서 먼저 지워버려 앱이 즉각적으로 반응하게 만드는 기법임</p>
</blockquote>
<p>비동기 통신의 지연 시간(Latency)을 사용자 경험(UX)으로 극복하는 고급 기술이다.</p>
<hr>
<h3 id="삭제-로직의-3단계-흐름">삭제 로직의 3단계 흐름</h3>
<p>데이터 삭제는 단순히 명령을 보내는 것이 아니라, UI와 서버 데이터를 동기화하는 섬세한 과정이 필요하다.</p>
<p>❖ <strong>구현 프로세스:</strong></p>
<ol>
<li><strong>화면 선제 업데이트</strong>: <code>setUserPlaces</code>를 호출하여 클릭한 항목을 리스트에서 즉시 제거한다.</li>
<li><strong>서버 통신 수행</strong>: <code>fetch</code> 요청을 통해 서버에 삭제 명령을 보낸다.</li>
<li><strong>에러 핸들링 (복구)</strong>: 만약 서버 삭제가 실패하면, 화면에서 지웠던 데이터를 다시 복구시킨다.</li>
</ol>
<p><span style="background-color:#5A5AFF">사용자는 &quot;내 명령이 즉시 실행되었다&quot;고 느끼며, 앱이 매우 빠르다는 인상을 받게 된다.</span></p>
<hr>
<h3 id="실전-코드-분석">실전 코드 분석</h3>
<pre><code class="language-js">const handleRemovePlace = useCallback(async function handleRemovePlace() {
  // 1. 낙관적 업데이트: UI에서 먼저 제거
  setUserPlaces((prevPickedPlaces) =&gt;
    prevPickedPlaces.filter((place) =&gt; place.id !== selectedPlace.current.id)
  );

  try {
    // 2. 서버에 실제 삭제 요청
    await updateUserPlaces(
      userPlaces.filter((place) =&gt; place.id !== selectedPlace.current.id)
    );
  } catch (error) {
    // 3. 실패 시 복구 로직: 이전 상태로 되돌림
    setUserPlaces(userPlaces);
    setErrorUpdatingPlaces({
      message: error.message || &#39;삭제에 실패했습니다.&#39;,
    });
  }
}, [userPlaces]);</code></pre>
<hr>
<h4 id="-삭제-및-복구-프로세스"><strong>### 삭제 및 복구 프로세스</strong></h4>
<blockquote>
<ol>
<li><strong>배열 필터링</strong>: <code>filter</code> 메서드를 사용하여 선택된 ID를 제외한 새로운 배열을 생성하고 상태를 업데이트함</li>
<li><strong>비동기 요청</strong>: <code>await</code>를 사용하여 서버의 응답을 기다리지만, 사용자는 이미 지워진 화면을 보고 있음</li>
<li><strong>롤백(Rollback)</strong>: 네트워크 단절 등으로 서버 작업이 실패하면 <code>catch</code> 문에서 기존 <code>userPlaces</code>를 다시 세팅하여 항목을 화면에 다시 나타나게 함</li>
</ol>
</blockquote>
<hr>
<h3 id="-왜-usecallback으로-감싸나"><strong>### 왜 useCallback으로 감싸나</strong></h3>
<p>이 삭제 함수는 보통 자식 컴포넌트(모달이나 버튼)로 전달되는 경우가 많다.</p>
<p>❖ <strong>최적화 이유:</strong></p>
<ul>
<li><strong>함수 주소 고정</strong>: <code>useCallback</code>을 쓰지 않으면 부모가 리렌더링될 때마다 삭제 함수가 새로 만들어진다.</li>
<li><strong>불필요한 자식 렌더링 방지</strong>: 함수 주소가 바뀌면 이 함수를 전달받은 자식 컴포넌트(<code>memo</code> 처리된 경우)까지 다시 그려지게 되므로, 이를 방지하기 위해 주소를 박제한다.</li>
<li><span style='color:yellowgreen'>의존성 배열에 <code>userPlaces</code>를 넣어 최신 데이터 상태를 유지하도록 설계한다.</span></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 가상 DOM과 렌더링 원리]]></title>
            <link>https://velog.io/@jungo_0/%EA%B0%80%EC%83%81-DOM%EA%B3%BC-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@jungo_0/%EA%B0%80%EC%83%81-DOM%EA%B3%BC-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Fri, 19 Sep 2025 13:48:35 GMT</pubDate>
            <description><![CDATA[<h2 id="가상-dom과-렌더링-원리">가상 DOM과 렌더링 원리</h2>
<blockquote>
<p>리액트가 왜 빠른지, 그리고 우리가 개발자 도구에서 목격하는 &quot;깜빡임&quot;의 정체가 무엇인지 파헤쳐 봅니다.</p>
</blockquote>
<hr>
<h3 id="1-초기-렌더링-the-initial-render">1. 초기 렌더링 (The Initial Render)</h3>
<p>처음 웹사이트에 접속하면 리액트는 전체 컴포넌트 트리를 훑으며 <strong>가상 DOM 스냅샷</strong>을 찍습니다. 그리고 이 설계도대로 실제 빈 HTML의 <code>&lt;div&gt;</code> 안에 내용을 꽉 채워 넣습니다.</p>
<p>❖ <strong>초기 단계:</strong></p>
<ul>
<li><strong>실제 DOM</strong>: 텅 비어 있음 (<code>&lt;div id=&quot;root&quot;&gt;&lt;/div&gt;</code>)</li>
<li><strong>가상 DOM</strong>: 컴포넌트 트리 전체 구조를 메모리에 생성</li>
<li><strong>결과</strong>: 가상 DOM 전체가 실제 DOM으로 뿅! 하고 나타남</li>
</ul>
<hr>
<h3 id="2-컴포넌트-재실행과-가상-dom의-비교">2. 컴포넌트 재실행과 가상 DOM의 비교</h3>
<p>사용자가 버튼을 클릭해서 <code>counter</code> 값이 바뀌면 리액트가 다시 움직이기 시작합니다. 단순히 화면을 바꾸는 게 아니라 내부적으로 정교한 비교 과정을 거칩니다.</p>
<p>❖ <strong>비교 프로세스:</strong></p>
<ul>
<li><strong>새 스냅샷 생성</strong>: 상태가 변한 컴포넌트를 다시 실행해서 새로운 가상 DOM 스냅샷을 생성</li>
<li><strong>Diffing (비교)</strong>: 리액트가 <strong>&quot;이전 스냅샷&quot;</strong>과 <strong>&quot;새 스냅샷&quot;</strong>을 1:1로 비교 (마치 &#39;틀린 그림 찾기&#39;와 같음)</li>
<li><strong>최소 변경 사항 도출</strong>: &quot;다른 건 그대로인데 <code>&lt;span&gt;</code> 안의 숫자만 5에서 6으로 바뀌었네?&quot;라고 딱 그 부분만 찾아냄</li>
</ul>
<hr>
<h3 id="3-실제-dom-업데이트-reconciliation">3. 실제 DOM 업데이트 (Reconciliation)</h3>
<p>리액트가 영리한 이유는 바로 이 지점입니다. 전체 페이지를 다시 그리는 대신, 찾아낸 그 <strong>&#39;차이점&#39;</strong>만 실제 DOM에 적용합니다.</p>
<ul>
<li><strong>비효율적 방식</strong>: 전체 HTML을 싹 지우고 새로 쓰기 (브라우저 부하 가중)</li>
<li><strong>리액트 방식</strong>: 딱 <code>&lt;span&gt;</code> 요소의 텍스트 노드만 교체 (사용자가 느끼지 못할 정도로 빠름)</li>
</ul>
<p><span style="background-color:#5A5AFF">개발자 도구에서 <strong>&quot;해당 요소만 깜빡이는 현상&quot;</strong>이 바로 리액트가 최소한의 일만 하고 있다는 증거입니다!</span></p>
<hr>
<h3 id="4-왜-실제-dom을-직접-안-쓰나요">4. 왜 실제 DOM을 직접 안 쓰나요?</h3>
<p>실제 DOM은 브라우저 화면을 그리는 복잡한 정보(스타일, 레이아웃 등)를 다 가지고 있어서, 하나만 건드려도 브라우저가 계산을 다시 해야 합니다.</p>
<p>❖ <strong>가상 DOM의 장점:</strong></p>
<ul>
<li><strong>속도</strong>: 자바스크립트 객체일 뿐이라 메모리 안에서 수만 번 비교해도 엄청나게 빠름</li>
<li><strong>전략</strong>: &quot;메모리에서 신나게 비교하고, <strong>결론만 실제 DOM에 딱 한 번</strong> 알려주는&quot; 효율적인 방식</li>
</ul>
<hr>
<h3 id="💡-요약-정리"><strong>💡 요약 정리</strong></h3>
<table>
<thead>
<tr>
<th align="left">단계</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>함수 실행 $\neq$ 업데이트</strong></td>
<td align="left">함수가 실행된다고 해서 무조건 화면이 바뀌는 건 아님</td>
</tr>
<tr>
<td align="left"><strong>가상 DOM</strong></td>
<td align="left">메모리에 존재하는 가벼운 복사본 설계도</td>
</tr>
<tr>
<td align="left"><strong>Diffing</strong></td>
<td align="left">이전 설계도와 새 설계도의 차이점 찾기</td>
</tr>
<tr>
<td align="left"><strong>업데이트</strong></td>
<td align="left">바뀐 부분만 실제 DOM에 &#39;수술&#39;하듯 집어넣기</td>
</tr>
</tbody></table>
<hr>
<h3 id="결론"><strong>결론</strong></h3>
<p>리액트는 단순히 UI를 그리는 라이브러리가 아니라, <strong>최소한의 변경으로 최적의 경험을 제공하는 전략가</strong>입니다. 가상 DOM이라는 완충 지대를 두어 브라우저의 부담을 덜어주는 것이 그 핵심입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] JSON]]></title>
            <link>https://velog.io/@jungo_0/JavaScript-JSON</link>
            <guid>https://velog.io/@jungo_0/JavaScript-JSON</guid>
            <pubDate>Thu, 28 Aug 2025 06:16:49 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jungo_0/post/e4443ae8-4b12-422c-a394-6ef2c8ed17b5/image.png" alt=""></p>
<h3 id="json-기본-이해-jsonstringify와-jsonparse-활용-및-json-형식과-문자열의-차이">JSON 기본 이해: JSON.stringify와 JSON.parse 활용 및 JSON 형식과 문자열의 차이</h3>
<h2 id="1-json이란-무엇인가">1. JSON이란 무엇인가</h2>
<blockquote>
<p>JSON은 &quot;JavaScript Object Notation&quot;의 약자입니다. 이는 데이터를 구조화하여 표현하기 위한 개방형 표준 형식이며, 사람이 읽고 쓰기 용이하며 기계가 파싱하고 생성하기 쉬운 형태로 설계되었습니다. 웹 서비스에서 서버와 클라이언트 간의 데이터 교환에 주로 사용됩니다.</p>
</blockquote>
<h4 id="json의-주요-특징">JSON의 주요 특징</h4>
<p><strong>가독성</strong>: key-value 쌍으로 구성되어 데이터의 의미를 쉽게 파악할 수 있습니다.
<strong>경량성</strong>: 다른 데이터 교환 형식(예: XML)에 비해 파일 크기가 작아 네트워크 전송 효율이 높습니다.
<strong>범용성</strong>: 대부분의 프로그래밍 언어에서 JSON을 처리하는 라이브러리 및 기능이 내장되어 있습니다.</p>
<h4 id="json-데이터의-일반적인-구조">JSON 데이터의 일반적인 구조</h4>
<pre><code class="language-js">{
  &quot;department&quot;: &quot;IT 개발&quot;,
  &quot;employeeId&quot;: &quot;mjh&quot;,
  &quot;isActive&quot;: true,
  &quot;skills&quot;: [&quot;JavaScript&quot;, &quot;React&quot;, &quot;Spring&quot;],
  &quot;contact&quot;: {
    &quot;email&quot;: &quot;contact@example.com&quot;,
    &quot;phone&quot;: &quot;010-xxxx-xxxx&quot;
  }
}</code></pre>
<h2 id="2-jsonstringify와-jsonparse의-기능-및-형태">2. JSON.stringify()와 JSON.parse()의 기능 및 형태</h2>
<blockquote>
<p>JSON 데이터는 주로 문자열 형태로 통신되거나 저장됩니다. 자바스크립트에서는 이러한 변환을 위해 JSON.stringify()와 JSON.parse() 두 가지 내장 함수를 제공합니다.</p>
</blockquote>
<p>JSON.stringify(): 자바스크립트 객체를 JSON 문자열로 변환</p>
<ul>
<li>자바스크립트 객체 또는 값을 JSON 형식의 문자열로 직렬화(serialize)합니다. 이 함수는 주로 객체를 웹 서버로 전송하거나 localStorage와 같은 웹 스토리지에 저장하기 전에 사용됩니다.<pre><code class="language-js">// 변환 전: 자바스크립트 객체
const userDataObject = {
name: &quot;김철수&quot;,
age: 30,
isAdmin: false,
roles: [&quot;user&quot;, &quot;guest&quot;]
};
</code></pre>
</li>
</ul>
<p>console.log(&quot;변환 전 타입:&quot;, typeof userDataObject);
console.log(&quot;변환 전 값:&quot;, userDataObject);
/* 출력:
변환 전 타입: object
변환 전 값: { name: &#39;김철수&#39;, age: 30, isAdmin: false, roles: [ &#39;user&#39;, &#39;guest&#39; ] }
*/</p>
<p>// JSON.stringify() 적용: 객체를 JSON 문자열로 변환
const jsonStringifiedData = JSON.stringify(userDataObject);</p>
<p>console.log(&quot;변환 후 타입:&quot;, typeof jsonStringifiedData);
console.log(&quot;변환 후 값:&quot;, jsonStringifiedData);
/* 출력:
변환 후 타입: string
변환 후 값: {&quot;name&quot;:&quot;김철수&quot;,&quot;age&quot;:30,&quot;isAdmin&quot;:false,&quot;roles&quot;:[&quot;user&quot;,&quot;guest&quot;]}
*/</p>
<pre><code>변환 후 jsonStringifiedData 변수는 JavaScript의 object 타입이 아닌 string 타입의 값을 가지게 되며, 모든 키와 문자열 값은 이중 따옴표(&quot;)로 감싸집니다.



 JSON.parse(): JSON 문자열을 자바스크립트 객체로 변환

- J JSON 형식의 문자열을 파싱(parse)하여 자바스크립트 객체로 역직렬화(deserialize)합니다. 이 함수는 주로 웹 서버로부터 JSON 응답을 수신하거나 localStorage에서 JSON 데이터를 읽어올 때 사용됩니다.
```js
// 변환 전: JSON 형식의 문자열 (예: 서버로부터 수신된 데이터)
const receivedJsonString = &#39;{&quot;name&quot;:&quot;박영희&quot;,&quot;age&quot;:25,&quot;isAdmin&quot;:true,&quot;roles&quot;:[&quot;admin&quot;,&quot;editor&quot;]}&#39;;

console.log(&quot;변환 전 타입:&quot;, typeof receivedJsonString);
console.log(&quot;변환 전 값:&quot;, receivedJsonString);
/* 출력:
변환 전 타입: string
변환 전 값: {&quot;name&quot;:&quot;박영희&quot;,&quot;age&quot;:25,&quot;isAdmin&quot;:true,&quot;roles&quot;:[&quot;admin&quot;,&quot;editor&quot;]}
*/

// JSON.parse() 적용: JSON 문자열을 자바스크립트 객체로 변환
const parsedDataObject = JSON.parse(receivedJsonString);

console.log(&quot;변환 후 타입:&quot;, typeof parsedDataObject);
console.log(&quot;변환 후 값:&quot;, parsedDataObject);
/* 출력:
변환 후 타입: object
변환 후 값: { name: &#39;박영희&#39;, age: 25, isAdmin: true, roles: [ &#39;admin&#39;, &#39;editor&#39; ] }
*/</code></pre><p>변환 후 parsedDataObject 변수는 JavaScript의 object 타입이 되며, 해당 객체의 속성에 직접 접근하여 데이터를 활용할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] 화살표함수 / this]]></title>
            <link>https://velog.io/@jungo_0/JavaScript-%ED%99%94%EC%82%B4%ED%91%9C%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@jungo_0/JavaScript-%ED%99%94%EC%82%B4%ED%91%9C%ED%95%A8%EC%88%98</guid>
            <pubDate>Sun, 15 Dec 2024 09:14:53 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jungo_0/post/e4443ae8-4b12-422c-a394-6ef2c8ed17b5/image.png" alt=""></p>
<p><em>function키워드를 사용해서 만든 함수는 동적인 <code>this</code>때문에 이리저리 바인딩하느라 혼동을 줄 수 있음 화살표 함수를 사용하면 어떤방법으로 호출하던 <code>this</code>의 값이 변하지않음
화살표함수의 <code>this</code>값은 그 함수를 감싸는 스코프의 <code>this</code>를 그대로 가져와서 사용 
전통적인 함수의 <code>this</code>는 함수를 호출하는 방법에 따라 동적으로 바뀜</em></p>
<p><a href="https://velog.io/@jungo_0/df-7jqff55x">this 복습</a></p>
<hr>
<h3 id="일반함수-this">일반함수 this</h3>
<pre><code class="language-js">const object = {
  name:&#39;jungo&#39;,
  main: function () {
    console.log(this);
  },
};
object.main();
// {name:&#39;jungo&#39;,main:f}
</code></pre>
<p>함수를 어떤 객체가 호출했냐에 따라 <code>this</code>가 달라짐
-&gt; object 객체</p>
<ul>
<li>일반함수가 가지는 <code>this</code>값은 함수가 선언된 위치랑은 관련 x</li>
<li>함수가 호출된 방법에 따라 달라짐</li>
</ul>
<h3 id="화살표함수-this">화살표함수 this</h3>
<blockquote>
<p>화살표함수는 자신만의 <code>this</code>를 가지고 있지 않는다.
화살표함수 내부에서 this에 접근하면 외부에서 가져와서 사용</p>
</blockquote>
<pre><code class="language-js">const object = {
  name:&#39;jungo&#39;,
  main: function () {
    console.log(this);
  },
  mainArrow:()=&gt;{
    console.log(this)
  }
};

object.mainArrow();
</code></pre>
<p>결과
<img src="https://velog.velcdn.com/images/jungo_0/post/075bbe5f-7f43-4db9-b223-dea523a789be/image.png" alt="">
window가 this값으로 나온다.
화살표함수는 자신만의 this를 가지지 않기 때문에 자신을 감싸고있는 스코프로 올라가서 this를 찾는다.</p>
<ul>
<li>선언된 위치에서 <code>this</code>가 결정된다. === 함수가 호출된 방법과는 무관하다. </li>
</ul>
<p>따라서</p>
<pre><code class="language-js">const object = {
  name:&#39;jungo&#39;,
  main: function () {
    console.log(this);
  },
  mainArrow:()=&gt;{
    console.log(this.name)    //아무것도 출력 x
  }
};

object.mainArrow();
</code></pre>
<p><span style="background-color:#5A5AFF">화살표함수는 this가 더이상 object가 되지않기 때문에  object속성에 접근할 수 없음</span></p>
<blockquote>
<p>객체의 메서드로는 화살표함수보다 일반함수를 사용하는 것이 적절</p>
</blockquote>
<hr>
<h2 id="예제">예제</h2>
<pre><code class="language-js">const object = {
  name:&#39;jungo&#39;,
  main: function () {
    const innerFunction = function (){
      console.log(this)
    };
    innerFunction()
  },
};

object.main();
</code></pre>
<h4 id="결과---window">결과 - window</h4>
<p><img src="https://velog.velcdn.com/images/jungo_0/post/b1a8953e-ba9f-4343-9fb7-087044dfb9ba/image.png" alt=""></p>
<p>this값이 object가 아닌 window가 출력되는 이유</p>
<ul>
<li>innerFunction은 어떤 객체로부터 직접적으로 호출된게 아님 따라서 innerFunction안에 this는 window</li>
</ul>
<h3 id="수정-예제화살표함수">수정 예제(화살표함수)</h3>
<pre><code class="language-js">const object = {
  name:&#39;jungo&#39;,
  main: function () {
    const innerFunction = () =&gt; {
      console.log(this)
    };
    innerFunction()
  },
};

object.main();
</code></pre>
<h4 id="결과">결과</h4>
<pre><code class="language-js">//object 객체
{name:&#39;jungo&#39;, main:f}</code></pre>
<p>화살표 함수의 this는 감싸는 외부 스코프(main함수)의 this를 그대로 사용 </p>
<ul>
<li>main함수 안에있는 this값은 object객체 </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] 클로저]]></title>
            <link>https://velog.io/@jungo_0/JavaScript-%ED%81%B4%EB%A1%9C%EC%A0%80</link>
            <guid>https://velog.io/@jungo_0/JavaScript-%ED%81%B4%EB%A1%9C%EC%A0%80</guid>
            <pubDate>Mon, 28 Oct 2024 14:34:30 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jungo_0/post/e4443ae8-4b12-422c-a394-6ef2c8ed17b5/image.png" alt=""></p>
<h3 id="렉시컬-스코프">렉시컬 스코프</h3>
<blockquote>
<p>스코프란 변수나 함수의 접근가능한 유효 범위를 의미하며, 렉시컬 스코프(정적 스코프) 규칙을 따름
JavaScript 스코프는 전역/로컬 스코프로 나뉘며 렉시컬 스코프 규칙에 따라 결정된다.</p>
</blockquote>
<p>❖렉시컬 스코프 규칙:변수가 선언된 위치에 따라 접근 가능 범위가 결정</p>
<h2 id="자바스크립트-변수-참조-과정">자바스크립트 변수 참조 과정</h2>
<h4 id="함수가-실행될때마다-메모리상에-실행컨텍스트-생성">함수가 실행될때마다 메모리상에 실행컨텍스트 생성</h4>
<ul>
<li>실행컨텍스트 안에는 렉시컬 환경 존재</li>
<li>여기에 현재 스코프에서 선언한 모든 변수와 함수들이 저장된 환경 레코드 존재</li>
<li>현재 환경에서 찾지 못한 변수 상위 스코프에서 찾기위해 상위 스코프를 가리키는 외부 렉시컬 환경 참조도 갖고있음</li>
</ul>
<h4 id="코드-상에서-변수나-함수를-참조하면">코드 상에서 변수나 함수를 참조하면</h4>
<ol>
<li>현재 환경 레코드에서 찾기</li>
<li>외부 렉시컬환경 참조를 통해 상위 스코프 참조</li>
<li>없을경우 전역 스코프까지 올라가며, 없으면 <code>Reference Error</code></li>
</ol>
<hr>
<p><span style="background-color:#5A5AFF">클로저 개념을 이해해도 언제 어떻게 쓰이는지 모르겠음</span></p>
<pre><code class="language-js">function countWithoutClosure {
  let count = 0;
    return count;
}
console. log(countWithoutClosure()); // &#39;0&#39;
console. log(count); // ReferenceError: count is not defined</code></pre>
<p>렉시컬 스코프 규칙에 따라 함수안에서 선언한 변수는 함수 안에서만 접근 가능</p>
<blockquote>
<ol>
<li>함수가 실행되면 함수 내부 변수(count)는 메모리에 올라감</li>
<li>return -&gt; 메모리 상 제거</li>
<li>함수바깥에서는 변수가 제거된 상태이므로 참조 불가</li>
</ol>
</blockquote>
<h4 id="기본적인-closure-형태">기본적인 closure 형태</h4>
<blockquote>
<p>클로저는 함수가 생성될 때의 스코프(외부 변수)에 접근할 수 있는 성질을 의미
함수 선언문 바깥에서도 함수 스코프를 참조할 수 있음
<span style='color:yellowgreen'>React useState에서 클로저 개념 사용</span></p>
</blockquote>
<pre><code class="language-js">function countWithClosure() {
let count = 0;
return {
  increase: function () {
    count++;
    return count;
    },
  decrease: function () {
    count--；
    return count;
  },
  getCount: function () {
    return count;
  },
 };
}</code></pre>
<p>함수 return문에서 함수 내부의 변수를 사용하는 함수를 다시 return하면 함수 선언문 바깥에서도 스코프에 접근 가능</p>
<pre><code class="language-js">const counter = countWithClosure;
console. log(counter getCount()); // 0
counter. increase();
console. log(counter.getCount()); // 1
counter. decrease();
console. log(counter.getCount)); // 0</code></pre>
<blockquote>
<p>counter 변수: <code>function increase</code>,<code>function decrease</code>,<code>function getCount</code> 클로저에 의해 return될때 함수 실행에 필요한 상위 스코프도 메모리로 가져옴(<code>counter</code>)
--&gt; <code>counter</code>가 클로저에 의해 메모리에 캡쳐되고있음</p>
</blockquote>
<hr>
<h2 id="정리">정리</h2>
<blockquote>
<p>Closre
자바스크립트는 렉시컬 스코프 규칙을 따름
따라서, 일반적인 방식으로는 함수 선언문 밖에서 함수 스코프에 접근할 수 없음 이를 보완하기 위한 방법이 클로저 함수임
클로저 수는 스코프가 상위 함수의 스코프를 참조하는 항수를 return 하는 방식 React의 useState, moment 라이브러리에서 사용하는 방식</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>