<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>a_young1210.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 06 Mar 2026 16:40:02 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>a_young1210.log</title>
            <url>https://velog.velcdn.com/images/a_young1210/profile/50104109-1e77-4612-976b-e7f77edcc1ad/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. a_young1210.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/a_young1210" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[004]]></title>
            <link>https://velog.io/@a_young1210/32</link>
            <guid>https://velog.io/@a_young1210/32</guid>
            <pubDate>Fri, 06 Mar 2026 16:40:02 GMT</pubDate>
            <description><![CDATA[<p>004</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[003]]></title>
            <link>https://velog.io/@a_young1210/31</link>
            <guid>https://velog.io/@a_young1210/31</guid>
            <pubDate>Mon, 19 Jan 2026 06:35:30 GMT</pubDate>
            <description><![CDATA[<p>003</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[002]]></title>
            <link>https://velog.io/@a_young1210/30</link>
            <guid>https://velog.io/@a_young1210/30</guid>
            <pubDate>Sat, 17 Jan 2026 05:57:46 GMT</pubDate>
            <description><![CDATA[<p>002</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[001]]></title>
            <link>https://velog.io/@a_young1210/29</link>
            <guid>https://velog.io/@a_young1210/29</guid>
            <pubDate>Thu, 15 Jan 2026 13:02:44 GMT</pubDate>
            <description><![CDATA[<p>001</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 심화] 반응형 웹]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-%EB%B0%98%EC%9D%91%ED%98%95-%EC%9B%B9</link>
            <guid>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-%EB%B0%98%EC%9D%91%ED%98%95-%EC%9B%B9</guid>
            <pubDate>Mon, 31 Mar 2025 06:58:18 GMT</pubDate>
            <description><![CDATA[<h1 id="📌">📌</h1>
<p>인터넷 사용 시 기기마다 화면 크기가 상이함. 컴퓨터는 화면이 크고 휴대폰은 작음. 
반응형 디자인은 이러한 다양한 기기 환경에 맞춰 <strong>화면 레이아웃이 자동으로 변하는 디자인</strong>을 의미함.</p>
<h1 id="◼-필요성">◼ 필요성</h1>
<ul>
<li><strong>유저 편의성:</strong> 사용자는 항상 쾌적한 환경을 원함. 휴대폰 사용 시 글씨가 너무 작거나 레이아웃이 깨지면 사용성이 크게 저하됨.</li>
<li><strong>경험 최적화:</strong> 모든 기기에서 콘텐츠가 잘 보이게 하여 긍정적인 사용자 경험(UX)을 제공하기 위함.</li>
</ul>
<h1 id="◼-장점"><strong>◼ 장점</strong></h1>
<ul>
<li><strong>효율성:</strong> 디바이스별로 사이트를 별도 제작할 필요가 없음.</li>
<li><strong>리소스 절약:</strong> 한 번의 구현으로 모든 기기에 대응 가능하여 개발 시간과 노력을 획기적으로 단축함.</li>
</ul>
<h1 id="◼-반응형-웹과-적응형-웹"><strong>◼ 반응형 웹과 적응형 웹</strong></h1>
<h2 id="반응형-웹responsive-web">반응형 웹(Responsive Web)</h2>
<p>반응형 웹은 <strong>화면 크기에 따라 레이아웃이 유동적으로 변경되는 방식.</strong></p>
<p><strong>참고사이트</strong></p>
<p>🔗 애플 <a href="https://www.apple.com/kr/store">https://www.apple.com/kr/store</a></p>
<p>🔗 에어비앤비 <a href="https://www.airbnb.co.kr/">https://www.airbnb.co.kr</a></p>
<p>🔗 H&amp;M <a href="https://www2.hm.com/ko_kr/index.html">https://www2.hm.com/ko_kr/index.html</a></p>
<h2 id="적응형-웹adaptive-web">적응형 웹(Adaptive Web)</h2>
<p>적응형 웹은 <strong>기기별로 미리 정의된 화면 레이아웃을 제공하는 방식</strong>.
각 기기에 최적화된 디자인이 가능하나, 정의되지 않은 크기에서는 화면이 부자연스러울 수 있음.
접속 디바이스에 따라 URL이 변경되기도 함 (예: m.naver.com)</p>
<p><strong>참고사이트</strong></p>
<p>🔗 네이버 PC <a href="https://www.naver.com/">https://www.naver.com</a></p>
<p>🔗 네이버 Mobile <a href="https://m.naver.com/">https://m.naver.com</a></p>
<p>🔗 무신사 PC <a href="https://www.musinsa.com/">https://www.musinsa.com</a></p>
<p>무신사 Mobile: 무신사 접근 시점에 device 모드(개발자 도구 - ctrl(command) + shift + M)일 경우 확인 가능</p>
<h1 id="◼-반응형-웹-개발방법"><strong>◼ 반응형 웹 개발방법</strong></h1>
<h2 id="css-media-query-활용"><strong>CSS Media Query 활용</strong></h2>
<p>CSS 파일에 미디어쿼리를 작성하여 리액트 컴포넌트에 적용하는 기본적인 방식.</p>
<pre><code class="language-css">/* styles.css */

.container {
  padding: 20px;
}

@media (max-width: 768px) {
  .container {
    padding: 10px;
  }
}</code></pre>
<pre><code class="language-jsx">import React from &#39;react&#39;;
import &#39;./styles.css&#39;;

const SampleComponent = () =&gt; {
  return (
    &lt;div className=&quot;container&quot;&gt;
      반응형 웹
    &lt;/div&gt;
  );
}

export default SampleComponent;</code></pre>
<h2 id="styled-components-활용">Styled-components 활용</h2>
<p>JavaScript 파일 내에서 스타일과 미디어쿼리를 함께 정의하여 관리.</p>
<pre><code class="language-jsx">// SampleComponent.jsx

import React from &#39;react&#39;;
import styled from &#39;styled-components&#39;;

const Container = styled.div`
  padding: 20px;

  @media (max-width: 768px) {
    padding: 10px;
  }
`;

const SampleComponent = () =&gt; {
  return (
    &lt;Container&gt;
      반응형 웹
    &lt;/Container&gt;
  );
}

export default SampleComponent;</code></pre>
<h2 id="react-responsive-라이브러리-활용">react-responsive 라이브러리 활용</h2>
<p><code>useMediaQuery</code> 훅을 사용하여 JS 로직 내에서 분기 처리.</p>
<pre><code class="language-jsx">// SampleComponent.jsx

import React from &#39;react&#39;;
import { useMediaQuery } from &#39;react-responsive&#39;;

const SampleComponent = () =&gt; {
  const isMobile = useMediaQuery({ query: &#39;(max-width: 768px)&#39; });

  return (
    &lt;div style={{ padding: isMobile ? &#39;10px&#39; : &#39;20px&#39; }}&gt;
      반응형 웹
    &lt;/div&gt;
  );
}

export default SampleComponent;</code></pre>
<h2 id="tailwind-css-활용">Tailwind CSS 활용</h2>
<p><code>tailwind.config.js</code>의 <strong>theme 설정</strong>을 활용하면 다양한 디자인 프리셋 구성 가능.
프로젝트 전반에서 <strong>일관된 디자인 시스템 구축 가능.</strong></p>
<p>🔗 참고 URL <a href="https://tailwindcss.com/docs/theme">https://tailwindcss.com/docs/theme</a></p>
<h3 id="스크린-사이즈-지정break-point"><strong>스크린 사이즈 지정(Break Point)</strong></h3>
<p>반응형 웹 구현 시 <strong>디바이스별 대표 화면 너비(break point) 기준으로 디자인 구성.</strong> 
break point는 <strong>레이아웃이 변경되는 기준 지점</strong> 역할을 함. 
보통 디자이너가 기준 해상도를 정의함. <code>tailwind.config.js</code>에서 <code>screens</code> 설정 가능.
Tailwind는 기본적으로 <strong>min-width 기준의 Mobile First 방식 사용.</strong></p>
<p><strong>모바일 퍼스트 (min-width):</strong> 작은 화면부터 큰 화면 순으로 스타일 적용.</p>
<pre><code class="language-jsx">/** @type {import(&#39;tailwindcss&#39;).Config} */
module.exports = {
  theme: {
    screens: {
      &#39;sm&#39;: &#39;640px&#39;, // =&gt; @media (min-width: 640px) { ... }
      &#39;md&#39;: &#39;768px&#39;, // =&gt; @media (min-width: 768px) { ... }
      &#39;lg&#39;: &#39;1024px&#39;, // =&gt; @media (min-width: 1024px) { ... }
      &#39;xl&#39;: &#39;1280px&#39;, // =&gt; @media (min-width: 1280px) { ... }
      &#39;2xl&#39;: &#39;1536px&#39;, // =&gt; @media (min-width: 1536px) { ... }
    }
  }
}</code></pre>
<p><strong>데스크탑 퍼스트 (max-width):</strong> 큰 화면부터 작은 화면 순으로 스타일 적용.</p>
<pre><code class="language-jsx">/** @type {import(&#39;tailwindcss&#39;).Config} */
module.exports = {
  theme: {
    screens: {
      &#39;2xl&#39;: {&#39;max&#39;: &#39;1535px&#39;}, // =&gt; @media (max-width: 1535px) { ... }
      &#39;xl&#39;: {&#39;max&#39;: &#39;1279px&#39;}, // =&gt; @media (max-width: 1279px) { ... }
      &#39;lg&#39;: {&#39;max&#39;: &#39;1023px&#39;}, // =&gt; @media (max-width: 1023px) { ... }
      &#39;md&#39;: {&#39;max&#39;: &#39;767px&#39;}, // =&gt; @media (max-width: 767px) { ... }
      &#39;sm&#39;: {&#39;max&#39;: &#39;639px&#39;}, // =&gt; @media (max-width: 639px) { ... }
    }
  }
}</code></pre>
<p>🔗 참고 URL <a href="https://tailwindcss.com/docs/responsive-design#using-custom-breakpoints">https://tailwindcss.com/docs/responsive-design#using-custom-breakpoints</a></p>
<h3 id="컬러-지정"><strong>컬러 지정</strong></h3>
<p>프로젝트에서 자주 사용하는 색상을 <strong>theme에 미리 정의 가능.</strong> 
디자인 일관성 유지, 코드 가독성 향상, 유지보수 편의성 향상의 장점이 있음.</p>
<pre><code class="language-jsx">/** @type {import(&#39;tailwindcss&#39;).Config} */
module.exports = {
  theme: {
    screens: {
      sm: &#39;480px&#39;,
      md: &#39;768px&#39;,
      lg: &#39;976px&#39;,
      xl: &#39;1440px&#39;,
    },
    colors: {
      &#39;blue&#39;: &#39;#1fb6ff&#39;,
      &#39;purple&#39;: &#39;#7e5bef&#39;,
      &#39;pink&#39;: &#39;#ff49db&#39;,
      &#39;orange&#39;: &#39;#ff7849&#39;,
      &#39;green&#39;: &#39;#13ce66&#39;,
      &#39;yellow&#39;: &#39;#ffc82c&#39;,
      &#39;gray-dark&#39;: &#39;#273444&#39;,
      &#39;gray&#39;: &#39;#8492a6&#39;,
      &#39;gray-light&#39;: &#39;#d3dce6&#39;,
    }
  }
}</code></pre>
<h3 id="프리셋-사용"><strong>프리셋 사용</strong></h3>
<p>모바일 퍼스트 기준 screen 설정 예시</p>
<pre><code class="language-jsx">/** @type {import(&#39;tailwindcss&#39;).Config} */
module.exports = {
  theme: {
    screens: {
      &#39;sm&#39;: &#39;640px&#39;,
      // =&gt; @media (min-width: 640px) { ... }

      &#39;md&#39;: &#39;768px&#39;,
      // =&gt; @media (min-width: 768px) { ... }

      &#39;lg&#39;: &#39;1024px&#39;,
      // =&gt; @media (min-width: 1024px) { ... }

      &#39;xl&#39;: &#39;1280px&#39;,
      // =&gt; @media (min-width: 1280px) { ... }

      &#39;2xl&#39;: &#39;1536px&#39;,
      // =&gt; @media (min-width: 1536px) { ... }
    }
  }
}</code></pre>
<p><strong>실제 태그 적용 예시</strong></p>
<pre><code class="language-jsx">&lt;img class=&quot;w-16 md:w-32 lg:w-48&quot; src=&quot;...&quot;&gt;</code></pre>
<p>클래스 접두사(<code>md:</code>, <code>lg:</code>)를 사용하여 사이즈별 스타일을 제어.
Tailwind CSS의 <code>w-{number}</code> 유틸리티는 <code>0.25rem</code> 단위를 사용함.</p>
<p>img의 width값은 
기본(sm)일때 <em>width:</em> 4rem(64px), md일때 <em>width: 8rem(*128px</em>),* lg일때 width <em>12rem(</em>192px)으로 적용됨.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 심화] Tailwind CSS]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-Tailwind-CSS</link>
            <guid>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-Tailwind-CSS</guid>
            <pubDate>Fri, 28 Mar 2025 09:28:08 GMT</pubDate>
            <description><![CDATA[<h1 id="📌">📌</h1>
<p>Tailwind CSS는 <strong>디자인 시스템</strong>을 직접 구축할 수 있도록 도와주는 CSS 프레임워크. 
가볍고 사용이 간단한 프레임워크이며, <strong>유틸리티 퍼스트(Utility-First) 방식</strong>을 사용함. 
빠르고 쉽게 스타일을 적용할 수 있는 다양한 클래스를 제공함.</p>
<h1 id="◼-등장배경">◼ 등장배경</h1>
<ol>
<li><strong>CSS 프레임워크의 중요성</strong><ul>
<li>웹 애플리케이션 규모가 커질수록 CSS 관리의 복잡도 급증함.</li>
<li>일관된 디자인 시스템 구축 및 유지보수 효율화를 위해 프레임워크 사용 필수적임.</li>
</ul>
</li>
<li><strong>기존 스타일링 방식의 한계</strong><ul>
<li><strong>전통적인 CSS/SASS:</strong> 클래스 네임 명명 규칙(BEM 등) 준수가 어렵고, 프로젝트가 커질수록 CSS 파일 크기가 비대해짐.</li>
<li><strong>CSS-in-JS (Styled-Components 등):</strong> 런타임 성능 오버헤드 발생 가능 및 스타일 정의를 위해 끊임없이 새로운 컴포넌트를 생성해야 하는 번거로움 존재함.</li>
<li><strong>클래스 네임 충돌:</strong> 전역 스타일 오염이나 의도치 않은 스타일 중복 적용 문제 빈번함.</li>
</ul>
</li>
</ol>
<p>→ 이러한 문제를 해결하기 위해 등장한 프레임워크가 <strong>Tailwind CSS</strong></p>
<p>→ 유틸리티 클래스 기반 스타일링 지원</p>
<p>→ CSS 관리 단순화 가능</p>
<p>→ 빠르고 일관된 UI 스타일 구축 가능</p>
<h1 id="◼-주요특징">◼ 주요특징</h1>
<h2 id="유틸리티-퍼스트"><strong>유틸리티 퍼스트</strong></h2>
<p>HTML 요소에 직접 클래스를 추가하는 방식 사용함.</p>
<p><strong>유틸리티 클래스란?</strong>
특정 스타일 속성을 나타내는 짧고 간단한 CSS 클래스. 
HTML만으로 스타일 적용 가능하며 별도의 CSS 파일 작성 필요 없음. 
예시: <code>bg-blue-500</code> → 배경색 파랑, <code>p-4</code> → 패딩 설정</p>
<h2 id="성능-최적화"><strong>성능 최적화</strong></h2>
<p>Tailwind CSS는 <strong>필요한 스타일만 포함하는 방식</strong>으로 성능 최적화 지원.</p>
<ul>
<li><strong>Purging CSS</strong> 기능을 통해 사용하지 않는 CSS를 자동 제거하여 최종 빌드 파일 크기를 최소화함.</li>
<li>애플리케이션 로딩 속도 개선 가능.</li>
</ul>
<p><strong>Purging CSS?</strong>
Purging CSS는 <strong>사용하지 않는 CSS를 제거하여 빌드 파일 크기를 줄이는 과정</strong>을 의미함. 
Tailwind CSS는 빌드 과정에서 사용되지 않는 스타일을 자동으로 제거함.</p>
<h2 id="react와의-통합"><strong>React와의 통합</strong></h2>
<ul>
<li>React의 <code>JSX</code> 문법과 완벽히 호환되어 스타일링이 간편함.</li>
<li>클래스 네임 충돌 걱정 없이 독립적인 컴포넌트 스타일링 지원함.</li>
</ul>
<pre><code class="language-jsx">&lt;div className=&quot;bg-blue-500 text-white p-4&quot;&gt;
  Hello, Tailwind CSS!
&lt;/div&gt;</code></pre>
<h2 id="커스터마이징"><strong>커스터마이징</strong></h2>
<ul>
<li>전통적인 프레임워크 대비 유연성이 매우 높음.</li>
<li><code>tailwind.config.js</code> 설정을 통해 색상, 폰트, 스페이싱 등 프로젝트만의 디자인 시스템 구축 가능함.</li>
</ul>
<pre><code class="language-jsx">// tailwind.config.js

module.exports = {
  theme: {
    extend: {
      colors: {
        primary: &#39;#1DA1F2&#39;,
      },
    },
  },
};</code></pre>
<h1 id="◼-설치-및-기본-사용법">◼ 설치 및 기본 사용법</h1>
<h2 id="설치"><strong>설치</strong></h2>
<pre><code class="language-jsx">npm install tailwindcss@3.3.5 postcss autoprefixer
npx tailwindcss init -p</code></pre>
<p><strong>tailwind.config.js 변경</strong></p>
<pre><code class="language-jsx">/** @type {import(&#39;tailwindcss&#39;).Config} */
export default {
  content: [
    &quot;./index.html&quot;,
    &quot;./src/**/*.{js,ts,jsx,tsx}&quot;,
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}</code></pre>
<h2 id="기본-사용법"><strong>기본 사용법</strong></h2>
<pre><code class="language-jsx">// src/index.css

@tailwind base;
@tailwind components;
@tailwind utilities;</code></pre>
<p><strong>적용 예시 (Header 컴포넌트)</strong></p>
<pre><code class="language-jsx">// src/components/Header.jsx
// 변경 전

// ... 생략
&lt;header
  style={{
    display: &quot;flex&quot;,
    justifyContent: &quot;space-between&quot;,
    alignItems: &quot;center&quot;,
    padding: &quot;0 20px&quot;,
    backgroundColor: &quot;lightgray&quot;,
  }}
/&gt;
// ... 생략

// 변경 후

// ... 생략
&lt;header className=&quot;flex justify-between items-center px-5 bg-gray-200&quot; /&gt;
// ... 생략</code></pre>
<h1 id="◼-tailwind-css-vs-styled-components">◼ Tailwind CSS vs Styled-Components</h1>
<h2 id="설정-및-사용법-비교">설정 및 사용법 비교</h2>
<h3 id="tailwind-css">Tailwind CSS</h3>
<p>유틸리티 클래스를 사용하여 <strong>HTML에서 직접 스타일 적용</strong>.</p>
<pre><code class="language-jsx">&lt;div className=&quot;bg-blue-500 text-white p-4&quot;&gt;
  Hello, Tailwind CSS!
&lt;/div&gt;</code></pre>
<h3 id="styled-components">Styled-Components</h3>
<p>JavaScript 파일에서 스타일 정의 후 컴포넌트에 적용.</p>
<pre><code class="language-jsx">import styled from &#39;styled-components&#39;;

const Button = styled.button`
  background-color: blue;
  color: white;
  padding: 1rem;
`;

function App() {
  return &lt;Button&gt;Hello, Styled-Components!&lt;/Button&gt;;
}</code></pre>
<h2 id="장단점-비교">장단점 비교</h2>
<h3 id="tailwind-css-1">Tailwind CSS</h3>
<p><strong>장점</strong></p>
<ul>
<li>유틸리티 클래스 기반 빠른 스타일링 가능</li>
<li>사용하지 않는 CSS 제거로 성능 최적화 지원</li>
<li>높은 커스터마이징 가능</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>클래스 네임 길어질 수 있음</li>
<li>초기 설정 필요</li>
</ul>
<h3 id="styled-components-1">Styled-Components</h3>
<p><strong>장점</strong></p>
<ul>
<li>JavaScript 파일에서 스타일 관리 가능</li>
<li>동적 스타일링 지원</li>
<li>컴포넌트 기반 스타일 구조 구성 가능</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>초기 학습 필요</li>
<li>런타임 스타일 처리로 성능 이슈 발생 가능</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 심화] 인증/인가 2 - API 문서, Thunder Client]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-%EC%9D%B8%EC%A6%9D%EC%9D%B8%EA%B0%80-2-API-%EB%AC%B8%EC%84%9C-Thunder-Client</link>
            <guid>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-%EC%9D%B8%EC%A6%9D%EC%9D%B8%EA%B0%80-2-API-%EB%AC%B8%EC%84%9C-Thunder-Client</guid>
            <pubDate>Wed, 26 Mar 2025 15:41:56 GMT</pubDate>
            <description><![CDATA[<h1 id="◼-api-문서">◼ API 문서</h1>
<p><strong>서버 API URL</strong></p>
<p><a href="https://api.example.com/">https://api.example.com</a></p>
<h2 id="회원가입"><strong>회원가입</strong></h2>
<p>아이디, 비밀번호, 닉네임으로 DB에 회원정보를 저장.</p>
<ul>
<li><p><strong>Request</strong></p>
<ul>
<li><p><strong>Method</strong>: <code>POST</code></p>
</li>
<li><p><strong>URL PATH</strong>: <code>/register</code></p>
</li>
<li><p><strong>Body (JSON)</strong>:</p>
<pre><code class="language-json">JSON
{
  &quot;id&quot;: &quot;유저 아이디&quot;,
      &quot;password&quot;: &quot;유저 비밀번호&quot;,
      &quot;nickname&quot;: &quot;유저 닉네임&quot;
}</code></pre>
</li>
</ul>
</li>
<li><p><strong>Response</strong></p>
<pre><code class="language-json">  {
    &quot;message&quot;: &quot;회원가입 완료&quot;,
    &quot;success&quot;: true
  }</code></pre>
</li>
</ul>
<h2 id="로그인"><strong>로그인</strong></h2>
<p>아이디와 비밀번호 일치 시 <code>accessToken</code>, <code>userId</code>, <code>avatar</code>, <code>nickname</code> 총 4가지 유저 정보를 응답.</p>
<ul>
<li><p><strong>Request</strong></p>
<ul>
<li><p><strong>Method</strong>: <code>POST</code></p>
</li>
<li><p><strong>URL PATH</strong>: <code>/login</code></p>
</li>
<li><p><strong>Body (JSON)</strong>:</p>
<pre><code class="language-json">JSON
{
&quot;id&quot;:&quot;유저 아이디&quot;,
&quot;password&quot;: &quot;유저 비밀번호&quot;
}</code></pre>
</li>
</ul>
</li>
<li><p><strong>Query String (선택)</strong>: <code>accessToken</code> 유효시간 조정을 위해 사용함.</p>
<ul>
<li>미기입 시 기본 1시간 설정.</li>
<li><code>expiresIn</code> 파라미터로 시간 단위(s, m, h) 지정 가능. (ex. <code>10s</code>, <code>10m</code>, <code>10h</code>)</li>
<li><strong>TIP</strong>: 토큰 만료 후 자동 로그아웃 로직을 테스트할 때 유용함.</li>
<li>예시: <code>/login?expiresIn=10m</code> (유효시간 10분 요청)</li>
</ul>
</li>
<li><p><strong>Response</strong></p>
<pre><code class="language-json">  {
    &quot;accessToken&quot;: &quot;eyJhbGciOiJIUzI1Ni...&quot;,
    &quot;userId&quot;: &quot;유저 아이디&quot;,
    &quot;success&quot;: true,
    &quot;avatar&quot;: &quot;프로필 이미지 URL&quot;,
    &quot;nickname&quot;: &quot;유저 닉네임&quot;
  }</code></pre>
</li>
</ul>
<h2 id="회원정보-확인"><strong>회원정보 확인</strong></h2>
<p><code>accessToken</code>이 유효한 경우, 비밀번호를 제외한 본인 정보를 응답함.</p>
<pre><code class="language-jsx">// authorization 속성 정의
const response = await axios.get(`${BASE_URL}/user`, {
  headers: {
    &quot;Content-Type&quot;: &quot;application/json&quot;,
    Authorization: `Bearer ${accessToken}`,
  },
});</code></pre>
<ul>
<li><p><strong>Request</strong></p>
<ul>
<li><p><strong>Method</strong>: <code>GET</code></p>
</li>
<li><p><strong>URL PATH</strong>: <code>/user</code></p>
</li>
<li><p><strong>Header</strong>:</p>
<pre><code class="language-jsx">  {
      &quot;Authorization&quot;: &quot;Bearer AccessToken&quot;
  }</code></pre>
</li>
</ul>
</li>
<li><p><strong>Response</strong></p>
<pre><code class="language-json">  {
    &quot;id&quot;: &quot;사용자 아이디&quot;,
    &quot;nickname&quot;: &quot;사용자 닉네임&quot;,
    &quot;avatar&quot;: null,
    &quot;success&quot;: true
  }</code></pre>
</li>
</ul>
<h2 id="프로필-변경"><strong>프로필 변경</strong></h2>
<p><code>accessToken</code>이 유효한 경우, <code>FormData</code>를 통해 이미지나 닉네임 수정 가능. 수정 완료 후 변경된 정보를 응답함.</p>
<pre><code class="language-jsx">// 이미지파일을 FormData에 담는 방법

const formData = new FormData();
// avatar와 nickname 중 하나 또는 모두 변경 가능
formData.append(&quot;avatar&quot;, imgFile);
formData.append(&quot;nickname&quot;, nickname);

// 요청 시 Content-Type에 유의
const response = await axios.patch(`${BASE_URL}/profile`, formData, {
  headers: {
    &quot;Content-Type&quot;: &quot;multipart/form-data&quot;,
    Authorization: `Bearer ${accessToken}`,
  },
});</code></pre>
<ul>
<li><p><strong>Request</strong></p>
<ul>
<li><p><strong>Method</strong>: <code>PATCH</code></p>
</li>
<li><p><strong>URL PATH</strong>: <code>/profile</code></p>
</li>
<li><p><strong>Header</strong>:</p>
<pre><code class="language-jsx">  {
      &quot;Authorization&quot;: &quot;Bearer AccessToken&quot;
  }</code></pre>
</li>
<li><p><strong>Body (Form-Data):</strong></p>
<pre><code class="language-json">  {
      &quot;avatar&quot;: [이미지파일],
      &quot;nickname&quot;: &quot;변경할 닉네임&quot;
  }</code></pre>
</li>
</ul>
</li>
<li><p><strong>Response</strong></p>
<pre><code class="language-json">  {
    &quot;avatar&quot;: &quot;변경된 이미지 URL&quot;,
    &quot;nickname&quot;: &quot;변경된 닉네임&quot;,
    &quot;message&quot;: &quot;프로필이 업데이트되었습니다.&quot;,
    &quot;success&quot;: true
  }</code></pre>
</li>
</ul>
<h1 id="◼-thunder-client">◼ Thunder Client</h1>
<p>HTTP 요청을 보내고 응답을 확인할 수 있는 <strong>API 테스트 도구</strong>.
VS Code 내부에서 <strong>API 요청 테스트 및 디버깅 가능</strong>.</p>
<h2 id="설치방법"><strong>설치방법</strong></h2>
<p>VS Code Extensions에서 [thunder Client] 검색 후 설치 → 사이드바의 아이콘 선택</p>
<h2 id="기본-사용-방법"><strong>기본 사용 방법</strong></h2>
<ol>
<li>New Request 클릭</li>
<li>원하는 HTTP Method 선택 및 URL 입력</li>
<li>Payload가 필요할 경우 Body에 추가</li>
<li>[Send]를 눌러 응답 확인</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 심화] 인증/인가 1 - 쿠키, 세션, 토큰, JWT]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-%EC%9D%B8%EC%A6%9D%EC%9D%B8%EA%B0%80-1-%EC%BF%A0%ED%82%A4-%EC%84%B8%EC%85%98-%ED%86%A0%ED%81%B0-JWT</link>
            <guid>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-%EC%9D%B8%EC%A6%9D%EC%9D%B8%EA%B0%80-1-%EC%BF%A0%ED%82%A4-%EC%84%B8%EC%85%98-%ED%86%A0%ED%81%B0-JWT</guid>
            <pubDate>Wed, 26 Mar 2025 01:26:35 GMT</pubDate>
            <description><![CDATA[<h1 id="◼-인증과-인가">◼ 인증과 인가</h1>
<h2 id="인증authentication">인증(Authentication)</h2>
<p>서비스를 이용하려는 사용자가 등록된 회원인지 확인하는 절차. 일반적으로 로그인 과정.</p>
<h2 id="인가authorization">인가(Authorization)</h2>
<p>인증이 완료된 사용자가 특정 리소스에 접근할 수 있는 권한이 있는지 확인하는 절차.
로그인 이후, 기능 또는 데이터 접근 권한을 검사하는 과정.</p>
<h1 id="◼-http-프로토콜-특징">◼ HTTP 프로토콜 특징</h1>
<h2 id="무상태stateless"><strong>무상태(stateless)</strong></h2>
<ul>
<li><strong>독립적 요청 처리</strong> : HTTP는 무상태 프로토콜로, 서버가 이전 요청에 대한 정보를 기억하지 않음.</li>
<li><strong>클라이언트 책임</strong> : 서버가 이전 요청의 상태나 정보를 기억하지 않으므로, 클라이언트는 매 요청마다 필요한 상태 정보를 포함하여 전달해야 함.</li>
<li><strong>서버 운영 효율</strong> : 상태값은 매 요청마다 클라이언트가 가지고 오기 때문에, 서버는 클라이언트의 상태를 별도로 기억할 필요없이 주문받은 대로 응답.</li>
<li><strong>수평 확장성(Scale-out) 유리</strong> : 각 서버는 상태를 기억할 필요가 없어 독립적으로 요청을 처리할 수 있음. 하나의 서버에 장애가 발생하더라도 다른 서버가 계속해서 요청을 처리할 수 있어 안전.</li>
</ul>
<p><strong>스케일 아웃(Scale-out)이란?</strong> 
동일 애플리케이션을 여러 대의 서버에 배포하고 로드 밸런서를 통해 요청을 여러 서버로 분산하는 방식.</p>
<h2 id="비연결성connectionless"><strong>비연결성(connectionless)</strong></h2>
<ul>
<li><strong>연결 즉시 종료</strong> : 클라이언트와 서버가 요청과 응답을 주고받은 후 바로 연결을 끊음.</li>
<li><strong>자원 관리</strong> : 서버 입장에서는 연결을 유지하는 비용이 들지 않아 최소한의 자원으로 많은 수의 요청 대응 가능.</li>
<li><strong>한계점</strong> : 매번 새로운 연결을 맺어야 하므로, 요청이 잦은 서비스에서는 비효율적일 수 있음.</li>
</ul>
<h1 id="◼-쿠키-세션-토큰">◼ 쿠키, 세션, 토큰</h1>
<h2 id="쿠키cookie">쿠키(Cookie)</h2>
<p>브라우저에 저장되는 작은 텍스트 데이터 조각 (Key-Value 형태).</p>
<ul>
<li><strong>HTTP의 무상태성, 비연결성 보완</strong> : 쿠키를 사용하여 마치 서버가 클라이언트의 인증 상태를 기억하는 것처럼 구현.</li>
<li><strong>자동 전송</strong> : 별도로 삭제 처리하거나 유효기간이 만료되지 않는 한, 서버와 통신할 때 자동으로 주고받음(단, 동일 Origin 또는 CORS 허용 범위 내에서만 작동).</li>
<li><strong>Set-Cookie</strong> : 서버에 특정 API 요청을 했을 때(ex: 로그인 요청)서버가 응답 헤더에 이 속성을 담으면 브라우저가 알아서 저장. 서버에서 브라우저에게 저장하도록 지시할 수 있다는 점에서는 <code>localStorage</code>나 <code>sessionStorage</code>와는 다름.</li>
</ul>
<p><strong>Origin 이란?</strong>
protocol + host + port 를 의미.</p>
<p><strong>CORS 란?</strong>
Cross Origin Resource Sharing(CORS)는 다른 출처에 리소스 요청하는 것을 허용하는 정책.
브라우저는 보안상의 이유로 Same Origin Policy(SOP)를 원칙으로 하고 있지만, 서버와 클라이언트 각각 CORS 설정을 통해 상호합의된 웹사이트는 예외적으로 서로 다른 출처(Cross-Origin)임에도 API 요청이 가능.</p>
<h2 id="세션session">세션(Session)</h2>
<p>서버와 클라이언트 간의 연결/인증이 유지되고 있는 상태.</p>
<ul>
<li><strong>인증 과정</strong><ol>
<li>로그인 성공 시 서버에서 세션 생성 및 저장소(DB, Redis 등)에 보관.</li>
<li>생성된 <code>sessionId</code>를 쿠키에 담아 브라우저로 응답.</li>
<li>브라우저는 이후 요청마다 쿠키(<code>sessionId</code>)를 함께 보냄.</li>
<li>서버는 수신한 ID를 저장소에서 조회하여 인증 처리.</li>
</ol>
</li>
<li><strong>상태값</strong><ul>
<li><strong>세션 유지</strong> : 서버 저장소에 해당 데이터가 존재함</li>
<li><strong>세션 만료</strong> : 서버 저장소에서 데이터가 삭제됨 (로그아웃 또는 시간 초과).</li>
</ul>
</li>
</ul>
<h2 id="토큰token">토큰(Token)</h2>
<p>클라이언트가 직접 보관하는 암호화/인코딩된 인증 정보.</p>
<ul>
<li><strong>장점</strong> : 세션처럼 서버에서 사용자의 인증 정보를 보관할 필요가 없어 서버 부담이 적음.</li>
<li><strong>주요 방식</strong> : 주로 JWT(JSON Web Token) 형식을 사용.</li>
<li><strong>암호화 vs 인코딩</strong><ul>
<li><strong>암호화</strong> : 데이터 보호 목적. 데이터를 특정 알고리즘으로 변환하여 인가된 사용자만이 읽을 수 있도록 하는 과정이며, 기밀성을 유지.</li>
<li><strong>인코딩</strong> : 전송/처리 목적. 데이터를 다른 형식으로 변환하여 전송 및 저장을 용이하게 하는 과정으로, 특별한 키 없이도 원래 형태로 복원할 수 있음.</li>
</ul>
</li>
</ul>
<h1 id="◼-jwt">◼ <strong>JWT</strong></h1>
<p>토큰 기반 인증의 표준 규격으로, 로그인 후 API 요청 시 신분증 역할을 함.</p>
<h2 id="구조">구조</h2>
<ol>
<li><strong>헤더(Header)</strong> : 토큰 종류 및 서명 알고리즘 정보 포함.</li>
<li><strong>본문(Payload)</strong> : 실제 데이터 부분. 예를 들어, 사용자 ID, 토큰의 만료 시간 등.</li>
<li><strong>서명(Signature)</strong> :  토큰 위조여부를 확인하는 역할. 서버만 알 수 있는 비밀키로 서명되어 있음. 이 서명 때문에 토큰의 무결성이 보장됨.</li>
</ol>
<h2 id="특징"><strong>특징</strong></h2>
<ul>
<li><strong>위조 방지</strong> : 인코딩된 토큰을 누구나 복호화하여 payload를 볼 수 있습니다. 즉, 토큰의 용도는 데이터 보호가 아니라 &quot;이 데이터를 서버가 발행한 진짜가 맞는가?&quot;를 검증하는 위조 방지 역할.</li>
<li><strong>무결성 보장</strong>:  Secret Key가 있어야 Signature 검증이 가능하므로 클라이언트가 임의로 수정 불가.</li>
</ul>
<h3 id="인증-보안-강화access--refresh">인증 보안 강화(Access &amp; Refresh)</h3>
<p>프론트엔드 개발 시 백엔드와 협업하여 보안을 강화하는 방식.</p>
<ul>
<li><strong>Access Token :</strong> 리소스 접근용. 만료 기간을 짧게(예: 30분) 설정하여 탈취 위험 감소.</li>
<li><strong>Refresh Token :</strong> Access Token 재발급용. 만료 기간을 길게(예: 1~2주) 설정.</li>
<li><strong>작동 원리</strong> : Access Token이 만료되면 Refresh Token을 서버에 보내 새 Access Token을 받아옴. 둘 다 만료되면 다시 로그인 필요.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 심화] UX향상 - throttling & debouncing]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-UX%ED%96%A5%EC%83%81-throttling-debouncing</link>
            <guid>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-UX%ED%96%A5%EC%83%81-throttling-debouncing</guid>
            <pubDate>Tue, 25 Mar 2025 09:38:29 GMT</pubDate>
            <description><![CDATA[<h1 id="◼-throttling이란">◼ Throttling이란?</h1>
<p>Throttling은 짧은 시간 간격으로 연속 발생한 이벤트를 일정 시간 단위(delay)로 제한하는 기법. 지정한 시간 동안 처음 또는 마지막 이벤트만 실행 가능. 
주로 <strong>무한스크롤, 스크롤 이벤트, 버튼 연속 클릭 방지</strong> 등에 사용.</p>
<p>이벤트 반복 발생 시 처리 방식은 다음과 같이 구분 가능.</p>
<table>
<thead>
<tr>
<th>타입</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>Leading Edge</td>
<td>이벤트가 처음 발생할 때 핸들러가 실행됨. 이후 지정 시간 동안 추가 이벤트 무시.</td>
<td>사용자가 스크롤을 시작할 때 처음에만 API 호출이 이루어지고, 일정 시간 동안 추가 호출이 무시됨</td>
</tr>
<tr>
<td>Trailing Edge</td>
<td>이벤트가 반복적으로 실행될 때, 주어진 시간(delay)이 지나면 마지막 이벤트를 실행.</td>
<td>Leading Edge와 비슷하지만 주어진 시간의 마지막 이벤트에 API 호출이 이루어짐.</td>
</tr>
<tr>
<td>Leading &amp; Trailing Edge</td>
<td>처음 이벤트 때 핸들러가 실행되고, 주어진 시간이 지나면 마지막 이벤트도 실행.</td>
<td>사용자가 버튼을 여러 번 클릭할 때 처음 클릭 시 바로 API 호출이 이루어지고, 주어진 시간의 마지막 이벤트에도 API 호출이 이루어짐.</td>
</tr>
</tbody></table>
<h1 id="◼-debouncing란">◼ Debouncing란?</h1>
<p>Debouncing은 짧은 시간 간격으로 연속 발생하는 이벤트가 멈출 때까지 기다렸다가, 마지막 이벤트 기준으로 일정 시간(delay) 이후 한 번만 실행하는 기법. 
주로 <strong>입력값 실시간 검색, 자동완성 기능, 화면 resize 이벤트</strong> 등에 사용.
서버에 대한 불필요한 API 호출을 줄이고 짧은 시간에 많은 이벤트가 발생하는 상황에서 UI 과부하 방지.</p>
<h1 id="◼-메모리-누수memory-leak란">◼ 메모리 누수(Memory Leak)란?</h1>
<p>필요하지 않은 메모리를 계속 점유하는 현상.</p>
<p><strong>setTimeout이 메모리 누수를 유발하는가?</strong></p>
<p>쓰로틀링과 디바운싱에서 setTimeout을 자주 사용하기 때문에, 이 함수의 사용으로 인한 메모리 누수 가능성을 이해하는 것이 중요. 상황에 따라 메모리 누수를 일으킬 수도 있고 아닐 수도 있음. </p>
<p>하나의 페이지에서 페이지 이동 없이 setTimeout을 동작시키고 타이머 함수가 종료될 때까지 기다린다면 메모리 누수는 없음. 
React 기반 SPA에서는 페이지 이동 시 컴포넌트가 언마운트됨. 페이지 이동 전에 <code>setTimeout</code> 으로 인해 타이머가 동작중인데 clearTimeout을 호출하지 않고 페이지를 이동한다면, 컴포넌트는 언마운트 되었음에도 불구하고 타이머는 여전히 메모리를 차지하고 동작하고 있음. 이 경우 메모리 누수(Memory Leak)에 해당.</p>
<p>따라서 cleanup 함수에서 반드시 clearTimeout 처리 필요.</p>
<h1 id="◼-실습">◼ 실습</h1>
<h2 id="직접-만들어보는-throttling--debouncing"><strong>직접 만들어보는 throttling &amp; debouncing</strong></h2>
<p><strong>프로젝트 생성</strong></p>
<pre><code class="language-bash">npm create vite thro-debo-app --template react</code></pre>
<p><strong>페이지 이동 테스트를 위한 react-router-dom 설치</strong></p>
<pre><code class="language-bash">npm install react-router-dom</code></pre>
<p><strong>App 컴포넌트 작성</strong></p>
<pre><code class="language-jsx">// src/App.jsx

import { BrowserRouter, Route, Routes } from &quot;react-router-dom&quot;;
import Home from &quot;pages/Home&quot;;
import Company from &quot;pages/Company&quot;;

function App() {
  return (
    &lt;BrowserRouter&gt;
      &lt;Routes&gt;
        &lt;Route path=&quot;/&quot; element={&lt;Home /&gt;} /&gt;
                &lt;Route path=&quot;/company&quot; element={&lt;Company /&gt;} /&gt;
      &lt;/Routes&gt;
    &lt;/BrowserRouter&gt;
  );
}

export default App;</code></pre>
<p><strong>Home 컴포넌트 작성</strong></p>
<p><code>throttling</code>, <code>debouncing</code> 동작에 집중.
페이지 이동 시 <code>cleanup</code>이 없다면 타이머가 계속 동작. 이 경우 메모리 누수 발생 가능.</p>
<pre><code class="language-jsx">// src/pages/Home.jsx

import { useEffect, useState } from &quot;react&quot;;
import { useNavigate } from &quot;react-router-dom&quot;;

export default function Home() {
  // const [state, setState] = useState(false);
  const navigate = useNavigate();
  let timerId = null;

  // Leading Edge Throttling
  const throttle = (delay) =&gt; {
    if (timerId) {
      // timerId가 있으면 바로 함수 종료
      return;
    }
    // setState(!state);
    console.log(`API요청 실행! ${delay}ms 동안 추가요청 안받음`);
    timerId = setTimeout(() =&gt; {
      console.log(`${delay}ms 지남 추가요청 받음`);
      // alert(&quot;Home / 쓰로틀링 쪽 API호출!&quot;);
      timerId = null;
    }, delay);
  };

  // Trailing Edge Debouncing
  const debounce = (delay) =&gt; {
    if (timerId) {
      // 할당되어 있는 timerId에 해당하는 타이머 제거
      clearTimeout(timerId);
    }
    timerId = setTimeout(() =&gt; {
      // timerId에 새로운 타이머 할당
      console.log(`마지막 요청으로부터 ${delay}ms지났으므로 API요청 실행`);
      timerId = null;
    }, delay);
  };

  useEffect(() =&gt; {
    return () =&gt; {
      // 페이지 이동 시 실행
      if (timerId) {
        // 메모리 누수 방지
        clearTimeout(timerId);
      }
    };
  }, [timerId]);

  return (
    &lt;div style={{ paddingLeft: 20, paddingRight: 20 }}&gt;
      &lt;h1&gt;Button 이벤트 예제&lt;/h1&gt;
      &lt;button onClick={() =&gt; throttle(2000)}&gt;쓰로틀링 버튼&lt;/button&gt;
      &lt;button onClick={() =&gt; debounce(2000)}&gt;디바운싱 버튼&lt;/button&gt;
            &lt;div&gt;
        &lt;button onClick={() =&gt; navigate(&quot;/company&quot;)}&gt;페이지 이동&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}</code></pre>
<h2 id="lodash-활용">lodash 활용</h2>
<p>lodash는 JavaScript 유틸리티 라이브러리로, 배열, 객체, 문자열 등의 데이터 조작을 쉽게 할 수 있는 다양한 함수들을 제공. 
성능 최적화와 코드 가독성을 높이는 데 유용. 특히, throttle과 debounce 같은 함수도 포함되어 있어 편리.</p>
<p><strong>lodash를 활용한 debouncing 테스트</strong> </p>
<pre><code class="language-jsx">// src/pages/Home.jsx

import { useState, useCallback } from &quot;react&quot;;
import _ from &quot;lodash&quot;;

function Home() {
  const [searchText, setSearchText] = useState(&quot;&quot;);
  const [inputText, setInputText] = useState(&quot;&quot;);

  const handleSearchText = _.debounce((text) =&gt; setSearchText(text), 2000);

  const handleChange = (e) =&gt; {
    setInputText(e.target.value);
    handleSearchText(e.target.value);
  };

  return (
    &lt;div
      style={{
        paddingLeft: 20,
        paddingRight: 20,
      }}
    &gt;
      &lt;h1&gt;디바운싱 예제&lt;/h1&gt;
      &lt;br /&gt;
      &lt;input
        placeholder=&quot;입력값을 넣고 디바운싱 테스트를 해보세요.&quot;
        style={{ width: &quot;300px&quot; }}
        onChange={handleChange}
        type=&quot;text&quot;
      /&gt;
      &lt;p&gt;Search Text: {searchText}&lt;/p&gt;
      &lt;p&gt;Input Text: {inputText}&lt;/p&gt;
    &lt;/div&gt;
  );
}

export default Home;</code></pre>
<p><strong>문제 발생</strong></p>
<p>input에서 입력한 값이 delay 간격으로 한 글자씩 반영되는 현상 발생.
원인 : 컴포넌트에서 리렌더링이 일어나면서 debounce 함수를 계속해서 생성하기 때문.</p>
<pre><code class="language-jsx">const handleSearchText = _.debounce((text) =&gt; setSearchText(text), 2000);</code></pre>
<p><strong>해결 방법</strong></p>
<p>useCallback으로 함수를 memoization.</p>
<pre><code class="language-jsx">  const handleSearchText = useCallback(
    _.debounce((text) =&gt; setSearchText(text), 2000),
    []
  );</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 심화] Zustand란?]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-Zustand%EB%9E%80</link>
            <guid>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-Zustand%EB%9E%80</guid>
            <pubDate>Mon, 24 Mar 2025 09:18:12 GMT</pubDate>
            <description><![CDATA[<h1 id="📌">📌</h1>
<p>Zustand는 <strong>상태관리 본연의 기능에 집중</strong>한 라이브러리. 
복잡성을 줄이고, <strong>간단하고 직관적인 상태관리</strong> 기능 제공. 단순화된 Flux 패턴 기반의 small, fast, scalable 상태관리 솔루션이며 Hooks 기반의 간편한 API 제공.</p>
<h1 id="◼-등장배경">◼ <strong>등장배경</strong></h1>
<p><strong>상태관리의 중요성</strong></p>
<p>기존 상태관리 라이브러리인 Redux는 강력한 기능과 다양한 미들웨어 지원하지만, 설정과 사용 방식이 복잡한 편.
상태관리는 모든 React 애플리케이션에서 핵심 요소. 
작은 규모에서는 상태관리가 간단하지만, 애플리케이션이 커질수록 상태관리는 더 복잡해짐.</p>
<p>→ Zustand는 복잡성을 줄이고, 간단하고 직관적인 상태관리 기능 제공.</p>
<h1 id="◼-주요특징">◼ <strong>주요특징</strong></h1>
<ul>
<li><strong>간결함</strong> : 간단한 API 제공하기 때문에, 학습 곡선이 완만. 적은 설정 코드로 상태관리 구현 가능.</li>
<li><strong>성능</strong> : 불필요한 리렌더링을 방지하는 등 성능최적화가 잘 되어 있음. 상태 변경 시 해당 상태를 구독(subscribe)한 컴포넌트만 리렌더링.</li>
<li><strong>React와의 통합</strong> : React의 훅(Hook)과 자연스럽게 통합 가능. 상태를 정의하고 이를 React 컴포넌트에서 쉽게 사용할 수 있어, 기존 React 개발 경험을 그대로 활용할 수 있음.</li>
</ul>
<p><strong>구독(subscribe)이란?</strong>
구독은 상태 변경을 감지하고, 해당 변경에 반응하는 컴포넌트만 업데이트하는 메커니즘 의미. 
상태 일부가 변경되더라도, 그 값을 사용하지 않는 컴포넌트는 리렌더링되지 않음. 애플리케이션 전체 리렌더링 방지. 이를 통해 <strong>성능을 최적화</strong>하고 리렌더링으로 인한 <strong>성능 저하를 방지</strong>할 수 있음.</p>
<h1 id="◼-설치-및-기본-사용법">◼ <strong>설치 및 기본 사용법</strong></h1>
<p><strong>설치</strong></p>
<pre><code class="language-bash">npm install zustand</code></pre>
<p><strong>기본 사용법</strong></p>
<pre><code class="language-jsx">// src/zustand/bearsStore.js

import { create } from &quot;zustand&quot;;

const useBearsStore = create((set) =&gt; ({
  bears: 0,
  increasePopulation: () =&gt; set((state) =&gt; ({ bears: state.bears + 1 })),
  removeAllBears: () =&gt; set({ bears: 0 }),
}));

export default useBearsStore;</code></pre>
<pre><code class="language-jsx">// src/App.jsx

import &quot;./App.css&quot;;
import useBearsStore from &quot;./zustand/bearsStore&quot;;

function App() {
  **const bears = useBearsStore((state) =&gt; state.bears);
  const increasePopulation = useBearsStore((state) =&gt; state.increasePopulation);**
  return (
    &lt;div&gt;
      &lt;h1&gt;{bears} around here ...&lt;/h1&gt;
      &lt;button onClick={increasePopulation}&gt;one up&lt;/button&gt;
    &lt;/div&gt;
  );
}
export default App;</code></pre>
<h1 id="◼-zustand-vs-redux-toolkit">◼ <strong>Zustand vs Redux Toolkit</strong></h1>
<ul>
<li><strong>설정과 사용법</strong>: Zustand는 간단한 설정과 직관적인 사용 방식 제공. Redux Toolkit은 더 구조화된 방법을 제공.</li>
<li><strong>성능</strong>: 두 라이브러리 모두 성능 최적화가 잘 되어 있지만, Zustand는 불필요한 리렌더링을 방지하는데 더 초점</li>
<li><strong>유연성</strong>: Zustand는 필요한 부분만 선택적으로 사용 가능. Redux Toolkit은 보다 강력한 구조화된 방법을 제공.</li>
<li><strong>커뮤니티와 자료</strong>: Zustand는 빠르게 성장 중이며 증가 추세. Redux Toolkit은 대형 커뮤니티와 풍부한 레퍼런스 보유.</li>
</ul>
<h2 id="zustand"><strong>Zustand</strong></h2>
<p>보일러플레이트 최소화. 상태 정의와 사용 과정 직관적.</p>
<pre><code class="language-jsx">import { create } from &quot;zustand&quot;;

const useStore = create(set =&gt; ({
  bears: 0,
  increasePopulation: () =&gt; set(state =&gt; ({ bears: state.bears + 1 }))
}))</code></pre>
<h2 id="redux-toolkit"><strong>Redux Toolkit</strong></h2>
<p>보일러플레이트 많음. 비교적 많은 설정 코드 요구.</p>
<pre><code class="language-jsx">import { configureStore, createSlice } from &#39;@reduxjs/toolkit&#39;

const slice = createSlice({
  name: &#39;counter&#39;,
  initialState: { value: 0 },
  reducers: {
    increment: state =&gt; { state.value += 1 }
  }
})

const store = configureStore({ reducer: slice.reducer })</code></pre>
<h1 id="◼-zustand-장점과-단점">◼ <strong>Zustand</strong> 장점과 단점</h1>
<h2 id="장점"><strong>장점</strong></h2>
<ul>
<li><strong>간편한 사용</strong>: 간단한 API와 직관적인 사용법 제공.</li>
<li><strong>성능 최적화</strong>: 불필요한 리렌더링을 방지.</li>
<li><strong>React와의 완벽한 통합</strong>: React Hooks 기반 자연스러운 통합 지원.</li>
<li><strong>미들웨어 지원</strong>: persist, devtools 등.</li>
<li><strong>유연성</strong>: 필요한 기능만 선택 사용 가능.</li>
</ul>
<h2 id="단점">단점</h2>
<ul>
<li><strong>규모가 커지면 관리 어려움</strong>: 상태가 많아지면 관리가 복잡해짐.</li>
</ul>
<h1 id="◼-예시">◼ <strong>예시</strong></h1>
<h2 id="기본-예제">기본 예제</h2>
<p><strong>상태 정의</strong></p>
<pre><code class="language-jsx">// src/zustand/todosStore.js

import { create } from &quot;zustand&quot;;

const useTodosStore = create(set =&gt; ({
  todos: [],
  addTodo: (todo) =&gt; set(state =&gt; ({ todos: [...state.todos, todo] })),
  removeTodo: (index) =&gt; set(state =&gt; ({
    todos: state.todos.filter((_, i) =&gt; i !== index)
  }))
}))

export default useTodosStore;</code></pre>
<p><strong>상태 사용</strong></p>
<pre><code class="language-jsx">// src/App.jsx

import React, { useState } from &quot;react&quot;;
import useTodosStore from &quot;./zustand/todosStore&quot;;

function App() {
  const todos = useTodosStore((state) =&gt; state.todos);
  const addTodo = useTodosStore((state) =&gt; state.addTodo);
  const removeTodo = useTodosStore((state) =&gt; state.removeTodo);
  const [input, setInput] = useState(&quot;&quot;);

  return (
    &lt;div&gt;
      &lt;h1&gt;Todo List&lt;/h1&gt;
      &lt;input value={input} onChange={(e) =&gt; setInput(e.target.value)} /&gt;
      &lt;button
        onClick={() =&gt; {
          addTodo(input);
          setInput(&quot;&quot;);
        }}
      &gt;
        Add Todo
      &lt;/button&gt;
      &lt;ul&gt;
        {todos.map((todo, index) =&gt; (
          &lt;li key={index}&gt;
            {todo} &lt;button onClick={() =&gt; removeTodo(index)}&gt;Remove&lt;/button&gt;
          &lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<h2 id="zustand와-immer의-결합"><strong>Zustand와 Immer의 결합</strong></h2>
<p><strong>immer란?</strong>
Immer는 JavaScript에서 상태를 쉽게 변경할 수 있게 해주는 라이브러리. 원본 데이터를 변경하지 않고 마치 직접 수정하는 것처럼 코드를 작성할 수 있으며, Immer가 자동으로 불변성을 유지한 새 상태를 만들어줌.</p>
<p><strong>직접 중첩된 상태를 업데이트 했을 때의 문제점</strong></p>
<p>Zustand의 간편한 상태 업데이트 방식은 깊은 중첩 구조를 가지는 상태를 업데이트할 때 문제가 발생할 수 있음.
<strong>배열이나 객체의 중첩된 상태를 업데이트</strong>할 때 불변성을 유지하지 않으면 상태 반영이 제대로 이루어지지 않아, 예상치 못한 오류가 발생할 수 있음. </p>
<pre><code class="language-jsx">import create from &quot;zustand&quot;;

// Zustand 스토어 생성
const useTodosStore = create((set) =&gt; ({
  todos: [],
  addTodo: (text) =&gt;
    set((state) =&gt; {
      // 불변성을 어기는 예시: 직접 배열을 수정
      state.todos.push({ id: Date.now(), text, completed: false });
      return state;
    }),
  toggleTodo: (id) =&gt;
    set((state) =&gt; {
      // 불변성을 어기는 예시: 직접 객체를 수정
      const todo = state.todos.find((todo) =&gt; todo.id === id);
      if (todo) {
        todo.completed = !todo.completed;
      }
      return state;
    }),
}));

export default useTodosStore;
</code></pre>
<pre><code class="language-jsx">// src/App.jsx

import useTodosStore from &quot;./zustand/todosStore&quot;;

function App() {
  const { todos, addTodo, toggleTodo } = useTodosStore();

  return (
    &lt;div&gt;
      &lt;ul&gt;
        {todos.map((todo) =&gt; (
          &lt;li key={todo.id}&gt;
            &lt;span
              style={{
                textDecoration: todo.completed ? &quot;line-through&quot; : &quot;none&quot;,
              }}
              onClick={() =&gt; toggleTodo(todo.id)}
            &gt;
              {todo.text}
            &lt;/span&gt;
          &lt;/li&gt;
        ))}
      &lt;/ul&gt;
      &lt;button
        onClick={() =&gt; addTodo(prompt(&quot;새로운 todolist를 입력해주세요.&quot;))}
      &gt;
        Add Todo
      &lt;/button&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<p>리스트가 추가/수정 되더라도 다른 요소에 의해 리렌더링이 일어나지 않는 한 UI에 반영이 안됨.</p>
<p><strong>immer 설치</strong></p>
<pre><code class="language-jsx">npm install immer</code></pre>
<p><strong>todosStore.js 수정</strong></p>
<pre><code class="language-jsx">import { create } from &quot;zustand&quot;;
import { immer } from &quot;zustand/middleware/immer&quot;;

const useTodosStore = create(
  immer((set) =&gt; ({
    todos: [
      {
        id: 1,
        title: &quot;Learn Zustand&quot;,
        tasks: [{ id: 1, task: &quot;Read documentation&quot;, done: false }],
      },
    ],
    addTask: (todoId, newTask) =&gt;
      set((state) =&gt; {
        const todo = state.todos.find((todo) =&gt; todo.id === todoId);
        if (todo) {
          todo.tasks.push(newTask); // 불변성 유지: immer가 자동으로 처리
        }
        // return { todos: state.todos }; // 변경된 참조가 기존 상태와 같아 리렌더링되지 않음
      }),
    toggleTask: (todoId, taskId) =&gt;
      set((state) =&gt; {
        const todo = state.todos.find((todo) =&gt; todo.id === todoId);
        if (todo) {
          const task = todo.tasks.find((task) =&gt; task.id === taskId);
          if (task) {
            task.done = !task.done; // 불변성 유지: immer가 자동으로 처리
          }
        }
        // return { todos: state.todos }; // 변경된 참조가 기존 상태와 같아 리렌더링되지 않음
      }),
  }))
);

export default useTodosStore;</code></pre>
<h2 id="기타-유용한-패턴"><strong>기타 유용한 패턴</strong></h2>
<p><strong>선택적 상태 구독</strong></p>
<p>상태의 특정 부분만 구독하여 성능 최적화 할 수 있음. 리렌더링 범위 최소화 가능.</p>
<pre><code class="language-jsx">// src/App.jsx

const todos = useTodosStore((state) =&gt; state.todos);
const addTask = useTodosStore((state) =&gt; state.addTask);
const toggleTask = useTodosStore((state) =&gt; state.toggleTask);</code></pre>
<p><strong>미들웨어 사용</strong></p>
<pre><code class="language-jsx">import { produce } from &quot;immer&quot;;
import { create } from &quot;zustand&quot;;
import { persist } from &quot;zustand/middleware&quot;;

const useTodosStore = create(
  persist(
    (set) =&gt; ({
      todos: [
        {
          id: 1,
          title: &quot;Learn Zustand&quot;,
          tasks: [{ id: 1, task: &quot;Read documentation&quot;, done: false }],
        },
      ],
      addTask: (todoId, newTask) =&gt;
        set(
          produce((state) =&gt; {
            const todo = state.todos.find((todo) =&gt; todo.id === todoId);
            if (todo) {
              todo.tasks.push(newTask); // 불변성 깨짐: 직접 수정
            }
            // return { todos: state.todos }; // 변경된 참조가 기존 상태와 같아 리렌더링되지 않음
          })
        ),
      toggleTask: (todoId, taskId) =&gt;
        set(
          produce((state) =&gt; {
            const todo = state.todos.find((todo) =&gt; todo.id === todoId);
            if (todo) {
              const task = todo.tasks.find((task) =&gt; task.id === taskId);
              if (task) {
                task.done = !task.done; // 불변성 깨짐: 직접 수정
              }
            }
            // return { todos: state.todos }; // 변경된 참조가 기존 상태와 같아 리렌더링되지 않음
          })
        ),
    }),
    {
      name: &quot;todos-storage&quot;, // 저장소 이름 설정.
      // getStorage: () =&gt; sessionStorage, // localStorage가 아닌 곳에 저정하고 싶다면.
    }
  )
);

export default useTodosStore;
</code></pre>
<p>persist를 이용해서 새로고침을 하더라도 데이터 유지. persist는 zustand 내장이므로 별도 설치할 필요가 없음. 
위와 같이 하면 새로고침 이후에도 localStorage에서 데이터를 관리하기 때문에 지속성을 유지할 수 있음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 심화] TanStack Query 3 - 심화]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-TanStack-Query-3-%EC%8B%AC%ED%99%94</link>
            <guid>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-TanStack-Query-3-%EC%8B%AC%ED%99%94</guid>
            <pubDate>Thu, 20 Mar 2025 09:23:49 GMT</pubDate>
            <description><![CDATA[<h1 id="◼-query-cancellation">◼ Query Cancellation</h1>
<p>다운로드 UI가 있거나 UX를 저해하는 불필요한 네트워크 요청을 제거할 때 사용.
대용량 fetching을 중간에 취소하거나, 사용하지 않는 컴포넌트에서 fetching이 진행 중이면 자동으로 취소해 불필요한 네트워크 비용을 절감함.</p>
<h2 id="사용방법">사용방법</h2>
<p><strong>QueryFunctionContext</strong>
<code>queryFn</code> 은 매개변수로 <code>QueryFunctionContext</code> 이란 객체를 받음.</p>
<pre><code class="language-jsx">export const getTodos = async (queryFnContext) =&gt; {
  const { queryKey, pageParam, signal, meta } = queryFnContext;
    // queryKey: 배열형태의 쿼리키
    // pageParam: useInfiniteQuery 사용 시 getNextPageParam 실행 결과 반영
    // signal: AbortSignal을 의미(네트워크 요청을 중간에 중단시킬 수 있는 장치)
    // meta: query에 대한 정보를 추가적으로 메모를 남길 수 있는 string 필드

  const response = await axios.get(&quot;http://localhost:5000/todos&quot;, { signal });
  return response.data;
};

useQuery({
  queryKey: [&quot;todos&quot;],
  queryFn: getTodos,
})
// example: &lt;div onClick={(event) =&gt; {}}</code></pre>
<p><strong>페이지 컴포넌트 unmount 시 Query 취소</strong>
API 요청의 기본 설정은 컴포넌트가 unmount 되어도 네트워크 요청이 중단되지 않음.
GET 요청은 abort signal이 옵션으로 전달된 경우에만 unmount 시 자동으로 네트워크 요청이 취소됨.</p>
<pre><code class="language-jsx">import axios from &#39;axios&#39;

const query = useQuery({
  queryKey: [&#39;todos&#39;],
  queryFn: ({ signal }) =&gt;
    axios.get(&#39;/todos&#39;, {
      // Pass the signal to `axios`
      signal,
    }),
})</code></pre>
<p><strong>수동으로 Query 취소</strong>
<code>queryClient.cancelQueries</code> 사용해 특정 쿼리 취소 가능.</p>
<pre><code class="language-jsx">const query = useQuery({
  queryKey: [&#39;todos&#39;],
  queryFn: async ({ signal }) =&gt; {
    const resp = await fetch(&#39;/todos&#39;, { signal })
    return resp.json()
  },
})

const queryClient = useQueryClient()

return (
  &lt;button
    onClick={(e) =&gt; {
      e.preventDefault()
      queryClient.cancelQueries({ queryKey: [&#39;todos&#39;] })
    }}
  &gt;
    Cancel
  &lt;/button&gt;
)</code></pre>
<h2 id="사용시-주의사항">사용시 주의사항</h2>
<p><strong>모든 GET 요청마다 Abort Signal 적용 필요 여부</strong>
불필요한 네트워크 요청을 최소화한다는 명분으로, 단순히 모든 GET 요청에 Abort Signal을 적용하는 것은 권장되지 않음.
동영상 다운로드처럼 대용량 fetching이 아닌 이상, 대부분의 GET 요청은 빠르게 완료되고 캐싱 처리되어 성능에 유의미한 영향을 주지 못함.
대용량 fetching이 있거나 Optimistic UI를 구현하는 경우처럼 필요한 상황에서만 적용하는 것을 권장.</p>
<h1 id="◼-optimistic-updates">◼ Optimistic Updates</h1>
<p>서버 요청이 성공할 것을 가정하고 UI를 먼저 변경한 뒤, 서버 요청을 보내는 방식.
요청이 실패하면 UI를 원상복구(revert/rollback)해야 함. 즉각적인 사용자 피드백을 제공할 수 있어 UX 향상에 효과적.</p>
<h1 id="◼-prefetching">◼ Prefetching</h1>
<p>페이지 이동 전에 이동할 페이지의 쿼리를 백그라운드에서 미리 호출(prefetching).
캐시 데이터가 있는 상태에서 해당 페이지로 이동하면 로딩 없이 바로 UI를 볼 수 있음.</p>
<pre><code class="language-jsx">const prefetchTodos = async () =&gt; {
  // 이동할 페이지의 queryKey, queryFn과 동일하게 설정 필요
  await queryClient.prefetchQuery({
    queryKey: [&#39;todos&#39;],
    queryFn: fetchTodos,
  })
}</code></pre>
<h1 id="◼-paginated--lagged-queries">◼ Paginated / Lagged Queries</h1>
<p>페이지 이동 시 매번 Loading UI를 표시하는 대신, 기존 UI를 유지하다가 서버에서 새로운 데이터를 받아온 뒤에 화면을 갱신하는 방식을 적용.
<code>useQuery</code> 옵션에서 <code>keepPreviousData: true</code>를 설정하면 이전 캐시 데이터를 기준으로 <code>isLoading</code> 여부를 판단하므로, 페이지 전환 시 UX를 더 부드럽게 구성할 수 있음.</p>
<h1 id="◼-infinite-queries">◼ Infinite Queries</h1>
<p>데이터 fetching이 일어날 때마다 기존 리스트 데이터에 fetching된 데이터를 추가하고 싶을 때 유용하게 사용할 수 있는 훅. 더보기 UI 또는 무한 스크롤 UI 구현에 적합.</p>
<pre><code class="language-jsx">const fetchProjects = async ({ pageParam = 0 }) =&gt; {
    const res = await fetch(&#39;/api/projects?cursor=&#39; + pageParam)
    return res.json()
  }

  const {
    data,
    error,
    fetchNextPage,
    hasNextPage,
    isFetching,
    isFetchingNextPage,
    status,
  } = useInfiniteQuery({
    queryKey: [&#39;projects&#39;],
    queryFn: fetchProjects,
    getNextPageParam: (lastPage, pages) =&gt; lastPage.nextCursor,
  })</code></pre>
<h2 id="실행-순서-정리">실행 순서 정리</h2>
<ol>
<li><code>queryFn</code> 실행</li>
<li>캐시에 <code>{ pages, pageParams }</code> 형태로 데이터 등록</li>
<li><code>getNextPageParam</code> 실행</li>
<li>반환된 <code>nextPageParam</code>을 훅 내부 메모리에 저장 (캐시 저장 아님)</li>
<li><code>nextPageParam</code>이 <code>undefined</code>가 아닐 경우 <code>hasNextPage</code>를 <code>true</code>로 상태 변경</li>
<li><code>fetchNextPage</code> 실행</li>
<li>내부 저장된 <code>nextPageParam</code>을 <code>queryFn</code>의 매개변수로 전달</li>
</ol>
<h2 id="pages-와-pageparams-를-갖는-캐시-데이터">pages 와 pageParams 를 갖는 캐시 데이터</h2>
<ul>
<li><code>useQuery</code>는 <code>queryFn</code>의 반환값이 그대로 캐시 데이터로 등록됨.</li>
<li><code>useInfiniteQuery</code>는 <code>queryFn</code>의 반환값이 <code>pages</code> 배열 요소로 추가됨.</li>
<li>매개변수로 전달받은 <code>pageParam</code>은 <code>pageParams</code> 배열 요소로 추가됨.</li>
</ul>
<h2 id="useinfinitequery-사용-시-주의사항">useInfiniteQuery 사용 시 주의사항</h2>
<ul>
<li>훅의 내부 동작 원리로 인해 예상보다 리렌더링이 자주 발생할 수 있음.</li>
<li>연산량이 많은 코드가 있다면 <code>useMemo</code>와 같은 메모이제이션 적용을 고려해야 함.</li>
<li>리렌더링이 발생하더라도 실제 브라우저 렌더링이 항상 발생하는 것은 아님. Virtual DOM 기반 diff 과정을 거친 뒤 변경 사항만 반영됨.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 심화] TanStack Query 2 - 동작원리]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-TanStack-Query-2-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-TanStack-Query-2-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Wed, 19 Mar 2025 02:12:16 GMT</pubDate>
            <description><![CDATA[<h1 id="📌">📌</h1>
<p>TanStack Query는 <strong>서버 상태 관리</strong>를 위한 라이브러리. <strong>서버 상태</strong>란 서버와의 통신을 통해 받아오는 데이터 의미.</p>
<p>클라이언트 상태와 달리 다음과 같은 관리 필요.</p>
<ul>
<li><strong>Fetching(패칭)</strong> : 서버에서 데이터를 가져옴.</li>
<li><strong>Caching(캐싱)</strong> : 가져온 데이터를 캐싱하여, 동일한 데이터를 반복해서 요청하지 않음.</li>
<li><strong>Synchronizing(동기화)</strong> : 서버의 데이터와 캐시된 데이터를 동기화.</li>
<li><strong>Updating(업데이트)</strong> : 서버의 데이터를 쉽게 업데이트하고, 이를 캐시에 반영.</li>
</ul>
<p>TanStack Query는 위 과정을 <strong>자동화 및 간소화 지원</strong>.</p>
<h2 id="stale-while-revalidateswr-전략">stale-while-revalidate(swr) 전략</h2>
<p>SWR은 <strong>최신 데이터가 도착하기 전</strong>까지 <strong>기존 캐시 데이터를 사용</strong>하는 전략. 
이를 통해 사용자는 최신 데이터를 기다리는 동안에도 즉시 UI 응답이 가능. 
TanStack Query는 <strong>SWR 전략을 사용</strong>하여 효율적으로 데이터를 관리. </p>
<h2 id="캐시-데이터는-어디에-보관">캐시 데이터는 어디에 보관?</h2>
<p><code>TanStack Query</code>는 캐시 데이터를 <strong>전역으로 관리</strong>함. 
<code>QueryClientProvider</code>를 사용하면 React 애플리케이션 전체에서 캐시에 접근할 수 있음. 
이는 내부적으로 <code>React Context API</code>를 사용하기 때문임. Provider 하위의 모든 <strong>자식 컴포넌트가 캐시 데이터에 접근</strong>할 수 있음.
이 내부 Context를 앞으로 <strong>캐시 컨텍스트</strong>, 그 안의 데이터를 <strong>캐시 데이터</strong>로 정의.</p>
<p><strong>Provider 설정</strong></p>
<pre><code class="language-jsx">// src/main.jsx
// &lt;Provider&gt;로 감싸준 범위 내부의 자식 컴포넌트 안에서 캐시 데이터를 공유할 수 있음.

import ReactDOM from &quot;react-dom/client&quot;;
import App from &quot;./App.jsx&quot;;
import &quot;./index.css&quot;;
import { QueryClient, QueryClientProvider } from &quot;@tanstack/react-query&quot;;

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById(&quot;root&quot;)).render(
  &lt;QueryClientProvider client={queryClient}&gt;
    &lt;App /&gt;
  &lt;/QueryClientProvider&gt;
);</code></pre>
<h1 id="◼-데이터-흐름">◼ 데이터 흐름</h1>
<p><strong>A 컴포넌트</strong></p>
<ol>
<li><code>useQuery</code> 실행. 이때 <code>todos</code>라는 <code>queryKey</code>를 기준으로 캐시 컨텍스트에 데이터를 요청.</li>
<li>초기 상태에서는 <code>todos</code>라는 <code>queryKey</code>에 아무 값도 저장되어 있지 않으므로 <code>const { data } = useQuery~~</code>의 data는 <strong>undefined</strong>가 됨.</li>
<li>이후 <code>getTodos</code>(query function)를 호출하고, 실행이 완료되면 외부에서 가져온 <code>todos</code> 데이터를 캐시 컨텍스트에 저장. 이 과정을 [&quot;todos&quot;]에 대한 데이터로 캐싱 처리한다고 표현.</li>
<li>A 컴포넌트에 값을 반영하기 전에 리렌더링이 발생.
React 컴포넌트는 상태나 props가 변경되면 다시 렌더링됨. <code>useQuery</code> 역시 데이터 패칭 상태가 변경될 때 컴포넌트를 다시 렌더링함.</li>
<li><code>useQuery</code>가 재실행되며, <code>todos</code>라는 <code>queryKey</code>를 기준으로 캐시 컨텍스트에 데이터를 요청.</li>
<li>이제 캐싱된 데이터가 존재하므로 값을 반환하고, <code>const { data } = useQuery~~</code>의 data에 값이 담김.</li>
</ol>
<p><strong>B 컴포넌트</strong></p>
<ol>
<li>B 컴포넌트에서 <code>useQuery</code>가 실행. 이때 <code>todos</code>라는 <code>queryKey</code>를 기준으로 캐시 컨텍스트에 데이터를 요청.</li>
<li>이미 캐싱된 데이터가 존재하므로 값을 반환하고, <code>const { data } = useQuery~~</code>의 data에 값이 담김.</li>
</ol>
<p><strong>C 컴포넌트</strong></p>
<ol>
<li>C 컴포넌트에서 어떤 액션으로 <code>addTodo</code>라는 API 호출이 발생하며 데이터 갱신을 시도.</li>
<li>호출이 성공적으로 완료되어도, 이때 <code>useQuery</code>에서 사용하던 캐시 데이터가 자동으로 갱신되지는 않음. 반드시 해당 <code>queryKey</code>를 기준으로 <code>invalidateQueries</code> 처리를 해줘야 기존에 가져왔던 오래된 데이터를 새 것으로 교체할 수 있음.</li>
<li><code>invalidateQueries</code>를 처리하면 <code>useQuery</code>로 캐시 데이터를 활용하던 모든 곳이 새로운(fresh) 데이터를 구독.</li>
</ol>
<h1 id="◼-lifecycle">◼ LifeCycle</h1>
<h2 id="캐시-데이터-lifecycle">캐시 데이터 LifeCycle</h2>
<p>TanStack Query의 생명주기는 데이터가 캐시되고 사용되며 갱신되는 과정을 포함함. 
아래는 주요 상태를 설명함.</p>
<table>
<thead>
<tr>
<th><strong>상태</strong></th>
<th><strong>설명</strong></th>
</tr>
</thead>
<tbody><tr>
<td>fresh</td>
<td>staleTime 경과 전 상태. 재패칭 불필요. 캐시 그대로 사용 가능.</td>
</tr>
<tr>
<td>(데이터를 새로 패칭할 필요가 없는 상태. <strong><code>staleTime</code></strong>이 지나지 않은 상태로, 캐시 데이터를 그대로 사용할 수 있음.)</td>
<td></td>
</tr>
<tr>
<td>stale</td>
<td>staleTime 경과 상태. 재패칭 대상.</td>
</tr>
<tr>
<td>(데이터를 새로 패칭해야 하는 상태. <strong><code>staleTime</code></strong>이 지난 후로, 새로운 데이터를 가져오기 위해 쿼리가 실행됨.)</td>
<td></td>
</tr>
<tr>
<td>active</td>
<td>현재 컴포넌트에서 사용 중인 쿼리.</td>
</tr>
<tr>
<td>(현재 컴포넌트에서 사용 중인 쿼리 상태. 컴포넌트가 마운트되어 쿼리를 사용하고 있을 때를 말함.)</td>
<td></td>
</tr>
<tr>
<td>inactive</td>
<td>더 이상 구독되지 않는 쿼리.</td>
</tr>
<tr>
<td>(더 이상 사용되지 않는 쿼리 상태. 컴포넌트가 언마운트되거나 쿼리가 더 이상 필요하지 않을 때를 말함.)</td>
<td></td>
</tr>
<tr>
<td>deleted</td>
<td>gcTime 경과 후 캐시에서 제거된 상태.</td>
</tr>
<tr>
<td>(캐시에서 제거된 쿼리 상태. <strong><code>gcTime</code></strong> 이 지나면 쿼리가 캐시에서 삭제되어 이 상태가 됨.)</td>
<td></td>
</tr>
<tr>
<td>fetching</td>
<td>서버 요청 진행 중 상태. isFetching = true</td>
</tr>
<tr>
<td>(데이터를 서버에서 가져오고 있는 상태. 이 상태에서는 <strong><code>isFetching</code></strong>이 true로 설정됨.)</td>
<td></td>
</tr>
</tbody></table>
<h2 id="default-config기본-설정">default config(기본 설정)</h2>
<table>
<thead>
<tr>
<th><strong>기본 설정</strong></th>
<th><strong>의미</strong></th>
</tr>
</thead>
<tbody><tr>
<td>staleTime: 0</td>
<td>기본적으로 모든 데이터 stale 취급.</td>
</tr>
<tr>
<td>(useQuery 또는 useInfiniteQuery에 등록된 queryFn 을 통해 fetch 받아온 데이터는 항상 stale data 취급.)</td>
<td></td>
</tr>
<tr>
<td>refetchOnMount: true</td>
<td>마운트 시 stale 데이터 자동 재요청.</td>
</tr>
<tr>
<td>(useQuery 또는 useInfiniteQuery 가 있는 컴포넌트가 마운트 시 stale data 를 refetch 자동 실행.)</td>
<td></td>
</tr>
<tr>
<td>refetchOnWindowFocus: true</td>
<td>브라우저 포커스 시 stale 데이터 재요청.</td>
</tr>
<tr>
<td>(실행중인 브라우저 화면을 focus 할 때 마다 stale data를 refetch 자동 실행.)</td>
<td></td>
</tr>
<tr>
<td>refetchOnReconnect: true</td>
<td>네트워크 재연결 시 stale 데이터 재요청.</td>
</tr>
<tr>
<td>(Network 가 끊겼다가 재연결 되었을 때 stale data를 refetch 자동 실행.)</td>
<td></td>
</tr>
<tr>
<td>gcTime(cacheTime): 5분</td>
<td></td>
</tr>
<tr>
<td>(1000 * 60 * 5 ms)</td>
<td>inactive 상태 5분 후 캐시 삭제.</td>
</tr>
<tr>
<td>(useQuery 또는 useInfiniteQuery가 있는 컴포넌트가 언마운트 되었을 때 inactive query라 부르며, inactive 상태가 5분 경과 후 GC(가비지콜렉터)에 의해 cache data 삭제 처리.)</td>
<td></td>
</tr>
<tr>
<td>retry: 3</td>
<td>실패 시 3회 자동 재시도.</td>
</tr>
<tr>
<td>(useQuery 또는 useInfiniteQuery에 등록된 queryFn 이 API 서버에 요청을 보내서 실패하더라도 바로 에러를 띄우지 않고 총 3번까지 재요청을 자동으로 시도.)</td>
<td></td>
</tr>
</tbody></table>
<h2 id="헷갈리는-개념-정리">헷갈리는 개념 정리</h2>
<ul>
<li><strong>staleTime</strong> : 데이터를 fresh로 유지할 시간 설정(default: 0)</li>
<li><strong>gcTime</strong> : inactive 이후 캐시 유지 시간(default: 5분, gcTime 0되면 삭제처리)</li>
<li><strong>staleTime과 stale/fresh 관계</strong> : staleTime &gt; 0 → 일정 시간 fresh 유지, staleTime = 0 → 즉시 stale 취급.</li>
<li><strong>isPending</strong> : 새로운 캐시 데이터를 서버에서 받고 있는 지 여부. 캐시 데이터가 있는 경우 isPending은 false, isFetching은 true.</li>
<li><strong>isFetching</strong> : 서버에서 데이터를 받고 있는 지 여부.</li>
</ul>
<h1 id="◼-must-know-options">◼ Must-know options</h1>
<h2 id="enabled">enabled</h2>
<p><code>enabled</code> 옵션은 쿼리(queryFn) 실행 여부를 제어함. 기본값은 <code>true</code>이며, <code>false</code>로 설정하면 쿼리가 자동으로 실행되지 않음. 이 옵션을 사용하면 특정 이벤트가 발생했을 때만 쿼리를 실행할 수 있음.</p>
<pre><code class="language-jsx">useQuery({
    queryKey: [&quot;todos&quot;],
    queryFn: getTodos,
    enabled: true
})</code></pre>
<h3 id="예제">예제</h3>
<p><strong>Disabling/Pausing Queries (이벤트 발생 시에만 수동 실행하고 싶을 때)</strong></p>
<pre><code class="language-jsx">const { data, refetch } = useQuery({
    queryKey: [&quot;todos&quot;],
    queryFn: getTodos,
    enabled: false
});

return (
    &lt;div&gt;
    &lt;button onClick={() =&gt; refetch()}&gt;데이터 불러오기&lt;/button&gt;
  &lt;/div&gt;
);</code></pre>
<p><strong>Dependent Queries(useQuery 2개 이상이며 실행순서 설정 필요할 때)</strong></p>
<pre><code class="language-jsx">// Dependent Query 예제 (순차적 query 실행)
// Get the user
const { data: user } = useQuery({
  queryKey: [&#39;user&#39;, email],
  queryFn: getUserByEmail,
})

const userId = user?.id

// Then get the user&#39;s projects
const {
  status,
  fetchStatus,
  data: projects,
} = useQuery({
  queryKey: [&#39;projects&#39;, userId],
  queryFn: getProjectsByUser,
  // userId 존재할 때만 두 번째 쿼리 실행 가능.
  enabled: !!userId
})
// userId는 Boolean(userId)와 같음.</code></pre>
<h2 id="select">select</h2>
<p><code>select</code> 옵션은 queryFn이 반환한 데이터를 가공해 사용할 때 활용 가능. UI에서 필요한 데이터만 추출하거나 변환하는 용도로 사용 가능. 단, 캐시에는 항상 원본 데이터 유지. 가공된 데이터는 해당 useQuery를 사용하는 컴포넌트에만 적용됨.</p>
<h3 id="예제-1">예제</h3>
<pre><code class="language-jsx">import { useQuery } from &#39;react-query&#39;

function User() {
  const { data } = useQuery({
      queryKey: [&quot;user&quot;],
      queryFn: fetchUser,
      select: user =&gt; user.username
  });

  return &lt;div&gt;Username: {data}&lt;/div&gt;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 심화] TanStack Query 1 - 등장배경, 기본사용법]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-TanStack-Query-1-%EB%93%B1%EC%9E%A5%EB%B0%B0%EA%B2%BD-%EA%B8%B0%EB%B3%B8%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-TanStack-Query-1-%EB%93%B1%EC%9E%A5%EB%B0%B0%EA%B2%BD-%EA%B8%B0%EB%B3%B8%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Mon, 17 Mar 2025 21:21:07 GMT</pubDate>
            <description><![CDATA[<h1 id="📌">📌</h1>
<p>TanStack Query는 <strong>서버 상태 관리 라이브러리</strong>. 
데이터 패칭, 캐싱, 동기화, 무효화 등의 기능을 제공. 비동기 로직을 간결하게 작성할 수 있어 유지보수성을 높일 수 있음.</p>
<h1 id="◼-등장배경">◼ 등장배경</h1>
<ol>
<li><strong>비동기 로직의 복잡성 해결 필요</strong><ul>
<li>기존의 <strong><code>useEffect</code></strong>, <strong><code>useState</code></strong>를 사용한 비동기 데이터 처리 방식은 상태 관리 로직이 분산되기 쉬움. 로딩, 에러, 데이터 상태를 각각 직접 관리해야 하므로 코드 중복이 발생할 수 있음.</li>
<li>Redux Thunk와 같은 미들웨어를 사용하더라도 비동기 로직 테스트가 복잡하고 보일러플레이트 코드가 많이 생기므로, 더 효율적인 도구가 필요.</li>
</ul>
</li>
<li><strong>서버 상태 관리의 어려움</strong><ul>
<li>서버 상태는 단순 클라이언트 상태와 달리 캐싱, 동기화, 재검증 등 관리 요소가 많아 기존 방식으로는 관리가 어려움.</li>
</ul>
</li>
</ol>
<p>→ 이러한 문제를 해결하기 위해 등장한 라이브러리가 <strong>TanStack Query</strong>.</p>
<p>→ 복잡한 비동기 로직을 간결하게 작성할 수 있음.</p>
<p>→ 서버 상태 관리를 단순화할 수 있음.</p>
<h1 id="◼-주요기능">◼ 주요기능</h1>
<ul>
<li><strong>데이터 캐싱</strong>: 동일한 데이터를 반복 요청하지 않도록 캐싱해 성능을 향상시킴.</li>
<li><strong>자동 리페칭</strong>: 데이터가 변경되면 자동으로 리페칭해 최신 상태를 유지함.</li>
<li><strong>쿼리 무효화</strong>: 특정 이벤트 발생 시 쿼리를 무효화하고 데이터를 다시 가져옴.</li>
</ul>
<h1 id="◼-설정">◼ 설정</h1>
<p><strong>프로젝트 생성</strong></p>
<pre><code class="language-bash">npm create vite tanstack-query-app --template react</code></pre>
<p><strong>설치</strong></p>
<pre><code class="language-bash">npm install @tanstack/react-query</code></pre>
<p>전역 적용을 위해 Provider 사용. <code>App.jsx</code> 또는 <code>main.jsx(index.jsx)</code>에 세팅 권장.</p>
<pre><code class="language-jsx">// main.jsx

import ReactDOM from &quot;react-dom/client&quot;;
import App from &quot;./App.jsx&quot;;
import &quot;./index.css&quot;;
import { QueryClient, QueryClientProvider } from &quot;@tanstack/react-query&quot;;

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById(&quot;root&quot;)).render(
  &lt;QueryClientProvider client={queryClient}&gt;
    &lt;App /&gt;
  &lt;/QueryClientProvider&gt;
);</code></pre>
<h1 id="◼-usequery">◼ useQuery</h1>
<p><code>useQuery</code>는 데이터 조회 훅. 
<code>queryKey</code>와 <code>queryFn</code>을 인자로 전달해 사용. 데이터, 로딩 상태, 에러 상태를 반환하므로 모든 상태를 직접 세팅할 필요 없음.</p>
<h2 id="기본-사용법">기본 사용법</h2>
<p><code>fetchTodos</code>와 같은 비동기 함수는 별도 파일 분리 권장. 현재는 학습 목적상 한 파일에서 진행.</p>
<pre><code class="language-jsx">import { useQuery } from &quot;@tanstack/react-query&quot;;
import axios from &quot;axios&quot;;

const App = () =&gt; {
  const fetchTodos = async () =&gt; {
    const response = await axios.get(&quot;http://localhost:4000/todos&quot;);
    return response.data;
  };

  const {
    data: todos,
    isPending,
    isError,
  } = useQuery({
    queryKey: [&quot;todos&quot;],
    queryFn: fetchTodos,
  });

  if (isPending) {
    return &lt;div&gt;로딩중입니다...&lt;/div&gt;;
  }

  if (isError) {
    return &lt;div&gt;데이터 조회 중 오류가 발생했습니다.&lt;/div&gt;;
  }

  return (
    &lt;div&gt;
      &lt;h3&gt;TanStack Query&lt;/h3&gt;
      &lt;ul&gt;
        {todos.map((todo) =&gt; {
          return (
            &lt;li
              key={todo.id}
              style={{
                display: &quot;flex&quot;,
                alignItems: &quot;center&quot;,
                gap: &quot;10px&quot;,
                backgroundColor: &quot;aliceblue&quot;,
              }}
            &gt;
              &lt;h4&gt;{todo.title}&lt;/h4&gt;
              &lt;p&gt;{todo.isDone ? &quot;Done&quot; : &quot;Not Done&quot;}&lt;/p&gt;
            &lt;/li&gt;
          );
        })}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
};

export default App;</code></pre>
<p><strong>테스트 환경(db.json 파일)</strong></p>
<pre><code class="language-json">{
  &quot;todos&quot;: [
    {
      &quot;id&quot;: &quot;1715926482394&quot;,
      &quot;title&quot;: &quot;리액트 공부하기&quot;,
      &quot;isDone&quot;: true
    },
    {
      &quot;id&quot;: &quot;1715926492887&quot;,
      &quot;title&quot;: &quot;Node.js 공부하기&quot;,
      &quot;isDone&quot;: true
    },
    {
      &quot;id&quot;: &quot;1715926495834&quot;,
      &quot;title&quot;: &quot;영화보기&quot;,
      &quot;isDone&quot;: false
    }
  ]
}</code></pre>
<h1 id="◼-usemutation">◼ useMutation</h1>
<p><code>useMutation</code>은 <strong>생성(Create), 수정(Update), 삭제(Delete)</strong> 작업 전용 훅. 
CUD 비동기 작업을 처리할 수 있음. 성공 또는 실패 후 추가 작업을 실행할 수 있음. 
작업 완료 후 관련 쿼리를 무효화할 수 있으며, <strong>최신 데이터 유지</strong>에 필수적임.</p>
<h2 id="기본-사용법-1">기본 사용법</h2>
<p><code>addTodo</code>도 별도 파일 분리 권장. 현재는 학습 목적상 한 파일에서 진행.</p>
<pre><code class="language-jsx">import { useMutation, useQuery } from &quot;@tanstack/react-query&quot;;
import axios from &quot;axios&quot;;
import { useState } from &quot;react&quot;;

const App = () =&gt; {
  const [todoItem, setTodoItem] = useState(&quot;&quot;);

  const fetchTodos = async () =&gt; {
    const response = await axios.get(&quot;http://localhost:4000/todos&quot;);
    return response.data;
  };

  const addTodo = async (newTodo) =&gt; {
    await axios.post(&quot;http://localhost:4000/todos&quot;, newTodo);
  };

  const {
    data: todos,
    isPending,
    isError,
  } = useQuery({
    queryKey: [&quot;todos&quot;],
    queryFn: fetchTodos,
  });

  const { mutate } = useMutation({
    mutationFn: addTodo,
  });

  if (isPending) {
    return &lt;div&gt;로딩중입니다...&lt;/div&gt;;
  }

  if (isError) {
    return &lt;div&gt;데이터 조회 중 오류가 발생했습니다.&lt;/div&gt;;
  }

  return (
    &lt;div&gt;
      &lt;h3&gt;TanStack Query&lt;/h3&gt;
      &lt;form
        onSubmit={(e) =&gt; {
          e.preventDefault();

          const newTodoObj = { title: todoItem, isDone: false };

          // useMutation 로직 필요
          mutate(newTodoObj);
        }}
      &gt;
        &lt;input
          type=&quot;text&quot;
          value={todoItem}
          onChange={(e) =&gt; setTodoItem(e.target.value)}
        /&gt;
        &lt;button&gt;추가&lt;/button&gt;
      &lt;/form&gt;
      &lt;ul&gt;
        {todos.map((todo) =&gt; {
          return (
            &lt;li
              key={todo.id}
              style={{
                display: &quot;flex&quot;,
                alignItems: &quot;center&quot;,
                gap: &quot;10px&quot;,
                backgroundColor: &quot;aliceblue&quot;,
              }}
            &gt;
              &lt;h4&gt;{todo.title}&lt;/h4&gt;
              &lt;p&gt;{todo.isDone ? &quot;Done&quot; : &quot;Not Done&quot;}&lt;/p&gt;
            &lt;/li&gt;
          );
        })}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
};

export default App;</code></pre>
<h1 id="◼-invalidatequeries">◼ invalidateQueries</h1>
<p><code>invalidateQueries</code>는 특정 쿼리를 무효화하여 해당 데이터를 다시 패칭하도록 만드는 함수. 
주로 <code>useMutation</code>과 함께 사용하며, 서버 데이터가 변경된 이후 관련 쿼리를 다시 가져오는 역할을 함. 
이를 통해 화면에 표시되는 데이터가 항상 최신 상태로 유지될 수 있도록 도와줌. 
예를 들어, 새로운 할 일을 추가한 뒤 <code>invalidateQueries</code>를 실행하면 기존의 할 일 목록을 다시 조회하게 되고, 추가된 항목이 포함된 최신 목록이 화면에 반영됨.</p>
<h2 id="기본-사용법-2">기본 사용법</h2>
<pre><code class="language-jsx">import { useMutation, useQuery, useQueryClient } from &quot;@tanstack/react-query&quot;;
import axios from &quot;axios&quot;;
import { useState } from &quot;react&quot;;

const App = () =&gt; {
  const queryClient = useQueryClient();

  const [todoItem, setTodoItem] = useState(&quot;&quot;);

  const fetchTodos = async () =&gt; {
    const response = await axios.get(&quot;http://localhost:4000/todos&quot;);
    return response.data;
  };

  const addTodo = async (newTodo) =&gt; {
    await axios.post(&quot;http://localhost:4000/todos&quot;, newTodo);
  };

  const {
    data: todos,
    isPending,
    isError,
  } = useQuery({
    queryKey: [&quot;todos&quot;],
    queryFn: fetchTodos,
  });

  const { mutate } = useMutation({
    mutationFn: addTodo,
    onSuccess: () =&gt; {
      // alert(&quot;데이터 삽입이 성공했습니다.&quot;);
      // ✅ invalidateQueries 추가
      queryClient.invalidateQueries([&quot;todos&quot;]);
    },
  });

  if (isPending) {
    return &lt;div&gt;로딩중입니다...&lt;/div&gt;;
  }

  if (isError) {
    return &lt;div&gt;데이터 조회 중 오류가 발생했습니다.&lt;/div&gt;;
  }

  return (
    &lt;div&gt;
      &lt;h3&gt;TanStack Query&lt;/h3&gt;
      &lt;form
        onSubmit={(e) =&gt; {
          e.preventDefault();

          const newTodoObj = { title: todoItem, isDone: false };

          // useMutation 로직 필요
          mutate(newTodoObj);
        }}
      &gt;
        &lt;input
          type=&quot;text&quot;
          value={todoItem}
          onChange={(e) =&gt; setTodoItem(e.target.value)}
        /&gt;
        &lt;button&gt;추가&lt;/button&gt;
      &lt;/form&gt;
      &lt;ul&gt;
        {todos.map((todo) =&gt; {
          return (
            &lt;li
              key={todo.id}
              style={{
                display: &quot;flex&quot;,
                alignItems: &quot;center&quot;,
                gap: &quot;10px&quot;,
                backgroundColor: &quot;aliceblue&quot;,
              }}
            &gt;
              &lt;h4&gt;{todo.title}&lt;/h4&gt;
              &lt;p&gt;{todo.isDone ? &quot;Done&quot; : &quot;Not Done&quot;}&lt;/p&gt;
            &lt;/li&gt;
          );
        })}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
};

export default App;</code></pre>
<p><code>useMutation</code>만 사용하면 “서버 변경”까지만 처리 가능(화면은 그대로).
<code>invalidateQueries</code>까지 추가해야 “UI 동기화”까지 완료됨.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 심화] axios 2 - custom instance, interceptors]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-axios-2-custom-instance-interceptors</link>
            <guid>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-axios-2-custom-instance-interceptors</guid>
            <pubDate>Fri, 14 Mar 2025 08:48:37 GMT</pubDate>
            <description><![CDATA[<h1 id="◼-custom-instance의-개념과-필요성">◼ <strong>Custom Instance의 개념과 필요성</strong></h1>
<h2 id="custom-instance의-필요성"><strong>Custom Instance의 필요성</strong></h2>
<p>지금까지는 아래와 같이 데이터를 통신함.</p>
<pre><code class="language-jsx">const data = await axios.get(&quot;http://localhost:4000/&quot;);</code></pre>
<p>별도의 설정 없이 기본 Axios 그대로 사용한 방식임. 하지만 애플리케이션 규모가 커질수록 동일한 설정을 반복 작성하게 됨. baseURL, 공통 헤더 등을 매 요청마다 작성해야 하는 비효율 발생. </p>
<p>이 문제를 해결하기 위해 <strong>Axios 인스턴스 사용 가능</strong>. <strong>공통 설정을 한 곳에서 관리</strong> 가능.</p>
<h2 id="custom-instance-생성"><strong>Custom Instance 생성</strong></h2>
<p>Axios 인스턴스를 사용하면 설정 중앙 관리 가능.
예를 들어 baseURL 설정 시, 서버 주소가 변경되더라도 한 파일만 수정하면 전체 요청에 반영 가능.</p>
<pre><code class="language-jsx">// src/axios/api.js

import axios from &quot;axios&quot;;

// axios.create의 입력값으로 들어가는 객체는 configuration 객체.
// https://axios-http.com/docs/req_config 참고
const api = axios.create({
  baseURL: &quot;http://localhost:4000&quot;,
});

export default api;</code></pre>
<p>이제 생성한 instance 사용하여 HTTP 요청 전송 가능.</p>
<pre><code class="language-jsx">// App.jsx

import &quot;./App.css&quot;;
import { useEffect } from &quot;react&quot;;
import api from &quot;./axios/api&quot;;

function App() {
  useEffect(() =&gt; {
    api
      .get(&quot;/cafe&quot;)
      .then((res) =&gt; {
        console.log(&quot;결과 =&gt; &quot;, res.data);
      })
      .catch((err) =&gt; {
        console.log(&quot;오류가 발생하였습니다!&quot;);
      });
  }, []);

  return &lt;div&gt;axios 예제입니다.&lt;/div&gt;;
}

export default App;</code></pre>
<p>서버 주소 변경 시 <code>api.js</code>만 수정하면 전체 요청에 반영 가능. 유지보수 용이.</p>
<h1 id="◼-interceptor의-개념과-필요성">◼ <strong>Interceptor의 개념과 필요성</strong></h1>
<h2 id="interceptor의-필요성"><strong>Interceptor의 필요성</strong></h2>
<p>Interceptor는 HTTP 요청과 응답을 <strong>가로채는 기능 제공</strong>. 요청 전, 응답 전 특정 로직 실행 가능. </p>
<p>동작 시점은 다음과 같음.</p>
<ol>
<li>요청(request) 전송 직전(요청을 보내기 전, 요청이 출발하기 전)</li>
<li>응답(response) 처리 직전(then(성공), catch(실패) 실행 전)</li>
</ol>
<p>즉, 요청/응답 흐름 중간에 개입 가능.</p>
<p>대표 활용 예시는 다음과 같음.</p>
<ul>
<li>공통 요청 헤더 추가</li>
<li>인증 관리(인증 토큰 자동 삽입)</li>
<li>로그 처리(로그 관련 로직 삽입)</li>
<li>공통 에러 핸들링</li>
</ul>
<p><strong>공통 로직을 한 곳에서 관리</strong> 가능. <strong>중복 코드 제거</strong> 가능.</p>
<h2 id="interceptor-적용"><strong>Interceptor 적용</strong></h2>
<p>요청 및 응답 시 특정 로직 실행 예시.</p>
<pre><code class="language-jsx">// src/axios/api.js

import axios from &quot;axios&quot;;

const instance = axios.create({
  baseURL: &quot;http://localhost:4000&quot;,
});

instance.interceptors.request.use(
  function (config) {
    // 요청을 보내기 전 수행
    console.log(&quot;인터셉트 요청 성공!&quot;);
    return config;
  },
  function (error) {
    // 오류 요청을 보내기 전 수행
    console.log(&quot;인터셉트 요청 오류.&quot;);
    return Promise.reject(error);
  }
);

instance.interceptors.response.use(
  function (response) {
    // 정상 응답 시
    console.log(&quot;인터셉트 응답 성공!&quot;);
    return response;
  },
  function (error) {
      // 응답 에러 발생 시
    console.log(&quot;인터셉트 응답 실패.&quot;);
    return Promise.reject(error);
  }
);

export default instance;</code></pre>
<p>브라우저 콘솔에서 요청/응답 중간에 인터셉터 실행 로그 확인 가능.</p>
<h2 id="요청실패-상황-구성"><strong>요청실패 상황 구성</strong></h2>
<pre><code class="language-jsx">// src/axios/api.js

import axios from &quot;axios&quot;;

const instance = axios.create({
  baseURL: &quot;http://localhost:4000&quot;,
  timeout: 1, // 1ms
});

export default instance;</code></pre>
<p>타임아웃 설정을 통해 강제로 실패 상황 구성. 이렇게 설정하면 1ms의 짧은 시간 안에 서버 응답을 받지 못해 오류가 발생. 요청은 성공했지만 응답을 받지 못한 로그를 보면 타임아웃이 초과되었음을 알 수 있음.</p>
<p>요청 주소를 잘못 입력하거나 json-server를 종료한 뒤 테스트해볼 수도 있음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 심화] axios 1 - 소개 및 설정]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-axios-1-%EC%86%8C%EA%B0%9C-%EB%B0%8F-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-axios-1-%EC%86%8C%EA%B0%9C-%EB%B0%8F-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Thu, 13 Mar 2025 11:52:09 GMT</pubDate>
            <description><![CDATA[<h1 id="📌">📌</h1>
<p>axios는 node.js와 브라우저를 위한 Promise 기반 HTTP 클라이언트.
즉, http를 이용해 서버와 통신할 때 사용하는 패키지.</p>
<h1 id="◼-설치"><strong>◼ 설치</strong></h1>
<pre><code class="language-jsx">npm install axios</code></pre>
<h1 id="◼-설정">◼ 설정</h1>
<h2 id="dbjson-설정"><strong>db.json 설정</strong></h2>
<p>프로젝트 root 경로에 db.json 파일 생성 후 아래 json 코드 삽입.</p>
<pre><code class="language-json">{
  &quot;todos&quot;: [
    {
      &quot;id&quot;: &quot;1&quot;,
      &quot;title&quot;: &quot;react&quot;
    }
  ]
}</code></pre>
<h2 id="env-설정">.env 설정</h2>
<p>민감 정보는 <code>.env</code> 파일로 관리.</p>
<h3 id="vite로-만든-프로젝트"><strong>Vite로 만든 프로젝트</strong></h3>
<pre><code class="language-jsx">// .env

VITE_원하는이름 = 123
VITE_EXAMPLE_SERVER_URL = http://localhost:4000
VITE_API_KEY = test1234
VITE_SECRET_KEY = abcdefg</code></pre>
<pre><code class="language-jsx">// src/main.js 또는 src/App.jsx

const apiKey = import.meta.env.VITE_API_KEY;
console.log(&#39;API Key:&#39;, apiKey);</code></pre>
<h3 id="cra로-만든-프로젝트"><strong>CRA로 만든 프로젝트</strong></h3>
<pre><code class="language-jsx">// .env

REACT_APP_원하는이름 = 123
REACT_APP_EXAMPLE_SERVER_URL = http://localhost:4000
REACT_APP_API_KEY = test123
REACT_APP_SECRET_KEY = abcdefg</code></pre>
<pre><code class="language-jsx">// src/index.js 또는 src/App.jsx

const apiKey = process.env.REACT_APP_API_KEY;
console.log(&#39;API Key:&#39;, apiKey);</code></pre>
<h2 id="gitignore-설정"><strong>.gitignore 설정</strong></h2>
<p>원격 환경(github)에 보안 정보는 포함되지 않도록(git repository에 포함되지 않도록) 관리 필요. 
root 경로에 <code>.gitignore</code> 파일 생성 후 <code>.env</code>(환경 설정 파일) 추가.</p>
<p><strong>파일경로</strong></p>
<pre><code class="language-jsx">├json_folder
│├node_modules
│├public
│└src
├.eslintrc.cjs
├.gitignore
├index.html
├package.json
├README.md
└vite.config.js</code></pre>
<p><strong>.gitignore 파일</strong></p>
<pre><code># Node.js 관련 파일
/node_modules

# 환경 설정 파일
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# 빌드 출력 파일
/build
/dist

# 기타
.DS_Store</code></pre><h1 id="◼-get">◼ GET</h1>
<p>서버 <strong>데이터 조회</strong> 시 사용. </p>
<pre><code class="language-jsx">// GET(url은 매개변수, 대괄호([]) 안의 값은 선택사항.
axios.get(url[, config]) </code></pre>
<p><strong>url</strong>에는 서버의 url. <strong>config</strong>에는 설정추가 가능. 세부옵션은 axios 공식문서에서 확인.</p>
<p>🔗 <a href="https://axios-http.com/kr/docs/req_config">Axios config 공식문서</a></p>
<h2 id="json-server-api-명세서-확인">json-server API 명세서 확인</h2>
<p>Axios는 HTTP 요청을 도와주는 도구일 뿐임. 어떤 방식으로 요청해야 하는지는 API 명세서 기준으로 결정.
예를 들어 GET 요청을 할 때 path variable 방식일지 query 방식일지는 API를 설계한 사람이 정의한 규칙을 따름.</p>
<p>json-server의 공식문서를 보면 전체 정보나 상세 정보는 <strong>path variable</strong>로 url 작성.</p>
<pre><code class="language-jsx">GET /posts
GET /posts/1</code></pre>
<p>filter와 같은 기능을 위해서 GET 요청을 하고자할 때는 <strong>query</strong>로 보내라고 명시.</p>
<pre><code class="language-jsx">GET /posts?title=json-server&amp;author=typicode 
GET /posts?id=1&amp;id=2
GET /comments?author.name=typicode</code></pre>
<h2 id="예시"><strong>예시</strong></h2>
<p>json-server에 데이터를 axios로 fetching 후 useState로 관리하는 로직 작성. </p>
<pre><code class="language-jsx">// src/App.js

import React, { useEffect, useState } from &quot;react&quot;;
import axios from &quot;axios&quot;; // axios import

const App = () =&gt; {
  const [todos, setTodos] = useState(null);

    // axios를 통해서 get 요청을 하는 함수를 생성.
    // 비동기처리를 해야하므로 async/await 구문을 통해서 처리.
  const fetchTodos = async () =&gt; {
    const { data } = await axios.get(&quot;http://localhost:4000/todos&quot;);
    setTodos(data); // 서버로부터 fetching한 데이터를 useState의 state로 set.
  };

    // 생성한 함수를 컴포넌트가 mount된 후 실행하기 위해 useEffect를 사용.
  useEffect(() =&gt; {
        // effect 구문에 생성한 함수를 넣어 실행.
    fetchTodos();
  }, []);

    // data fetching이 정상적으로 되었는지 콘솔을 통해 확인.
  console.log(todos);
  return &lt;div&gt;App&lt;/div&gt;;
};

export default App;</code></pre>
<p><strong>콘솔</strong></p>
<pre><code class="language-jsx">null
--------------------------------------------
▼ [{...}] 1
    ► 0: {id: &#39;1&#39;, title: &#39;react&#39;}
        Length: 1
    ► [[Prototype]]: Array(0)</code></pre>
<p>useEffect에서 최초 1회 실행. axios로 데이터 요청. 데이터를 state에 저장. 
콘솔에서 null → 배열 데이터 변화 확인 가능.</p>
<h1 id="◼-post">◼ POST</h1>
<p>서버 <strong>데이터 추가</strong> 시 사용.</p>
<pre><code class="language-jsx">axios.post(url[, data[, config]])</code></pre>
<p>POST 요청 로직은 BE 개발자가 구현하므로 데이터 추가 외의 용도로도 사용할 수 있음. 
보통 body에 데이터를 담아 전송.</p>
<h2 id="예시-1"><strong>예시</strong></h2>
<p>GET 예시에 POST 코드 추가. 
input 값 입력 → 버튼 클릭 → <code>onSubmitHandler</code> 실행 → todo를 body에 담아 서버로 전송.</p>
<pre><code class="language-jsx">// src/App.jsx

import React, { useEffect, useState } from &quot;react&quot;;
import axios from &quot;axios&quot;; // axios import

const App = () =&gt; {
    // ✅ S: 추가코드
  // 새롭게 생성하는 todo를 관리하는 state.
  const [todo, setTodo] = useState({
    title: &quot;&quot;,
  });
    // ✅ E: 추가코드

  const [todos, setTodos] = useState(null);

  const fetchTodos = async () =&gt; {
    const { data } = await axios.get(&quot;http://localhost:4000/todos&quot;);
    setTodos(data);
  };

    // ✅ S: 추가코드
    // axios는 내부적으로 JSON.stringify 자동 처리.
  const onSubmitHandler = async(todo) =&gt; {
    await axios.post(&quot;http://localhost:4000/todos&quot;, todo);
  };

    // fetch 사용 시 직접 JSON.stringify 필요.
  // await fetch(&quot;http://localhost:4000/todos&quot;, {
  //   method: &quot;POST&quot;,
  //   headers: {
  //     &quot;Content-Type&quot;: &quot;application/json&quot;,
  //   },
  //   body: JSON.stringify(todo),
  // });
     // ✅ E: 추가코드

  useEffect(() =&gt; {
    fetchTodos();
  }, []);

  return (
         {/* ✅ S: 추가코드 */}
    &lt;&gt;
      &lt;form
        onSubmit={(e) =&gt; {
                    // submit했을 때 브라우저의 새로고침을 방지. 
          e.preventDefault();
          onSubmitHandler(todo);
        }}
      &gt;
        &lt;input
          type=&quot;text&quot;
          onChange={(ev) =&gt; {
            const { value } = ev.target;
            setTodo({
              ...todo,
              title: value,
            });
          }}
        /&gt;
        &lt;button&gt;추가하기&lt;/button&gt;
      &lt;/form&gt;
      &lt;div&gt;
        {todos?.map((todo) =&gt; (
          &lt;div key={todo.id}&gt;{todo.title}&lt;/div&gt;
        ))}
      &lt;/div&gt;
    &lt;/&gt;
         {/* ✅ E: 추가코드 */}
  );
};

export default App;</code></pre>
<p><strong>새로고침 없이 화면 반영 방법</strong></p>
<p>POST 요청을 마치면 새로고침 해야만 새로운 정보가 표시됨. 
POST 요청은 <strong>서버 데이터만 변경</strong>함. React 화면은 <strong>state가 변경되어야만 리렌더링 발생</strong>함.
현재 코드에서는 POST 요청 전송 → 서버 DB에는 데이터 저장 → 하지만 todos state는 그대로 유지.
즉, <strong>클라이언트 state와 서버 데이터가 동기화되지 않은 상태</strong>.</p>
<pre><code class="language-jsx">// (...중략)
 const onSubmitHandler = async(todo) =&gt; {
   await axios.post(&quot;http://localhost:4000/todos&quot;, todo);

    setTodos([...todos, todo]);
 };</code></pre>
<p>새로고침 없이 업데이트하려면 기존 배열 복사 → 새 todo 추가 → state 변경 발생 → 리렌더링 실행 → 화면 즉시 반영 가능.</p>
<h2 id="네트워크-탭-확인"><strong>네트워크 탭 확인</strong></h2>
<p>개발 시 Network 탭 확인 필요. 문제가 생겼을 때 이 정보를 통해 디버깅을 할 수 있음.</p>
<p><strong>headers</strong></p>
<ul>
<li>Request URL : 의도한 URL로 POST 요청을 보냈는지 확인.</li>
<li>Request Method : POST 메서드 사용여부 확인.</li>
<li>Status Code : 201이면 정상 생성.
status code는 자동으로 생성되지 않음. BE 개발자가 정의. 만약 BE 개발자가 구현해 두지 않았으면 문맥과 다른 status code가 브라우저에 표시될 수 있음.</li>
</ul>
<p><strong>payload</strong></p>
<ul>
<li>Payload : 전송한 body 확인 가능</li>
</ul>
<p><strong>response</strong></p>
<ul>
<li>Response : 서버 응답 데이터 확인 가능. 
response는 자동으로 생성되지 않음. FE 개발자가 BE 개발자에게 요청한 내용을 BE에서 직접 구현해야 응답 값이 생김. json-server는 POST 요청 시 클라이언트가 보낸 body를 그대로 응답하도록 만들어진 패키지.</li>
</ul>
<h1 id="◼-delete">◼ DELETE</h1>
<p>저장된 <strong>데이터 삭제</strong> 시 사용.</p>
<pre><code class="language-jsx">axios.delete(url[, config])</code></pre>
<h2 id="예시-2"><strong>예시</strong></h2>
<p>GET, POST에 이어서 코드 추가. 
삭제 버튼 추가 → <code>onClickDeleteButtonHandler</code> 구현 → 해당 id로 DELETE 요청 전송.
성공 후 새로고침 시 데이터 삭제 확인 가능.</p>
<pre><code class="language-jsx">// src/App.jsx

import React, { useEffect, useState } from &quot;react&quot;;
import axios from &quot;axios&quot;; 

const App = () =&gt; {
  const [todo, setTodo] = useState({
    title: &quot;&quot;,
  });

  const [todos, setTodos] = useState(null);

  const fetchTodos = async () =&gt; {
    const { data } = await axios.get(&quot;http://localhost:4000/todos&quot;);
    setTodos(data); 
  };

  const onSubmitHandler = (todo) =&gt; {
    axios.post(&quot;http://localhost:4000/todos&quot;, todo);
  };

    // ✅ S: 추가코드
    // 새롭게 추가한 삭제 버튼 이벤트 핸들러 
  const onClickDeleteButtonHandler = (todoId) =&gt; {
    axios.delete(`http://localhost:4000/todos/${todoId}`);
  };
  // ✅ E: 추가코드

  useEffect(() =&gt; {
    fetchTodos();
  }, []);

  return (
    &lt;&gt;
      &lt;form
        onSubmit={(e) =&gt; {
          e.preventDefault();
          onSubmitHandler(todo);
        }}
      &gt;
        &lt;input
          type=&quot;text&quot;
          onChange={(ev) =&gt; {
            const { value } = ev.target;
            setTodo({
              ...todo,
              title: value,
            });
          }}
        /&gt;
        &lt;button&gt;추가하기&lt;/button&gt;
      &lt;/form&gt;
      &lt;div&gt;
        {todos?.map((todo) =&gt; (
          &lt;div key={todo.id}&gt;
            {todo.title}

                      {/* ✅ S: 추가코드 */}
            &lt;button
              type=&quot;button&quot;
              onClick={() =&gt; onClickDeleteButtonHandler(todo.id)}
            &gt;
              삭제하기
            &lt;/button&gt;
            {/* ✅ E: 추가코드 */}
          &lt;/div&gt;
        ))}
      &lt;/div&gt;
    &lt;/&gt;
  );
};

export default App;</code></pre>
<h1 id="◼-patch">◼ PATCH</h1>
<p><strong>데이터 수정</strong> 시 사용.</p>
<pre><code class="language-jsx">axios.patch(url[, data[, config]])</code></pre>
<p>HTTP 관례상 수정은 PATCH 또는 PUT 사용. 필수 규칙은 아니지만 관례적으로 사용.</p>
<h2 id="예시-3"><strong>예시</strong></h2>
<p>GET, POST, DELETE에 이어서 코드 추가. PUT은 PATCH와 원리가 같아 생략.
Todo 수정에 필요한 데이터는 수정대상 id, 수정할 값. </p>
<p>보통 수정 기능을 만들 때 직접 id를 입력받아 처리하는 방식은 거의 없음. 
이번 예시에서는 아주 간단한 코드로 기능을 구현하기 때문에 이와 같이 처리.</p>
<pre><code class="language-jsx">// src/App.jsx

import React, { useEffect, useState } from &quot;react&quot;;
import axios from &quot;axios&quot;;

const App = () =&gt; {
  const [todo, setTodo] = useState({
    title: &quot;&quot;,
  });
  const [todos, setTodos] = useState(null);

  // patch에서 사용할 id, 수정값의 state를 추가.
  const [targetId, setTargetId] = useState(null);
  const [editTodo, setEditTodo] = useState({
    title: &quot;&quot;,
  });

  const fetchTodos = async () =&gt; {
    const { data } = await axios.get(&quot;http://localhost:4000/todos&quot;);
    setTodos(data);
  };

  const onSubmitHandler = (todo) =&gt; {
    axios.post(&quot;http://localhost:4000/todos&quot;, todo);
  };

  const onClickDeleteButtonHandler = (todoId) =&gt; {
    axios.delete(`http://localhost:4000/todos/${todoId}`);
  };

    // ✅ S: 추가코드
  // 수정버튼 이벤트 핸들러 추가
  const onClickEditButtonHandler = (todoId, edit) =&gt; {
    axios.patch(`http://localhost:4000/todos/${todoId}`, edit);
  };
  // ✅ E: 추가코드

  useEffect(() =&gt; {
    fetchTodos();
  }, []);

  return (
    &lt;&gt;
      &lt;form
        onSubmit={(e) =&gt; {
          e.preventDefault();
          onSubmitHandler(todo);
        }}
      &gt;
          {/* ✅ S: 추가코드 */}
        {/* 수정기능에 필요한 id, 수정값 input 2개와 수정하기 버튼을 추가 */}
        &lt;div&gt;
          &lt;input
            type=&quot;text&quot;
            placeholder=&quot;수정하고싶은 Todo ID&quot;
            onChange={(ev) =&gt; {
              setTargetId(ev.target.value);
            }}
          /&gt;
          &lt;input
            type=&quot;text&quot;
            placeholder=&quot;수정값 입력&quot;
            onChange={(ev) =&gt; {
              setEditTodo({
                ...editTodo,
                title: ev.target.value,
              });
            }}
          /&gt;
          &lt;button
                        // type=&#39;button&#39; 을 추가해야 form의 영향에서 벗어남
            type=&quot;button&quot;
            onClick={() =&gt; onClickEditButtonHandler(targetId, editTodo)}
          &gt;
            수정하기
          &lt;/button&gt;
        &lt;/div&gt;
        {/* ✅ E: 추가코드 */}
        &lt;input
          type=&quot;text&quot;
          onChange={(ev) =&gt; {
            const { value } = ev.target;
            setTodo({
              ...todo,
              title: value,
            });
          }}
        /&gt;
        &lt;button&gt;추가하기&lt;/button&gt;
      &lt;/form&gt;
      &lt;div&gt;
        {todos?.map((todo) =&gt; (
          &lt;div key={todo.id}&gt;
              {/* ✅ S: 추가코드 */}
                        {/* todo의 아이디를 화면에 표시 */}
            {todo.id} :{todo.title}
            {/* ✅ E: 추가코드 */}
            &lt;button
              type=&quot;button&quot;
              onClick={() =&gt; onClickDeleteButtonHandler(todo.id)}
            &gt;
              삭제하기
            &lt;/button&gt;
          &lt;/div&gt;
        ))}
      &lt;/div&gt;
    &lt;/&gt;
  );
};

export default App;</code></pre>
<h1 id="◼-장점">◼ 장점</h1>
<p>fetch와 axios는 모두 HTTP 요청(GET, POST…)을 처리하기 위한 JavaScript 라이브러리.
React에서는 <strong>axios를 선호</strong>하는데 <strong>다음과 같은 장점</strong> 때문임.</p>
<h2 id="1-기본-설정-및-인터셉터-지원">1. 기본 설정 및 인터셉터 지원</h2>
<p><strong>기본 설정</strong> : axios는 기본 설정을 정의해 모든 요청에 공통 설정을 적용할 수 있음.</p>
<pre><code class="language-jsx">const axiosInstance = axios.create({
  baseURL: &#39;https://api.example.com&#39;,
  timeout: 1000,
  headers: { &#39;X-Custom-Header&#39;: &#39;foobar&#39; }
});</code></pre>
<p><strong>인터셉터</strong> : 요청, 응답을 가로채 전처리나 후처리를 할 수 있음. 이를 통해 인증 토큰을 자동으로 추가하거나 오류를 일괄 처리할 수 있음.</p>
<pre><code class="language-jsx">axios.interceptors.request.use(config =&gt; {
  config.headers.Authorization = `Bearer ${token}`;
  return config;
}, error =&gt; {
  return Promise.reject(error);
});</code></pre>
<h2 id="2-오류-처리-방식">2. 오류 처리 방식</h2>
<p><strong>에러 핸들링</strong> : axios는 4xx, 5xx 상태 코드를 자동으로 catch에서 처리할 수 있어 일관된 오류 처리가 가능. fetch는 네트워크 오류만 catch에서 처리되며, 4xx, 5xx 오류는 then에서 처리해야 함.</p>
<pre><code class="language-jsx">axios.get(&#39;/user/12345&#39;)
  .then(response =&gt; console.log(response.data))
  .catch(error =&gt; {
    if (error.response) {
      // 서버가 4xx, 5xx 응답을 반환
      console.log(error.response.data);
      console.log(error.response.status);
      console.log(error.response.headers);
    } else if (error.request) {
      // 요청이 전송되었지만 응답이 없음
      console.log(error.request);
    } else {
      // 요청 설정 중에 발생한 오류
      console.log(&#39;Error&#39;, error.message);
    }
  });</code></pre>
<h2 id="3-브라우저-호환성">3. 브라우저 호환성</h2>
<p><strong>구형 브라우저 지원</strong> : axios는 구형 브라우저 대응 용이. fetch는 일부 환경에서 폴리필 필요.</p>
<h2 id="4-문법-간결성">4. 문법 간결성</h2>
<p><strong>간결한 문법</strong>: axios는 간결하고 직관적인 문법 사용. fetch 대비 코드 간결성 확보 가능.</p>
<pre><code class="language-jsx">axios.get(&#39;/user&#39;)
  .then(response =&gt; console.log(response.data))
  .catch(error =&gt; console.error(error));</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 심화] json-server란?]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-json-server%EB%9E%80</link>
            <guid>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-json-server%EB%9E%80</guid>
            <pubDate>Wed, 12 Mar 2025 12:39:12 GMT</pubDate>
            <description><![CDATA[<h1 id="📌">📌</h1>
<p>json-server는 <strong>간단한 DB와 API 서버 생성 패키지</strong>.
백엔드에서 실제 DB와 API Server가 구축되기 전까지, 프론트엔드 개발에 <strong>임시로 사용</strong>할 <strong>mock data 생성 목적</strong>으로 사용.
json-server 사용 시 BE(Backend) 작업을 기다리지 않고 FE(Frontend) 로직과 화면 구현 가능. 협업 효율 향상 가능.</p>
<p><strong>Supabase, Firebase 쓰면 되는 것 아닌가?(feat. RESTful API)</strong></p>
<ul>
<li><strong>json-server</strong>는 RESTful API 기본 개념 학습 지원 도구.
빠른 실습 가능. API 설계 흐름 이해 가능. CRUD 구조 체험 가능.</li>
<li><strong>Supabase, Firebase</strong>는 실제 서비스 구축에 적합한 BaaS임.
백엔드 없이 운영 환경 구성 가능.</li>
<li>학습 목적, REST 구조 이해 목적이라면 json-server가 더 적합한 선택.
목적에 따라 json-server와 BaaS 선택 사용.</li>
</ul>
<h1 id="◼-설치">◼ 설치</h1>
<pre><code class="language-jsx">npm install json-server
npm install json-server -D // 개발 환경인 경우, -D 옵션을 함께 입력.</code></pre>
<h1 id="◼-사용">◼ 사용</h1>
<h2 id="실행">실행</h2>
<p>json-server가 간단한 패키지이긴 하나, 간이 백엔드 서버임. 그래서 React와는 별개로 <strong>따로 실행</strong> 필요. 
즉, <strong>React도 실행</strong>해야 하고 <strong>json-server도 실행</strong>해야 함. 그래야 React와 json-server가 서로 통신 할 수 있음.</p>
<h2 id="dbjson-파일-생성"><strong>db.json 파일 생성</strong></h2>
<p>프로젝트 루트 경로에 db.json파일 생성.</p>
<pre><code class="language-jsx">├json_folder
│├node_modules
│├public
│└src
├.gitignore
├db.json
├package.json
└README.md</code></pre>
<p>아래와 같이 기본 데이터 작성.</p>
<pre><code class="language-json">{
  &quot;todos&quot;: []
}</code></pre>
<h2 id="서버-실행"><strong>서버 실행</strong></h2>
<p>React 실행 터미널과 별도로 <strong>새 터미널 창에서 실행</strong> 필요.</p>
<pre><code class="language-bash">yarn json-server db.json --port 4000</code></pre>
<p>명령어의 뜻은 db.json을 데이터베이스로 사용하고, 4000번 포트에서 서버를 실행하겠다는 뜻.</p>
<h2 id="dbjson-수정-후-브라우저-확인">db.json 수정 후 브라우저 확인</h2>
<p>데스트용 데이터 추가.</p>
<pre><code class="language-json">{
  &quot;todos&quot;: [
    {
      &quot;id&quot;: 1,
      &quot;title&quot;: &quot;json-server&quot;,
      &quot;content&quot;: &quot;json-server를 배워봅시다.&quot;
    }
  ]
}</code></pre>
<p>브라우저에서 아래 주소 입력.</p>
<pre><code class="language-jsx">http://localhost:4000/todos</code></pre>
<p>터미널에서는 아래와 같이 로그 출력.</p>
<pre><code class="language-jsx">Resources
http://localhost:3001/todos

Home
http://localhost:3001

GET /todos 200 11.036ms - 101</code></pre>
<p>브라우저 주소에 URL을 입력한다는 것은 GET 요청을 했다는 것. API 서버에 GET 요청을 한 셈.
서버의 터미널에서는 “누군가 GET을 했어” 라고 알려줌.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 심화] HTTP 메서드 및 Rest API]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%EB%B0%8F-Rest-API</link>
            <guid>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%EB%B0%8F-Rest-API</guid>
            <pubDate>Tue, 11 Mar 2025 13:24:46 GMT</pubDate>
            <description><![CDATA[<h1 id="📌">📌</h1>
<p>HTTP 메서드는 <strong>클라이언트가 서버에게 요청의 성격을 알리는 데</strong> 사용.
REST API는 이러한 HTTP 메서드를 사용해 CRUD 작업 수행 가능.</p>
<h1 id="◼-get">◼ GET</h1>
<p>서버에 <strong>데이터 요청</strong> 시 사용. 요청 데이터는 URL에 포함되어 전송. 
주로 데이터 조회에 사용.</p>
<p><strong>REST API에서 사용</strong> : 특정 리소스 조회 시 사용.</p>
<h1 id="◼-post">◼ POST</h1>
<p>서버에 <strong>데이터 제출</strong> 시 사용. 요청 데이터는 요청 본문(body)에 포함되어 전송. 
주로 새로운 데이터 생성 또는 제출에 사용.</p>
<p><strong>REST API에서의 사용</strong> : 새로운 리소스 생성 시 사용.</p>
<h1 id="◼-put-patch">◼ PUT, PATCH</h1>
<p>서버의 <strong>데이터 업데이트</strong> 시 사용. 요청 데이터는 요청 본문(body)에 포함되어 전송. 
주로 기존 데이터 수정시 사용(PUT:리소스 전체 수정, PATCH:리소스 일부 수정)</p>
<p><strong>REST API에서의 사용</strong>: 기존 리소스 수정 시 사용.</p>
<h1 id="◼-delete">◼ DELETE</h1>
<p>서버의 <strong>데이터 삭제</strong> 시 사용. </p>
<p><strong>REST API에서의 사용</strong>: 특정 리소스 삭제 시 사용.</p>
<h1 id="◼-restful-원칙-기반-api-명세서">◼ RESTful 원칙 기반 API 명세서</h1>
<p><strong>기본 URL</strong></p>
<p><a href="https://api.example.com/">https://api.example.com</a></p>
<h2 id="엔드포인트">엔드포인트</h2>
<table>
<thead>
<tr>
<th><strong>요청내용</strong></th>
<th><strong>method</strong></th>
<th><strong>url</strong></th>
</tr>
</thead>
<tbody><tr>
<td>게시글 추가</td>
<td>POST</td>
<td>/posts</td>
</tr>
<tr>
<td>모든 게시글 조회</td>
<td>GET</td>
<td>/posts</td>
</tr>
<tr>
<td>특정 게시글 조회</td>
<td>GET</td>
<td>/posts/:id</td>
</tr>
<tr>
<td>특정 게시글 업데이트</td>
<td>PUT</td>
<td>/posts/:id</td>
</tr>
<tr>
<td>특정 게시글 일부 수정</td>
<td>PATCH</td>
<td>/posts/:id</td>
</tr>
<tr>
<td>특정 게시글 삭제</td>
<td>DELETE</td>
<td>/posts/:id</td>
</tr>
</tbody></table>
<h2 id="응답요청">응답/요청</h2>
<p><strong>Content-Type: application/json란?</strong>
<code>Content-Type: application/json</code>은 HTTP 요청 또는 응답 본문 데이터 형식을 명시하는 헤더. 본문 데이터가 JSON 형식임을 서버 또는 클라이언트에 알리는 역할 수행.</p>
<h3 id="post-posts"><strong>POST /posts</strong></h3>
<p>새로운 게시글 생성.</p>
<ul>
<li><p><strong>URL</strong>: <code>/posts</code></p>
</li>
<li><p><strong>Method</strong>: <code>POST</code></p>
</li>
<li><p><strong>Headers</strong>:</p>
<ul>
<li><code>Content-Type</code>: <code>application/json</code></li>
</ul>
</li>
<li><p><strong>Request Body</strong></p>
<pre><code class="language-json">  {
    &quot;title&quot;: &quot;string&quot;,
    &quot;body&quot;: &quot;string&quot;,
    &quot;userId&quot;: &quot;integer&quot;
  }</code></pre>
</li>
<li><p><strong>Response</strong>:</p>
<ul>
<li><p><strong>201 Created</strong>: 생성 성공.</p>
<pre><code class="language-json">  {
    &quot;id&quot;: &quot;integer&quot;,
    &quot;title&quot;: &quot;string&quot;,
    &quot;body&quot;: &quot;string&quot;,
    &quot;userId&quot;: &quot;integer&quot;
  }</code></pre>
</li>
<li><p><strong>400 Bad Request</strong>: 요청 본문 오류.</p>
</li>
</ul>
</li>
</ul>
<h3 id="get-posts"><strong>GET /posts</strong></h3>
<p>모든 게시글 조회 요청.</p>
<ul>
<li><p><strong>URL</strong>: <code>/posts</code></p>
</li>
<li><p><strong>Method</strong>: <code>GET</code></p>
</li>
<li><p><strong>Headers</strong>: 없음</p>
</li>
<li><p><strong>Response</strong>:</p>
<ul>
<li><p><strong>200 OK</strong>: 조회 성공.</p>
<pre><code class="language-json">  [
    {
      &quot;id&quot;: &quot;integer&quot;,
      &quot;title&quot;: &quot;string&quot;,
      &quot;body&quot;: &quot;string&quot;,
      &quot;userId&quot;: &quot;integer&quot;
    },
    ...
  ]</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="get-postsid"><strong>GET /posts/{id}</strong></h3>
<p>특정 게시글 조회.</p>
<ul>
<li><p><strong>URL</strong>: <code>/posts/{id}</code></p>
</li>
<li><p><strong>Method</strong>: <code>GET</code></p>
</li>
<li><p><strong>Headers</strong>: 없음</p>
</li>
<li><p><strong>Response</strong>:</p>
<ul>
<li><p><strong>200 OK</strong>: 조회 성공.</p>
<pre><code class="language-json">  {
    &quot;id&quot;: &quot;integer&quot;,
    &quot;title&quot;: &quot;string&quot;,
    &quot;body&quot;: &quot;string&quot;,
    &quot;userId&quot;: &quot;integer&quot;
  }</code></pre>
</li>
<li><p><strong>404 Not Found</strong>: 해당 ID 데이터(게시글) 없음.</p>
</li>
</ul>
</li>
</ul>
<h3 id="put-postsid"><strong>PUT /posts/{id}</strong></h3>
<p>특정 게시글 전체 수정(전체 업데이트)</p>
<ul>
<li><p><strong>URL</strong>: <code>/posts/{id}</code></p>
</li>
<li><p><strong>Method</strong>: <code>PUT</code></p>
</li>
<li><p><strong>Headers</strong>:</p>
<ul>
<li><code>Content-Type</code>: <code>application/json</code></li>
</ul>
</li>
<li><p><strong>Request Body</strong>:</p>
<pre><code class="language-json">  {
    &quot;title&quot;: &quot;string&quot;,
    &quot;body&quot;: &quot;string&quot;,
    &quot;userId&quot;: &quot;integer&quot;
  }</code></pre>
</li>
<li><p><strong>Response</strong>:</p>
<ul>
<li><p><strong>200 OK</strong>: 수정 성공.</p>
<pre><code class="language-json">  {
    &quot;id&quot;: &quot;integer&quot;,
    &quot;title&quot;: &quot;string&quot;,
    &quot;body&quot;: &quot;string&quot;,
    &quot;userId&quot;: &quot;integer&quot;
  }</code></pre>
</li>
<li><p><strong>400 Bad Request</strong>: 요청 본문 오류.</p>
</li>
<li><p><strong>404 Not Found</strong>: 해당 ID 데이터(게시글) 없음.</p>
</li>
</ul>
</li>
</ul>
<h3 id="patch-postsid"><strong>PATCH /posts/{id}</strong></h3>
<p>특정 게시글 일부 수정.</p>
<ul>
<li><p><strong>URL</strong>: <code>/posts/{id}</code></p>
</li>
<li><p><strong>Method</strong>: <code>PATCH</code></p>
</li>
<li><p><strong>Headers</strong>:</p>
<ul>
<li><code>Content-Type</code>: <code>application/json</code></li>
</ul>
</li>
<li><p><strong>Request Body</strong> (모든 필드가 선택 사항):</p>
<pre><code class="language-json">  {
    &quot;title&quot;: &quot;string&quot;,
    &quot;body&quot;: &quot;string&quot;,
    &quot;userId&quot;: &quot;integer&quot;
  }</code></pre>
</li>
<li><p><strong>Response</strong>:</p>
<ul>
<li><p><strong>200 OK</strong>: 수정 성공.</p>
<pre><code class="language-json">  {
    &quot;id&quot;: &quot;integer&quot;,
    &quot;title&quot;: &quot;string&quot;,
    &quot;body&quot;: &quot;string&quot;,
    &quot;userId&quot;: &quot;integer&quot;
  }</code></pre>
</li>
<li><p><strong>400 Bad Request</strong>: 요청 본문 오류.</p>
</li>
<li><p><strong>404 Not Found</strong>: 해당 ID 데이터(게시글) 없음.</p>
</li>
</ul>
</li>
</ul>
<h3 id="delete-postsid"><strong>DELETE /posts/{id}</strong></h3>
<p>특정 게시글 삭제.</p>
<ul>
<li><strong>URL</strong>: <code>/posts/{id}</code></li>
<li><strong>Method</strong>: <code>DELETE</code></li>
<li><strong>Headers</strong>: 없음</li>
<li><strong>Response</strong>:<ul>
<li><strong>200 OK</strong>: 삭제 성공.</li>
<li><strong>404 Not Found</strong>: 해당 ID 데이터(게시글) 없음.</li>
</ul>
</li>
</ul>
<h1 id="◼-react에서-http-요청-예시">◼ React에서 HTTP 요청 예시</h1>
<p>React에서 HTTP 요청을 보내는 대표적인 방법은 fetch API 또는 axios 라이브러리 사용하는 것.
아래는 fetch 사용 예시.</p>
<h2 id="get-요청-예시데이터-요청"><strong>GET 요청 예시(데이터 요청)</strong></h2>
<pre><code class="language-jsx">import React, { useState, useEffect } from &quot;react&quot;;

function App() {
  const [data, setData] = useState(null);

  useEffect(() =&gt; {
    const fetchData = async () =&gt; {
      try {
        // get 요청 시, fetch는 method를 명시하지 않아도 됨.
        const response = await fetch(
          &quot;https://jsonplaceholder.typicode.com/posts/1&quot;
        );
        const result = await response.json();
        setData(result);
      } catch (error) {
        console.error(&quot;Error fetching data:&quot;, error);
      }
    };

    fetchData();
  }, []);

  return &lt;div&gt;{data ? &lt;div&gt;{data.title}&lt;/div&gt; : &lt;div&gt;Loading...&lt;/div&gt;}&lt;/div&gt;;
}

export default App;</code></pre>
<p>method 미작성 시 기본값은 GET.
response.json() 사용하여 JSON 데이터 파싱(문자열 → 객체 변환 과정 필요) 가능.</p>
<h2 id="post-요청-예시데이터-생성"><strong>POST 요청 예시(데이터 생성)</strong></h2>
<pre><code class="language-jsx">import React, { useState } from &quot;react&quot;;

function App() {
  const [title, setTitle] = useState(&quot;&quot;);
  const [body, setBody] = useState(&quot;&quot;);
  const [response, setResponse] = useState(null);

  const handleSubmit = async (event) =&gt; {
    event.preventDefault();
    try {
      const res = await fetch(&quot;https://jsonplaceholder.typicode.com/posts&quot;, {
        method: &quot;POST&quot;,
        headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;,
        },
        body: JSON.stringify({
          title: title,
          body: body,
          userId: 1,
        }),
      });
      const result = await res.json();
      setResponse(result);
    } catch (error) {
      console.error(&quot;Error creating data:&quot;, error);
    }
  };

  return (
    &lt;div&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;input
          type=&quot;text&quot;
          value={title}
          onChange={(e) =&gt; setTitle(e.target.value)}
          placeholder=&quot;Title&quot;
        /&gt;
        &lt;textarea
          value={body}
          onChange={(e) =&gt; setBody(e.target.value)}
          placeholder=&quot;Body&quot;
        /&gt;
        &lt;button type=&quot;submit&quot;&gt;Create Post&lt;/button&gt;
      &lt;/form&gt;
      {response &amp;&amp; &lt;div&gt;Created Post ID: {response.id}&lt;/div&gt;}
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<p>method 명시 필수.
headers에 Content-Type 설정 필요.
body는 JSON.stringify 사용하여 문자열 변환 후 전송.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 심화] HTTP란?]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-HTTP%EB%9E%80</link>
            <guid>https://velog.io/@a_young1210/React-%EC%8B%AC%ED%99%94-HTTP%EB%9E%80</guid>
            <pubDate>Mon, 10 Mar 2025 00:10:03 GMT</pubDate>
            <description><![CDATA[<h1 id="📌">📌</h1>
<p>HTTP(HyperText Transfer Protocol)는 웹에서 데이터를 주고받기 위한 프로토콜(데이터 통신을 원활하게 하기 위한 통신 규약)
클라이언트와 서버 간의 요청과 응답을 정의.</p>
<h1 id="◼-개념">◼ 개념</h1>
<p>HTTP는 클라이언트-서버 모델을 기반으로 동작. 
클라이언트가 요청을 보내면 서버가 응답을 반환. 요청과 응답은 텍스트 기반 메시지로 구성됨.</p>
<h1 id="◼-특징">◼ 특징</h1>
<ul>
<li><strong>무상태성</strong>
HTTP는 상태 저장하지 않음.
각 요청은 독립적으로 처리. 이전 요청 정보 기억하지 않음. 이것을 무상태(stateless)라고 함.</li>
<li><strong>확장성</strong>
HTTP는 다양한 확장 헤더를 추가하여 기능 확장 가능.</li>
<li><strong>유연성</strong>
다양한 데이터 형식 전송 가능. 텍스트, 이미지, 비디오 등 다양한 형식 지원.</li>
</ul>
<h1 id="◼-메시지-구조">◼ 메시지 구조</h1>
<p>HTTP 메시지는 요청(Request)과 응답(Response)으로 구성됨.</p>
<h2 id="요청메시지">요청메시지</h2>
<p>클라이언트가 서버에 데이터 요청 시 사용.</p>
<ul>
<li><strong>요청 라인</strong>
메서드(GET, POST, 등), URL, HTTP 버전</li>
<li><strong>헤더(Header)</strong>
요청의 추가 정보(메타데이터)를 담고 있어요. 브라우저 정보, 인증 정보 등.</li>
<li><strong>본문(Body)</strong>
선택적 요소. 주로 POST 요청에서 사용.</li>
</ul>
<p><strong>예시</strong></p>
<pre><code class="language-vbnet">GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html</code></pre>
<h3 id="요청-헤더와-인증-정보"><strong>요청 헤더와 인증 정보</strong></h3>
<p><strong>요청 헤더에 인증 정보 포함 이유</strong></p>
<p>서버는 요청을 보낸 사용자가 누구인지 확인이 필요함. 이 과정을 인증(Authentication)이라고 함.
인증을 통해 사용자 식별을 하고 사용자에게 권한이 있는지 확인함.</p>
<p><strong>요청 헤더에 인증 정보 포함 방법</strong></p>
<p>요청 헤더는 요청에 대한 추가 정보 전달 용도. 
헤더에 인증 정보를 포함해서 서버가 요청을 받았을 때 해당 정보를 통해 사용자 인증 처리.</p>
<pre><code>GET /protected-resource HTTP/1.1
Host: example.com
**Authorization: Bearer &lt;Access-Token&gt;**</code></pre><p>Access-Token은 로그인한 사용자임을 증명(로그인했음을 알려주는 정도)하는 키 역할 수행함.</p>
<h2 id="응답-메시지"><strong>응답 메시지</strong></h2>
<p>서버가 클라이언트 요청에 대한 결과 반환 시 사용.</p>
<ul>
<li><strong>상태 라인</strong>
HTTP 버전, 상태코드(200, 404, 등), 상태 메시지</li>
<li><strong>헤더(Header)</strong>
응답에 대한 추가 정보 전달 역할 수행. 콘텐츠 타입, 데이터 길이 등 포함 가능.</li>
<li><strong>본문(Body)</strong>
선택적 요소. 주로 응답 데이터.</li>
</ul>
<p><strong>예시</strong></p>
<pre><code class="language-jsx">HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1354

&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;Example&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;Hello, World!&lt;/h1&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<h1 id="◼-상태코드">◼ 상태코드</h1>
<p>HTTP 상태코드는 서버가 클라이언트 요청 처리 결과를 나타내는 값.세 자리 숫자로 구성. 첫 번째 자리 숫자를 기준으로 의미 구분 가능.</p>
<h2 id="1xx--정보"><strong>1xx : 정보</strong></h2>
<ul>
<li><p><strong>100 Continue</strong></p>
<p>  요청 일부를 서버가 받았음. 나머지 계속 보내라는 의미.</p>
</li>
</ul>
<h2 id="2xx--성공"><strong>2xx : 성공</strong></h2>
<ul>
<li><p><strong>200 OK</strong></p>
<p>  요청 정상 처리 완료.</p>
</li>
<li><p><strong>201 Created</strong></p>
<p>  요청 성공 및 새로운 리소스 생성 완료.</p>
</li>
</ul>
<h2 id="3xx--리다이렉션"><strong>3xx : 리다이렉션</strong></h2>
<ul>
<li><p><strong>301 Moved Permanently</strong>
요청 리소스가 새로운 URL로 영구 이동.</p>
</li>
<li><p><strong>302 Found</strong></p>
<p>  요청 리소스가 새로운 URL로 임시 이동.</p>
</li>
</ul>
<h2 id="4xx--클라이언트-오류"><strong>4xx : 클라이언트 오류</strong></h2>
<ul>
<li><p><strong>400 Bad Request</strong></p>
<p>  잘못된 요청.</p>
</li>
<li><p><strong>401 Unauthorized</strong></p>
<p>  인증 필요 상태.</p>
</li>
<li><p><strong>404 Not Found</strong></p>
<p>  요청한 리소스를 찾을 수 없음.</p>
</li>
</ul>
<h2 id="5xx--서버-오류"><strong>5xx : 서버 오류</strong></h2>
<ul>
<li><p><strong>500 Internal Server Error</strong></p>
<p>  서버가 요청을 처리하는 동안 오류 발생.</p>
</li>
<li><p><strong>502 Bad Gateway</strong></p>
<p>  서버가 게이트웨이 또는 프록시 역할을 하는 서버로부터 유효하지 않은 응답을 받음.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 숙련] Supabase]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%88%99%EB%A0%A8-Supabase</link>
            <guid>https://velog.io/@a_young1210/React-%EC%88%99%EB%A0%A8-Supabase</guid>
            <pubDate>Sun, 09 Mar 2025 07:28:02 GMT</pubDate>
            <description><![CDATA[<h1 id="◼-baas">◼ BaaS</h1>
<p>웹과 모바일 앱 개발을 쉽고 빠르게 할 수 있도록 도와주는 <strong>클라우드 기반 백엔드 서비스</strong>.
BaaS를 사용하면 복잡한 백엔드 시스템을 직접 관리하지 않아도 됨.
그 결과 프론트엔드 개발에 더 집중 가능함.</p>
<h2 id="웹-애플리케이션의-필수-구성-요소"><strong>웹 애플리케이션의 필수 구성 요소</strong></h2>
<p>웹 애플리케이션은 크게 세 부분으로 구성.</p>
<ul>
<li><strong>프론트엔드</strong>
사용자가 상호작용하는 영역. 화면에 보이는 모든 시각적 요소를 담당.</li>
<li><strong>백엔드</strong>
서버에서 데이터 처리, 사용자 관리, 로직 처리 담당. 사용자에게 직접 보이지 않는 영역.</li>
<li><strong>데이터베이스</strong>
사용자 정보, 게시글 등 데이터를 저장하는 공간. 필요할 때 데이터를 조회하는 역할 수행.</li>
</ul>
<h2 id="baas-필요-이유">BaaS 필요 이유</h2>
<p>프론트엔드 개발에 집중하고 싶어도 서버 설정, 보안, 인증까지 직접 처리해야 한다면 부담 큼.
서버 구축 없이 필요한 기능을 클라우드에서 바로 사용 가능함.</p>
<h2 id="인기-있는-baas-플랫폼">인기 있는 BaaS 플랫폼</h2>
<ul>
<li><strong>Firebase</strong>
Google에서 운영하는 BaaS 플랫폼
실시간 데이터베이스, 인증, 애널리틱스 기능 제공.</li>
<li><strong>Parse</strong>
오픈 소스 기반 BaaS.
커스터마이징 자유도가 높음.</li>
<li><strong>AWS Amplify</strong>
AWS 인프라 기반 BaaS.
복잡한 백엔드 작업을 간편하게 처리 가능.</li>
</ul>
<h2 id="장단점">장단점</h2>
<h3 id="장점"><strong>장점</strong></h3>
<ul>
<li><strong>개발 속도 향상</strong>
백엔드를 직접 구현하지 않아도 되고 이미 준비된 기능을 바로 사용 가능해 개발시간 단축가능.</li>
<li><strong>유지보수 간편</strong>
서버 관리 부담이 감소해 운영과 유지보수 효율 증가.</li>
<li><strong>자동 확장</strong>
사용자가 늘어나도 자동으로 스케일업 지원하니 서버 부하 걱정 없이 안정적인 서비스 제공 가능.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li><strong>유연성 부족</strong>
제공 기능 외 요구사항 대응 어려움.</li>
<li><strong>비용 예측 어려움</strong>
사용량 증가 시 비용 상승 가능성 있음.</li>
<li><strong>플랫폼 의존성</strong>
한 플랫폼에 의존하면 나중에 다른 서비스로의 이전이 어려울 수 있음.</li>
</ul>
<h1 id="◼-supabase">◼ Supabase</h1>
<p>Supabase는 PostgreSQL 기반의 오픈 소스 BaaS 플랫폼.
<strong>관계형 데이터를 기반</strong>으로 하면서 <strong>실시간 데이터 변경 감지</strong> 기능 제공.
관계형 데이터베이스 사용 시 데이터 간 관계 표현 용이함. 데이터 변경 시 UI가 자동으로 반영됨. </p>
<h2 id="사용이유">사용이유</h2>
<h3 id="1-관계형-데이터-모델과-실무-역량"><strong>1. 관계형 데이터 모델과 실무 역량</strong></h3>
<p>Supabase는 <strong>관계형 데이터베이스</strong>를 사용함. 
실무에서는 <strong>데이터 일관성과 정확성이 중요</strong>하며 대부분의 기업 시스템은 관계형 데이터베이스 사용함.</p>
<pre><code class="language-sql">-- 주문과 연관된 고객 정보를 함께 조회
SELECT orders.order_id, customers.name
FROM orders
JOIN customers ON orders.customer_id = customers.id
WHERE customers.region = &#39;Asia&#39;;</code></pre>
<h3 id="2-실시간-기능의-실무적-활용"><strong>2. 실시간 기능의 실무적 활용</strong></h3>
<p>Firebase도 실시간 기능 제공하지만 Supabase는 <strong>관계형 데이터베이스의 장점</strong>과 결합하여 제공.
실시간 데이터 반영은 현대 웹 애플리케이션에서 필수 요소.
예를 들어 주식 거래, 실시간 대시보드, 협업 도구 등 <strong>실시간 데이터 처리가 중요</strong>한 어플리케이션 개발에 매우 유용.</p>
<pre><code class="language-jsx">// 제품 가격 변경을 실시간으로 구독하는 코드
import { supabase } from &#39;./supabaseClient&#39;;

function subscribeToPriceChanges() {
  supabase
    .from(&#39;products&#39;)
    .on(&#39;UPDATE&#39;, payload =&gt; {
      alert(`Price updated! New price: ${payload.new.price}`);
    })
    .subscribe();
}</code></pre>
<h1 id="◼-react에서-사용하기">◼ React에서 사용하기</h1>
<h2 id="설치-및-세팅">설치 및 세팅</h2>
<pre><code class="language-bash">npm install @supabase/supabase-js</code></pre>
<pre><code class="language-jsx">import { createClient } from &quot;@supabase/supabase-js&quot;;

// project url
const SUPABASE_API_URL = &quot;YOUR_SUPABASE_API_URL&quot;;

// anon public key
const SUPABASE_API_KEY = &quot;YOUR_SUPABASE_API_KEY&quot;;

const supabase = createClient(SUPABASE_API_URL, SUPABASE_API_KEY);
export default supabase;</code></pre>
<p>설치 후 Supabase 클라이언트 초기화하는 코드 작성. 
이를 위해 Supabase 프로젝트의 URL과 API 키가 필요함.</p>
<h2 id="데이터베이스-입력">데이터베이스 입력</h2>
<p>Supabase 콘솔에서 직접 데이터베이스에 데이터 입력 가능.
웹사이트 프로젝트 대시보드에서 Table Editor를 사용해 행 추가 가능.</p>
<h2 id="데이터베이스-읽기">데이터베이스 읽기</h2>
<p>useEffect와 useState를 사용하여 데이터 조회 처리. 조회된 데이터 화면에 출력.</p>
<pre><code class="language-jsx">// src/App.jsx

import &quot;./App.css&quot;;
import FetchData from &quot;./components/FetchData&quot;;

function App() {
  return (
    &lt;&gt;
      &lt;h1&gt;Supabase&lt;/h1&gt;
      &lt;FetchData /&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
<pre><code class="language-jsx">// src/components/FetchData.jsx

import { useEffect, useState } from &quot;react&quot;;
import supabase from &quot;../supabaseClient&quot;;

const FetchData = () =&gt; {
  const [users, setUsers] = useState([]);

  useEffect(() =&gt; {
    const fetchData = async () =&gt; {
      const { data, error } = await supabase.from(&quot;NACAMP_SAMPLE&quot;).select(&quot;*&quot;);
      if (error) {
        console.log(&quot;error =&gt; &quot;, error);
      } else {
        console.log(&quot;data =&gt; &quot;, data);
        setUsers(data);
      }
    };

    fetchData();
  }, []);

  return (
    &lt;div&gt;
      &lt;h3&gt;유저정보&lt;/h3&gt;
      {users.map((user) =&gt; {
        return (
          &lt;div
            key={user.id}
            style={{
              border: &quot;1px solid black&quot;,
            }}
          &gt;
            &lt;h5&gt;아이디 : {user.id}&lt;/h5&gt;
            &lt;h5&gt;이름 : {user.name}&lt;/h5&gt;
            &lt;h5&gt;나이 : {user.age}&lt;/h5&gt;
            &lt;h5&gt;주소 : {user.address}&lt;/h5&gt;
          &lt;/div&gt;
        );
      })}
    &lt;/div&gt;
  );
};

export default FetchData;</code></pre>
<h2 id="데이터베이스-쓰기">데이터베이스 쓰기</h2>
<p>입력 폼을 통해 데이터 추가 가능. insert 메서드 사용.</p>
<pre><code class="language-jsx">// src/App.jsx

import &quot;./App.css&quot;;
import AddData from &quot;./components/AddData&quot;;
import FetchData from &quot;./components/FetchData&quot;;

function App() {
  return (
    &lt;&gt;
      &lt;h1&gt;Supabase&lt;/h1&gt;
      &lt;FetchData /&gt;
      &lt;AddData /&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
<pre><code class="language-jsx">// src/components/AddData.jsx

import React, { useState } from &quot;react&quot;;
import supabase from &quot;../supabaseClient&quot;;

const AddData = () =&gt; {
  const [name, setName] = useState(&quot;&quot;);
  const [age, setAge] = useState(0);
  const [address, setAddress] = useState(&quot;&quot;);

  const handleAdd = async () =&gt; {
    const { data, error } = await supabase.from(&quot;NACAMP_SAMPLE&quot;).insert({
      name,
      age,
      address,
    });

    if (error) {
      console.log(&quot;error =&gt; &quot;, error);
    } else {
      alert(&quot;데이터가 정상적으로 입력됐습니다.&quot;);
      console.log(&quot;data =&gt; &quot;, data);
    }
  };

  return (
    &lt;div
      style={{
        border: &quot;1px solid red&quot;,
      }}
    &gt;
      &lt;h2&gt;데이터 추가 로직&lt;/h2&gt;
      &lt;div&gt;
        이름 :{&quot; &quot;}
        &lt;input
          type=&quot;text&quot;
          value={name}
          onChange={(e) =&gt; {
            setName(e.target.value);
          }}
        /&gt;
      &lt;/div&gt;
      &lt;div&gt;
        나이 :{&quot; &quot;}
        &lt;input
          type=&quot;number&quot;
          value={age}
          onChange={(e) =&gt; {
            setAge(e.target.value);
          }}
        /&gt;
      &lt;/div&gt;
      &lt;div&gt;
        주소 :{&quot; &quot;}
        &lt;input
          type=&quot;text&quot;
          value={address}
          onChange={(e) =&gt; {
            setAddress(e.target.value);
          }}
        /&gt;
      &lt;/div&gt;
      &lt;button onClick={handleAdd}&gt;등록&lt;/button&gt;
    &lt;/div&gt;
  );
};

export default AddData;</code></pre>
<h2 id="데이터베이스-수정">데이터베이스 수정</h2>
<p>특정 ID 기준 데이터 수정 가능. update와 eq 조건 사용.</p>
<pre><code class="language-jsx">// src/App.jsx

import &quot;./App.css&quot;;
import AddData from &quot;./components/AddData&quot;;
import FetchData from &quot;./components/FetchData&quot;;
import UpdateData from &quot;./components/UpdateData&quot;;

function App() {
  return (
    &lt;&gt;
      &lt;h1&gt;Supabase&lt;/h1&gt;
      &lt;FetchData /&gt;
      &lt;UpdateData /&gt;
      &lt;AddData /&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
<pre><code class="language-jsx">// src/components/UpdateData.jsx

import React, { useState } from &quot;react&quot;;
import supabase from &quot;../supabaseClient&quot;;

const UpdateData = () =&gt; {
  const [targetId, setTargetId] = useState(0);
  const [address, setAddress] = useState(&quot;&quot;);

  const handleChange = async () =&gt; {
    const { error } = await supabase
      .from(&quot;NACAMP_SAMPLE&quot;)
      .update({
        address,
      })
      .eq(&quot;id&quot;, targetId);

    if (error) {
      console.log(&quot;error =&gt; &quot;, error);
    }
  };

  return (
    &lt;div
      style={{
        border: &quot;1px solid blue&quot;,
      }}
    &gt;
      &lt;h2&gt;데이터 수정 로직&lt;/h2&gt;
      아이디 :{&quot; &quot;}
      &lt;input
        type=&quot;number&quot;
        value={targetId}
        onChange={(e) =&gt; setTargetId(e.target.value)}
      /&gt;
      &lt;br /&gt;
      수정주소 :{&quot; &quot;}
      &lt;input
        type=&quot;text&quot;
        value={address}
        onChange={(e) =&gt; setAddress(e.target.value)}
      /&gt;
      &lt;button onClick={handleChange}&gt;변경&lt;/button&gt;
    &lt;/div&gt;
  );
};

export default UpdateData;</code></pre>
<h2 id="데이터베이스-삭제">데이터베이스 삭제</h2>
<p>ID 기준 데이터 삭제 처리. delete 메서드 사용.</p>
<pre><code class="language-jsx">// src/App.jsx

import &quot;./App.css&quot;;
import AddData from &quot;./components/AddData&quot;;
import DeleteData from &quot;./components/DeleteData&quot;;
import FetchData from &quot;./components/FetchData&quot;;
import UpdateData from &quot;./components/UpdateData&quot;;

function App() {
  return (
    &lt;&gt;
      &lt;h1&gt;Supabase&lt;/h1&gt;
      &lt;DeleteData /&gt;
      &lt;FetchData /&gt;
      &lt;UpdateData /&gt;
      &lt;AddData /&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
<pre><code class="language-jsx">// src/components/DeleteData.jsx

import React, { useState } from &quot;react&quot;;
import supabase from &quot;../supabaseClient&quot;;

const DeleteData = () =&gt; {
  const [targetId, setTargetId] = useState(0);

  const handleDelete = async () =&gt; {
    const { error } = await supabase
      .from(&quot;NACAMP_SAMPLE&quot;)
      .delete()
      .eq(&quot;id&quot;, targetId);

    if (error) {
      console.log(&quot;error =&gt; &quot;, error);
    }
  };

  return (
    &lt;div
      style={{
        border: &quot;1px solid blue&quot;,
      }}
    &gt;
      &lt;h2&gt;데이터 삭제 로직&lt;/h2&gt;
      아이디 :{&quot; &quot;}
      &lt;input
        type=&quot;number&quot;
        value={targetId}
        onChange={(e) =&gt; setTargetId(e.target.value)}
      /&gt;
      &lt;button onClick={handleDelete}&gt;삭제&lt;/button&gt;
    &lt;/div&gt;
  );
};

export default DeleteData;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React 숙련] React Router DOM 2 - Dynamic Route, 중첩된 라우트]]></title>
            <link>https://velog.io/@a_young1210/React-%EC%88%99%EB%A0%A8-React-Router-DOM-2-Dynamic-Route-%EC%A4%91%EC%B2%A9%EB%90%9C-%EB%9D%BC%EC%9A%B0%ED%8A%B8</link>
            <guid>https://velog.io/@a_young1210/React-%EC%88%99%EB%A0%A8-React-Router-DOM-2-Dynamic-Route-%EC%A4%91%EC%B2%A9%EB%90%9C-%EB%9D%BC%EC%9A%B0%ED%8A%B8</guid>
            <pubDate>Sat, 08 Mar 2025 03:56:10 GMT</pubDate>
            <description><![CDATA[<h1 id="◼-dynamic-route">◼ <strong>Dynamic Route</strong></h1>
<p>Dynamic Route는 동적 라우팅이라고도 부르며 
path에 유동적인 값을 포함해 특정 페이지로 이동하도록 구현하는 방식임.</p>
<pre><code class="language-jsx">// ❌️
&lt;Route path=&quot;/works/1&quot; element={&lt;Works /&gt;} /&gt;
&lt;Route path=&quot;/works/2&quot; element={&lt;Works /&gt;} /&gt;
&lt;Route path=&quot;/works/3&quot; element={&lt;Works /&gt;} /&gt;</code></pre>
<p>예를 들어 works 페이지에 여러 개의 work가 있고, 각 work마다 독립적인 상세 페이지가 필요하다고 가정함.
이 경우 works/1, works/2, works/3처럼 모든 경로를 하나씩 직접 작성하는게 아니라.
react-router-dom에서 제공하는 Dynamic Routes 기능을 사용해서 동적으로 변하는 경로를 간결하게 처리 가능함.</p>
<h2 id="설정"><strong>설정</strong></h2>
<pre><code class="language-jsx">// src/shared/Router.js

import React from &quot;react&quot;;
import { BrowserRouter, Route, Routes } from &quot;react-router-dom&quot;;
import Home from &quot;../pages/Home&quot;;
import About from &quot;../pages/About&quot;;
import Contact from &quot;../pages/Contact&quot;;
import Works from &quot;../pages/Works&quot;;
import Work from &quot;../pages/Work&quot;;

const Router = () =&gt; {
  return (
    &lt;BrowserRouter&gt;
      &lt;Routes&gt;
        &lt;Route path=&quot;/&quot; element={&lt;Home /&gt;} /&gt;
        &lt;Route path=&quot;about&quot; element={&lt;About /&gt;} /&gt;
        &lt;Route path=&quot;contact&quot; element={&lt;Contact /&gt;} /&gt;
        &lt;Route path=&quot;works&quot; element={&lt;Works /&gt;} /&gt;
                {/* path에 :id 사용 */}
        &lt;Route path=&quot;works/:id&quot; element={&lt;Work /&gt;} /&gt;
      &lt;/Routes&gt;
    &lt;/BrowserRouter&gt;
  );
};

export default Router;</code></pre>
<p>Works 페이지에 여러 개의 Work가 있고, 클릭 시 각각의 상세 페이지로 이동하도록 구현.</p>
<p>기존과 달리 path에 <code>:id</code> 사용함. <code>:id</code>는 동적인 값을 받겠다는 의미임.
따라서 works/1, works/2 … works/100 모두 같은 <code>&lt;Work /&gt;</code> 컴포넌트로 이동함.
이때 id 값은 useParams 훅을 통해 조회 가능.</p>
<h1 id="◼-useparams">◼ useParams</h1>
<p>Dynamic Routes를 사용하면 
같은 페이지 컴포넌트(Work.js)를 모두 동일하게 렌더링하게 됨.
하지만 useParams를 사용하면 
같은 컴포넌트를 렌더링 하더라도 각각 다른 id 값 조회 가능함.</p>
<p>useParams는 <strong>path의 있는 id 값을 조회할 수 있게 해주는 훅(</strong>path parameter 조회 목적).
예를 들어 works/100으로 이동하면 { id: &quot;100&quot; } 형태의 객체 반환함.</p>
<pre><code class="language-jsx">// src/pages/Works.js

import React from &#39;react&#39;;
import { Link } from &#39;react-router-dom&#39;;

const data = [
  { id: 1, todo: &#39;리액트 배우기&#39; },
  { id: 2, todo: &#39;노드 배우기&#39; },
  { id: 3, todo: &#39;자바스크립트 배우기&#39; },
  { id: 4, todo: &#39;파이어 베이스 배우기&#39; },
  { id: 5, todo: &#39;넥스트 배우기&#39; },
  { id: 6, todo: &#39;HTTP 프로토콜 배우기&#39; },
];

function Works() {
  return (
    &lt;div&gt;
      {data.map((work) =&gt; {
        return (
          &lt;div key={work.id}&gt;
            &lt;div&gt;할일: {work.id}&lt;/div&gt;
            {/* 링크 목록추가 */}
            &lt;Link to={`/works/${work.id}`}&gt;
              &lt;span style={{ cursor: &#39;pointer&#39; }}&gt;➡️ Go to: {work.todo}&lt;/span&gt;
            &lt;/Link&gt;
          &lt;/div&gt;
        );
      })}
    &lt;/div&gt;
  );
}

export default Works;</code></pre>
<pre><code class="language-jsx">// src/pages/Work.js

import React from &#39;react&#39;;
import { useParams } from &#39;react-router-dom&#39;;

const data = [
  { id: 1, todo: &#39;리액트 배우기&#39; },
  { id: 2, todo: &#39;노드 배우기&#39; },
  { id: 3, todo: &#39;자바스크립트 배우기&#39; },
  { id: 4, todo: &#39;파이어 베이스 배우기&#39; },
  { id: 5, todo: &#39;넥스트 배우기&#39; },
  { id: 6, todo: &#39;HTTP 프로토콜 배우기&#39; },
];

function Work() {
    // useParams 사용
  const param = useParams();
  // param 객체에서 id 값 추출 가능. 
  // 해당 id를 기준으로 데이터 필터링 가능.
  const work = data.find(
      (work) =&gt; work.id === parseInt(param.id)
    );

  return &lt;div&gt;{work.todo}&lt;/div&gt;;
}

export default Work;</code></pre>
<h1 id="◼-중첩된-라우트">◼ 중첩된 라우트</h1>
<p>중첩 라우팅은 <strong>특정 라우트 내부에 추가로 라우트를 정의하는 방식</strong>.
여러 계층의 UI 구성 시 유용함.</p>
<p>예시로 대시보드 구조가 있음. 
/dashboard 하위에 여러 섹션 존재하는 구조임.</p>
<pre><code class="language-jsx">// src/shared/Router.js

import { BrowserRouter, Routes, Route } from &#39;react-router-dom&#39;;
import DashboardLayout from &#39;./DashboardLayout&#39;;
import Profile from &#39;./Profile&#39;;
import Settings from &#39;./Settings&#39;;
import Reports from &#39;./Reports&#39;;

function App() {
  return (
    &lt;BrowserRouter&gt;
      &lt;Routes&gt;
        &lt;Route path=&quot;/dashboard&quot; element={&lt;DashboardLayout /&gt;}&gt;
          &lt;Route index element={&lt;Profile /&gt;} /&gt;
          &lt;Route path=&quot;settings&quot; element={&lt;Settings /&gt;} /&gt;
          &lt;Route path=&quot;reports&quot; element={&lt;Reports /&gt;} /&gt;
        &lt;/Route&gt;
      &lt;/Routes&gt;
    &lt;/BrowserRouter&gt;
  );
}</code></pre>
<p>/dashboard는 공통 레이아웃(DashboardLayout)을 사용함.
하위 경로마다 서로 다른 페이지 렌더링함.
라우트 구조가 명확해지고 UX 개선에 도움 됨.</p>
<h1 id="◼-outlet">◼ Outlet</h1>
<p>자식 라우트가 렌더링될 위치를 지정하는 컴포넌트.
복잡한 라우트 구조를 명확하게 관리 가능함.</p>
<pre><code class="language-jsx">// src/shared/Router.js

import React from &quot;react&quot;;
import { BrowserRouter, Route, Routes } from &quot;react-router-dom&quot;;
import Layout from &#39;./Layout&#39;;
import Home from &quot;../pages/Home&quot;;
import About from &quot;../pages/About&quot;;

const Router = () =&gt; {
  return (
    &lt;BrowserRouter&gt;
        &lt;Routes&gt;
              &lt;Route path=&quot;/&quot; element={&lt;Layout /&gt;}&gt;
                &lt;Route index element={&lt;Home /&gt;} /&gt;
                &lt;Route path=&quot;about&quot; element={&lt;About /&gt;} /&gt;
              &lt;/Route&gt;
            &lt;/Routes&gt;
    &lt;/BrowserRouter&gt;
  );
};

export default Router;</code></pre>
<pre><code class="language-jsx">// src/shared/Layout.js

import { Outlet } from &#39;react-router-dom&#39;;

function Layout() {
  return (
    &lt;div&gt;
      &lt;header&gt;Header Section&lt;/header&gt;
      &lt;main&gt;
        &lt;Outlet /&gt; {/* 이 위치에 자식 라우트의 컴포넌트 렌더링. */}
      &lt;/main&gt;
      &lt;footer&gt;Footer Section&lt;/footer&gt;
    &lt;/div&gt;
  );
}</code></pre>
<h1 id="◼-children">◼ children</h1>
<p>props와 chilren을 활용한 공통 Layout.</p>
<p>children은 어떤 자식 엘리먼트가 들어올지 모를 때 사용.
Sidebar, Dialog 같은 범용 컴포넌트에서 자주 사용.</p>
<p>Layout 역할의 컴포넌트로 이해 가능. composition(합성) 개념 기반 구조.
Header, Footer, Page를 조합하여 공통 레이아웃 구성.</p>
<pre><code class="language-jsx">// src/shared/Layout.js

import React from &#39;react&#39;;

const HeaderStyles = {
  width: &#39;100%&#39;,
  background: &#39;black&#39;,
  height: &#39;50px&#39;,
  display: &#39;flex&#39;,
  alignItems: &#39;center&#39;,
  paddingLeft: &#39;20px&#39;,
  color: &#39;white&#39;,
  fontWeight: &#39;600&#39;,
};
const FooterStyles = {
  width: &#39;100%&#39;,
  height: &#39;50px&#39;,
  display: &#39;flex&#39;,
  background: &#39;black&#39;,
  color: &#39;white&#39;,
  alignItems: &#39;center&#39;,
  justifyContent: &#39;center&#39;,
  fontSize: &#39;12px&#39;,
};

const layoutStyles = {
  display: &#39;flex&#39;,
    flexDirection: &#39;column&#39;,
  justifyContent: &#39;center&#39;,
  alignItems: &#39;center&#39;,
  minHeight: &#39;90vh&#39;,
}

function Header() {
  return (
    &lt;div style={{ ...HeaderStyles }}&gt;
      &lt;span&gt;Sparta Coding Club - Let&#39;s learn React&lt;/span&gt;
    &lt;/div&gt;
  );
}

function Footer() {
  return (
    &lt;div style={{ ...FooterStyles }}&gt;
      &lt;span&gt;copyright @SCC&lt;/span&gt;
    &lt;/div&gt;
  );
}

function Layout({ children }) {
  return (
    &lt;div&gt;
      &lt;Header /&gt;
      &lt;div style={{...layoutStyles}}&gt;
        {children}
      &lt;/div&gt;
      &lt;Footer /&gt;
    &lt;/div&gt;
  );
}

export default Layout;
</code></pre>
<pre><code class="language-jsx">// src/shared/Router.js

import React from &#39;react&#39;;
import { BrowserRouter, Route, Routes } from &#39;react-router-dom&#39;;
import Home from &#39;../pages/Home&#39;;
import About from &#39;../pages/About&#39;;
import Contact from &#39;../pages/Contact&#39;;
import Works from &#39;../pages/Works&#39;;
import Layout from &#39;./Layout&#39;;

const Router = () =&gt; {
  return (
    &lt;BrowserRouter&gt;
        {/* Layout으로 Routes 감쌈 */}
      &lt;Layout&gt;
        &lt;Routes&gt;
          &lt;Route path=&quot;/&quot; element={&lt;Home /&gt;} /&gt;
          &lt;Route path=&quot;about&quot; element={&lt;About /&gt;} /&gt;
          &lt;Route path=&quot;contact&quot; element={&lt;Contact /&gt;} /&gt;
          &lt;Route path=&quot;works&quot; element={&lt;Works /&gt;} /&gt;
        &lt;/Routes&gt;
      &lt;/Layout&gt;
    &lt;/BrowserRouter&gt;
  );
};

export default Router;
</code></pre>
<p>공통 UI 유지하면서 페이지별 내용만 교체 가능.
레이아웃 재사용에 적합한 구조.</p>
<h1 id="◼-중첩라우팅-vs-공통-레이아웃">◼ 중첩라우팅 vs 공통 레이아웃</h1>
<h2 id="중첩-라우팅outlet">중첩 라우팅(Outlet)</h2>
<pre><code class="language-jsx">&lt;Routes&gt;
  &lt;Route path=&quot;/&quot; element={&lt;Layout /&gt;}&gt;
    &lt;Route index element={&lt;Home /&gt;} /&gt;
    &lt;Route path=&quot;about&quot; element={&lt;About /&gt;} /&gt;
  &lt;/Route&gt;
&lt;/Routes&gt;</code></pre>
<p>라우트마다 다른 레이아웃 적용 가능.
동적이고 유연한 라우트 관리를 제공.</p>
<h2 id="공통-레이아웃children">공통 레이아웃(children)</h2>
<pre><code class="language-jsx">&lt;Layout&gt;
  &lt;Routes&gt;
    &lt;Route path=&quot;/&quot; element={&lt;Home /&gt;} /&gt;
    &lt;Route path=&quot;about&quot; element={&lt;About /&gt;} /&gt;
  &lt;/Routes&gt;
&lt;/Layout&gt;</code></pre>
<p>모든 페이지에 동일한 레이아웃 적용 시 적합.
구조가 단순해서 관리에 용이.</p>
<h2 id="권장-방식---중첩-라우팅--outlet">권장 방식 - 중첩 라우팅 + Outlet</h2>
<h3 id="이유1-라우트url-구조와-ui-구조-일치">이유1. 라우트(URL) 구조와 UI 구조 일치</h3>
<ul>
<li>URL 구조와 컴포넌트 구조가 자연스럽게 매칭됨</li>
<li>/dashboard, /dashboard/settings 같은 계층 표현에 적합함</li>
<li>라우트만 봐도 화면 구조 파악 가능함</li>
</ul>
<h3 id="이유2-레이아웃-분기-처리-용이">이유2. 레이아웃 분기 처리 용이</h3>
<ul>
<li>페이지마다 서로 다른 레이아웃 적용 가능함
예시 - 로그인 페이지: 헤더 없음, 메인 페이지: 헤더 있음</li>
<li>조건 분기 코드 없이 라우트 단위로 처리 가능함</li>
</ul>
<h3 id="이유-3-유지보수-및-확장성-우수">이유 3. 유지보수 및 확장성 우수</h3>
<ul>
<li>라우트 추가 시 구조 변경 최소화</li>
<li>대규모 서비스에서 구조 안정적임</li>
<li>팀 협업 시 역할 분리 명확함</li>
</ul>
<h2 id="공통-레이아웃-방식이-적합한-경우">공통 레이아웃 방식이 적합한 경우</h2>
<ul>
<li>모든 페이지가 동일한 레이아웃 사용</li>
<li>소규모 프로젝트</li>
<li>페이지 수 적음</li>
<li>구조 단순함</li>
</ul>
<h3 id="한계">한계</h3>
<ul>
<li>페이지별 레이아웃 분기 어려움</li>
<li>조건부 렌더링 증가</li>
<li>라우트 구조와 UI 구조 분리됨</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>