<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jungkyu_lol.log</title>
        <link>https://velog.io/</link>
        <description>롤보다 개발이 재밌는 프론트엔드 개발자입니다 :D</description>
        <lastBuildDate>Mon, 23 Feb 2026 13:57:14 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jungkyu_lol.log</title>
            <url>https://velog.velcdn.com/images/jungkyu_lol/profile/42921f40-da85-454e-aeec-de32667c0f50/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jungkyu_lol.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jungkyu_lol" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Next.js 위젯 설계: 일관성 있는 시스템을 만드는 컴포넌트 아키텍처]]></title>
            <link>https://velog.io/@jungkyu_lol/20260203</link>
            <guid>https://velog.io/@jungkyu_lol/20260203</guid>
            <pubDate>Mon, 23 Feb 2026 13:57:14 GMT</pubDate>
            <description><![CDATA[<h2 id="🏁-서론-위젯-단위의-독립적인-설계">🏁 서론: 위젯 단위의 독립적인 설계</h2>
<p>단순한 네비게이션이라도 프로젝트 규모가 커지면 관리 포인트가 기하급수적으로 늘어납니다. 이번 프로젝트에서는 단순히 UI를 그리는 수준을 넘어, 어느 환경에서도 일관되게 동작하고 스스로 데이터를 관리하는 <strong>&#39;독립적인 위젯(Widget)&#39;</strong> 구조를 설계하는 데 집중했습니다.</p>
<h2 id="🧩-구조적-설계-관심사의-분리">🧩 구조적 설계: 관심사의 분리</h2>
<p>기능을 세분화하여 각 파일이 하나의 책임만 갖게 했습니다. 이는 정규화된 스크립트가 파일을 인식하기 좋게 만들 뿐만 아니라, 유지보수 효율을 극대화합니다.</p>
<pre><code class="language-text">src/widgets/navigation/
├── lib/
│   └── useNavigation.js       // 비즈니스 로직 (Data Engine)
├── ui/
│   ├── NavigationWidget.jsx   // 엔트리 포인트 (조립 공장)
│   ├── NavigationContext.jsx  // 데이터 공유 (중앙 방송국)
│   ├── Navigation.jsx         // 배치 설계도 (Layout)
│   ├── NavigationLayouts.jsx  // 레이아웃 디자인 패턴 (Pattern Components)
│   └── Brand.jsx, NavLinks.jsx... // 세부 UI 부품
└── index.js                   // 외부 노출 인터페이스</code></pre>
<ul>
<li><strong>비즈니스 로직 엔진</strong>: URL 상태와 전역 상수를 조합해 데이터를 생성합니다. (#1)</li>
<li><strong>중앙 방송국</strong>: 위젯 내부 어디서든 데이터에 접근하게 해줍니다. (#2)</li>
<li><strong>레이아웃 설계도</strong>: UI의 뼈대와 배치 로직을 별도로 분리하여 시각적 일관성을 관리합니다. (#4)</li>
<li><strong>순수 UI 부품</strong>: 각자의 스타일과 역할에만 집중합니다. (#5)</li>
</ul>
<h2 id="⚠️-해결하고자-했던-문제-props-drilling">⚠️ 해결하고자 했던 문제: Props Drilling</h2>
<p>파일을 세분화할수록 최상단에서 구한 데이터를 하위 부품까지 전달하는 과정이 번거로워집니다. 중간 단계 컴포넌트가 배달부 역할을 하게 되면, UI 구조를 살짝만 바꿔도 모든 Props 경로를 수정해야 하는 비효율이 발생합니다.</p>
<p>이 <strong>결합도(Coupling)</strong> 문제를 해결하기 위해 <strong>Context API를 활용한 캡슐화</strong> 전략을 선택했습니다.</p>
<h2 id="🎯-핵심-전략-context-api를-통한-위젯-캡슐화">🎯 핵심 전략: Context API를 통한 위젯 캡슐화</h2>
<p>위젯 내부를 하나의 전용 컨텍스트로 감싸, 부품들이 중간 단계 없이 필요한 데이터를 직접 구독하도록 설계했습니다.</p>
<h3 id="이-구조의-이점">이 구조의 이점:</h3>
<ol>
<li><strong>독립성</strong>: 위젯은 외부로부터 어떤 데이터 주입도 필요로 하지 않습니다. 호출하는 즉시 스스로 동작합니다.</li>
<li><strong>확장성</strong>: 새로운 기능이 추가되어도 레이아웃 코드를 수정할 필요 없이, 해당 부품에서 컨텍스트 훅만 호출하면 됩니다.</li>
<li><strong>가독성</strong>: RootLayout은 복잡한 로직을 모른 채 위젯 한 줄로 깨끗하게 유지됩니다. (#6)</li>
</ol>
<hr>
<p><em><strong>이어서 이 구조를 구현하기 위한 상세 코드와 함께, 서버 컴포넌트의 장점을 알고도 클라이언트 방식을 택한 &#39;기술적 트레이드오프&#39;를 공유하겠습니다.</strong></em></p>
<hr>
<h1 id="nextjs-위젯-구현">Next.js 위젯 구현</h1>
<h2 id="1-데이터-로직의-추상화">1. 데이터 로직의 추상화</h2>
<p>위젯에 필요한 모든 데이터와 상태 로직을 UI와 분리하여 커스텀 훅으로 관리합니다. 상세한 계산 로직은 훅 내부로 숨기고, UI에는 오직 필요한 데이터와 함수만을 반환하는 깨끗한 인터페이스를 제공합니다.</p>
<pre><code class="language-javascript">// #1. 비즈니스 로직 인터페이스 (src/widgets/navigation/lib/useNavigation.js)
// 상세 로직은 캡슐화하고 UI에 필요한 데이터 구조만 정의합니다.
export const useNavigation = () =&gt; {
  // ... 내부 계산 로직 (Github API 연동, URL 파라미터 파싱 등)은 생략

  return {
    username,      // 표시될 유저 이름
    avatarUrl,     // 프로필 이미지 경로
    customUsername,// 현재 커스텀 모드 여부
    getHref,       // 상태를 유지하며 경로를 생성하는 함수
    pathname       // 현재 경로 정보
  };
};</code></pre>
<h2 id="2-기술적-구현-context와-widget">2. 기술적 구현: Context와 Widget</h2>
<p>일관성 있는 시스템을 위해 위젯 내부의 데이터 방송국을 구축했습니다. 이 컨텍스트는 내부 부품들이 외부 간섭 없이 데이터를 수급하는 통로가 됩니다.</p>
<pre><code class="language-javascript">// #2. 중앙 방송국 (src/widgets/navigation/ui/NavigationContext.jsx)
&#39;use client&#39;;
import { createContext, useContext } from &#39;react&#39;;

const NavigationContext = createContext(null);

export const useNavContext = () =&gt; {
  const context = useContext(NavigationContext);
  if (!context) throw new Error(&#39;useNavContext는 NavigationProvider 안에서만 쓰세요!&#39;);
  return context;
};

export const NavigationProvider = NavigationContext.Provider;</code></pre>
<p>그다음, 비즈니스 로직과 UI를 결합하는 <strong>조립 공장(Widget)</strong>을 구축했습니다. 이 위젯은 외부에서 데이터를 주입받지 않고 스스로 로직을 수행하는 <strong>Self-contained</strong> 컴포넌트입니다. </p>
<pre><code class="language-javascript">// #3. 조립 공장 (src/widgets/navigation/ui/NavigationWidget.jsx)
&#39;use client&#39;;
import { useNavigation } from &#39;../lib/useNavigation&#39;;
import { NavigationProvider } from &#39;./NavigationContext&#39;;
import { Navigation } from &#39;./Navigation&#39;;

export const NavigationWidget = () =&gt; {
  const navData = useNavigation();
  return (
    &lt;NavigationProvider value={navData}&gt;
      &lt;Navigation /&gt;
    &lt;/NavigationProvider&gt;
  );
};</code></pre>
<h2 id="3-레이아웃-로직의-분리">3. 레이아웃 로직의 분리</h2>
<p>UI의 배치 로직(<code>Navigation.jsx</code>)과 실제 뼈대(<code>NavigationLayouts.jsx</code>)를 분리했습니다. 이를 통해 <code>Navigation.jsx</code>는 데이터 흐름이나 복잡한 스타일 코드 없이, 어떤 부품이 어디에 위치하는지 한눈에 보여주는 <strong>&#39;설계도&#39;</strong> 역할만 수행하게 됩니다.</p>
<pre><code class="language-javascript">// #4. 레이아웃 설계도 (src/widgets/navigation/ui/Navigation.jsx)
// 데이터를 받지 않고 오직 부품의 배치(Layout)만 정의합니다.
import * as Layout from &#39;./NavigationLayouts&#39;;
import { Brand } from &#39;./Brand&#39;;
import { NavLinks } from &#39;./NavLinks&#39;;
import { DarkModeToggle } from &#39;./DarkModeToggle&#39;;
import { TryYourself } from &#39;./TryYourself&#39;;

export const Navigation = () =&gt; {
  return (
    &lt;Layout.Root&gt;
      &lt;Layout.LeftSide&gt;
        &lt;Brand /&gt;
        &lt;Layout.ShowOnMobile&gt;&lt;DarkModeToggle /&gt;&lt;/Layout.ShowOnMobile&gt;
      &lt;/Layout.LeftSide&gt;

      &lt;Layout.RightSide&gt;
        &lt;TryYourself /&gt;
        &lt;Layout.MenuWrapper&gt;
          &lt;NavLinks /&gt;
          &lt;Layout.ShowOnDesktop&gt;&lt;DarkModeToggle /&gt;&lt;/Layout.ShowOnDesktop&gt;
        &lt;/Layout.MenuWrapper&gt;
      &lt;/Layout.RightSide&gt;
    &lt;/Layout.Root&gt;
  );
};</code></pre>
<h2 id="4-ui-부품의-변화-데이터-직접-구독">4. UI 부품의 변화: 데이터 직접 구독</h2>
<p>하위 부품들은 Props 배달을 기다리지 않습니다. 중앙 방송국에서 필요한 데이터만 직접 수령하는 구조를 통해 코드의 간결함을 유지합니다.</p>
<pre><code class="language-javascript">// #5. 데이터 직접 구독 부품 (src/widgets/navigation/ui/Brand.jsx)
import { useNavContext } from &#39;./NavigationContext&#39;;

export const Brand = ({ size = 32 }) =&gt; {
  const { avatarUrl, username, getHref } = useNavContext();
  return (
    &lt;Link href={getHref(&#39;/&#39;)}&gt;
      &lt;Image src={avatarUrl} alt={username} width={size} height={size} /&gt;
      &lt;span&gt;{username}&lt;/span&gt;
    &lt;/Link&gt;
  );</code></pre>
<h2 id="5-기술적-트레이드오프-서버-컴포넌트와-dx">5. 기술적 트레이드오프: 서버 컴포넌트와 DX</h2>
<p>이 설계의 가장 큰 고민은 <strong>&quot;순수 UI임에도 &#39;use client&#39;가 불가피한가?&quot;</strong>였습니다. RSC(서버 컴포넌트)의 이점은 분명하지만, 저는 <strong>클라이언트 중심의 위젯 캡슐화</strong>를 선택했습니다.</p>
<h3 id="①-위젯의-독립성-확보">① 위젯의 독립성 확보</h3>
<p>RSC 방식을 유지하려면 데이터를 부모에서 구하거나 수동으로 넘겨야 하며, 이는 네비게이션을 레이아웃의 종속물로 만듭니다. 저는 어디에 꽂아도 즉시 돌아가는 <strong>독립 모듈</strong>로서의 가치를 우선했습니다.</p>
<h3 id="②-유지보수와-확장성">② 유지보수와 확장성</h3>
<p>프로젝트가 커질수록 Props 경로를 추적하는 비용은 커집니다. Context를 활용하면 구조가 깊어져도 하위 부품을 자유롭게 추가/삭제할 수 있는 <strong>유연한 아키텍처</strong>를 가질 수 있습니다.</p>
<h3 id="③-응집도의-역설">③ 응집도의 역설</h3>
<p><code>use client</code>를 통해 위젯을 캡슐화하면 외부와의 결합도는 낮아지고 내부 응집도는 높아집니다. 최종적으로 <code>layout.jsx</code>는 비즈니스 로직을 전혀 모른 채 아래와 같이 선언적인 형태를 유지하게 됩니다.</p>
<pre><code class="language-javascript">// #6. 최종 레이아웃 적용 (src/app/layout.jsx)
import { NavigationWidget } from &#39;@/widgets&#39;;

export default function RootLayout({ children }) {
  return (
    &lt;html lang=&quot;ko&quot;&gt;
      &lt;body&gt;
        &lt;LayoutContainer nav={&lt;NavigationWidget /&gt;}&gt;
          {children}
        &lt;/LayoutContainer&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}</code></pre>
<h2 id="6-🏁-마치며-최선의-선택">6. 🏁 마치며: 최선의 선택</h2>
<p>모든 기술 선택에는 트레이드오프가 존재합니다. 이번 설계는 <strong>&quot;서버 컴포넌트의 순수성&quot;</strong>과 <strong>&quot;위젯 아키텍처의 편리함&quot;</strong> 사이의 균형점을 찾는 과정이었습니다.</p>
<p>설계에 정답은 없지만, 자신의 철학을 코드로 녹여내고 그 이유를 증명하는 과정이 시니어 개발자로 가는 핵심임을 다시 한번 체감했습니다.</p>
<p>👉 <a href="https://jungkyu-dev-pro.vercel.app">프로젝트 라이브 데모 바로가기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 외부 API 연동, 클라이언트 401 에러를 서버와 API Route로 해결한 과정]]></title>
            <link>https://velog.io/@jungkyu_lol/20250629</link>
            <guid>https://velog.io/@jungkyu_lol/20250629</guid>
            <pubDate>Sat, 28 Jun 2025 22:00:16 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="목차">목차</h2>
<ol>
<li>문제: 왜 클라이언트에서 인증 API 호출이 실패할까?</li>
<li>첫 번째 시도: 클라이언트 컴포넌트에서 직접 호출 (실패)</li>
<li>두 번째 시도: 서버 컴포넌트에서 데이터 패칭 (성공)</li>
<li>세 번째 시도: API Route(프록시) + 클라이언트 컴포넌트 (성공)</li>
<li>최종 정리: Next.js에서 인증이 필요한 외부 API는 반드시 서버(API Route, 서버 컴포넌트)에서 호출하자!</li>
<li>회고</li>
</ol>
<hr>
<h2 id="1-문제-왜-클라이언트에서-인증-api-호출이-실패할까">1. 문제: 왜 클라이언트에서 인증 API 호출이 실패할까?</h2>
<p>Next.js 15에서 조직의 GitHub 레포지토리 목록을 가져오는 기능을 만들고 싶었습니다.<br>처음에는 단순히 클라이언트 컴포넌트에서 fetch로 GitHub API를 호출하면 될 거라 생각했습니다.</p>
<p>하지만,  </p>
<ul>
<li><strong>401 Unauthorized</strong></li>
<li><strong>CORS 에러</strong></li>
<li><strong>환경변수 노출 불가</strong></li>
</ul>
<p>등의 문제에 부딪혔습니다.</p>
<hr>
<h2 id="2-첫-번째-시도-클라이언트-컴포넌트에서-직접-호출-실패">2. 첫 번째 시도: 클라이언트 컴포넌트에서 직접 호출 (실패)</h2>
<p>처음엔 아래처럼 작성했습니다.</p>
<pre><code class="language-jsx">// app/org-test/page.jsx
&quot;use client&quot;;
import { useState } from &quot;react&quot;;
import { getOrgRepos } from &quot;../data&quot;; // 서버에서 인증 토큰을 붙여 fetch하는 함수

export default function OrgTestPage() {
  const [orgName, setOrgName] = useState(&quot;&quot;);
  const [repos, setRepos] = useState([]);

  // ❌ 브라우저에서 직접 서버 함수 호출
  const handleFetch = async () =&gt; {
    const data = await getOrgRepos(orgName);
    setRepos(data);
  };

  return (
    &lt;div&gt;
      &lt;input value={orgName} onChange={e =&gt; setOrgName(e.target.value)} /&gt;
      &lt;button onClick={handleFetch}&gt;레포 가져오기&lt;/button&gt;
      &lt;ul&gt;
        {repos.map(repo =&gt; &lt;li key={repo.id}&gt;{repo.name}&lt;/li&gt;)}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
}

// app/data.js
export async function getOrgRepos(orgName) {
  const res = await fetch(
    `https://api.github.com/orgs/${orgName}/repos?per_page=100`,
    {
      headers: { Authorization: `Bearer ${process.env.GH_TOKEN}` }, // 환경변수 사용
    }
  );
  if (!res.ok) {
    // 에러 처리
    return [];
  }
  return res.json();
}</code></pre>
<p><strong>문제점</strong></p>
<ul>
<li>브라우저에서는 환경변수(GH_TOKEN)에 접근할 수 없음</li>
<li>인증 헤더가 누락되어 401 에러 발생</li>
<li>CORS 정책에 막힘</li>
</ul>
<hr>
<h2 id="3-두-번째-시도-서버-컴포넌트에서-데이터-패칭-성공">3. 두 번째 시도: 서버 컴포넌트에서 데이터 패칭 (성공)</h2>
<p>Next.js의 서버 컴포넌트는 서버에서만 실행되므로, 환경변수와 인증 토큰을 안전하게 사용할 수 있습니다.</p>
<pre><code class="language-jsx">// app/org-test/page.jsx
import { getOrgRepos } from &quot;../data&quot;;

export default async function OrgTestPage() {
  const orgName = &quot;preOnBorading-Idle&quot;;
  // ✅ 서버에서 환경변수(GH_TOKEN)로 안전하게 fetch
  const repos = await getOrgRepos(orgName);

  return (
    &lt;div&gt;
      &lt;h1&gt;조직명: {orgName}&lt;/h1&gt;
      &lt;ul&gt;
        {repos.map(repo =&gt; &lt;li key={repo.id}&gt;{repo.name}&lt;/li&gt;)}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><strong>결과</strong></p>
<ul>
<li>서버에서 안전하게 인증 토큰을 사용해 GitHub API 호출 성공</li>
<li>브라우저에는 토큰이 노출되지 않음</li>
</ul>
<hr>
<h2 id="4-세-번째-시도-api-route프록시--클라이언트-컴포넌트-성공">4. 세 번째 시도: API Route(프록시) + 클라이언트 컴포넌트 (성공)</h2>
<p>클라이언트 컴포넌트에서 직접 외부 API를 호출하는 대신, 
<strong>내 서버의 API Route(프록시)</strong>를 통해 데이터를 받아오는 구조로 변경했습니다.</p>
<h3 id="4-1-api-route-생성-appapiorg-reposroutejs">4-1. API Route 생성 (app/api/org-repos/route.js)</h3>
<pre><code class="language-js">// app/api/org-repos/route.js
export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const org = searchParams.get(&quot;org&quot;);
  const response = await fetch(
    `https://api.github.com/orgs/${org}/repos?per_page=100`,
    {
      headers: { Authorization: `Bearer ${process.env.GH_TOKEN}` },
    }
  );
  const data = await response.json();
  return Response.json(data, { status: response.status });
}</code></pre>
<h3 id="4-2-클라이언트-컴포넌트에서-api-route로-요청">4-2. 클라이언트 컴포넌트에서 API Route로 요청</h3>
<pre><code class="language-jsx">// app/org-test/page.jsx
&quot;use client&quot;;
import { useState } from &quot;react&quot;;

export default function OrgTestPage() {
  const [orgName, setOrgName] = useState(&quot;&quot;);
  const [repos, setRepos] = useState([]);

  // ✅ 내 서버의 API Route로만 요청
  const handleFetch = async () =&gt; {
    const res = await fetch(`/api/org-repos?org=${encodeURIComponent(orgName)}`);
    const data = await res.json();
    setRepos(data);
  };

  return (
    &lt;div&gt;
      &lt;input value={orgName} onChange={e =&gt; setOrgName(e.target.value)} /&gt;
      &lt;button onClick={handleFetch}&gt;레포 가져오기&lt;/button&gt;
      &lt;ul&gt;
        {repos.map(repo =&gt; &lt;li key={repo.id}&gt;{repo.name}&lt;/li&gt;)}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><strong>결과</strong></p>
<ul>
<li>브라우저에서는 내 서버의 API Route(<code>/api/org-repos</code>)로만 요청</li>
<li>서버(API Route)에서 환경변수/인증 토큰을 사용해 GitHub API에 안전하게 요청</li>
<li>브라우저에는 인증 정보가 노출되지 않으면서, 정상적으로 데이터가 출력됨</li>
</ul>
<hr>
<h2 id="5-✅-최종-정리-nextjs에서-인증이-필요한-외부-api는-반드시-서버api-route-서버-컴포넌트에서-호출하자">5. ✅ 최종 정리: Next.js에서 인증이 필요한 외부 API는 반드시 서버(API Route, 서버 컴포넌트)에서 호출하자!</h2>
<ul>
<li><strong>브라우저(클라이언트 컴포넌트)에서는 외부 API에 직접 요청하지 않는다.</strong><ul>
<li>인증 정보 노출, 401 에러, CORS 문제 등 다양한 위험이 있다.</li>
</ul>
</li>
<li><strong>API Route(프록시) 또는 서버 컴포넌트에서만 외부 API와 통신한다.</strong><ul>
<li>환경변수(토큰)를 안전하게 사용할 수 있고, 보안과 CORS 문제를 모두 해결할 수 있다.</li>
</ul>
</li>
<li><strong>클라이언트 컴포넌트에서는 내 서버의 API Route만 호출한다.</strong><ul>
<li>서버가 외부 API와 통신한 결과만 받아서 화면에 렌더링한다.</li>
</ul>
</li>
<li><strong>Next.js 15의 app/api 구조를 활용하면, 별도의 백엔드 없이도 안전하고 효율적으로 API 연동이 가능하다.</strong></li>
</ul>
<hr>
<h2 id="6-회고">6. 회고</h2>
<blockquote>
<p><strong>동적 입력/상호작용이 필요하면 프록시(API Route),</strong>
<strong>정적 데이터는 서버 컴포넌트에서 직접 패칭!</strong></p>
</blockquote>
<p>처음엔 클라이언트에서 바로 외부 API를 호출하려다 401 에러에 막혀서 당황했지만, 이번 경험을 통해서 동적 입력이 필요할 땐 프록시(API Route)를 사용하고 정적 데이터는 서버 컴포넌트를 써야 한다는 걸 이번에 확실히 배웠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React·TypeScript 프로젝트에서 통합된 API 에러 핸들링 구축기]]></title>
            <link>https://velog.io/@jungkyu_lol/20250519</link>
            <guid>https://velog.io/@jungkyu_lol/20250519</guid>
            <pubDate>Sun, 18 May 2025 15:35:13 GMT</pubDate>
            <description><![CDATA[<p>웹 애플리케이션에서는 <strong>네트워크 오류</strong>, <strong>HTTP 상태 오류</strong>, <strong>비즈니스 로직 오류</strong>가 뒤섞여 발생합니다.
이럴 때 오류를 개별적으로 처리하다 보면 코드가 난잡해지고, 사용자 경험도 일관성을 잃게 됩니다.
이 글에서는 <strong>커스텀 에러 클래스(ApiError), axios 인터셉터 기반 네트워크 레이어, safeAxios 래퍼</strong>를 통해 이러한 문제를 어떻게 해결할 수 있는지 살펴보겠습니다.</p>
<hr>
<h2 id="기본적인-에러-유형">기본적인 에러 유형</h2>
<p>API 호출 실패는 크게 <strong>3가지 레이어</strong>에서 발생할 수 있습니다.
각 레이어가 “어떤 오류인지”를 구분해야, 그에 맞는 처리를 할 수 있습니다.</p>
<h3 id="transport-level-에러">Transport-level 에러</h3>
<ul>
<li><p>** HTTP 프로토콜** 수준에서 리턴된 오류입니다.</p>
</li>
<li><p><strong>예시:</strong>
서버가 <code>4xx</code>/<code>5xx</code> 상태 코드를 응답했을 때</p>
<pre><code class="language-http">HTTP/1.1 404 Not Found
{ &quot;message&quot;: &quot;Not Found&quot; }</code></pre>
<pre><code class="language-http">HTTP/1.1 500 Internal Server Error
&lt;html&gt;…서버 오류 페이지…&lt;/html&gt;</code></pre>
<pre><code class="language-ts">  // Fetch
try {
const res = await fetch(&#39;/api/data&#39;)
// HTTP 오류라도 res.ok가 false면 직접 예외를 던져야 함
if (!res.ok) throw new Error(&#39;요청 실패&#39;)
const data = await res.json()
} catch (err) {
  console.error(err) // 어떤 상태 코드인지 알 수 없음
}

// Axios
try {
const { data } = await axios.get(&#39;/api/data&#39;)
} catch (err) {
// err.response?.status에 접근해야 401/500 구분 가능
console.error(err) 
}</code></pre>
</li>
<li><p><strong>특징:</strong></p>
<ul>
<li>HTTP 상태 코드 자체가 실패를 의미</li>
<li>Fetch는 <code>res.ok</code> 검사, Axios는 자동으로 <code>AxiosError</code>를 던짐</li>
<li>본문 형식(JSON, HTML 등)이 일정치 않아 파싱 로직이 복잡해질 수 있음</li>
</ul>
</li>
</ul>
<hr>
<h3 id="business-level-에러">Business-level 에러</h3>
<ul>
<li><p><strong>HTTP 요청은 성공(200 OK)</strong>이지만, <strong>응답 바디 내부에서 “비즈니스 로직 실패”로 표시된 오류</strong>입니다.</p>
</li>
<li><p><strong>예시:</strong></p>
<ul>
<li>RESTful: <code>{ success: false, code: &#39;X&#39;, message: &#39;…&#39; }</code></li>
<li>레거시: <code>{ result: 0, message: &#39;…&#39; }</code></li>
</ul>
<pre><code class="language-http">HTTP/1.1 200 OK
Content-Type: application/json

{
&quot;success&quot;: false,
&quot;code&quot;: &quot;INVALID_INPUT&quot;,
&quot;message&quot;: &quot;이름을 입력해주세요.&quot;
}</code></pre>
<pre><code class="language-http">HTTP/1.1 200 OK
Content-Type: application/json

{
&quot;result&quot;: 0,
&quot;message&quot;: &quot;비밀번호가 일치하지 않습니다.&quot;
}</code></pre>
</li>
</ul>
<ul>
<li><p><strong>특징:</strong></p>
<ul>
<li>HTTP 200이지만 애플리케이션 로직에서 실패로 간주</li>
<li><code>success</code>·<code>code</code>·<code>result</code> 등 필드명이 API마다 달라, 매번 커스텀 판별 코드가 필요</li>
</ul>
</li>
</ul>
<hr>
<h3 id="network-level-에러">Network-level 에러</h3>
<ul>
<li><p><strong>요청이 서버에 도달하지 못하거나, 응답 자체를 받지 못했을 때</strong> 발생하는 오류입니다.</p>
</li>
<li><p><strong>예시:</strong></p>
<ul>
<li>인터넷 끊김, DNS 실패, CORS 차단</li>
<li>클라이언트 타임아웃 설정 초과</li>
</ul>
<pre><code class="language-ts">// Fetch
fetch(&#39;/api/data&#39;)
  .catch(err =&gt; console.error(&#39;Network Error:&#39;, err.message))

// Axios
axios.get(&#39;/api/data&#39;)
  .catch(err =&gt; {
    if (!err.response) {
      console.error(&#39;Network or CORS Error:&#39;, err.message)
    }
  })</code></pre>
</li>
<li><p><strong>특징:</strong></p>
<ul>
<li><code>err.response</code>가 <code>undefined</code>이므로 HTTP 상태를 알 수 없음</li>
<li>재시도, 오프라인 처리, 사용자 안내(UI) 등을 별도 구현해야 함</li>
</ul>
</li>
</ul>
<hr>
<h2 id="전역화된-에러-처리-전략">전역화된 에러 처리 전략</h2>
<p>API 호출 중 발생하는 오류를 적절히 처리하지 않으면, 사용자 경험도 망가지고 디버깅도 어려워집니다.
특히 RESTful API가 아닌 레거시 응답 형식, 네트워크 끊김, 서버 내부 오류까지 모두 포괄하려면 단순한 <code>try/catch</code>를 넘어서 <strong>전역화된 에러 처리 전략</strong>이 필요합니다.</p>
<h3 id="🔑-구현한-방법">🔑 구현한 방법</h3>
<blockquote>
<p><strong>ApiError</strong></p>
</blockquote>
<ul>
<li>HTTP·네트워크 오류와 비즈니스 실패를 하나로 묶어 전달</li>
<li>message, status, code, cause를 포함해 일관된 에러 분기 지원</li>
</ul>
<blockquote>
<p><strong>apiClient 인터셉터</strong></p>
</blockquote>
<ul>
<li>모든 4xx/5xx·네트워크 예외를 ApiError로 변환</li>
<li>호출 코드에서는 instanceof ApiError만 체크하면 됨</li>
</ul>
<blockquote>
<p><strong>safeAxios</strong></p>
</blockquote>
<ul>
<li>“통신은 성공했지만 비즈니스 로직 실패”(success: false 등) 감지</li>
<li>validateSuccess·extractPayload 옵션으로 어떤 응답 구조도 재사용 가능</li>
</ul>
<blockquote>
<p><strong>Global Error Handler (handleApiError)</strong></p>
</blockquote>
<ul>
<li>에러 메시지 토스트, 로그인 리다이렉트, Sentry 로깅 등을 한곳에서 관리</li>
<li>React Query onError → 전역 설정으로 중복 제거</li>
</ul>
<hr>
<h3 id="apierror---error형태-정의">ApiError - Error형태 정의</h3>
<pre><code class="language-ts">export class ApiError extends Error {
  readonly name = &#39;ApiError&#39;
  readonly status?: number    // HTTP 상태 코드
  readonly code?: string      // 서버 비즈니스 오류 식별자
  readonly cause?: unknown    // 원본 응답이나 내부 예외

  constructor(message: string, options?: {status?:number;code?:string;cause?:unknown}) {
    super(message)
    this.status = options?.status
    this.code   = options?.code
    this.cause  = options?.cause
    Error.captureStackTrace?.(this, ApiError)
  }
}</code></pre>
<ul>
<li><p><strong>왜 필요한가?</strong></p>
<ul>
<li>기본 <code>Error</code>엔 <code>status</code>나 <code>code</code> 같은 메타정보가 없습니다.</li>
<li><code>ApiError</code>의 메타정보를 활용해서 분기처리가 가능해집니다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="apiclient-인터셉터---http네트워크-오류-일원화">apiClient 인터셉터 - HTTP/네트워크 오류 일원화</h3>
<pre><code class="language-ts">const apiClient = axios.create({ baseURL, timeout:10000 })
apiClient.interceptors.response.use(
  response =&gt; response,
  err =&gt; {
    const status = err.response?.status
    const data   = err.response?.data ?? {}
    const msg    = data.message || err.message || &#39;서버 오류 발생&#39;
    throw new ApiError(msg, { status, code: data.code, cause: data })
  }
)</code></pre>
<ul>
<li><p><strong>무엇을 하나?</strong></p>
<ul>
<li>4xx/5xx, 네트워크 끊김 등 axios가 던지는 모든 에러를 가로채서 <code>ApiError</code>로 변환합니다.</li>
<li>이후 호출 코드에선 <code>axios.isAxiosError</code> 체크 대신 단순 <code>err instanceof ApiError</code>만 쓰면 됩니다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="safeaxios---비즈니스-실패-감지--데이터-추출">safeAxios - 비즈니스 실패 감지 + 데이터 추출</h3>
<pre><code class="language-ts">async function safeAxios&lt;T&gt;(
  config: AxiosRequestConfig,
  options?: {
    validateSuccess?: (data:any) =&gt; boolean,
    extractPayload?: (data:any) =&gt; T
  }
): Promise&lt;T&gt; {
  const res  = await apiClient.request(config)    // HTTP 레벨은 인터셉터가 처리
  const data = res.data ?? {}

  // 1) 비즈니스 실패 감지
  const isSuccess = options?.validateSuccess?.(data) ?? data.success !== false
  if (!isSuccess) throw new ApiError(data.message||&#39;요청 실패&#39;, { status:res.status, code:data.code, cause:data })

  // 2) 필요한 부분만 꺼내기
  return options?.extractPayload?.(data) ?? (data.data ?? data)
}</code></pre>
<ul>
<li><p><strong>왜 나눴나?</strong></p>
<ul>
<li><code>apiClient</code>는 “통신 실패”만,</li>
<li><code>safeAxios</code>는 “통신은 됐는데 비즈니스 로직상 실패”를 처리합니다.</li>
<li><code>validateSuccess</code>/<code>extractPayload</code> 옵션으로 어떤 응답 구조도 유연하게 대응 가능합니다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="global-error-handler---ux와-로깅-통합">Global Error Handler - UX와 로깅 통합</h3>
<pre><code class="language-ts">function handleApiError(err: unknown) {
  if (err instanceof ApiError) {
    if (err.status === 401) return navigate(&#39;/login&#39;)
    toast.error(err.message)
    Sentry.captureException(err)
  } else {
    toast.error(&#39;알 수 없는 오류&#39;)
  }
}</code></pre>
<ul>
<li><p><strong>어디서 쓰나?</strong></p>
<ul>
<li>React Query <code>onError</code></li>
<li>컴포넌트 <code>catch</code> 블록</li>
<li>전역 에러 바운더리 등</li>
</ul>
</li>
</ul>
<hr>
<h3 id="react-query-전역-설정">React Query 전역 설정</h3>
<pre><code class="language-tsx">const queryClient = new QueryClient({
  defaultOptions: {
    queries:   { onError: handleApiError },
    mutations:{ onError: handleApiError },
  }
})</code></pre>
<ul>
<li><p><strong>장점</strong></p>
<ul>
<li>모든 <code>useQuery</code>/<code>useMutation</code>에서 중복 없이 일관된 에러 UX 보장</li>
</ul>
</li>
</ul>
<hr>
<p>이렇게 ApiError, apiClient 인터셉터, safeAxios, 그리고 Global Error Handler를 조합함으로써 모든 API 호출에 대해 일관된 에러 처리 체계를 확립할 수 있었습니다.
이 코드를 적용한 뒤에는 에러 대응 로직이 한눈에 명확해지고, 유지보수성과 시스템 안정성이 크게 향상된 것을 실감하고 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 Supabase 프로필 이미지 업로드: 서비스와 쿼리를 분리한 구조 설계]]></title>
            <link>https://velog.io/@jungkyu_lol/20250425</link>
            <guid>https://velog.io/@jungkyu_lol/20250425</guid>
            <pubDate>Fri, 25 Apr 2025 04:16:53 GMT</pubDate>
            <description><![CDATA[<h3 id="💡-개요">💡 개요</h3>
<p>Supabase의 Storage를 이용해 프로필 이미지를 업로드하고 불러오는 기능을 구현하면서,<br>React Query와 Zustand Store, API 서비스 레이어를 <strong>역할별로 명확하게 분리한 구조</strong>를 소개합니다.</p>
<hr>
<h2 id="🧩-문제-인식">🧩 문제 인식</h2>
<p>처음에는 컴포넌트 내부에서 Supabase API를 직접 호출하고,<br>업로드나 이미지 조회 상태도 모두 컴포넌트에서 관리하고 있었습니다.</p>
<p>하지만 다음과 같은 문제가 발생했죠:</p>
<ul>
<li>✅ 컴포넌트가 너무 많은 책임을 가짐 (API 호출 + 상태 관리 + 로직)</li>
<li>✅ 다른 페이지에서 재사용하려면 같은 로직을 반복해야 함</li>
<li>✅ 테스트와 유지보수가 어려움</li>
</ul>
<hr>
<h2 id="🛠-구조-설계-방향">🛠 구조 설계 방향</h2>
<p>이런 문제를 해결하기 위해 다음과 같이 <strong>역할을 분리</strong>했습니다.</p>
<table>
<thead>
<tr>
<th>책임</th>
<th>위치</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>API 호출</td>
<td><code>profileService</code></td>
<td>Supabase 호출만 담당 (순수 함수)</td>
</tr>
<tr>
<td>React Query</td>
<td><code>useProfileImageSuspenseQuery</code>, <code>useUploadProfileImageMutation</code></td>
<td>상태·에러 처리·캐시 무효화 포함</td>
</tr>
<tr>
<td>UI 상태</td>
<td>컴포넌트 내부</td>
<td>파일 선택, 미리보기 등 UI 전용 상태</td>
</tr>
<tr>
<td>사용자 정보</td>
<td><code>useUserStore</code></td>
<td>상태 전역 저장소에서 관리</td>
</tr>
</tbody></table>
<hr>
<h2 id="📦-1-서비스-레이어-profileservicets">📦 1. 서비스 레이어 (<code>profileService.ts</code>)</h2>
<pre><code class="language-ts">export const profileService = {
  async uploadProfileImage(file: File, userId: string) {
    const { data, error } = await supabase.storage
      .from(&#39;images&#39;)
      .upload(`profile/${userId}`, file, { upsert: true })
    if (error) throw error
    return data
  },

  async getProfileImageUrl(userId: string): Promise&lt;string&gt; {
    const { data } = supabase.storage.from(&#39;images&#39;).getPublicUrl(`profile/${userId}`)
    if (!data?.publicUrl) throw new Error(&#39;이미지 URL이 없습니다.&#39;)
    return data.publicUrl
  },
}</code></pre>
<ul>
<li>👉 Supabase 호출만 담당</li>
<li>👉 <strong>캐시 무효화나 토스트는 하지 않음</strong> (퓨어 로직만)</li>
</ul>
<hr>
<h2 id="⚙️-2-쿼리-훅-useprofileimagesuspensequeryts-useuploadprofileimagemutationts">⚙️ 2. 쿼리 훅 (<code>useProfileImageSuspenseQuery.ts</code>, <code>useUploadProfileImageMutation.ts</code>)</h2>
<pre><code class="language-ts">export const useProfileImageSuspenseQuery = (profileId: string) =&gt;
  useSuspenseQuery({
    queryKey: profileKeys.image(profileId),
    queryFn: () =&gt; profileService.getProfileImageUrl(profileId),
  })

export const useUploadProfileImageMutation = (profileId: string) =&gt; {
  const queryClient = useQueryClient()
  return useMutation({
    mutationFn: ({ file }) =&gt; profileService.uploadProfileImage(file, profileId),
    meta: {
      toastError: true,
      toastSuccess: true,
      toastSuccessMessage: &#39;프로필 이미지가 정상적으로 업로드되었습니다&#39;,
    },
    onSuccess: () =&gt; {
      queryClient.invalidateQueries({ queryKey: profileKeys.detail(profileId) })
      queryClient.invalidateQueries({ queryKey: profileKeys.image(profileId) })
    },
    retry: 0,
  })
}</code></pre>
<ul>
<li>👉 <strong>쿼리 로직은 여기서 끝</strong></li>
<li>👉 캐시 무효화, 토스트 등 부가 처리도 <strong>훅 내부에서 관리</strong></li>
</ul>
<hr>
<h2 id="🎨-3-사용-예시-컴포넌트">🎨 3. 사용 예시 (컴포넌트)</h2>
<pre><code class="language-tsx">const profileId = useUserStore((s) =&gt; s.profileId)
const { data: uploadedUrl } = useProfileImageSuspenseQuery(profileId)
const uploadMutation = useUploadProfileImageMutation(profileId)</code></pre>
<ul>
<li>✅ 컴포넌트는 <strong>데이터를 가져오기만</strong> 하면 됨</li>
<li>✅ <code>uploadedUrl</code>만 있으면 이미지를 그릴 수 있고</li>
<li>✅ <code>uploadMutation.mutate({ file })</code>만 호출하면 끝</li>
</ul>
<hr>
<h2 id="✅-구조-리팩터링-후의-이점">✅ 구조 리팩터링 후의 이점</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>리팩터링 전</th>
<th>리팩터링 후</th>
</tr>
</thead>
<tbody><tr>
<td>API 호출 위치</td>
<td>컴포넌트 내부</td>
<td><code>profileService</code></td>
</tr>
<tr>
<td>상태 관리</td>
<td>컴포넌트에서 직접</td>
<td>React Query가 캐시 관리</td>
</tr>
<tr>
<td>관심사 분리</td>
<td>혼재되어 있음</td>
<td>서비스 / 쿼리 / UI 명확 분리</td>
</tr>
<tr>
<td>재사용성</td>
<td>낮음</td>
<td>높음 (어디서든 훅만 호출하면 됨)</td>
</tr>
<tr>
<td>유지보수</td>
<td>중복 코드 많음</td>
<td>로직 한 곳에서 통합 관리</td>
</tr>
</tbody></table>
<hr>
<h2 id="✍️-마무리">✍️ 마무리</h2>
<p>이번 구조 리팩터링을 통해 <strong>컴포넌트는 순수 UI</strong>만 담당하도록 정리했고,<br>비즈니스 로직과 API 호출은 <strong>퓨어한 서비스 함수와 React Query 훅</strong>으로 분리했습니다.</p>
<blockquote>
<p>👇 필요한 데이터만 넘기면 되니까…</p>
</blockquote>
<pre><code class="language-tsx">const uploadMutation = useUploadProfileImageMutation(profileId)
uploadMutation.mutate({ file })</code></pre>
<p>서비스의 규모가 커질수록 이런 구조가 <strong>협업과 유지보수에서 진가를 발휘</strong>합니다.<br>당신의 프로젝트에도 이 구조를 한번 적용해보세요! 😊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Core Web Vitals와 Lighthouse를 활용한 웹 성능 측정 및 개선]]></title>
            <link>https://velog.io/@jungkyu_lol/20250418</link>
            <guid>https://velog.io/@jungkyu_lol/20250418</guid>
            <pubDate>Fri, 18 Apr 2025 04:29:23 GMT</pubDate>
            <description><![CDATA[<p>웹 성능은 단순히 &quot;빠르게 보이도록 만드는 것&quot;을 넘어서, <strong>실제 사용자가 체감하는 품질을 어떻게 높일 것인가</strong>에 대한 문제입니다.<br>이번 프로젝트에서 저는 <strong>웹 성능 측정 및 분석</strong>을 담당하면서, Google이 제안하는 주요 지표인 <strong>Core Web Vitals</strong>와 <strong>Lighthouse</strong>를 중심으로 진단을 진행하고, 그 결과를 기반으로 성능 개선 작업을 수행했습니다.</p>
<p>이 글에서는 이 두 가지 도구가 어떤 역할을 하고, 어떻게 측정 결과를 활용했는지 구체적으로 공유하려고 합니다.</p>
<h2 id="😊-목차">😊 목차</h2>
<p><strong>1. Core Web Vitals란 무엇인가<br>2. 각 지표 설명 + 실측 적용 사례<br>3. Lighthouse란 무엇이고, 왜 같이 써야 하는가<br>4. 프로젝트에 어떻게 적용했고 어떤 결과를 얻었는가<br>5. 결론 및 인사이트</strong></p>
<hr>
<h2 id="✅-core-web-vitals란">✅ Core Web Vitals란?</h2>
<p><strong>Core Web Vitals</strong>는 Google이 발표한 웹 성능 핵심 지표 세트로, <strong>실제 사용자 환경에서의 경험 품질(User Experience)을 수치로 측정</strong>하는 데 초점을 맞춥니다.</p>
<p>크게 세 가지 지표로 구성되며, 각각은 웹사이트에서 사용자가 체감할 수 있는 가장 핵심적인 영역을 다루고 있습니다.</p>
<hr>
<h2 id="📊-주요-지표-분석-및-실측-결과">📊 주요 지표 분석 및 실측 결과</h2>
<h3 id="1-lcp-largest-contentful-paint">1. <strong>LCP (Largest Contentful Paint)</strong></h3>
<p><strong>정의:</strong> 페이지 로딩이 시작된 시점부터 <strong>뷰포트 내 가장 큰 콘텐츠(주로 이미지 또는 텍스트 블록)</strong>가 화면에 완전히 렌더링되기까지의 시간을 측정합니다.</p>
<p><strong>사용자 입장에서는 페이지가 얼마나 빨리 &quot;보이는지&quot;를 체감하는 데 핵심적인 지표입니다.</strong></p>
<h4 id="🧪-적용-사례">🧪 적용 사례</h4>
<p>우리는 <strong>홈페이지(메인 리스트 뷰)</strong>를 대상으로 측정했습니다. 해당 페이지는 썸네일과 카드가 다수 포함되어 있어 LCP에 민감한 구조였습니다.</p>
<ul>
<li><strong>측정 결과:</strong> <code>2040ms</code>  </li>
<li><strong>Google 기준:</strong> 2500ms 이하면 &quot;좋음&quot;<br>→ 사용자가 큰 콘텐츠를 2초 내로 인지할 수 있어 성능이 우수한 것으로 평가되었습니다.</li>
</ul>
<hr>
<h3 id="2-inp-interaction-to-next-paint">2. <strong>INP (Interaction to Next Paint)</strong></h3>
<p><strong>정의:</strong> 사용자가 클릭, 탭, 키보드 입력 등의 상호작용을 했을 때, <strong>그에 대한 시각적 반응(다음 렌더링)</strong>이 이뤄지기까지 걸린 시간을 측정합니다.</p>
<p>2024년부터 INP는 FID(First Input Delay)를 대체하는 <strong>공식 Core Web Vitals 반응성 지표</strong>로 채택되었습니다.</p>
<h4 id="🧪-적용-사례-1">🧪 적용 사례</h4>
<p>우리는 <strong>데이터 입력/수정이 빈번한 폼 페이지</strong>를 중심으로 INP를 측정했습니다. 해당 페이지에서는 실시간 검증, 오토세이브 등의 기능이 있어 반응성 측정이 매우 중요했습니다.</p>
<ul>
<li><strong>측정 결과:</strong> <code>96ms</code>  </li>
<li><strong>Google 기준:</strong> 200ms 이하면 &quot;좋음&quot;<br>→ 유저가 상호작용했을 때 빠르게 피드백이 이뤄졌으며, 전체적인 UX가 빠르고 민첩한 것으로 나타났습니다.</li>
</ul>
<hr>
<h3 id="3-cls-cumulative-layout-shift">3. <strong>CLS (Cumulative Layout Shift)</strong></h3>
<p><strong>정의:</strong> 페이지 로딩 중 발생하는 <strong>레이아웃 이동(예: 버튼이 밀리거나 광고가 늦게 떠서 내용이 아래로 밀리는 현상)</strong>의 누적 비율을 측정합니다.</p>
<p>예상치 못한 움직임은 사용자에게 혼란을 주고, 의도하지 않은 행동(예: 잘못된 클릭)을 유발할 수 있어 중요한 UX 요소입니다.</p>
<h4 id="🧪-적용-사례-2">🧪 적용 사례</h4>
<p>우리는 <strong>무한 스크롤 + 필터링 기능이 적용된 홈페이지 리스트 뷰</strong>에서 CLS를 측정했습니다.<br>콘텐츠가 동적으로 변경되고 레이아웃이 재정렬되는 시점이 있었기에 시각적 안정성 확인이 필요했습니다.</p>
<ul>
<li><strong>측정 결과:</strong> <code>0.002</code>  </li>
<li><strong>Google 기준:</strong> 0.1 이하면 &quot;좋음&quot;<br>→ 레이아웃 변화가 거의 없어 매우 안정적인 사용자 환경이 유지되고 있음을 확인했습니다.</li>
</ul>
<hr>
<h2 id="🛠-lighthouse는-어떤-역할을-할까">🛠 Lighthouse는 어떤 역할을 할까?</h2>
<p><strong>Lighthouse</strong>는 Chrome DevTools에 내장된 웹 진단 도구로, 단순한 성능 측정에 그치지 않고 <strong>문제가 되는 요소와 개선 가이드를 함께 제공</strong>하는 것이 가장 큰 장점입니다.</p>
<h3 id="📋-lighthouse-진단-항목">📋 Lighthouse 진단 항목</h3>
<ol>
<li><strong>Performance (성능):</strong> 페이지 속도, 요청 수, LCP, INP 등</li>
<li><strong>Accessibility (접근성):</strong> 시멘틱 마크업, 대비, 키보드 네비게이션 등</li>
<li><strong>Best Practices:</strong> HTTPS, 최신 JS 사용 여부, 보안 관련 사항</li>
<li><strong>SEO:</strong> 메타 태그, 링크 구조, 크롤링 친화성 등</li>
<li><strong>PWA:</strong> Progressive Web App 지원 여부</li>
</ol>
<h3 id="🔍-lighthouse-vs-core-web-vitals">🔍 Lighthouse vs Core Web Vitals</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>Core Web Vitals</th>
<th>Lighthouse</th>
</tr>
</thead>
<tbody><tr>
<td>측정 방식</td>
<td><strong>실 사용자 기반</strong></td>
<td><strong>시뮬레이션 기반 (Lab data)</strong></td>
</tr>
<tr>
<td>초점</td>
<td>체감 UX 품질</td>
<td>기술 진단 + 개선 가이드</td>
</tr>
<tr>
<td>결과 활용</td>
<td>정량적 분석</td>
<td>리팩토링 가이드라인 제공</td>
</tr>
</tbody></table>
<hr>
<h2 id="📈-프로젝트에-어떻게-적용했는가">📈 프로젝트에 어떻게 적용했는가?</h2>
<ol>
<li><strong>초기 진단:</strong> Lighthouse로 전체 페이지에 대한 성능 진단을 수행 → 개선이 필요한 영역 파악  </li>
<li><strong>실제 사용자 기준 측정:</strong> Core Web Vitals를 통해 UX 품질 수치화 (LCP, INP, CLS 수치 확보)  </li>
<li><strong>기준 설정:</strong> Google의 권장 범위를 기준으로 &quot;좋음/보통/나쁨&quot; 구간 설정  </li>
<li><strong>리팩토링 우선순위 정렬:</strong> 응답 지연, DOM 크기, 이미지 최적화 등 항목 중심 개선  </li>
<li><strong>지표 추적:</strong> 리팩토링 후 반복 측정을 통해 개선 효과 확인</li>
</ol>
<hr>
<h2 id="🎯-최종-진단-결과-lighthouse-평균">🎯 최종 진단 결과 (Lighthouse 평균)</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>점수</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Performance</strong></td>
<td>96.25</td>
</tr>
<tr>
<td><strong>Accessibility</strong></td>
<td>78.25</td>
</tr>
<tr>
<td><strong>SEO</strong></td>
<td>75</td>
</tr>
<tr>
<td><strong>Best Practices</strong></td>
<td>99</td>
</tr>
</tbody></table>
<hr>
<h2 id="✍️-결론-및-인사이트">✍️ 결론 및 인사이트</h2>
<p>이번 프로젝트를 통해 <strong>단순한 &quot;속도 측정&quot;에서 벗어나</strong>, 실제 사용자가 겪는 체감 성능을 정량화하고, 그 수치를 기반으로 한 리팩토링이 <strong>UX 품질을 크게 향상</strong>시킨다는 점을 경험할 수 있었습니다.</p>
<ul>
<li><strong>Core Web Vitals</strong>는 UX를 직접 반영한 핵심 지표로써, 페이지가 실제로 얼마나 빠르고 안정적인지에 대한 사용자 중심의 판단 기준을 제공합니다.  </li>
<li><strong>Lighthouse</strong>는 기술적인 관점에서 개선 포인트를 제시해주어, <strong>두 도구는 함께 사용할 때 시너지가 큽니다.</strong></li>
</ul>
<p>성능을 단지 개발자의 시선이 아닌, <strong>사용자의 입장에서 바라보는 것</strong>이 웹 품질을 향상시키는 첫걸음임을 다시 한 번 느꼈습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Zustand 완전 정복 (v5 기준) ]]></title>
            <link>https://velog.io/@jungkyu_lol/20250411</link>
            <guid>https://velog.io/@jungkyu_lol/20250411</guid>
            <pubDate>Thu, 10 Apr 2025 05:17:29 GMT</pubDate>
            <description><![CDATA[<h2 id="1-기본-사용법">1. 기본 사용법</h2>
<pre><code class="language-tsx">import { create } from &#39;zustand&#39;
export const use이름Store = create((set, get) =&gt; {
  return {
    상태: 초깃값,
    액션: 함수
  }
})</code></pre>
<blockquote>
<p><code>create</code> 함수로 스토어를 생성합니다.
<code>create</code> 함수의 콜백은 <code>set</code>, <code>get</code> 매개변수를 가지며, 이를 통해 상태를 변경하거나 조회할 수 있습니다.
<code>create</code> 함수의 콜백이 반환하는 객체에서의 속성은 상태(State)이고, 메소드는 액션(Action)이라고 부릅니다.
<code>create</code> 함수 호출에서 반환하는 스토어 훅(Hook)은, useCountStore와 같이 use 접두사와 Store 접미사로 명명해 각 컴포넌트에서 사용할 수 있습니다.</p>
</blockquote>
<h3 id="바로-사용해보자">바로 사용해보자!</h3>
<pre><code class="language-ts">// src/store/useCountStore.ts
import { create } from &#39;zustand&#39;;

interface CountState {
  count: number;
  inc: () =&gt; void;
}

export const useCountStore = create&lt;CountState&gt;()((set) =&gt; ({
  count: 0,
  inc: () =&gt; set((s) =&gt; ({ count: s.count + 1 })),
}));</code></pre>
<pre><code class="language-tsx">// App.tsx
const App = () =&gt; {
  const { count, inc } = useCountStore();
  return (
    &lt;button onClick={inc}&gt;
      Clicked {count} times
    &lt;/button&gt;
  );
};
</code></pre>
<blockquote>
<p>✅ 사용법이 매우 단순합니다.</p>
</blockquote>
<hr>
<h2 id="2-많이-사용되는-slice패턴으로-스토어-사용해보기">2. 많이 사용되는 Slice 패턴으로 스토어 사용해보기</h2>
<p>모듈별로 “slice”를 쪼개고 합치는 구조. 의존성 최소화 &amp; 타입 분리에 유리합니다. (zustand.docs.pmnd.rs)</p>
<h3 id="🟢-step0--기본-방식">🟢 STEP 0 ― 기본 방식</h3>
<pre><code class="language-ts">// store.ts
import { create } from &#39;zustand&#39;;

export const useStore = create()((set) =&gt; ({
  count: 0,
  inc: () =&gt; set((s) =&gt; ({ count: s.count + 1 })),
  user: null as null | { name: string },
  login: (name: string) =&gt; set({ user: { name } }),
}));</code></pre>
<table>
<thead>
<tr>
<th>문제점</th>
</tr>
</thead>
<tbody><tr>
<td>모든 상태·액션이 <strong>한 파일</strong>에 뒤섞임 → 커질수록 가독성 ↓</td>
</tr>
<tr>
<td>타입이 커지면 <code>inc</code>, <code>login</code> 같은 액션이 <strong>자동 완성에서 묻힘</strong></td>
</tr>
</tbody></table>
<hr>
<h3 id="🟡-step1--파일-분리--함수형slice">🟡 STEP 1 ― 파일 분리 + 함수형 Slice</h3>
<pre><code class="language-ts">// slices/counterSlice.ts
export const createCounterSlice = (set) =&gt; ({
  count: 0,
  inc: () =&gt; set((s) =&gt; ({ count: s.count + 1 })),
});

// slices/userSlice.ts
export const createUserSlice = (set) =&gt; ({
  user: null as null | { name: string },
  login: (name: string) =&gt; set({ user: { name } }),
});

// store.ts
import { create } from &#39;zustand&#39;;
import { createCounterSlice } from &#39;./slices/counterSlice&#39;;
import { createUserSlice } from &#39;./slices/userSlice&#39;;

export const useStore = create()((set, get) =&gt; ({
  ...createCounterSlice(set, get),
  ...createUserSlice(set, get),
}));</code></pre>
<p><strong>이득</strong> : 도메인별 파일 분리 → 도메인별 로직이 분리</p>
<hr>
<h3 id="🟠-step2--slice-타입-분리--재사용-액션">🟠 STEP 2 ― Slice 타입 분리 &amp; 재사용 액션</h3>
<pre><code class="language-ts">// types/counter.ts
export interface CounterSlice {
  count: number;
  inc: () =&gt; void;
}

// slices/counterSlice.ts
import { CounterSlice } from &#39;../types/counter&#39;;

export const createCounterSlice = (
  set,
): CounterSlice =&gt; ({
  count: 0,
  inc: () =&gt; set((s) =&gt; ({ count: s.count + 1 })),
});</code></pre>
<p><strong>이득</strong> :  </p>
<ul>
<li>각 Slice가 <strong>명시적 인터페이스</strong>를 갖게 되어 type확인이 가능 </li>
</ul>
<hr>
<h3 id="🟣-step3--selectorsts-도입">🟣 STEP 3 ― <code>selectors.ts</code> 도입</h3>
<pre><code class="language-ts">// selectors/counter.ts
import { useStore } from &#39;../store&#39;;

export const useCount = () =&gt;
  useStore((s) =&gt; s.count);               // 필요한 값만 호출

export const useInc = () =&gt;
  useStore((s) =&gt; s.inc, (a, b) =&gt; a === b); // 필요한 함수만 호출</code></pre>
<p><strong>이득</strong> :  </p>
<ul>
<li>컴포넌트가 <strong>필요한 값만</strong> 구독 → 불필요 렌더링 감소  </li>
<li>컴포넌트 파일이 “UI 로직”에 집중, 스토어 호출은 한 줄</li>
</ul>
<hr>
<h3 id="🔵-step4--immer·devtools·persist-한번에-래핑">🔵 STEP 4 ― Immer·DevTools·Persist 한번에 래핑</h3>
<pre><code class="language-ts">import { create } from &#39;zustand&#39;;
import { devtools, persist, immer } from &#39;zustand/middleware&#39;;
import { createCounterSlice } from &#39;./slices/counterSlice&#39;;
import { createUserSlice } from &#39;./slices/userSlice&#39;;

export const useStore = create(
  devtools(
    persist(
      immer((set, get) =&gt; ({
        ...createCounterSlice(set, get),
        ...createUserSlice(set, get),
      })),
      {
        name: &#39;app-storage&#39;,
        partialize: (s) =&gt; ({ // persist 대상만 선택
          user: s.user,
        }),
      },
    ),
    { name: &#39;AppStore&#39; },
  ),
);</code></pre>
<p><strong>이득</strong> :  </p>
<ul>
<li><strong>불변성(immer) + DevTools + 영속화</strong>를 Slice 코드 변경 없이 장착  </li>
<li><code>partialize</code>로 <strong>필요 필드만 저장</strong> → 용량·보안 부담 ↓</li>
</ul>
<hr>
<h3 id="🟤-step5--테스트·ssr-친화-storefactory">🟤 STEP 5 ― 테스트·SSR 친화 Store Factory</h3>
<h3 id="🏭-storefactory란">🏭 Store Factory란?</h3>
<blockquote>
<p><strong>“필요할 때마다 새로운 Zustand 스토어 인스턴스를 만들어 주는 함수”</strong><br> <code>create()</code> 를 직접 호출하는 대신 <strong>팩토리 함수</strong>를 정의해 두고, 거기서 fresh store를 리턴하도록 하는 패턴입니다.</p>
</blockquote>
<hr>
<h2 id="1-왜-필요한가">1. 왜 필요한가?</h2>
<table>
<thead>
<tr>
<th>상황</th>
<th>Store Factory가 해결해 주는 문제</th>
</tr>
</thead>
<tbody><tr>
<td><strong>SSR / Next.js</strong></td>
<td>요청(Request)마다 독립된 스토어가 필요합니다. 싱글턴을 그대로 쓰면 <strong>다른 사용자의 데이터가 섞이는</strong> 버그·메모리 누수가 발생할 수 있어요.</td>
</tr>
<tr>
<td><strong>테스트</strong></td>
<td>단위 테스트마다 <strong>초기 상태가 다른 스토어</strong>를 만들어야 합니다. Factory로 간단히 주입할 수 있습니다.</td>
</tr>
<tr>
<td><strong>동적 인스턴스</strong></td>
<td>탭·모달처럼 “여러 개가 동시에 떠서 서로 영향을 주면 안 되는” UI를 구현할 때 각 인스턴스마다 스토어를 새로 만들 수 있습니다.</td>
</tr>
<tr>
<td><strong>라이브러리/패키지</strong></td>
<td>NPM 패키지로 배포되는 컴포넌트가 내부적으로 Zustand를 쓰면서, 호스트 앱의 전역 네임스페이스를 <strong>오염시키지 않도록</strong> 합니다.</td>
</tr>
</tbody></table>
<hr>
<h2 id="2-기본-형태">2. 기본 형태</h2>
<pre><code class="language-ts">// makeStore.ts
import { createStore } from &#39;zustand&#39;;
import { createCounterSlice } from &#39;./slices/counterSlice&#39;;
import { createUserSlice } from &#39;./slices/userSlice&#39;;

export type RootState = CounterSlice &amp; UserSlice;

export function makeStore(preloaded?: Partial&lt;RootState&gt;) {
  return createStore&lt;RootState&gt;()((set, get) =&gt; ({
    ...createCounterSlice(set, get),
    ...createUserSlice(set, get),
    ...preloaded,            // 초기 상태 오버라이드
  }));
}</code></pre>
<ul>
<li><strong>createStore</strong> (= vanilla 모드)로 리액트 의존 없이 스토어 인스턴스를 생성.</li>
<li><strong>preloaded</strong> 매개변수로 테스트·SSR에서 초기 값을 주입 가능.</li>
</ul>
<hr>
<h2 id="3-사용-예시">3. 사용 예시</h2>
<h3 id="31-csr브라우저-싱글턴">3‑1. <strong>CSR(브라우저) 싱글턴</strong></h3>
<pre><code class="language-ts">// useClientStore.ts
import { useStore as useZustandStore } from &#39;zustand&#39;;
import { makeStore } from &#39;./makeStore&#39;;

const clientStore = makeStore();       // 탭 당 1개
export const useClientStore = &lt;T,&gt;(sel: (s: RootState) =&gt; T) =&gt;
  useZustandStore(clientStore, sel);</code></pre>
<h3 id="32-nextjs-ssr">3‑2. <strong>Next.js SSR</strong></h3>
<pre><code class="language-ts">// pages/index.tsx
export async function getServerSideProps() {
  const store = makeStore();
  await store.getState().fetchInitialData();   // 서버에서 데이터 채우기
  return {
    props: { initialZustandState: store.getState() },
  };
}</code></pre>
<p>클라이언트에서는 <code>makeStore(props.initialZustandState)</code> 로 <strong>하이드레이션</strong>.</p>
<h3 id="33-테스트">3‑3. <strong>테스트</strong></h3>
<pre><code class="language-ts">test(&#39;counter increments&#39;, () =&gt; {
  const store = makeStore({ count: 5 });
  store.getState().inc();
  expect(store.getState().count).toBe(6);
});</code></pre>
<hr>
<h2 id="4-장단점">4. 장단점</h2>
<table>
<thead>
<tr>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>상태 격리 → <strong>데이터 섞임·메모리 누수 방지</strong></td>
<td>코드가 약간 길어짐 (팩토리 + 훅 래퍼 필요)</td>
</tr>
<tr>
<td>초기 상태 주입이 쉬움 (테스트·SSR)</td>
<td>DevTools 같은 미들웨어를 훅 래퍼에서 다시 감싸야 함</td>
</tr>
<tr>
<td>동적 인스턴스 지원</td>
<td>대부분의 단순 SPA는 굳이 필요 없음</td>
</tr>
</tbody></table>
<hr>
<h2 id="5-언제-안-써도-되나">5. 언제 <strong>안</strong> 써도 되나?</h2>
<ul>
<li><strong>순수 CSR SPA</strong>이고,  </li>
<li>전역 스토어를 <strong>싱글턴 하나</strong>로 충분히 쓰며,  </li>
<li>SSR·멀티 인스턴스·라이브러리 배포 요구사항이 없다면</li>
</ul>
<blockquote>
<p>그냥 <code>create()</code> 한 번 호출해서 내보내는 <strong>싱글턴 스토어</strong>면 됩니다.<br>Store Factory는 “격리가 꼭 필요한 시나리오”에서만 도입하세요.</p>
</blockquote>
<ul>
<li><strong>Store Factory = 스토어 인스턴스를 만들어 주는 함수</strong>  </li>
<li>SSR, 테스트, 동적 UI, 패키지 개발처럼 <strong>상태 격리가 필요한</strong> 상황에서 유용  </li>
<li>평범한 CSR 앱이라면 <strong>싱글턴 스토어</strong>로도 충분—필요할 때만 쓰자!</li>
</ul>
<pre><code class="language-ts">// makeStore.ts
import { StateCreator, StoreApi, createStore } from &#39;zustand&#39;;
import { createCounterSlice } from &#39;./slices/counterSlice&#39;;
import { createUserSlice } from &#39;./slices/userSlice&#39;;

export type RootState = CounterSlice &amp; UserSlice;

export const makeStore = (preloaded?: Partial&lt;RootState&gt;): StoreApi&lt;RootState&gt; =&gt;
  createStore&lt;RootState&gt;((set, get) =&gt; ({
    ...createCounterSlice(set, get),
    ...createUserSlice(set, get),
    ...preloaded,
  }));</code></pre>
<pre><code class="language-ts">// store.ts (CSR 전용 훅)
import { useStore as useZustandStore } from &#39;zustand&#39;;
import { makeStore } from &#39;./makeStore&#39;;

const store = makeStore();
export const useStore = &lt;T,&gt;(selector: (s: RootState) =&gt; T) =&gt;
  useZustandStore(store, selector);</code></pre>
<ul>
<li><strong>테스트</strong>: <code>const testStore = makeStore({ count: 42 })</code> → 단위 테스트에서 원하는 초기 상태 주입  </li>
<li><strong>Next.js</strong>: <code>getServerSideProps</code> 마다 <code>makeStore()</code> 호출 → 요청 간 데이터 격리</li>
</ul>
<hr>
<h2 id="📌-최종-구조-요약">📌 최종 구조 요약</h2>
<pre><code>src/
 ├─ store/
 │   ├─ makeStore.ts          # SSR·테스트 겸용
 │   ├─ store.ts              # CSR 훅
 │   └─ slices/
 │       ├─ counterSlice.ts
 │       └─ userSlice.ts
 ├─ selectors/
 │   ├─ counter.ts
 │   └─ user.ts
 └─ types/
     ├─ counter.ts
     └─ user.ts</code></pre><table>
<thead>
<tr>
<th>개선 포인트</th>
<th>결과</th>
</tr>
</thead>
<tbody><tr>
<td><strong>파일·타입·액션·셀렉터 분리</strong></td>
<td>가독성 ↑, 충돌 ↓</td>
</tr>
<tr>
<td><strong>미들웨어 통합 래핑</strong></td>
<td>Slice 수정 없이 기능 추가/제거</td>
</tr>
<tr>
<td><strong>Store Factory 도입</strong></td>
<td>테스트·SSR·MFE 재사용성 ↑</td>
</tr>
<tr>
<td><strong>부분 persist</strong></td>
<td>보안·성능 균형</td>
</tr>
</tbody></table>
<h3 id="🟢-나아가기--react-query-✕-zustand">🟢 나아가기 ― React Query ✕ Zustand </h3>
<blockquote>
<p><strong>서버 상태(server state)</strong>는 React Query, <strong>UI·글로벌 상태(client state)</strong>는 Zustand에 맡기는 것이 정석입니다. 둘을 자연스럽게 엮는 3가지 패턴을 살펴봅니다.</p>
</blockquote>
<h4 id="71-필터-slice-→-쿼리-키-연동">7‑1. 필터 Slice → 쿼리 키 연동</h4>
<pre><code class="language-ts">// slices/filterSlice.ts
export interface FilterSlice {
  keyword: string;
  setKeyword: (k: string) =&gt; void;
}

export const createFilterSlice = (set): FilterSlice =&gt; ({
  keyword: &#39;&#39;,
  setKeyword: (k) =&gt; set({ keyword: k }),
});</code></pre>
<pre><code class="language-tsx">// Posts.tsx
import { useQuery } from &#39;@tanstack/react-query&#39;;
import { useStore } from &#39;@/store&#39;;

const fetchPosts = async (keyword: string) =&gt; {
  const res = await fetch(`/api/posts?search=${keyword}`);
  return res.json();
};

export default function Posts() {
  const keyword = useStore((s) =&gt; s.keyword);
  const { data, isLoading } = useQuery([&#39;posts&#39;, keyword], () =&gt; fetchPosts(keyword));

  if (isLoading) return &lt;p&gt;Loading…&lt;/p&gt;;
  return (
    &lt;ul&gt;
      {data.map((p) =&gt; (
        &lt;li key={p.id}&gt;{p.title}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}</code></pre>
<ul>
<li><strong>쿼리 키에 Zustand 값을 포함</strong> → 키가 변하면 자동으로 refetch.  </li>
<li>React Query 캐시는 그대로, 필터 UI는 Zustand로 독립 유지.</li>
</ul>
<h4 id="72-액션에서-queryclientinvalidatequeries-호출">7‑2. 액션에서 <code>queryClient.invalidateQueries()</code> 호출</h4>
<pre><code class="language-ts">import { queryClient } from &#39;@/lib/queryClient&#39;;

export const createTodoSlice = (set) =&gt; ({
  addTodo: async (title: string) =&gt; {
    await fetch(&#39;/api/todos&#39;, { method: &#39;POST&#39;, body: JSON.stringify({ title }) });
    queryClient.invalidateQueries([&#39;todos&#39;]);  // ✅ 리스트 새로고침
  },
});</code></pre>
<ul>
<li><strong>비즈니스 액션</strong> 안에서 서버 호출 후 <strong>React Query 캐시 무효화</strong>.  </li>
<li>컴포넌트는 별도 로직 없이 최신 목록을 받습니다.</li>
</ul>
<h4 id="73-usequery-결과를-zustand에-동기화-옵션">7‑3. <code>useQuery</code> 결과를 Zustand에 동기화 (옵션)</h4>
<blockquote>
<p>서버 데이터를 전역에서 <strong>읽기 전용</strong>으로 여러 컴포넌트가 소비해야 할 때만 권장.</p>
</blockquote>
<pre><code class="language-tsx">const { data } = useQuery([&#39;settings&#39;], fetchSettings, {
  onSuccess: (settings) =&gt; useStore.setState({ settings }),
});</code></pre>
<ul>
<li>React Query가 <strong>캐싱·로딩·에러</strong>를 담당하고,  </li>
<li>Zustand는 <strong>다른 액션이 참조할 수 있는 전역 스냅샷</strong>을 보관.</li>
</ul>
<table>
<thead>
<tr>
<th>패턴</th>
<th>언제 쓰나</th>
<th>장점</th>
</tr>
</thead>
<tbody><tr>
<td><strong>쿼리 키에 Slice 값 포함</strong></td>
<td>검색·필터·페이지네이션</td>
<td>구현 단순, 자동 refetch</td>
</tr>
<tr>
<td><strong>invalidateQueries()</strong></td>
<td>POST/PUT/DELETE 이후 목록 동기화</td>
<td>컴포넌트 수정 없이 최신화</td>
</tr>
<tr>
<td><strong>onSuccess → setState</strong></td>
<td>설정·권한처럼 다수 컴포넌트가 필요</td>
<td>중복 fetch 방지, 전역 접근</td>
</tr>
</tbody></table>
<blockquote>
<p>핵심은 <strong>역할 분리</strong>: <em>서버 동기화</em>는 React Query, <em>앱 로컬 상태</em>는 Zustand가 맡고, 교집합은 <strong>쿼리 키 or 캐시 무효화</strong>로 느슨하게 연결합니다.</p>
</blockquote>
<hr>
<h2 id="📌-최종-구조-요약-1">📌 최종 구조 요약</h2>
<pre><code>src/
 ├─ store/
 │   ├─ makeStore.ts          # SSR·테스트 겸용 스토어 팩토리
 │   ├─ useClientStore.ts     # CSR 전용 훅 (싱글턴)
 │   └─ slices/
 │       ├─ counterSlice.ts
 │       ├─ userSlice.ts
 │       └─ filterSlice.ts
 ├─ selectors/                # (선택) 공통 셀렉터 모음
 │   ├─ counter.ts
 │   └─ user.ts
 ├─ types/                    # Slice 인터페이스 정의
 │   ├─ counter.ts
 │   └─ user.ts
 ├─ lib/
 │   └─ queryClient.ts        # React‑Query 전역 클라이언트
 ├─ pages/ or components/ …   # UI 컴포넌트</code></pre><table>
<thead>
<tr>
<th>파일/폴더</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><strong>store/makeStore.ts</strong></td>
<td>요청·테스트마다 fresh store를 만드는 팩토리 함수</td>
</tr>
<tr>
<td><strong>store/useClientStore.ts</strong></td>
<td>브라우저 탭 당 1개의 싱글턴 스토어 + 얇은 훅 래퍼</td>
</tr>
<tr>
<td><strong>store/slices/</strong></td>
<td>도메인별 순수 Slice 함수들 (비즈니스 로직만)</td>
</tr>
<tr>
<td><strong>selectors/</strong></td>
<td>(선택) 재사용 셀렉터 훅으로 리렌더 최적화</td>
</tr>
<tr>
<td><strong>types/</strong></td>
<td>Slice 인터페이스를 모아 타입 의존성 분리</td>
</tr>
<tr>
<td><strong>lib/queryClient.ts</strong></td>
<td>React Query <code>QueryClient</code> 싱글턴 &amp; 설정</td>
</tr>
</tbody></table>
<h3 id="✅-구조-설계-체크리스트">✅ 구조 설계 체크리스트</h3>
<ul>
<li><strong>Slice는 상태·액션만</strong>: UI 의존 코드(React import) 금지 → 테스트 용이.</li>
<li><strong>미들웨어 래핑은 한곳</strong>: <code>useClientStore.ts</code>에서 DevTools·Persist 등 통합.</li>
<li><strong>폴더 깊이 최소화</strong>: 3‑depth를 넘기지 않도록 유지하면 탐색·import가 편해집니다.</li>
<li><strong>SSR/CSR 분기 명확</strong>: 서버 로직은 <code>makeStore</code>, 클라이언트 로직은 <code>useClientStore</code>로 분리.</li>
</ul>
<p>이 구조를 베이스로 팀·프로젝트 규모에 맞춰 선택적으로 <code>selectors/</code>, <code>types/</code> 폴더를 생략하거나 추가하면 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Router + React Query: 데이터 로딩과 에러 처리 전략 완벽 가이드]]></title>
            <link>https://velog.io/@jungkyu_lol/post20250404</link>
            <guid>https://velog.io/@jungkyu_lol/post20250404</guid>
            <pubDate>Fri, 04 Apr 2025 04:49:23 GMT</pubDate>
            <description><![CDATA[<p>React 프로젝트에서 라우팅과 데이터 패칭을 다룰 때,<br><code>react-router-dom</code>과 <code>@tanstack/react-query</code>(구: React Query)를 조합하면<br>🔥 강력하면서도 유연한 데이터 흐름을 만들 수 있습니다.</p>
<p>하지만 동시에 이런 질문들이 생기죠:</p>
<ul>
<li>loader와 React Query는 언제 어떤 기준으로 써야 하지?</li>
<li>에러는 어디서 어떻게 잡지? Suspense랑 ErrorBoundary는 어떻게 엮어야 해?</li>
<li>캐싱은 누가, 어디서 해주는 거야?</li>
</ul>
<p>이 포스팅에서는 이 모든 흐름을 <strong>실전 기준</strong>으로 정리해봅니다.</p>
<hr>
<h2 id="📍-loader-vs-react-query-언제-어떤-걸-써야-할까">📍 loader vs React Query: 언제 어떤 걸 써야 할까?</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>loader</th>
<th>React Query</th>
</tr>
</thead>
<tbody><tr>
<td>실행 시점</td>
<td>페이지 진입 <strong>전에 실행됨</strong> (라우팅 전)</td>
<td>컴포넌트 <strong>렌더 후 실행</strong></td>
</tr>
<tr>
<td>캐싱</td>
<td>❌ 없음</td>
<td>✅ 있음 (<code>staleTime</code>, <code>cacheTime</code>)</td>
</tr>
<tr>
<td>UX</td>
<td>❌ 재방문 시에도 fetch</td>
<td>✅ 캐시로 빠르게 표시 가능</td>
</tr>
<tr>
<td>용도</td>
<td>인증/404 등 <strong>라우팅 결정이 필요한 경우</strong></td>
<td>대부분의 데이터 패칭은 이걸로 충분</td>
</tr>
</tbody></table>
<h3 id="✅-3가지-케이스-요약표">✅ 3가지 케이스 요약표</h3>
<table>
<thead>
<tr>
<th>케이스</th>
<th>설명</th>
<th>구성 요소</th>
</tr>
</thead>
<tbody><tr>
<td>🔐 인증/404 판단</td>
<td>페이지 접근을 사전 차단하고 싶을 때</td>
<td>loader + errorElement</td>
</tr>
<tr>
<td>🔁 일반 리스트</td>
<td>빠른 렌더, 캐싱 중심 UX</td>
<td>React Query 단독</td>
</tr>
<tr>
<td>🚀 빠른 진입 + 캐싱</td>
<td>진입 속도 + 캐시 동시 잡고 싶을 때</td>
<td>loader + React Query + hydrate</td>
</tr>
</tbody></table>
<hr>
<h2 id="1-🔐-인증-404-판단-→-loader--errorelement">1. 🔐 인증, 404 판단 → <code>loader + errorElement</code></h2>
<h3 id="✔️-목적">✔️ 목적</h3>
<ul>
<li>로그인하지 않은 사용자는 로그인 페이지로 리다이렉트</li>
<li>존재하지 않는 ID면 404 페이지 표시</li>
<li>데이터는 컴포넌트에서 따로 fetch</li>
</ul>
<h3 id="💡-라우터-코드">💡 라우터 코드</h3>
<pre><code class="language-tsx">&lt;Route
  path=&quot;/admin/posts/:id&quot;
  loader={protectedPostLoader}
  element={&lt;AdminPost /&gt;}
  errorElement={&lt;ErrorPage /&gt;}
/&gt;</code></pre>
<h3 id="💡-loader-예시">💡 loader 예시</h3>
<pre><code class="language-tsx">// protectedPostLoader.ts
export async function protectedPostLoader({ params }) {
  const user = await checkAuth();
  if (!user) {
    return redirect(&#39;/login&#39;); // 🔐 인증 체크
  }

  const res = await fetch(`/api/posts/${params.id}`);
  if (res.status === 404) {
    throw new Response(&#39;Not Found&#39;, { status: 404 }); // ❗ 존재하지 않으면 404
  }

  return null;
}</code></pre>
<h3 id="💡-컴포넌트-예시">💡 컴포넌트 예시</h3>
<pre><code class="language-tsx">export default function AdminPost() {
  const { id } = useParams();

  const { data} = useQuery({
    queryKey: [&#39;post&#39;, id],
    queryFn: () =&gt; fetchPost(id), // ✅ 실제 데이터 요청은 여기서
    suspense: false,
    useErrorBoundary: false,
  });

  return (
    &lt;div&gt;
      &lt;h1&gt;{data.title}&lt;/h1&gt;
      &lt;p&gt;{data.content}&lt;/p&gt;
    &lt;/div&gt;
  );
}</code></pre>
<hr>
<h2 id="2-🔁-일반-페이지-→-react-query-단독">2. 🔁 일반 페이지 → <code>React Query</code> 단독</h2>
<h3 id="✔️-목적-1">✔️ 목적</h3>
<ul>
<li>필터링, 탭, 리스트 페이지</li>
<li>인증, 404 등의 판단 불필요</li>
<li>빠른 캐싱과 상태 관리가 목적</li>
</ul>
<h3 id="💡-라우터-코드-1">💡 라우터 코드</h3>
<pre><code class="language-tsx">&lt;Route
  path=&quot;/products&quot;
  element={
    &lt;ErrorBoundary fallback={&lt;ErrorPage /&gt;}&gt;
      &lt;Suspense fallback={&lt;Loading /&gt;}&gt;
        &lt;ProductList /&gt;
      &lt;/Suspense&gt;
    &lt;/ErrorBoundary&gt;
  }
/&gt;</code></pre>
<h3 id="💡-컴포넌트-예시-1">💡 컴포넌트 예시</h3>
<pre><code class="language-tsx">function ProductList() {
  const { data } = useQuery({
    queryKey: [&#39;products&#39;],
    queryFn: fetchProducts,
    suspense: true,
    useErrorBoundary: true,
    staleTime: 60_000, // 1분 캐시
  });

  return (
    &lt;ul&gt;
      {data.map((product) =&gt; (
        &lt;li key={product.id}&gt;{product.name}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}</code></pre>
<hr>
<h2 id="3-🚀-빠른-퍼스트-로딩--캐싱-→-loader--react-query-조합">3. 🚀 빠른 퍼스트 로딩 + 캐싱 → <code>loader + React Query 조합</code></h2>
<h3 id="✔️-목적-2">✔️ 목적</h3>
<ul>
<li>페이지 진입 전에 loader로 먼저 fetch</li>
<li>컴포넌트는 React Query 캐시 재사용</li>
<li>UX + 퍼포먼스 균형</li>
</ul>
<h3 id="💡-라우터-코드-2">💡 라우터 코드</h3>
<pre><code class="language-tsx">&lt;Route
  path=&quot;/posts&quot;
  loader={postsLoader}
  element={
    &lt;ErrorBoundary fallback={&lt;ErrorPage /&gt;}&gt;
      &lt;Suspense fallback={&lt;Loading /&gt;}&gt;
          &lt;PostsRoute /&gt;
      &lt;/Suspense&gt;
    &lt;/ErrorBoundary&gt;
  }
  errorElement={&lt;ErrorPage /&gt;}
/&gt;</code></pre>
<h3 id="💡-loader">💡 loader</h3>
<pre><code class="language-tsx">export async function postsLoader() {
  const queryClient = new QueryClient();

  await queryClient.prefetchQuery({
    queryKey: [&#39;posts&#39;],
    queryFn: fetchPosts,
    staleTime: 10_000,
  });

  return {
    dehydratedState: dehydrate(queryClient),
  };
}</code></pre>
<h3 id="💡-컴포넌트">💡 컴포넌트</h3>
<pre><code class="language-tsx">function PostsRoute() {
  const { dehydratedState } = useLoaderData();
  const queryClient = new QueryClient();

  return (
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;Hydrate state={dehydratedState}&gt;
        &lt;Suspense fallback={&lt;Loading /&gt;}&gt;
          &lt;Posts /&gt;
        &lt;/Suspense&gt;
      &lt;/Hydrate&gt;
    &lt;/QueryClientProvider&gt;
  );
}</code></pre>
<h3 id="💡-poststsx">💡 Posts.tsx</h3>
<pre><code class="language-tsx">function Posts() {
  const { data } = useQuery({
    queryKey: [&#39;posts&#39;],
    queryFn: fetchPosts,
    suspense: true,
    useErrorBoundary: true,
    staleTime: 10_000,
  });

  return &lt;ul&gt;{data.map(p =&gt; &lt;li key={p.id}&gt;{p.title}&lt;/li&gt;)}&lt;/ul&gt;;
}</code></pre>
<hr>
<h2 id="⚠️-loader-only의-단점">⚠️ loader-only의 단점</h2>
<p>loader는 매번 페이지 이동 시마다 fetch 요청을 다시 보냅니다.<br>즉, 같은 페이지를 왔다 갔다 해도 <strong>캐시 재사용 없이 계속 로딩</strong>됩니다.</p>
<p>→ 사용자 입장에선 <strong>UI가 깜빡이고 느려지는 경험</strong><br>→ 서버 입장에선 <strong>불필요한 요청 증가</strong></p>
<hr>
<h2 id="🧱-에러-처리-구조-에러는-어디서-어떻게-처리하나">🧱 에러 처리 구조: 에러는 어디서 어떻게 처리하나?</h2>
<table>
<thead>
<tr>
<th>에러 위치</th>
<th>처리 방법</th>
</tr>
</thead>
<tbody><tr>
<td>loader 내부</td>
<td><code>throw new Response()</code> → <code>errorElement</code> + <code>useRouteError()</code></td>
</tr>
<tr>
<td>useQuery 내부</td>
<td><code>useErrorBoundary: true</code> → <code>ErrorBoundary</code>에서 catch</td>
</tr>
<tr>
<td>로딩 중</td>
<td><code>suspense: true</code> → <code>&lt;Suspense fallback={...} /&gt;</code></td>
</tr>
</tbody></table>
<hr>
<h3 id="📦-loader에서-발생한-에러-예시">📦 loader에서 발생한 에러 예시</h3>
<pre><code class="language-tsx">export async function postsLoader() {
  const res = await fetch(&#39;/api/posts&#39;);

  if (!res.ok) {
    throw new Response(&#39;Not Found&#39;, { status: 404 });
  }

  return res.json();
}</code></pre>
<pre><code class="language-tsx">// ErrorPage.tsx
const error = useRouteError();
if (isRouteErrorResponse(error)) {
  return &lt;p&gt;{error.status} - {error.statusText}&lt;/p&gt;;
}</code></pre>
<hr>
<h3 id="⚠️-errorelement를-쓸-땐-loader가-반드시-있어야-함">⚠️ <code>errorElement</code>를 쓸 땐 <code>loader</code>가 반드시 있어야 함!</h3>
<pre><code class="language-tsx">// 잘못된 구조 ❌ (loader 없음 + errorElement 있음 → 작동안함)
&lt;Route
  path=&quot;/posts&quot;
  element={&lt;Posts /&gt;}
  errorElement={&lt;ErrorPage /&gt;} // 작동 안 함!
/&gt;</code></pre>
<pre><code class="language-tsx">// 올바른 구조 ✅
&lt;Route
  path=&quot;/posts&quot;
  loader={postsLoader}
  element={&lt;Posts /&gt;}
  errorElement={&lt;ErrorPage /&gt;}
 /&gt;</code></pre>
<hr>
<h2 id="✅-staletime은-얼마나-주는-게-좋을까">✅ staleTime은 얼마나 주는 게 좋을까?</h2>
<pre><code class="language-tsx">staleTime: 10000 // 10초 동안 &quot;fresh&quot;로 간주 → fetch 생략</code></pre>
<h3 id="상황별-추천-기준">상황별 추천 기준</h3>
<table>
<thead>
<tr>
<th>데이터 성격</th>
<th>staleTime</th>
</tr>
</thead>
<tbody><tr>
<td>거의 바뀌지 않는 공지사항, 목록</td>
<td>1~10분</td>
</tr>
<tr>
<td>채팅, 알림처럼 자주 바뀌는 데이터</td>
<td>1~5초</td>
</tr>
<tr>
<td>상품 상세처럼 정적인 콘텐츠</td>
<td>Infinity</td>
</tr>
<tr>
<td>그냥 무난하게 UX/성능 잡고 싶을 때</td>
<td>10초</td>
</tr>
</tbody></table>
<hr>
<h2 id="🎯-최종-추천-전략-요약">🎯 최종 추천 전략 요약</h2>
<table>
<thead>
<tr>
<th>상황</th>
<th>추천 방식</th>
</tr>
</thead>
<tbody><tr>
<td>로그인, 권한 체크</td>
<td><code>loader + redirect()</code></td>
</tr>
<tr>
<td>존재하지 않는 리소스 (404)</td>
<td><code>loader + throw new Response()</code></td>
</tr>
<tr>
<td>캐시 활용하고 싶은 리스트</td>
<td><code>React Query (staleTime)</code></td>
</tr>
<tr>
<td>빠른 첫 렌더 + 캐시까지</td>
<td><code>loader + React Query 조합</code></td>
</tr>
<tr>
<td>에러 처리 (fetch)</td>
<td><code>useErrorBoundary: true + ErrorBoundary</code></td>
</tr>
<tr>
<td>에러 처리 (라우팅)</td>
<td><code>loader + errorElement + useRouteError()</code></td>
</tr>
</tbody></table>
<h3 id="✅-loader--react-query-조합">✅ loader + React Query 조합</h3>
<table>
<thead>
<tr>
<th>페이지 유형</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>초기 진입 성능이 중요한 페이지</td>
<td>홈, 대시보드, 인기 콘텐츠 등</td>
</tr>
<tr>
<td>재방문 가능성이 높은 리스트 페이지</td>
<td>게시글, 검색결과 등</td>
</tr>
<tr>
<td>사용자 맞춤 데이터를 처음부터 보여줘야 하는 페이지</td>
<td>마이페이지, 내 활동, 내 문서</td>
</tr>
<tr>
<td>SSR UX를 CSR로 구현하고 싶은 페이지</td>
<td>성능 + 캐시 다 챙겨야 할 때</td>
</tr>
</tbody></table>
<h3 id="✅-반대로-react-query만-쓰는-게-나은-페이지">✅ 반대로 <strong>React Query만 쓰는 게 나은 페이지</strong></h3>
<table>
<thead>
<tr>
<th>페이지 유형</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td>무한 스크롤, 필터가 많음</td>
<td>loader로는 처리하기 불편함</td>
</tr>
<tr>
<td>실시간 데이터가 자주 바뀜</td>
<td>매번 loader가 fetch하는 건 비효율</td>
</tr>
<tr>
<td>URL이 바뀌지 않는 탭 기반 페이지</td>
<td>loader는 작동안 함, useQuery만 가능</td>
</tr>
</tbody></table>
<hr>
<h2 id="✅-마무리">✅ 마무리</h2>
<blockquote>
<p>loader는 <strong>라우팅 제어</strong>에,<br>React Query는 <strong>데이터 캐싱과 UX 최적화</strong>에 집중!</p>
</blockquote>
<p>너무 많이 쓰기보다는, <strong>필요한 곳에만 loader를 정확히 사용하는 게 진짜 최적화입니다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 프론트엔드 개발자가 왜 인프라를 알아야 할까?]]></title>
            <link>https://velog.io/@jungkyu_lol/post7</link>
            <guid>https://velog.io/@jungkyu_lol/post7</guid>
            <pubDate>Fri, 28 Mar 2025 04:57:11 GMT</pubDate>
            <description><![CDATA[<h1 id="인프라를-왜-알아야-할까">인프라를 왜 알아야 할까?</h1>
<p>앱 프론트엔드(안드로이드,IOS)는 프로그램 자체를 앱 스토어를 통해서 사용자가 다운받기 때문에 백엔드에 요청만 보낸다.
<strong>하지만, 웹 프론트엔드(브라우저)는 사용자들한테 백엔드에서 관련된 프로그램을 전송하기 때문에
앱 프론트엔드 개발자보다 상대적으로 밀접하게 연결되어 있다.</strong></p>
<h2 id="가장-기본적인-인프라">가장 기본적인 인프라</h2>
<p><img src="https://velog.velcdn.com/images/jungkyu_lol/post/19348d48-76a9-4798-8b50-4ec2348229a7/image.png" alt=""></p>
<blockquote>
<p><strong>생각해보기: 사용자가 많아져서 요청이 늘어나면 어떻게 될까?</strong></p>
</blockquote>
<p>서버에 요청이 많아질테니 서버의 응답속도를 보장하기 위해서 방안을 마련해야 한다
-<strong>&gt; 스케일 아웃(Scale Out) or 스케일 업(Scale Up)</strong></p>
<blockquote>
</blockquote>
<p><em>서버에 에러가 발생할 수 있기 때문에 서버의 성능을 높히는 방식보다는 서버의 개수를 늘리는 <strong>스케일 아웃 방식이 선호</strong>됨</em></p>
<p><strong>스케일 아웃(Scale Out)  / 스케일 인(Scale In)</strong>
서버(컴퓨터)의 개수를 늘리는 것 / 서버(컴퓨터)의 개수를 줄이는 것</p>
<p><img src="https://velog.velcdn.com/images/jungkyu_lol/post/76744b4c-05b7-4303-8634-9bfa8f499d7f/image.png" alt=""></p>
<p><strong>스케일 업(Scale Up) / 스케일 다운(Scale Down)</strong>
서버(컴퓨터)의 성능을 높이는 것 / 서버(컴퓨터)의 성능을 낮추는 것
<img src="https://velog.velcdn.com/images/jungkyu_lol/post/1bf1a3b0-df2a-400a-9cae-80f3a7b93b9f/image.png" alt=""></p>
<h2 id="알아두자">알아두자</h2>
<p><strong>DNS(Domain Name System)</strong>
IP 주소(예: 192.0.2.44)를 사람이 읽을 수 있는  도메인 이름(예: <a href="http://www.amazon.com/">www.amazon.com</a>)으로 변환해주는 시스템</p>
<p><strong>LB(Load Balancing)</strong>
애플리케이션을 지원하는 리소스 풀 전체에 네트워크 트래픽을 균등하게 배포하는 방법</p>
<p><strong>Auto Scaling</strong>
클라우드 서비스의 핵심기술로 CPU, 메모리, 디스크, <strong>네트워크 트래픽</strong>과 같은 시스템 자원들을 <strong>모니터링</strong>하여 <strong>서버 사이즈를 자동으로 조절</strong> 하는 방법</p>
<p><strong>VPC(Virtual Private Cloud)</strong>
격리된 가상 네트워크 환경에서 리소스를 사용하는 방법</p>
<h2 id="아마존에서-제공하는-서비스를-활용한-scale-out">아마존에서 제공하는 서비스를 활용한 Scale Out</h2>
<p><img src="https://velog.velcdn.com/images/jungkyu_lol/post/fab3aa55-cf58-40ff-bf43-38aa2816f71f/image.png" alt=""></p>
<h3 id="ec2elastic-compute-cloud---서버역할">EC2(Elastic Compute Cloud) - 서버역할</h3>
<p>클라우드 환경에서 컴퓨터를 사용할 수 있게 빌려주는 서비스</p>
<p><strong>*컴퓨터만 빌려주는 방식이기 때문에 모든 설정을 내가 해야함</strong></p>
<h3 id="route53---dns역할">Route53 - DNS역할</h3>
<p><strong>DNS 역할</strong>과 <strong>설정된 서버 컴퓨터와 매칭</strong>하는 역할을 하는 서비스</p>
<h3 id="elbelastic-load-balancing---load-balancing-역할">ELB(Elastic Load Balancing) - Load Balancing 역할</h3>
<p>둘 이상의 가용 영역에서 EC2 인스턴스, 컨테이너, IP 주소 등 여러 대상에 걸쳐 수신되는 트래픽을 자동으로 분산해주는 서비스</p>
<h3 id="asgauto-scaling-group---auto-scaling-역할">ASG(Auto Scaling Group) - Auto Scaling 역할</h3>
<p>EC2와 같은 서비스를 그룹 단위로 구성하고 오토스케일링을 제공하는 서비스</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[정규표현식]]></title>
            <link>https://velog.io/@jungkyu_lol/%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D</link>
            <guid>https://velog.io/@jungkyu_lol/%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D</guid>
            <pubDate>Fri, 07 Mar 2025 04:59:54 GMT</pubDate>
            <description><![CDATA[<h1 id="학습-내용-정리">학습 내용 정리</h1>
<hr>
<h2 id="정규-표현식">정규 표현식</h2>
<p>정규 표현식, 또는 정규식은 <strong>문자열에서 특정 문자 조합을 찾기 위한 패턴</strong>입니다.</p>
<h3 id="정규-표현식-읽는-법">정규 표현식 읽는 법</h3>
<ul>
<li>정규 표현식에서 슬래시(/) 안에 패턴을 넣고, 플래그를 통해 옵션을 줄 수 있습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jungkyu_lol/post/a9882275-6917-4bc6-95b2-509769b05f1d/image.png" alt=""></p>
<p>[출처]<a href="https://ji-musclecode.tistory.com/62">https://ji-musclecode.tistory.com/62</a></p>
<h3 id="메타문자">메타문자</h3>
<p>정규표현식의 기초이자, 원래 그 문자가 뜻하는 바가 아닌 <strong>특별한 의미로 사용되는 문자열</strong></p>
<table>
<thead>
<tr>
<th>메타문자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>.</td>
<td>임의의 한 문자와 매치됩니다.</td>
</tr>
<tr>
<td>+</td>
<td>바로 앞의 패턴이 1회 이상 반복되는 경우와 매치됩니다.</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>[ ]</td>
<td>대괄호 안에 나열된 문자 중 하나와 매치됩니다.</td>
</tr>
<tr>
<td>-</td>
<td>대괄호 안에서 사용될 때, 문자 범위를 지정합니다.</td>
</tr>
<tr>
<td>\</td>
<td>메타문자를 일반 문자열로 바꿔줍니다.</td>
</tr>
<tr>
<td>$</td>
<td>문자열의 끝과 매치됩니다.</td>
</tr>
<tr>
<td>{ }</td>
<td>중괄호 안에 숫자를 넣어 정확한 반복 횟수를 지정할 수 있습니다.</td>
</tr>
</tbody></table>
<p><strong>메타문자 ( . )</strong></p>
<p>한 문자를 매치합니다.
<img src="https://velog.velcdn.com/images/jungkyu_lol/post/b09f2ecb-ac6f-4fd2-bfea-07d90c623a65/image.png" alt=""></p>
<p><strong>메타문자 ( + )</strong></p>
<p>l이 1번 이상 반복된 경우(l,ll,lll…)을 매치합니다.
<img src="https://velog.velcdn.com/images/jungkyu_lol/post/01e8631f-1aee-419e-b324-0a68d193e465/image.png" alt=""></p>
<p><strong>메타문자 ( | )</strong></p>
<p>a 또는 b를 매치합니다.
<img src="https://velog.velcdn.com/images/jungkyu_lol/post/b4d15ad2-1ec8-43b0-80d1-04b8ebc2695a/image.png" alt=""></p>
<p><strong>메타문자 ( [ ] )</strong></p>
<p>대괄호 안에 나열된 문자 e 또는 a를 매치합니다.
<img src="https://velog.velcdn.com/images/jungkyu_lol/post/f23db520-9043-44b0-8a49-17918ff3718c/image.png" alt=""></p>
<p><strong>메타문자 ( - )</strong></p>
<p>대/소문자 알파벳 A부터 Z까지 매치합니다. 
<img src="https://velog.velcdn.com/images/jungkyu_lol/post/a1d59aea-5b3a-41bf-8568-fcdcbad45df2/image.png" alt=""></p>
<p><strong>메타문자 ( \ )</strong></p>
<p>메타문자(.)는 패턴에 사용될 수 없지만 .를 정상적으로 매치합니다.
*대괄호 안에 있는 메타문자는 메타문자()를 사용하지 않아도 기본적으로 매치합니다.
<img src="https://velog.velcdn.com/images/jungkyu_lol/post/71a66783-3991-4631-b24d-523541b3ddf5/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jungkyu_lol/post/f02602d4-522a-41fd-8587-0b76d518a5ef/image.png" alt=""></p>
<h3 id="플래그">플래그</h3>
<p>정규표현식의 <strong>옵션</strong>이자, 패턴 뒤에 붙여주는 문자열</p>
<table>
<thead>
<tr>
<th>플래그</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>g</td>
<td>전체 문자열에서 모든 매치를 찾음(전역탐색)</td>
</tr>
<tr>
<td>i</td>
<td>대소문자를 구분하지 않음</td>
</tr>
</tbody></table>
<p><strong>예시 :  /abc/</strong></p>
<ul>
<li><strong>g</strong>: <strong>/abc/g</strong>는 문자열 &quot;abc abc abc&quot;에서 모든 &quot;abc&quot;를 찾습니다.</li>
<li><strong>i</strong>: <strong>/abc/i</strong>는 &quot;abc&quot;, &quot;ABC&quot;, &quot;AbC&quot;와 모두 매치됩니다.</li>
</ul>
<h1 id="참고-자료">참고 자료</h1>
<p><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Regular_expressions">정규 표현식 - JavaScript | MDN</a></p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Cheatsheet">Regular expression syntax cheat sheet - JavaScript | MDN</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript를 사용하기 전 토막상식]]></title>
            <link>https://velog.io/@jungkyu_lol/Post4</link>
            <guid>https://velog.io/@jungkyu_lol/Post4</guid>
            <pubDate>Fri, 28 Feb 2025 04:54:19 GMT</pubDate>
            <description><![CDATA[<h1 id="핵심-주제">핵심 주제</h1>
<hr>
<ul>
<li>컴파일러 vs 인터프리터 with.트랜스파일러</li>
<li>TypeScript</li>
</ul>
<h1 id="학습-내용-정리">학습 내용 정리</h1>
<hr>
<h2 id="컴파일러-vs-인터프리터">컴파일러 vs 인터프리터</h2>
<p>컴파일러와 인터프리터는 모두 <strong>컴퓨터가 이해</strong>할 수 있는 <strong>기계어</strong>로 프로그램 코드를 <strong>변환</strong>하는 방식</p>
<h3 id="컴파일러-compiler">컴파일러 (Compiler)</h3>
<p>소스 코드를 전체를 <strong>기계어</strong>로 변환하여 실행 가능한 파일을 생성하는 프로그램</p>
<p>ex) C, C++, Rust, Go 등.</p>
<h3 id="인터프리터-interpreter---스크립트-언어">인터프리터 (Interpreter) - <em>스크립트 언어</em></h3>
<p>소스 코드를 한 줄씩 읽어들이고 <strong>기계어</strong>로 변환하여 실행하는 프로그램</p>
<p>ex) Python, <strong>JavaScript</strong>, Ruby, PHP 등.</p>
<ul>
<li>** JavaScript는 인터프리터 언어다.**
→ JavaScript는 소스 코드를 한 줄씩 읽어서 기계어로 변환하여 동작한다.</li>
</ul>
<h3 id="트랜스파일러">트랜스파일러</h3>
<p>트랜스파일러는 <strong>사람이 이해</strong>할 수 있는 <strong>다른 언어</strong>로 프로그램 코드를 <strong>변환</strong>하는 방식</p>
<p>ex) Babel, tsc(TypeScript Compiler), EsBuild 등.</p>
<h2 id="typescript">TypeScript</h2>
<h3 id="typescript-배경상황">TypeScript 배경상황</h3>
<ul>
<li>브라우저가 실행할 수 있는 언어는 2가지가 있다.<ol>
<li>JavaScript</li>
<li>WebAssembly(<strong>웹어셈블리)</strong></li>
</ol>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jungkyu_lol/post/5e18774c-2545-4f46-a209-0543164ef6aa/image.png" alt=""></p>
<h3 id="typescript-특징">TypeScript 특징</h3>
<ol>
<li><p><strong>TypeScript를 브라우저에서 사용하려면 변환이 이루어져야 한다.</strong></p>
<p> → TypeScript는 트랜스파일러를 통해 JavaScript로 변환이 되어야 브라우저에서 실행이 가능하다.</p>
</li>
<li><p><strong>TypeSciprt는 런타임 시점에서는 에러를 확인이 불가능하다.</strong></p>
<p> <strong>→</strong> TypeScript는 타입 안정성을 제공하는데 위와 같은 배경으로 인해서 IDE에서 오류를 통해서만 타입 안정성을 제공받을 수 있고 트랜스파일이 되어 JavaScript가 되면 런타임에서 확인이 불가능하다.</p>
</li>
</ol>
<h3 id="interface-vs-type-aliases">interface vs type a<strong>liases</strong></h3>
<ul>
<li>무엇을 쓸지는 일관성 또는 의미적 엄밀함에 따라서 기준을 나눌 수 있다.</li>
</ul>
<p>→ 어떤 기준으로 나눌지에 따라서 방법은 많기 때문에 일관성있게 작성만 되면 문제가 없다!</p>
<blockquote>
<p><strong>💡interface와 type기준 생각해보기</strong></p>
</blockquote>
<p><strong>함수 혹은 컴포넌트를 만들게 되면 2가지의 기준이 생긴다.</strong></p>
<ol>
<li>함수를 생성하는 관점 - 함수를 만드는 사람</li>
<li>함수를 사용하는 관점 - 함수를 가져다 쓰는 사람</li>
</ol>
<p><strong>이런 상황에서 커뮤니케이션을 하려면 기준이 되는 interface가 있어야 한다. 그래서 자연스럽게 함수와 컴포넌트를 사용하는 사람은 interface를 통해서 타입을 명시해주는게 자연스럽다.</strong></p>
<p>사용하는 사람은 컴포넌트에서 필요한 부분은 props로 넘기고, 함수에서 인자와 return 값을 통해서 사용한다.</p>
<p><strong>Type은 형식의 규격과 같이 특정한 데이터를 정의할 때 사용할 수 있다.</strong></p>
<p>ex) api 응답 데이터</p>
<p>→ api 응답 데이터도 사용자와 api가 통신하니까 interface로 볼 수 있지 않나?라고 생각이 들지만 특정한 데이터고 형식의 규격이라고 생각하고 접근하면 조금 납득이 될 수 있다.</p>
<h3 id="알아두자">알아두자</h3>
<pre><code class="language-jsx">interface User{
    getName() : string; //function
    getName: () =&gt; string; //arrow function
}</code></pre>
<ol>
<li>TypeScript Enum은 javaScript로 변환되면 코드가 길어지고 결과값이 생각과 다를 수 있는 가능성이 있어서 사용을 지양한다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redux 사용방법(Basic)]]></title>
            <link>https://velog.io/@jungkyu_lol/posts3</link>
            <guid>https://velog.io/@jungkyu_lol/posts3</guid>
            <pubDate>Fri, 21 Feb 2025 04:57:53 GMT</pubDate>
            <description><![CDATA[<h1 id="핵심-주제">핵심 주제</h1>
<hr>
<ul>
<li>redux의 사용흐름</li>
<li>useRef Hook 간단히 알아보기</li>
<li>redux 코드 개선하기</li>
</ul>
<h1 id="학습-내용-정리">학습 내용 정리</h1>
<hr>
<h2 id="redux의-사용흐름">redux의 사용흐름</h2>
<ol>
<li><p>reducer(리듀서)를 생성하고, createStore 메소드를 이용해서 리듀서를 스토어에 연결합니다.</p>
<pre><code class="language-jsx"> // reducer.js
 export default function reducer(state, action) {
   if (state === undefined) { //초기값을 주려고 undefined설정
     return {
       user: null, 
       languages: [
         {
           name: &quot;JavaScript&quot;,
           keyword: &quot;Web Frontend&quot;,
         },
       ],
     };
   }

   // main.jsx
   import { legacy_createStore as createStore } from &quot;redux&quot;;
   import reducer from &quot;./reducer.js&quot;;

   const store = createStore(reducer); </code></pre>
</li>
<li><p>사용하기 위한 컴포넌트의 상위 컴포넌트를 provider 컴포넌트로 감싸주고 store 속성에 생성해둔 스토어를 연결합니다.</p>
<pre><code class="language-jsx">   // main.jsx
   import { Provider } from &quot;react-redux&quot;;

   &lt;Provider store={store}&gt;
     &lt;App /&gt;
   &lt;/Provider&gt;</code></pre>
</li>
<li><p>필요한 상태를 가져오기 위해서는 useSelector를 사용해서 가져온다.</p>
<ul>
<li><p>(state) =&gt; state처럼 상태를 다 가져오는 것은 명시적이지 않기 떄문에 사용하려는 상태를 직접 전달하는 것이 좋다.
→ ❌ BAD: useSelector((state) =&gt; state) / ✅ GOOD: useSelector((state) =&gt; state.languages)</p>
<pre><code class="language-jsx">//component.jsx
import { useSelector } from &quot;react-redux&quot;;

export const Components = () =&gt; {
const languages = useSelector((state) =&gt; state.languages); 
}</code></pre>
</li>
</ul>
</li>
<li><p>상태를 변화시키기 위해서는 useDispatch를 사용해서 action(액션)을 호출합니다.</p>
<ul>
<li><p>액션 type은 별도의 상수로 관리하는 것이 좋다.</p>
<p>  → ❌ BAD:  add / ✅ GOOD: ADD_LANGUAGE</p>
</li>
</ul>
</li>
</ol>
<pre><code>```jsx
//component.jsx
import { useDispatch } from &quot;react-redux&quot;;

import { ADD_LANGUAGE } from &quot;./action-type&quot;

export const Components = () =&gt; {
const dispatch = useDispatch();

const addLanguage = () =&gt; {
  dispatch({
    type: ADD_LANGUAGE,
    name: &quot;JavaScript&quot;,
  });
};
  return (
    &lt;&gt;
      &lt;button onClick={addLanguage}&gt;추가하기&lt;/button&gt;
    &lt;/&gt;
  );
}

//reducer.js
import { ADD_LANGUAGE } from &quot;./action-type&quot;;

export default function reducer(state, action) {

  if (action.type === ADD_LANGUAGE) {
    return {
      ...state,
      languages: [
        ...state.languages,
        {
          name: action.name,
        },
      ],
    };
  }
}

// action-type.js
export const ADD_LANGUAGE = &quot;ADD_LANGUAGE&quot;;
```</code></pre><p>폼의 데이터를 처리하기 어렵다.</p>
<p>UI 업데이트 돔에 있지 않은 데이터를 참조하는 훅이 있다. → useRef</p>
<h2 id="useref">useRef</h2>
<ul>
<li>UI 업데이트 돔에 있지 않는 데이터(렌더링에 필요하지 않는 값)를 참조하는 훅</li>
</ul>
<p>폼과 관련된 데이터는 처리하기가 어렵다. 사용자의 이벤트를 처리하는건 어렵기 떄문에 이를 쉽게 지원하는 hook을 사용한다.</p>
<pre><code class="language-jsx">// input 요소를 생성하면 안에 value의 값이 있다.
const inputElement = document.querySelector(&#39;#myInput&#39;);
const inputValue = inputElement.value;

//useRef는 current라는 하나의 속성을 가지는 객체를 반환하는데,
//current 내부에 value의 값이 반환되도록 설계되어 있다.
inputRef = {
current: null,
};</code></pre>
<h3 id="사용-예시">사용 예시</h3>
<pre><code class="language-jsx">const inputRef = useRef(null);

const addLanguage = () =&gt; {
  dispatch({
    type: ADD_LANGUAGE,
    name: inputRef.current.value,
  });
};

//input요소에 ref 속성에 ref를 연결한다.
&lt;input type=&quot;text&quot; ref={inputRef} onChange={addLanguage} /&gt;</code></pre>
<h2 id="redux-코드-개선하기">redux 코드 개선하기</h2>
<ol>
<li><code>import { ADD_LANGUAGE } from &quot;./action-type</code> 를 사용해서 매번 type을 가져오는 것은 번거롭다. 이를 개선해보자.</li>
</ol>
<pre><code class="language-jsx">// action-type.js
export const ADD_LANGUAGE = &quot;ADD_LANGUAGE&quot;;

//data가 무엇이 될지 모르기 때문에 매개변수로 받아서 처리한다.
export const dispatchAddLang = (data) =&gt; ({
  type: ADD_LANGUAGE,
  ...data,
});</code></pre>
<ol>
<li>1번 방식으로 하는 것은 사용자가 내부 로직을 이해하고 있어야 하는 종속성 문제가 있다. 이를 개선해보자.</li>
</ol>
<pre><code class="language-jsx">// action-type.js
export const ADD_LANGUAGE = &quot;ADD_LANGUAGE&quot;;

export const dispatchAddLang = (name, keyword) =&gt; ({
  type: ADD_LANGUAGE,
  name,
  keyword,
});</code></pre>
<ol>
<li>아래처럼 2번 방식으로 동작하는 액션이 많이 생기는 바람에 동일한 로직이 반복되어 작성되는 문제가 생겼다. 이를 개선해보자. </li>
</ol>
<pre><code class="language-jsx">export const ADD_LANGUAGE = &quot;ADD_LANGUAGE&quot;;
export const EDIT_LANGUAGE = &quot;EDIT_LANGUAGE&quot;;

export const dispatchAddLang = (name, keyword) =&gt; ({
  type: ADD_LANGUAGE,
  name,
  keyword,
});

export const dispatchEditLang = (name, keyword) =&gt; ({
  type: EDIT_LANGUAGE,
  name,
  keyword,
});</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[에자일(
Agile) 방법론]]></title>
            <link>https://velog.io/@jungkyu_lol/post2</link>
            <guid>https://velog.io/@jungkyu_lol/post2</guid>
            <pubDate>Sat, 15 Feb 2025 04:41:47 GMT</pubDate>
            <description><![CDATA[<p>에자일 방법론에 대해 소개하도록 하겠습니다. 이 방법론은 소프트웨어 개발에서 특히 효과적인 접근 방식으로 자리 잡고 있습니다. 에자일은 개발의 유연성을 강조하며, 고객의 요구 사항에 신속하게 대응할 수 있도록 돕는 방법론입니다. 본 글에서는 에자일 방법론의 개요와 필요성, 원칙, 주요 프레임워크에 대해 심도 있게 다루어 보겠습니다.</p>
<p>에자일 방법론은 전통적인 개발 방식인 폭포수(Waterfall) 모델과는 차별화된 접근을 가지고 있습니다. 이 방법론은 반복적이고 점진적인 개발을 통해 빠르게 결과물을 제공하고, 필요에 따라 변경 사항을 쉽게 반영할 수 있는 장점이 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/jungkyu_lol/post/1dd0590c-f615-43fe-bc6b-98016ffac4b6/image.png" alt="">
<a href="https://realist.tistory.com/50">이미지출처</a></p>
<h2 id="에자일의-필요성과-배경">에자일의 필요성과 배경</h2>
<p>소프트웨어 개발 환경은 지속적으로 변화하고 있으며, 이런 변화에 적절히 대응하기 위해서는 유연성이 필수적입니다. 고객의 요구사항은 프로젝트의 진행 중에도 변화할 수 있으며, 이러한 요구에 발빠르게 대응할 수 있는 개발 방법론이 필요해졌습니다. 에자일 방법론은 이러한 요구를 충족시키기 위해 개발된 것입니다.</p>
<h2 id="에자일-방법론의-원칙">에자일 방법론의 원칙</h2>
<p>에자일 방법론의 기본 원칙은 고객의 최고의 가치를 제공하는 것입니다. 이는 고객과의 지속적인 협력을 통해 이루어지며, 팀원 간의 소통과 협업을 중요시합니다.</p>
<h2 id="애자일-매니페스토-소개">애자일 매니페스토 소개</h2>
<p>애자일 방법론의 가장 중요한 문서 중 하나는 애자일 매니페스토입니다. 이 문서에는 에자일의 핵심 가치와 원칙이 담겨 있습니다. 애자일 매니페스토는 다음과 같은 네 가지 핵심 가치를 제시합니다.</p>
<ul>
<li>개인과 상호작용을 프로세스와 도구보다 중요시한다.</li>
<li>작동하는 소프트웨어를 포괄적인 문서보다 중요시한다.</li>
<li>고객과의 협력을 계약 협상보다 중요시한다.</li>
<li>변화에 대한 반응을 계획을 따르는 것보다 중요시한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jungkyu_lol/post/d5a66dde-65df-4516-abd5-5301231865b2/image.png" alt="">
<a href="https://brunch.co.kr/@acc9b16b9f0f430/74">이미지출처</a></p>
<h3 id="12가지-원칙">12가지 원칙</h3>
<p>애자일 매니페스토에는 12가지 원칙이 포함되어 있습니다. 이 원칙들은 애자일 개발 팀이 따르는 기본적인 가이드라인 역할을 합니다. 이 중 몇 가지를 소개하자면:</p>
<ul>
<li>고객의 만족은 최우선 목표입니다.</li>
<li>변화는 개발 주기의 어느 단계에서든 환영합니다.</li>
<li>빈번한 배포로 소프트웨어를 제공해야 합니다.</li>
</ul>
<p>이외에도 여러 가지 원칙들이 있으며, 이들을 통해 팀은 고객의 요구를 충족시키기 위해 노력합니다.</p>
<h2 id="에자일-방법론의-주요-프레임워크">에자일 방법론의 주요 프레임워크</h2>
<p>에자일 방법론에는 여러 가지 주요 프레임워크가 존재합니다. 그 중에서 스크럼(Scrum), 칸반(Kanban), XP(익스트림 프로그래밍)가 널리 사용됩니다.</p>
<h4 id="스크럼scrum">스크럼(Scrum)</h4>
<p>스크럼은 에자일 방법론의 가장 대표적인 프레임워크 중 하나입니다. 이 방법론은 팀의 협업과 소통을 강조하며, 일정한 기간 내에 제품을 반복적으로 개선하는 방식으로 운영됩니다. 팀은 스프린트라는 짧은 주기를 설정하고, 이 주기 내에 목표를 달성하는 것을 목표로 합니다.</p>
<p><img src="https://velog.velcdn.com/images/jungkyu_lol/post/b8ca909f-51b2-432e-8049-3088be019647/image.png" alt="">
<a href="https://www.lgcns.com/blog/cns-tech/cloud/39440/">이미지 출처</a></p>
<h3 id="스크럼의-구성-요소">스크럼의 구성 요소</h3>
<p>스크럼은 주로 세 가지 역할로 구성됩니다: 제품 소유자(Product Owner), 스크럼 마스터(Scrum Master), 그리고 개발 팀입니다. 각 역할은 특정한 책임을 가지고 있으며, 팀원 간의 협력이 성공적인 스크럼을 위해 필수적입니다.</p>
<h4 id="역할과-의무">역할과 의무</h4>
<ul>
<li><strong>제품 소유자</strong> 는 프로젝트의 비전과 우선 순위를 설정하고, 개발 팀과의 소통을 통해 고객의 요구를 반영합니다.</li>
<li><strong>스크럼 마스터</strong> 는 팀이 스크럼을 올바르게 따르도록 돕고, 팀원 간의 장애물을 제거하는 역할을 합니다.</li>
<li><strong>개발 팀</strong> 은 실제로 제품을 개발하는 역할을 맡습니다.</li>
</ul>
<h3 id="칸반kanban">칸반(Kanban)</h3>
<p>칸반은 지속적인 흐름과 시각화를 통해 팀의 작업 프로세스를 관리하는 방법론입니다. 칸반 보드는 작업이 진행되는 과정을 시각적으로 나타내어, 팀원들이 현재 진행 중인 작업을 쉽게 이해하고 관리할 수 있도록 돕습니다.</p>
<p><img src="https://velog.velcdn.com/images/jungkyu_lol/post/be8672ef-dfc2-41b4-948d-43baf0e7b28d/image.png" alt="">
<a href="https://www.slideteam.net/blog/saempeulgwa-yejega-issneun-choegoui-7gaji-agile-methodology-peuloseseu-ppt-tempeullis?lang=Korean">이미지출처</a></p>
<h4 id="칸반의-기본-원칙">칸반의 기본 원칙</h4>
<p>칸반의 주요 원칙은 명확하게 정의된 작업 프로세스를 유지하고, 작업 흐름을 시각적으로 보여주는 것입니다. 이를 통해 팀원들은 각 작업의 상태를 쉽게 파악할 수 있습니다.</p>
<h4 id="칸반-보드-활용법">칸반 보드 활용법</h4>
<p>칸반 보드는 여러 가지 섹션으로 나뉘어져 있으며, 각 섹션은 작업의 상태를 나타냅니다. 예를 들어, &quot;작업 대기&quot;, &quot;진행 중&quot;, &quot;완료&quot;와 같은 상태로 나뉘어져 있으며, 각 작업의 진행 상태를 쉽게 확인할 수 있습니다. 이를 통해 팀원들은 작업의 진행 상황을 효과적으로 관리할 수 있습니다.</p>
<h3 id="xp익스트림-프로그래밍">XP(익스트림 프로그래밍)</h3>
<p>XP는 소프트웨어 개발의 품질을 높이기 위한 접근 방식입니다. 이 방법론은 고객과의 긴밀한 협력을 기반으로 하며, 테스트 주도 개발(TDD)과 같은 핵심 관행을 포함합니다.</p>
<h4 id="xp의-핵심-관행">XP의 핵심 관행</h4>
<p>XP의 핵심 관행으로는 지속적인 통합, 테스트 주도 개발(TDD), 페어 프로그래밍 등이 있습니다. 이러한 방법론들은 소프트웨어의 품질을 높이는 데 큰 도움을 줍니다.</p>
<h3 id="에자일-실천-방법">에자일 실천 방법</h3>
<p>에자일 방법론을 실천하는 과정에는 여러 가지 단계가 있습니다. 스프린트 계획, 일일 스크럼 미팅, 회고 및 개선 등의 단계가 포함됩니다.</p>
<h4 id="스프린트-계획-및-실행">스프린트 계획 및 실행</h4>
<p>스프린트 계획은 팀이 다음 스프린트에서 수행할 작업을 결정하는 단계입니다. 이 단계에서는 팀원들이 스프린트 동안의 목표와 작업을 명확히 정의합니다.</p>
<h4 id="일일-스크럼-미팅">일일 스크럼 미팅</h4>
<p>일일 스크럼 미팅은 팀원들이 하루 동안의 진행 상황을 공유하고, 각자의 작업을 조율하는 시간입니다. 이 미팅은 짧고 간결하게 진행되며, 팀 내의 소통을 활발하게 만들어 줍니다.</p>
<h4 id="회고-및-개선">회고 및 개선</h4>
<p>스프린트가 끝난 후, 팀은 회고를 통해 지난 스프린트 동안의 성과와 문제점을 분석합니다. 이를 통해 다음 스프린트에서의 개선점을 도출하고, 팀의 성장을 도모합니다.</p>
<h4 id="에자일의-장점과-단점">에자일의 장점과 단점</h4>
<p>에자일 방법론에는 여러 가지 장점과 단점이 존재합니다.</p>
<ul>
<li><p><strong>장점</strong>: 유연성, 고객 만족도, 팀 협업
에자일의 가장 큰 장점은 유연성입니다. 고객의 요구가 바뀔 때, 팀은 빠르게 이에 대응할 수 있습니다. 또한, 팀 간의 협력이 증진되어 더 나은 결과물을 생산할 수 있습니다.</p>
</li>
<li><p><strong>단점</strong>: 초기 학습 곡선, 팀원 간의 의사소통 문제
반면, 에자일 방법론은 초기 학습 곡선이 가파를 수 있으며, 팀원 간의 의사소통 문제가 발생할 수 있습니다. 이는 팀의 운영에 큰 영향을 미칠 수 있습니다.</p>
</li>
</ul>
<h4 id="에자일-도입-사례">에자일 도입 사례</h4>
<p>많은 조직들이 에자일 방법론을 도입하여 성공을 거두고 있습니다. 성공적인 도입 사례를 통해 어떻게 에자일을 잘 활용할 수 있는지 살펴보겠습니다.</p>
<h4 id="성공적인-에자일-도입-사례">성공적인 에자일 도입 사례</h4>
<p>일부 기업들은 에자일 방법론을 통해 고객의 요구에 더욱 효과적으로 대응하고, 팀의 협업을 강화하는 성과를 얻었습니다.</p>
<h4 id="실패-사례-및-교훈">실패 사례 및 교훈</h4>
<p>그러나 에자일 도입이 항상 성공적인 것은 아닙니다. 일부 팀은 올바른 방법론을 따르지 않거나 팀원 간의 소통이 원활하지 않아 실패를 경험하기도 했습니다. 이러한 실패 사례를 통해 얻은 교훈은 향후 에자일을 도입하는 데 중요한 가이드라인이 됩니다.</p>
<h4 id="에자일-도구-및-리소스">에자일 도구 및 리소스</h4>
<p>에자일 방법론을 효과적으로 구현하기 위해 다양한 도구와 리소스가 존재합니다. 대표적으로 JIRA, Trello와 같은 관리 도구가 있습니다. 이 도구들은 팀의 작업을 관리하고, 진행 상황을 시각화하는 데 유용합니다.</p>
<h4 id="인기-있는-에자일-도구-소개-jira-trello-등">인기 있는 에자일 도구 소개 (JIRA, Trello 등)</h4>
<ul>
<li><strong>JIRA</strong> : 프로젝트 관리 및 이슈 추적 도구로, 에자일 팀에 최적화되어 있습니다.</li>
<li><strong>Trello</strong> : 직관적인 칸반 보드를 제공하여 작업 관리를 쉽게 할 수 있습니다.
참고할 만한 서적과 온라인 리소스
에자일 방법론에 대한 깊이 있는 이해를 위해 다양한 서적과 온라인 자료를 참고하는 것도 좋습니다. 여러 블로그와 강의에서 유용한 정보들을 찾아볼 수 있습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[상태관리의 필요성(with.Redux)]]></title>
            <link>https://velog.io/@jungkyu_lol/post1</link>
            <guid>https://velog.io/@jungkyu_lol/post1</guid>
            <pubDate>Fri, 14 Feb 2025 04:45:25 GMT</pubDate>
            <description><![CDATA[<h2 id="상태-관리의-필요성">상태 관리의 필요성</h2>
<p>상태 관리가 잘 이루어지지 않을 경우, 프롭스 드릴링(Prop Drilling) 문제를 발생시킬 수 있습니다. 이는 컴포넌트 간의 의존성을 높히기 때문에 의존성을 낮춰야만 데이터 흐름과 이벤트 처리를 쉽게 할 수 있습니다.</p>
<h3 id="프롭스-드릴링prop-drilling">프롭스 드릴링(Prop Drilling)</h3>
<ul>
<li>여러 레벨의 컴포넌트가 있을 경우, 필요한 데이터가 최하위 컴포넌트까지 전달되기 위해 중간 컴포넌트이 이 데이터를 받아야 드릴처럼 밑으로 계속 전달하는 과정을 말합니다.</li>
</ul>
<h4 id="문제발생">문제발생</h4>
<ul>
<li>A 컴포넌트가 상태를 관리하고 B 컴포넌트가 A의 데이터를 사용하려고 할 때, B는 C에게 A의 데이터를 넘겨주고, C는 그 데이터를 받아서 UI를 렌더링해야 합니다.
이러한 방식은 상태 관리가 제대로 이루어지지 않아 데이터 흐름이 복잡해지고, 중간 컴포넌트를 거치는 과정에서 불필요한 데이터 전달이 증가하게 됩니다.</li>
</ul>
<h4 id="해결-방법-상태관리-라이브러리리덕스를-통한-상태-관리">해결 방법: 상태관리 라이브러리(리덕스)를 통한 상태 관리</h4>
<p>리덕스는 이러한 프롭스 드릴링 문제를 해결하는 데 매우 유용한 도구입니다.</p>
<h2 id="리덕스">리덕스</h2>
<p>리덕스는 복잡한 애플리케이션의 상태 관리 및 데이터 흐름을 단순화하기 위한 JavaScript 라이브러리입니다. React와 함께 자주 사용되며, 전역 상태 관리의 필요성을 해결해줍니다. 이 글에서는 리덕스의 구조와 사용법, 그리고 리액트와의 관계에 대해 살펴보겠습니다.</p>
<h3 id="리덕스의-데이터-흐름">리덕스의 데이터 흐름</h3>
<p>리덕스는 데이터의 흐름을 일방향으로 유지하는 구조를 가지고 있습니다. 이를 통해 다음과 같은 과정을 거쳐 상태가 관리됩니다:</p>
<ol>
<li><strong>액션 생성</strong>: 사용자의 상호작용으로 발생하는 이벤트에 대한 액션이 생성됩니다.</li>
<li><strong>액션 디스패치</strong>: 액션이 스토어에 전달되며, <code>dispatch()</code> 함수를 통해 호출됩니다.</li>
<li><strong>리듀서 호출</strong>: 현재 상태와 디스패치된 액션이 리듀서에 전달되어 새로운 상태를 반환합니다.</li>
<li><strong>UI 업데이트</strong>: 상태가 변경되면, 관련 컴포넌트가 다시 렌더링되어 UI를 업데이트합니다.</li>
</ol>
<h3 id="리덕스의-기본-구조">리덕스의 기본 구조</h3>
<ul>
<li><strong>스토어(Store)</strong>: 애플리케이션의 상태를 저장하는 객체입니다. 스토어는 오직 하나만 존재해야 하며, 모든 상태 관리가 이 스토어를 통해 이루어집니다.</li>
<li><strong>액션(Action)</strong>: 상태의 변화를 설명하는 객체입니다. 액션 객체는 최소한 <code>type</code> 속성을 가져야 하며, 추가적인 데이터(payload)를 포함할 수 있습니다.</li>
<li><strong>리듀서(Reducer)</strong>: 액션을 처리하여 새로운 상태를 반환하는 순수 함수입니다. 현재 상태와 액션을 인자로 받아 다음 상태를 반환합니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jungkyu_lol/post/66112f03-307a-417c-97d7-6b8e77e0209b/image.gif" alt=""></p>
<blockquote>
<h3 id="리덕스-3가지-원칙">리덕스 3가지 원칙</h3>
<p><strong>1. 진실은 하나의 근원으로부터</strong>
애플리케이션의 모든 상태는 하나의 저장소 안에 하나의 객체 트리 구조로 저장됩니다.
<strong>=&gt;스토어를 객체로 만들어라!</strong></p>
<p><strong>2. 상태는 읽기 전용이다</strong>
상태를 변화시키는 유일한 방법은 무슨 일이 벌어지는 지를 묘사하는 액션 객체를 전달하는 방법뿐입니다.
<strong>=&gt;모든 상태는 스토어에서 관리되며, 단순한 &#39;액션&#39;이라는 객체에 담아서만 상태를 변경한다.</strong></p>
<p><strong>3. 변화는 순수 함수로 작성되어야한다</strong>
액션에 의해 상태 트리가 어떻게 변화하는 지를 지정하기 위해 프로그래머는 순수 리듀서를 작성해야합니다.
<strong>=&gt;동일한 입력에 대해 항상 같은 결과를 반환하는 함수로 리듀서를 작성해야 됩니다.</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[JSX란? (특징, 부수효과)]]></title>
            <link>https://velog.io/@jungkyu_lol/JSX%EB%9E%80-%ED%8A%B9%EC%A7%95-%EB%B6%80%EC%88%98%ED%9A%A8%EA%B3%BC</link>
            <guid>https://velog.io/@jungkyu_lol/JSX%EB%9E%80-%ED%8A%B9%EC%A7%95-%EB%B6%80%EC%88%98%ED%9A%A8%EA%B3%BC</guid>
            <pubDate>Fri, 07 Feb 2025 04:53:40 GMT</pubDate>
            <description><![CDATA[<h2 id="jsx란">JSX란?</h2>
<blockquote>
<p>👉JSX라 하며 JavaScript를 확장한 문법입니다.
👉JSX는 React “엘리먼트(element)” 를 생성합니다.
<a href="https://ko.legacy.reactjs.org/docs/introducing-jsx.html#:~:text=JSX%EB%9D%BC%20%ED%95%98%EB%A9%B0%20JavaScript%EB%A5%BC%20%ED%99%95%EC%9E%A5%ED%95%9C%20%EB%AC%B8%EB%B2%95%EC%9E%85%EB%8B%88%EB%8B%A4.">-React 공식문서-</a></p>
</blockquote>
<p>JSX(JavaScript XML)는 JavaScript에서 HTML과 유사한 구문을 사용하여 UI를 구성하는 문법입니다. React와 함께 사용되며, 개발자가 컴포넌트를 더 직관적으로 작성할 수 있도록 도와줍니다.</p>
<hr>
<h3 id="특징-7가지">특징 7가지</h3>
<ol>
<li>Fragment
Fragment는 여러 요소를 그룹화할 수 있는 방법입니다. React에서 컴포넌트를 반환할 때, 여러 개의 자식 요소를 반환해야 할 경우 Fragment를 사용하면 불필요한 DOM 요소를 생성하지 않고도 이를 처리할 수 있습니다.</li>
</ol>
<pre><code class="language-jsx">import React from &#39;react&#39;;

const MyComponent = () =&gt; {
    return (
        &lt;&gt;
            &lt;h1&gt;제목&lt;/h1&gt;
            &lt;p&gt;내용이 여기에 들어갑니다.&lt;/p&gt;
        &lt;/&gt;
    );
};</code></pre>
<p>위의 예제에서 &lt;&gt;와 &lt;/&gt;는 Fragment를 나타내며, 두 개의 자식 요소가 하나의 부모 요소에 포함되어 있습니다.</p>
<p><strong>2. className</strong>
JSX에서는 HTML의 class 속성을 className으로 사용해야 합니다. 이는 JavaScript의 예약어인 class와의 충돌을 피하기 위한 조치입니다. 따라서 CSS 클래스를 지정할 때는 항상 className을 사용해야 합니다.</p>
<pre><code class="language-jsx">&lt;div className=&quot;my-class&quot;&gt;안녕하세요!&lt;/div&gt;</code></pre>
<p>위의 코드에서 my-class라는 CSS 클래스가 div 요소에 적용됩니다.</p>
<p><strong>3. HTML 태그 소문자</strong>
JSX에서는 HTML 태그를 소문자로 작성해야 합니다. 대문자로 작성하면 React는 이를 사용자 정의 컴포넌트로 인식하게 됩니다. 따라서 HTML 태그는 항상 소문자로 작성하는 것이 좋습니다.</p>
<pre><code class="language-jsx">// 올바른 예
const element = &lt;div&gt;Hello&lt;/div&gt;;

// 잘못된 예 (React는 이를 사용자 정의 컴포넌트로 인식)
const element = &lt;Div&gt;Hello&lt;/Div&gt;;</code></pre>
<p><strong>4. HTML 태그는 항상 닫아주기</strong>
JSX에서는 모든 HTML 태그를 반드시 닫아줘야 합니다. 이는 잘못된 HTML 구조를 방지하고, JSX 코드의 가독성을 높이는 데 기여합니다. 닫는 태그가 필요한 경우, 항상 명시적으로 닫아주는 것이 좋습니다.</p>
<pre><code class="language-jsx">// 올바른 예
&lt;p&gt;이것은 문단입니다.&lt;/p&gt;
&lt;img src=&quot;image.jpg&quot; alt=&quot;이미지&quot; /&gt;

// 잘못된 예
&lt;p&gt;이것은 문단입니다.&lt;/img&gt;</code></pre>
<p><strong>5. JSX의 동적 표현식</strong>
JSX에서는 중괄호 {}를 사용하여 JavaScript 표현식을 삽입할 수 있습니다. 이를 통해 동적으로 값을 렌더링할 수 있습니다.</p>
<pre><code class="language-jsx">const name = &quot;뤼튼&quot;;
const element = &lt;h1&gt;안녕하세요, {name}!&lt;/h1&gt;;</code></pre>
<p><strong>6. 스타일링 방법</strong>
JSX에서는 여러 가지 방법으로 스타일을 적용할 수 있습니다. 여기에는 인라인 스타일, CSS 파일을 통한 스타일링, CSS 모듈, 그리고 Styled Components 등이 포함됩니다. </p>
<p><strong>1. 인라인 스타일</strong>
JSX에서는 HTML의 style 속성을 사용하여 인라인 스타일을 적용할 수 있습니다. 이때 CSS 속성 이름은 카멜 케이스(camelCase)로 작성해야 하며, 값은 문자열로 지정합니다.</p>
<pre><code>```jsx
const element = (
    &lt;div style={{ color: &#39;blue&#39;, fontSize: &#39;20px&#39; }}&gt;
        인라인 스타일 예제
    &lt;/div&gt;
);
```

위의 예제에서 fontSize는 카멜 케이스로 작성되었고, 각각 문자열 값으로 지정되었습니다.</code></pre><p><strong>2. CSS 파일</strong>
CSS 파일을 생성하고 이를 컴포넌트에 import하여 스타일을 적용할 수 있습니다.</p>
<pre><code class="language-jsx">/* styles.css */
.my-class {
    color: red;
    font-size: 18px;
}

import &#39;./styles.css&#39;;

const element = &lt;div className=&quot;my-class&quot;&gt;CSS 파일 스타일 예제&lt;/div&gt;;</code></pre>
<p>이 방법은 스타일을 재사용하기 쉽고, CSS 파일에서 미디어 쿼리와 같은 복잡한 스타일링을 처리하는 데 유용합니다.</p>
<p><strong>3. CSS 모듈</strong>
CSS 모듈은 클래스 이름을 지역화하여 같은 이름의 클래스를 다른 컴포넌트에서 충돌 없이 사용할 수 있도록 도와줍니다. CSS 파일의 이름을 [name].module.css로 지정해야 합니다.</p>
<pre><code class="language-jsx">/* styles.module.css */
.myClass {
    color: green;
    font-size: 22px;
}

import styles from &#39;./styles.module.css&#39;;

const element = &lt;div className={styles.myClass}&gt;CSS 모듈 스타일 예제&lt;/div&gt;;</code></pre>
<p>이 방법은 컴포넌트 간의 스타일 충돌을 방지하고, 유지보수를 쉽게 합니다.</p>
<p><strong>4. Styled Components</strong>
Styled Components는 CSS-in-JS 라이브러리로, JavaScript 파일 내에서 컴포넌트의 스타일을 정의할 수 있게 해줍니다. 이 방법은 스타일과 로직을 함께 묶을 수 있어 코드의 가독성을 높입니다.</p>
<pre><code class="language-jsx">import styled from &#39;styled-components&#39;;

const StyledDiv = styled.div`
    color: purple;
    font-size: 24px;
`;

const element = &lt;StyledDiv&gt;Styled Components 예제&lt;/StyledDiv&gt;;</code></pre>
<p>Styled Components를 사용하면 컴포넌트에 스타일을 적용할 때 더 많은 유연성을 제공하며, 동적 스타일링도 쉽게 처리할 수 있습니다.</p>
<p><strong>7. 이벤트 처리</strong>
JSX에서는 이벤트 핸들러를 지정할 수 있으며, JavaScript 함수와의 연결이 간편합니다.</p>
<pre><code class="language-jsx">const handleClick = () =&gt; {
    alert(&#39;클릭되었습니다!&#39;);
};

const element = &lt;button onClick={handleClick}&gt;클릭하세요!&lt;/button&gt;;</code></pre>
<h3 id="부수효과-3가지">부수효과 3가지</h3>
<p><strong>1. UI 가독성이 올라간다</strong>
JSX는 HTML과 유사한 구문을 사용하여 UI를 구성하기 때문에, 코드를 읽고 이해하기가 쉽습니다. 개발자는 UI 구조를 직관적으로 파악할 수 있으며, HTML 요소와 JavaScript 로직이 함께 표현되므로 전체적인 흐름을 쉽게 이해할 수 있습니다. 이는 팀원 간의 협업을 원활하게 하고, 유지보수를 용이하게 합니다.</p>
<pre><code class="language-jsx">// Card.js
const Card = ({ title, content }) =&gt; {
    return (
        &lt;div style={{ border: &#39;1px solid #ccc&#39;, padding: &#39;16px&#39;, borderRadius: &#39;8px&#39; }}&gt;
            &lt;h2&gt;{title}&lt;/h2&gt;
            &lt;p&gt;{content}&lt;/p&gt;
        &lt;/div&gt;
    );
};

// App.js
const App = () =&gt; {
    return (
        &lt;div&gt;
            &lt;h1&gt;안녕하세요!&lt;/h1&gt;
            &lt;Card title=&quot;첫 번째 카드&quot; content=&quot;이것은 첫 번째 카드의 내용입니다.&quot; /&gt;
            &lt;Card title=&quot;두 번째 카드&quot; content=&quot;이것은 두 번째 카드의 내용입니다.&quot; /&gt;
        &lt;/div&gt;
    );
};</code></pre>
<p>이 예제에서 Card 컴포넌트는 제목과 내용을 받아서 UI를 구성합니다. JSX를 사용하여 HTML과 유사한 구조로 작성했기 때문에, 코드의 가독성이 높아졌습니다.</p>
<p><strong>2. 개별 요소로 쪼개져 있기 때문에 재사용이 가능하다</strong>
JSX는 컴포넌트 기반 아키텍처를 지원합니다. 각 UI 요소를 별도의 컴포넌트로 분리하면, 해당 컴포넌트를 여러 곳에서 재사용할 수 있습니다. 이는 코드 중복을 줄이고, 효율적인 개발을 가능하게 합니다. 예를 들어, 버튼, 입력 필드 등 자주 사용되는 UI 요소를 컴포넌트로 정의하면, 필요할 때마다 쉽게 호출하여 사용할 수 있습니다.</p>
<pre><code class="language-jsx">// Button.js
const Button = ({ label, onClick }) =&gt; {
    return (
        &lt;button onClick={onClick} style={{ padding: &#39;10px 20px&#39;, borderRadius: &#39;5px&#39;, backgroundColor: &#39;#007bff&#39;, color: &#39;#fff&#39; }}&gt;
            {label}
        &lt;/button&gt;
    );
};

// App.js
const App = () =&gt; {
    const handleClick = (buttonLabel) =&gt; {
        alert(`${buttonLabel} 버튼 클릭됨!`);
    };

    return (
        &lt;div&gt;
            &lt;h1&gt;안녕하세요!&lt;/h1&gt;
            &lt;Button label=&quot;버튼 1&quot; onClick={() =&gt; handleClick(&quot;버튼 1&quot;)} /&gt;
            &lt;Button label=&quot;버튼 2&quot; onClick={() =&gt; handleClick(&quot;버튼 2&quot;)} /&gt;
            &lt;Button label=&quot;버튼 3&quot; onClick={() =&gt; handleClick(&quot;버튼 3&quot;)} /&gt;
        &lt;/div&gt;
    );
};</code></pre>
<p>위의 예제에서 Button 컴포넌트는 라벨과 클릭 이벤트 핸들러를 받아서 여러 번 재사용되었습니다. 각각의 버튼 클릭 시 알림이 표시됩니다. 이처럼 컴포넌트를 분리하면 코드의 재사용성을 높이고, 유지보수가 용이합니다.</p>
<p><strong>3. 트랜스파일러(Babel)을 사용해야 합니다.</strong>
JSX는 브라우저가 직접 이해할 수 없는 문법입니다. Babel은 이러한 JSX 코드를 JavaScript로 변환해주는 트랜스파일러입니다. Babel을 사용하면, 최신 JavaScript 문법과 JSX를 구형 브라우저에서도 호환되도록 변환할 수 있습니다. 이를 통해 개발자는 최신 기능을 활용하면서도 넓은 호환성을 유지할 수 있습니다. 또한, Babel은 코드 최적화를 통해 성능을 향상시킬 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JavaScript 이벤트와 클로저(Closure) feat.커링]]></title>
            <link>https://velog.io/@jungkyu_lol/JavaScript-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%80Closure-feat.%EC%BB%A4%EB%A7%81</link>
            <guid>https://velog.io/@jungkyu_lol/JavaScript-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%80Closure-feat.%EC%BB%A4%EB%A7%81</guid>
            <pubDate>Fri, 24 Jan 2025 03:27:19 GMT</pubDate>
            <description><![CDATA[<p>이벤트 처리와 함수의 동작 방식을 이해하기 위해서 이번 포스트에서는 이벤트 버블링, 클로저, 그리고 이를 활용한 함수 생성에 대해 자세히 알아보겠습니다.
<img src="https://velog.velcdn.com/images/jungkyu_lol/post/2bdc5ade-2788-4df1-a1cd-c2c682017b2e/image.jpg" alt=""></p>
<h2 id="이벤트-버블링event-bubbling">이벤트 버블링(Event Bubbling)</h2>
<p>이벤트 버블링은 DOM에서 발생하는 이벤트 전파 방식 중 하나로, 이벤트가 발생한 요소에서 시작해 그 부모 요소로 전파되는 과정을 의미합니다. 예를 들어, 버튼 클릭 이벤트가 발생하면 그 이벤트는 버튼에서 시작하여 버튼의 부모 요소, 조부모 요소 등으로 전파됩니다. 이 과정을 통해 개발자는 특정 요소에서 발생한 이벤트를 부모 요소에서 처리할 수 있습니다.</p>
<h3 id="예시-코드">예시 코드</h3>
<pre><code class="language-javascript">document.getElementById(&#39;parent&#39;).addEventListener(&#39;click&#39;, function() {
    console.log(&#39;부모 요소 클릭됨&#39;);
});

document.getElementById(&#39;child&#39;).addEventListener(&#39;click&#39;, function() {
    console.log(&#39;자식 요소 클릭됨&#39;);
});</code></pre>
<p>위 코드에서 자식 요소를 클릭하면 &#39;자식 요소 클릭됨&#39;이 출력되고, 그 후 &#39;부모 요소 클릭됨&#39;이 출력됩니다. 이러한 특징은 이벤트 위임(Event Delegation) 기법을 사용할 때 유용합니다.</p>
<h2 id="이벤트-currenttarget-vs-target">이벤트 <code>currentTarget</code> vs <code>target</code></h2>
<p>이벤트 객체에는 <code>target</code>과 <code>currentTarget</code>이라는 두 가지 속성이 있습니다.</p>
<ul>
<li><strong><code>target</code></strong>: 이벤트가 발생한 실제 요소를 가리킵니다.</li>
<li><strong><code>currentTarget</code></strong>: 이벤트 리스너가 등록된 요소를 가리킵니다.</li>
</ul>
<h3 id="예시-코드-1">예시 코드</h3>
<pre><code class="language-javascript">document.getElementById(&#39;parent&#39;).addEventListener(&#39;click&#39;, function(event) {
    console.log(&#39;target:&#39;, event.target);
    console.log(&#39;currentTarget:&#39;, event.currentTarget);
});</code></pre>
<p>위 코드에서 부모 요소를 클릭하면 <code>target</code>은 클릭한 요소, <code>currentTarget</code>은 이벤트 리스너가 등록된 부모 요소를 가리킵니다.</p>
<h2 id="클로저closure">클로저(Closure)</h2>
<p>클로저는 함수가 자신의 외부 스코프에 접근할 수 있는 기능입니다. 즉, 함수가 생성될 때 그 함수가 정의된 환경을 기억하여, 나중에 호출될 때 그 환경에 접근할 수 있습니다. 클로저는 데이터 은닉, 상태 유지, 그리고 부분 적용과 같은 여러 가지 유용한 패턴을 가능하게 합니다.</p>
<h3 id="예시-코드-2">예시 코드</h3>
<pre><code class="language-javascript">function makeCounter() {
    let count = 0; // 클로저로 유지되는 상태

    return function() {
        count += 1;
        return count;
    };
}

const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2</code></pre>
<p>위 코드는 <code>makeCounter</code> 함수가 클로저를 사용하여 <code>count</code> 변수를 기억하고, 이를 통해 호출할 때마다 카운터를 증가시킵니다.</p>
<h2 id="커링currying">커링(Currying)</h2>
<p>커링은 여러 개의 인자를 받는 함수를 단일 인자 함수의 연쇄로 변환하는 기법입니다. 즉, 인자를 하나씩 받아서 함수를 반환하는 방식입니다. 이를 통해 함수의 재사용성을 높이고, 부분 적용을 쉽게 할 수 있습니다.</p>
<h3 id="커링의-장점">커링의 장점</h3>
<ol>
<li>함수의 이름을 지정하여 새로운 목적의 함수를 생성할 수 있다.</li>
<li>실행을 지연시킬 수 있다. </li>
</ol>
<h3 id="예시-코드-3">예시 코드</h3>
<pre><code class="language-javascript">function add(a) {
    return function(b) {
        return a + b;
    };
}

const add10 = add(10);
const add100 = add(100);

console.log(add10(5));  // 15 ,1. 함수의 이름을 지정하여 새로운 목적의 함수를 생성할 수 있다.
console.log(add100(5)); // 105 ,1. 함수의 이름을 지정하여 새로운 목적의 함수를 생성할 수 있다.</code></pre>
<pre><code class="language-javascript">function createCalculator(initialValue) {
    let value = initialValue;

    return {
      //2. 실행을 지연시킬 수 있다. , 지연시켜서 다른 동작을 하도록 구현할 수 있음
        add: function(num) {
            value += num;
            return value;
        },
        subtract: function(num) {
            value -= num;
            return value;
        },
        getValue: function() {
            return value;
        }
    };
}

const calculator = createCalculator(100);
console.log(calculator.add(50));      // 150
console.log(calculator.subtract(30));  // 120
console.log(calculator.getValue());    // 120</code></pre>
<p>위 코드에서 <code>createCalculator</code> 함수는 초기값을 받아 여러 가지 계산을 수행할 수 있는 객체를 반환합니다. 이 객체는 <code>add</code>, <code>subtract</code>, <code>getValue</code> 메서드를 통해 값을 조작하고 조회할 수 있습니다.</p>
<h2 id="redux와-클로저">Redux와 클로저</h2>
<p>Redux는 상태 관리를 위한 라이브러리로, 미들웨어를 사용하여 비동기 작업이나 부가적인 기능을 추가할 수 있습니다. 다음은 Redux에서 클로저를 사용하는 예시입니다.</p>
<h3 id="예시-코드-4">예시 코드</h3>
<pre><code class="language-javascript">const redux = store =&gt; action =&gt; next =&gt; {
    console.log(&#39;액션:&#39;, action);
    return next(action);
};</code></pre>
<p>위 코드는 액션이 발생할 때마다 로그를 남기는 미들웨어를 정의하는 코드입니다. 클로저를 통해 <code>store</code>와 <code>action</code>에 접근할 수 있습니다.</p>
<h2 id="결론">결론</h2>
<p>이벤트 버블링을 통해 효율적으로 이벤트를 처리할 수 있으며, 클로저는 함수의 유연성을 높여줍니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[javaScript 기초 정리]]></title>
            <link>https://velog.io/@jungkyu_lol/javaScript-%EA%B8%B0%EC%B4%88-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jungkyu_lol/javaScript-%EA%B8%B0%EC%B4%88-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 17 Jan 2025 04:59:37 GMT</pubDate>
            <description><![CDATA[<h1 id="자바스크립트의-함수와-클래스">자바스크립트의 함수와 클래스</h1>
<p>자바스크립트는 웹 개발에서 필수적인 프로그래밍 언어로, 함수형 프로그래밍과 객체 지향 프로그래밍을 지원합니다. 이번 포스트에서는 자바스크립트의 함수와 클래스에 대해 살펴보겠습니다.</p>
<h2 id="1-함수-functions">1. 함수 (Functions)</h2>
<h3 id="11-함수-정의">1.1. 함수 정의</h3>
<pre><code class="language-javascript">function 함수이름(매개변수1, 매개변수2) {
    // 실행할 코드
    return 반환값;
}</code></pre>
<h3 id="12-함수-호출">1.2. 함수 호출</h3>
<pre><code class="language-javascript">함수이름(값1, 값2);</code></pre>
<h3 id="13-익명-함수와-화살표-함수">1.3. 익명 함수와 화살표 함수</h3>
<h4 id="131-익명-함수">1.3.1. 익명 함수</h4>
<pre><code class="language-javascript">const 익명함수 = function(매개변수) {
    // 실행할 코드
};</code></pre>
<h4 id="132-화살표-함수">1.3.2. 화살표 함수</h4>
<pre><code class="language-javascript">const 화살표함수 = (매개변수) =&gt; {
    // 실행할 코드
};</code></pre>
<h4 id="133-화살표-함수의-특징">1.3.3. 화살표 함수의 특징</h4>
<ol>
<li><strong>문법적 간결성</strong>: 화살표 함수는 <code>function</code> 키워드를 생략할 수 있어 코드가 더 간결해집니다.</li>
<li><strong>this 바인딩</strong>: 화살표 함수는 자신만의 <code>this</code>를 가지지 않고, 외부 스코프의 <code>this</code>를 그대로 사용합니다.</li>
</ol>
<h3 id="14-즉시-실행-함수-iife">1.4. 즉시 실행 함수 (IIFE)</h3>
<pre><code class="language-javascript">(function() {
    // 실행할 코드
    console.log(&quot;즉시 실행 함수&quot;);
})();

(() =&gt; {
    console.log(&quot;화살표 즉시 실행 함수&quot;);
})();</code></pre>
<h3 id="15-콜백-함수-callback-functions">1.5. 콜백 함수 (Callback Functions)</h3>
<pre><code class="language-javascript">function 콜백함수() {
    console.log(&quot;콜백 함수가 실행되었습니다.&quot;);
}

function 함수에콜백전달(콜백) {
    console.log(&quot;기타 작업 수행 중...&quot;);
    콜백(); // 콜백 함수 호출
}

함수에콜백전달(콜백함수);</code></pre>
<h3 id="16-호이스팅-hoisting">1.6. 호이스팅 (Hoisting)</h3>
<pre><code class="language-javascript">console.log(함수이름()); // &quot;호이스팅 예제&quot;

function 함수이름() {
    return &quot;호이스팅 예제&quot;;
}</code></pre>
<p>반면 화살표 함수는 변수에 할당되므로 호이스팅되지 않습니다.</p>
<pre><code class="language-javascript">console.log(화살표함수()); // TypeError: 화살표함수 is not a function

const 화살표함수 = () =&gt; {
    return &quot;호이스팅 예제&quot;;
};</code></pre>
<h2 id="2-클래스-classes">2. 클래스 (Classes)</h2>
<h3 id="21-클래스-정의">2.1. 클래스 정의</h3>
<pre><code class="language-javascript">class 클래스이름 {
    constructor(매개변수) {
        this.속성 = 매개변수;
    }

    메서드이름() {
        // 실행할 코드
    }
}</code></pre>
<h3 id="22-클래스-인스턴스-생성">2.2. 클래스 인스턴스 생성</h3>
<pre><code class="language-javascript">const 인스턴스 = new 클래스이름(값);</code></pre>
<h3 id="23-상속">2.3. 상속</h3>
<pre><code class="language-javascript">class 부모클래스 {
    constructor() {
        this.부모속성 = &quot;부모 속성&quot;;
    }
}

class 자식클래스 extends 부모클래스 {
    constructor() {
        super(); // 부모 클래스의 생성자 호출
        this.자식속성 = &quot;자식 속성&quot;;
    }

    메서드이름() {
        console.log(this.부모속성, this.자식속성);
    }
}

const 자식인스턴스 = new 자식클래스();
자식인스턴스.메서드이름(); // &quot;부모 속성 자식 속성&quot;</code></pre>
<h2 id="3-함수와-클래스의-차이점">3. 함수와 클래스의 차이점</h2>
<ul>
<li><strong>기능</strong>: 함수는 특정 작업을 수행하기 위한 것이며, 클래스는 객체를 생성하고 그 객체의 상태와 행동을 정의하는 설계도입니다.</li>
<li><strong>재사용성</strong>: 함수는 단순한 작업을 반복적으로 수행하기 위한 것이고, 클래스는 객체 지향 프로그래밍을 통해 더 복잡한 구조를 만들 수 있습니다.</li>
</ul>
<h2 id="결론">결론</h2>
<p>자바스크립트에서 함수와 클래스는 각각의 목적과 특성을 가지고 있으며, 적절히 활용하면 더 효율적이고 유지보수가 쉬운 코드를 작성할 수 있습니다. 즉시 실행 함수와 콜백 함수, 화살표 함수의 특징 및 호이스팅 개념을 이해하는 것은 더욱 중요합니다. 웹 개발에서 이 두 가지 개념을 잘 이해하고 활용하는 것이 중요합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CSS Flexbox와 Grid, 배치 우선순위]]></title>
            <link>https://velog.io/@jungkyu_lol/CSS-Flexbox%EC%99%80-Grid-%EB%B0%B0%EC%B9%98-%EC%9A%B0%EC%84%A0%EC%88%9C%EC%9C%84</link>
            <guid>https://velog.io/@jungkyu_lol/CSS-Flexbox%EC%99%80-Grid-%EB%B0%B0%EC%B9%98-%EC%9A%B0%EC%84%A0%EC%88%9C%EC%9C%84</guid>
            <pubDate>Sun, 05 Jan 2025 14:53:22 GMT</pubDate>
            <description><![CDATA[<p>아래는 CSS Flexbox, Grid, 배치 우선순위에 대한 내용을 정리한 내용입니다.</p>
<h2 id="1-flexbox-flexible-box-layout">1. Flexbox (Flexible Box Layout)</h2>
<p>Flexbox는 CSS의 레이아웃 모듈 중 하나로, 복잡한 레이아웃을 간단하게 만들 수 있도록 도와줍니다.</p>
<h3 id="11-기본-개념">1.1. 기본 개념</h3>
<ul>
<li><strong>Flex Container</strong>: Flexbox를 적용할 부모 요소.</li>
<li><strong>Flex Item</strong>: Flex Container 내부의 자식 요소.</li>
</ul>
<h3 id="12-주요-속성">1.2. 주요 속성</h3>
<ul>
<li><code>flex-direction</code>: 주 축의 방향을 설정합니다.</li>
<li><code>justify-content</code>: 주 축을 따라 아이템을 정렬합니다.</li>
<li><code>align-items</code>: 교차 축을 따라 아이템을 정렬합니다.</li>
<li><code>flex-wrap</code>: 아이템의 줄 바꿈을 설정합니다.</li>
</ul>
<p><strong>예시: Flexbox 사용</strong></p>
<pre><code class="language-css">.flex-container {
    display: flex;
    justify-content: space-around;
}</code></pre>
<h2 id="2-grid-css-grid-layout">2. Grid (CSS Grid Layout)</h2>
<p>Grid는 2차원 레이아웃을 구성할 수 있는 강력한 CSS 모듈입니다. 행과 열로 구성된 그리드 시스템을 사용하여 복잡한 레이아웃을 쉽게 만들 수 있습니다.</p>
<h3 id="21-기본-개념">2.1. 기본 개념</h3>
<ul>
<li><strong>Grid Container</strong>: Grid를 적용할 부모 요소.</li>
<li><strong>Grid Item</strong>: Grid Container 내부의 자식 요소.</li>
</ul>
<h3 id="22-주요-속성">2.2. 주요 속성</h3>
<ul>
<li><code>grid-template-columns</code>: 열의 크기를 설정합니다.</li>
<li><code>grid-template-rows</code>: 행의 크기를 설정합니다.</li>
<li><code>grid-gap</code>: 그리드 아이템 간의 간격을 설정합니다.</li>
</ul>
<p><strong>예시: Grid 사용</strong></p>
<pre><code class="language-css">.grid-container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
}</code></pre>
<h2 id="3-배치-우선순위-stacking-context--z-index">3. 배치 우선순위 (Stacking Context &amp; z-index)</h2>
<p>CSS에서 요소의 배치 우선순위는 z축을 기준으로 하며, 요소가 다른 요소 위에 겹쳐 보일 때 어떤 요소가 위에 위치할지를 결정합니다.</p>
<h3 id="31-stacking-context">3.1. Stacking Context</h3>
<p>Stacking context는 요소가 쌓이는 방식에 대한 특정 규칙을 적용하는 환경입니다. 새로운 stacking context는 아래와 같은 경우에 생성됩니다:</p>
<ul>
<li>요소에 <code>position</code> 속성이 <code>relative</code>, <code>absolute</code>, <code>fixed</code>, 또는 <code>sticky</code>가 설정되고 <code>z-index</code> 값이 설정된 경우</li>
<li><code>opacity</code> 값이 0보다 작은 경우</li>
<li><code>transform</code>, <code>filter</code>, <code>perspective</code> 속성이 설정된 경우</li>
</ul>
<h3 id="32-z-index">3.2. z-index</h3>
<p><code>z-index</code> 속성은 요소의 쌓임 순서를 설정합니다. 값이 높을수록 위에 쌓입니다.</p>
<h3 id="33-배치-우선순위-규칙">3.3. 배치 우선순위 규칙</h3>
<ol>
<li>자연 순서: 문서의 흐름에 따라 위치.</li>
<li><code>z-index</code> 값에 따라 정렬.</li>
<li>Stacking context 내에서의 정렬.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[CSS 기초 정리]]></title>
            <link>https://velog.io/@jungkyu_lol/CSS-%EA%B8%B0%EC%B4%88-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jungkyu_lol/CSS-%EA%B8%B0%EC%B4%88-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 27 Dec 2024 13:48:14 GMT</pubDate>
            <description><![CDATA[<p>CSS(Cascading Style Sheets)는 HTML 문서의 스타일을 지정하기 위해 사용되는 언어입니다. CSS를 사용하면 웹 페이지의 레이아웃, 색상, 글꼴 및 기타 시각적 요소를 제어할 수 있습니다.</p>
<h2 id="1-css의-기본-구조">1. CSS의 기본 구조</h2>
<p>CSS는 선택자(selector)와 선언 블록(declaration block)으로 구성됩니다. 선택자는 스타일을 적용할 HTML 요소를 지정하며, 선언 블록은 적용할 스타일 속성과 값을 포함합니다.</p>
<pre><code class="language-css">선택자 {
    속성: 값;
}</code></pre>
<h3 id="예시">예시</h3>
<pre><code class="language-css">h1 {
    color: blue;
    font-size: 24px;
}</code></pre>
<h2 id="2-css-선택자-상세-정리">2. CSS 선택자 상세 정리</h2>
<p>CSS 선택자는 HTML 요소를 선택하여 스타일을 적용하는 데 사용됩니다. 선택자의 종류와 사용법을 자세히 살펴보겠습니다.</p>
<h3 id="21-기본-선택자">2.1 기본 선택자</h3>
<ul>
<li><p><strong>요소 선택자 (Type Selector)</strong>: 특정 HTML 요소를 선택합니다.</p>
<pre><code class="language-css">h1 {
    color: blue;
}</code></pre>
</li>
<li><p><strong>클래스 선택자 (Class Selector)</strong>: 특정 클래스를 가진 요소를 선택합니다. 클래스 이름 앞에 점(<code>.</code>)을 붙입니다.</p>
<pre><code class="language-css">.button {
    background-color: green;
}</code></pre>
</li>
<li><p><strong>아이디 선택자 (ID Selector)</strong>: 특정 아이디를 가진 요소를 선택합니다. 아이디 이름 앞에 해시(<code>#</code>)를 붙입니다.</p>
<pre><code class="language-css">#header {
    font-size: 24px;
}</code></pre>
</li>
</ul>
<h3 id="22-그룹-선택자">2.2 그룹 선택자</h3>
<p>여러 선택자를 한 번에 지정할 수 있습니다. 쉼표로 구분합니다.</p>
<pre><code class="language-css">h1, h2, h3 {
    color: purple;
}</code></pre>
<h3 id="23-자식-선택자">2.3 자식 선택자</h3>
<p>특정 요소의 직계 자식을 선택합니다. <code>&gt;</code> 기호를 사용합니다.</p>
<pre><code class="language-css">ul &gt; li {
    list-style-type: none;
}</code></pre>
<h3 id="24-후손-선택자">2.4 후손 선택자</h3>
<p>특정 요소의 모든 후손을 선택합니다. 공백을 사용합니다.</p>
<pre><code class="language-css">div p {
    color: red;
}</code></pre>
<h3 id="25-형제-선택자">2.5 형제 선택자</h3>
<ul>
<li><p><strong>일반 형제 선택자 (General Sibling Selector)</strong>: 같은 부모를 가진 모든 형제를 선택합니다. <code>~</code> 기호를 사용합니다.</p>
<pre><code class="language-css">h1 ~ p {
    color: gray;
}</code></pre>
</li>
<li><p><strong>인접 형제 선택자 (Adjacent Sibling Selector)</strong>: 바로 다음 형제 요소를 선택합니다. <code>+</code> 기호를 사용합니다.</p>
<pre><code class="language-css">h1 + p {
    margin-top: 0;
}</code></pre>
</li>
</ul>
<h3 id="26-속성-선택자">2.6 속성 선택자</h3>
<p>특정 속성을 가진 요소를 선택합니다.</p>
<ul>
<li><p><strong>특정 속성이 있는 요소</strong>:</p>
<pre><code class="language-css">a[href] {
    color: blue;
}</code></pre>
</li>
<li><p><strong>특정 속성 값이 있는 요소</strong>:</p>
<pre><code class="language-css">input[type=&quot;text&quot;] {
    border: 1px solid black;
}</code></pre>
</li>
</ul>
<h3 id="27-가상-클래스-선택자">2.7 가상 클래스 선택자</h3>
<p>특정 상태의 요소를 선택합니다.</p>
<ul>
<li><p><strong>호버 (Hover)</strong>: 마우스가 올라갔을 때</p>
<pre><code class="language-css">a:hover {
    text-decoration: underline;
}</code></pre>
</li>
<li><p><strong>포커스 (Focus)</strong>: 요소가 포커스 되었을 때</p>
<pre><code class="language-css">input:focus {
    border-color: blue;
}</code></pre>
</li>
<li><p><strong>첫 번째 자식 (First Child)</strong>: 첫 번째 자식을 선택합니다.</p>
<pre><code class="language-css">li:first-child {
    font-weight: bold;
}</code></pre>
</li>
</ul>
<h3 id="28-가상-요소-선택자">2.8 가상 요소 선택자</h3>
<p>특정 요소의 일부를 선택합니다.</p>
<ul>
<li><p><strong>첫 번째 줄 (First Line)</strong>: 요소의 첫 번째 줄을 선택합니다.</p>
<pre><code class="language-css">p::first-line {
    font-weight: bold;
}</code></pre>
</li>
<li><p><strong>첫 번째 문자 (First Letter)</strong>: 요소의 첫 번째 문자를 선택합니다.</p>
<pre><code class="language-css">p::first-letter {
    font-size: 2em;
}</code></pre>
</li>
</ul>
<h3 id="29-복합-선택자">2.9 복합 선택자</h3>
<p>여러 선택자를 조합하여 더 구체적인 선택을 할 수 있습니다.</p>
<ul>
<li><p><strong>클래스와 요소 결합</strong>:</p>
<pre><code class="language-css">div.myClass {
    background-color: yellow;
}</code></pre>
</li>
<li><p><strong>아이디와 클래스 결합</strong>:</p>
<pre><code class="language-css">#header .nav {
    color: white;
}</code></pre>
</li>
</ul>
<h2 id="3-선택자의-우선-순위">3. 선택자의 우선 순위</h2>
<p>CSS 선택자는 특정 요소에 스타일을 적용할 때 우선 순위를 가집니다. 일반적으로 우선 순위는 다음과 같습니다:</p>
<ol>
<li>인라인 스타일 (HTML 요소 내에서 직접 정의)</li>
<li>ID 선택자</li>
<li>클래스 선택자, 속성 선택자, 가상 클래스</li>
<li>요소 선택자, 가상 요소</li>
<li>일반 선택자</li>
</ol>
<p>우선 순위를 이해하고 사용하면 CSS를 더 효과적으로 관리할 수 있습니다.</p>
<h2 id="4-css-속성">4. CSS 속성</h2>
<p>CSS에는 다양한 속성이 있으며, 그 중 일부는 다음과 같습니다.</p>
<h3 id="41-색상-관련-속성">4.1 색상 관련 속성</h3>
<ul>
<li><strong>color</strong>: 텍스트 색상</li>
<li><strong>background-color</strong>: 배경 색상</li>
</ul>
<h3 id="42-텍스트-관련-속성">4.2 텍스트 관련 속성</h3>
<ul>
<li><strong>font-size</strong>: 글꼴 크기</li>
<li><strong>font-weight</strong>: 글꼴 두께</li>
<li><strong>text-align</strong>: 텍스트 정렬</li>
</ul>
<h3 id="43-박스-모델-관련-속성">4.3 박스 모델 관련 속성</h3>
<ul>
<li><strong>margin</strong>: 요소의 외부 여백</li>
<li><strong>padding</strong>: 요소의 내부 여백</li>
<li><strong>border</strong>: 요소의 테두리</li>
</ul>
<h2 id="5-레이아웃-방법">5. 레이아웃 방법</h2>
<p>CSS를 사용하여 웹 페이지의 레이아웃을 구성하는 방법에는 여러 가지가 있습니다.</p>
<h3 id="51-플렉스박스flexbox">5.1 플렉스박스(Flexbox)</h3>
<p>플렉스박스는 1차원 레이아웃을 쉽게 만들 수 있도록 도와줍니다.</p>
<pre><code class="language-css">.container {
    display: flex;
    justify-content: space-between;
}</code></pre>
<h3 id="52-그리드-레이아웃grid">5.2 그리드 레이아웃(Grid)</h3>
<p>그리드 레이아웃은 2차원 레이아웃을 만들기 위한 강력한 도구입니다.</p>
<pre><code class="language-css">.grid-container {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
}</code></pre>
<h2 id="6-미디어-쿼리">6. 미디어 쿼리</h2>
<p>미디어 쿼리는 다양한 화면 크기와 장치에 맞게 스타일을 조정할 수 있도록 해줍니다.</p>
<pre><code class="language-css">@media (max-width: 600px) {
    body {
        background-color: lightblue;
    }
}</code></pre>
<h2 id="7-마무리">7. 마무리</h2>
<p>CSS는 웹 페이지의 디자인과 레이아웃을 정의하는 데 필수적인 도구입니다. 이 포스트에서는 CSS의 기본 개념과 주요 기능을 살펴보았습니다. 더 깊이 있는 학습을 위해 공식 문서나 다양한 튜토리얼을 참고하시기 바랍니다.</p>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li><a href="https://developer.mozilla.org/ko/docs/Web/CSS">MDN Web Docs: CSS</a></li>
<li><a href="https://www.w3schools.com/css/">W3Schools: CSS Tutorial</a>
```</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Github] custom label & custom issue template]]></title>
            <link>https://velog.io/@jungkyu_lol/Github-Github-issue</link>
            <guid>https://velog.io/@jungkyu_lol/Github-Github-issue</guid>
            <pubDate>Mon, 23 Dec 2024 13:17:31 GMT</pubDate>
            <description><![CDATA[<p>팀 프로젝트를 시작하면서 팀원들에게 공유할 수 있는게 뭐가 있을까 고민을 하다가 github** custom label, custom issue template**을 적용하는 방법을 공유하기 위해서 글을 작성하게 되었습니다.</p>
<h2 id="✨custom-label-적용하기">✨custom label 적용하기</h2>
<h3 id="✅-완료-및-결과-화면">✅ 완료 및 결과 화면</h3>
<p><img src="https://velog.velcdn.com/images/jungkyu_lol/post/c1f69582-3efe-4f31-8eb4-4c12dd0c8e81/image.png" alt=""></p>
<blockquote>
<p><strong>3단계로 이루어진다.</strong></p>
<ol>
<li>labels.json파일을 생성한다.</li>
<li>personal access token을 생성한다.</li>
<li>명령어를 입력하여 labels.json 적용한다.</li>
</ol>
</blockquote>
<h4 id="1-labelsjson파일을-생성한다">1. labels.json파일을 생성한다.</h4>
<ul>
<li><strong>name, color, description</strong>을 포함한 형태로 작성한다.</li>
<li><em>labels.json*</em><pre><code>[
 {
   &quot;name&quot;: &quot;💣 bug&quot;,
   &quot;color&quot;: &quot;006b75&quot;,
   &quot;description&quot;: &quot;Bug issue&quot;
 },
 {
   &quot;name&quot;: &quot;🔧 chore&quot;,
   &quot;color&quot;: &quot;FDE9BC&quot;,
   &quot;description&quot;: &quot;Settings and other issues&quot;
 },
 {
   &quot;name&quot;: &quot;🚀 feat&quot;,
   &quot;color&quot;: &quot;C51181&quot;,
   &quot;description&quot;: &quot;Function implementation issues&quot;
 },
 {
   &quot;name&quot;: &quot;🍄 infra&quot;,
   &quot;color&quot;: &quot;E5B264&quot;,
   &quot;description&quot;: &quot;Infrastructure-related issues&quot;
 },
 {
   &quot;name&quot;: &quot;🔨 refactor&quot;,
   &quot;color&quot;: &quot;9AE970&quot;,
   &quot;description&quot;: &quot;Refactoring-related issues&quot;
 },
 {
   &quot;name&quot;: &quot;🌴 style&quot;,
   &quot;color&quot;: &quot;55AF2B&quot;,
   &quot;description&quot;: &quot;Style and css related issues&quot;
 },
 {
   &quot;name&quot;: &quot;🔍 test&quot;,
   &quot;color&quot;: &quot;C5DEF5&quot;,
   &quot;description&quot;: &quot;Test-related issues&quot;
 }
]
</code></pre></li>
</ul>
<pre><code>#### 2. personal access token을 생성한다.
- https://github.com/settings/apps 로 이동한다.
- 스크린샷으로 보여지는 방법으로 token을 발급받는다.
![](https://velog.velcdn.com/images/jungkyu_lol/post/437512a4-1808-4878-8c8e-56ae5fecf17f/image.png)
![](https://velog.velcdn.com/images/jungkyu_lol/post/b31e54a4-df4a-4a7c-a71f-b64430723ea1/image.png)
**완료가 되면 아래와 같이 출력된다.**
*작성자는 해당 token이 페이지를 벗어나면 더 이상 보여지지 않는 것으로 알고 있기 때문에 따로 메모해두길 바란다.*
![](https://velog.velcdn.com/images/jungkyu_lol/post/01113825-e13e-467d-9813-14d17cb2488e/image.png)


#### 3. 명령어를 입력하여 labels.json 적용한다.
- 아래 명령어 형식을 맞추어 입력한다.</code></pre><p>npx github-label-sync --access-token {본인의 token} --labels ./labels.json {라벨을 업데이트하고 싶은 본인의 레포지터리}</p>
<pre><code>
---

## ✨custom issue template 적용하기

 ### ✅ 완료 및 결과 화면
![](https://velog.velcdn.com/images/jungkyu_lol/post/136efbce-24fb-4c52-a0f7-7b0ba22ff6f3/image.png)
&gt;&gt; **github에서 제공하는 GUI를 사용하는 방법**
&gt;1. github에서 제공하는 templates 생성 기능을 이용한다.
&gt;2. templates을 원하는 형식으로 수정한다.
&gt;3. commit하여 업로드 한다.

--- 
### ✅ 완료 및 결과 화면

![](https://velog.velcdn.com/images/jungkyu_lol/post/78fa0b7d-76ed-4297-93c2-0f03709007a3/image.png)


&gt;&gt;  **직접 issue-form을 업로드하는 방법**
&gt;1. issue_form.yml파일을 생성한다.
&gt; 2. form을 원하는 형식으로 수정한다.
&gt; 3. commit하여 업로드 한다.

### github에서 제공하는 GUI를 사용하는 방법
#### 1. github에서 제공하는 templates 생성 기능을 이용한다.
- 스크린샷으로 보여지는 방법으로 templates를 생성한다.
![](https://velog.velcdn.com/images/jungkyu_lol/post/3949476d-7cdd-4ef5-9465-ef07ce85e1ab/image.png)
#### 2. templates을 원하는 형식으로 수정한다.
- 원하는 대로 수정하면 된다.
*작성자는 **issue template이 팀원에게 issue 내용을 공유하는 것과 반복적인 작업을 줄이기 위한 것**이라고 생각한다. 그렇기 때문에 팀원에게 간결하게 정보를 잘 전달하는 형식을 가지면서 작성하기 번거롭지 않은 형식을 채택하길 권장한다.
- 화살표를 보면 위에서 적용한 lebel도 적용해 줄 수 있다.

**작성자가 추천하는 template content 형식**</code></pre><h2 id="📄-describe">📄 Describe</h2>
<p>Please explain the issue in detail.</p>
<h2 id="☑️-tasks">☑️ Tasks</h2>
<ul>
<li><input disabled="" type="checkbox"> Write your to-do list here.</li>
</ul>
<h2 id="📋-ref">📋 Ref</h2>
<p>Additional information or reference documents.</p>
<pre><code>![](https://velog.velcdn.com/images/jungkyu_lol/post/42ff31f3-5aae-4e34-894d-7ff554706bc1/image.png)

#### 3. commit하여 업로드 한다.

![](https://velog.velcdn.com/images/jungkyu_lol/post/dfd69cb8-3807-4c35-8c2b-eb60d4e1f99e/image.png)

### 직접 issue-form을 업로드하는 방법
#### 1. issue_form.yml파일을 생성한다.
- .github/ISSUE_TEMPLATE 폴더 안에 생성한다.
#### 2. form을 원하는 형식으로 수정한다.
- 원하는 대로 수정하면 된다.
*아래 코드는 위에 작성자가 추천하는 template content 형식과 동일하게 만든 형태라고 보면 된다.
**issue_form.yml**</code></pre><p>name: &#39;✅ NEW Default&#39;
description: &#39;Feature 뉴 템플릿&#39;
labels: &quot;\U0001F680 feat&quot;
title: &#39;이슈 이름을 작성해주세요&#39;
assignees: [author]
body:</p>
<ul>
<li><p>type: input
id: description
attributes:
  label: &#39;이슈 내용(Description)&#39;
  description: &#39;이슈에 대해서 간략히 설명해주세요&#39;
validations:
  required: true</p>
</li>
<li><p>type: textarea
id: tasks
attributes:
  label: &#39;구현기능(Tasks)&#39;
  description: &#39;해당 이슈에 대해 필요한 작업목록을 작성해주세요&#39;
  value: |</p>
<pre><code>- [ ] Task1</code></pre><p>validations:
  required: true</p>
</li>
<li><p>type: textarea
id: references
attributes:
  label: &#39;참조(References)&#39;
  description: &#39;해당 이슈과 관련된 레퍼런스를 참조해주세요&#39;
  value: |</p>
<pre><code>- 참고자료</code></pre><p>validations:
  required: false</p>
</li>
</ul>
<p>```</p>
<h4 id="3-commit하여-업로드-한다">3. commit하여 업로드 한다.</h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git과 GitHub: 소프트웨어 개발 협업을 위해🚀]]></title>
            <link>https://velog.io/@jungkyu_lol/Git%EA%B3%BC-GitHub-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EA%B0%9C%EB%B0%9C-%ED%98%91%EC%97%85%EC%9D%84-%EC%9C%84%ED%95%B4</link>
            <guid>https://velog.io/@jungkyu_lol/Git%EA%B3%BC-GitHub-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EA%B0%9C%EB%B0%9C-%ED%98%91%EC%97%85%EC%9D%84-%EC%9C%84%ED%95%B4</guid>
            <pubDate>Fri, 20 Dec 2024 14:41:54 GMT</pubDate>
            <description><![CDATA[<p>Git과 GitHub은 협업 과정에서 코드 변경 사항을 효과적으로 관리하고 공유하는 데 중요한 역할을 합니다. 이번 포스팅에서는 Git과 GitHub의 기본 개념과 협업 과정을 살펴보겠습니다.</p>
<h3 id="1-git의-기본-개념">1. Git의 기본 개념</h3>
<ul>
<li><strong>Git 저장소 (Repository)</strong>: 프로젝트의 모든 파일과 변경 이력을 저장하는 공간. 로컬 저장소와 원격 저장소로 나뉩니다.</li>
<li><strong>커밋 (Commit)</strong>: 파일의 변경 사항을 저장하는 단위. 각 커밋은 고유한 해시값을 통해 이력을 추적합니다.</li>
<li><strong>브랜치 (Branch)</strong>: 독립적인 작업 공간으로, 여러 개발자가 동시에 작업할 수 있게 합니다.</li>
<li><strong>머지 (Merge)</strong> vs <strong>리베이스 (Rebase)</strong>:<ul>
<li><strong>머지</strong>: 두 브랜치를 합치는 과정.</li>
<li><strong>리베이스</strong>: 한 브랜치의 변경 사항을 다른 브랜치에 적용하여 커밋 이력을 깔끔하게 유지합니다.
<img src="https://velog.velcdn.com/images/jungkyu_lol/post/4292cfb6-99ec-462a-8bbb-a715c798b74c/image.png" alt="">
출처: <a href="https://static-assets.codecademy.com/Courses/learn-git-github/git-rebase/git-merge-vs-git-rebase.svg">https://static-assets.codecademy.com/Courses/learn-git-github/git-rebase/git-merge-vs-git-rebase.svg</a></li>
</ul>
</li>
</ul>
<h3 id="2-git-사용-준비">2. Git 사용 준비</h3>
<ol>
<li><strong>Git 설치</strong>: 운영체제에 맞는 설치 파일을 다운로드하여 설치합니다.</li>
<li><strong>GitHub 계정 생성 및 연동</strong>: GitHub에 가입 후 SSH 키를 생성하여 로컬 Git과 연동합니다.</li>
</ol>
<h3 id="3-브랜치-생성-및-관리">3. 브랜치 생성 및 관리</h3>
<p>브랜치 생성은 협업의 핵심입니다. 새로운 기능 개발 시 새로운 브랜치를 생성하세요.</p>
<pre><code class="language-bash">git checkout -b new-feature</code></pre>
<h3 id="4-코드-변경-및-커밋">4. 코드 변경 및 커밋</h3>
<p>변경 사항을 커밋할 때는 의미 있는 메시지를 작성하는 것이 중요합니다.</p>
<pre><code class="language-bash">git add .
git commit -m &quot;Add new feature&quot;</code></pre>
<h3 id="5-원격-저장소에-푸시">5. 원격 저장소에 푸시</h3>
<p>로컬 작업 내용을 원격 저장소에 푸시합니다.</p>
<pre><code class="language-bash">git push origin new-feature</code></pre>
<h3 id="6-pull-request-만들기">6. Pull Request 만들기</h3>
<p>Pull Request는 팀원들과 코드 변경 사항을 공유하고 리뷰를 요청하는 중요한 단계입니다.</p>
<ol>
<li>해당 브랜치로 이동.</li>
<li>&quot;Pull Request&quot; 버튼 클릭.</li>
<li>설명 작성 후 리뷰어 지정.</li>
</ol>
<h3 id="7-머지하기">7. 머지하기</h3>
<p>Pull Request가 승인되면 변경 사항을 메인 브랜치에 통합합니다. 머지 후 코드가 정상적으로 작동하는지 확인하세요.</p>
<blockquote>
<h3 id="fast-forward">Fast-forward</h3>
<p>Fast-forward는 Git에서 브랜치를 병합할 때 발생하는 특별한 상황입니다. 두 브랜치가 서로 다른 커밋을 가지고 있을 때, 한 브랜치를 다른 브랜치에 병합할 때 Fast-forward가 가능하면, Git은 병합 커밋을 생성하지 않고 단순히 브랜치 포인터를 이동시킵니다.
<img src="https://velog.velcdn.com/images/jungkyu_lol/post/7801c5d5-efcc-4e2c-97d5-fbe1a87c3876/image.png" alt="">출처: <a href="https://wikidocs.net/153871">https://wikidocs.net/153871</a></p>
</blockquote>
<h4 id="fast-forward의-특징">Fast-forward의 특징</h4>
<ul>
<li>간단한 병합: 별도의 병합 커밋 없이 브랜치 포인터를 이동합니다.</li>
<li>이력 유지: 커밋 히스토리가 직선으로 이어져 변경 사항 추적이 용이합니다.</li>
<li>조건: 병합하려는 브랜치가 현재 브랜치의 직계 후손이어야 Fast-forward가 가능합니다.<h4 id="fast-forward-방지">Fast-forward 방지</h4>
Fast-forward를 방지하고 별도의 병합 커밋을 생성하고 싶을 경우 --no-ff 플래그를 사용합니다.<blockquote>
<pre><code class="language-bash">git merge --no-ff feature</code></pre>
</blockquote>
<pre><code></code></pre></li>
</ul>
<h3 id="8-리베이스-사용하기">8. 리베이스 사용하기</h3>
<p>리베이스를 통해 커밋 이력을 깔끔하게 유지할 수 있습니다.</p>
<pre><code class="language-bash">git rebase main</code></pre>
<h3 id="9-충돌-해결">9. 충돌 해결</h3>
<p>머지나 리베이스 과정에서 충돌이 발생할 수 있습니다. 충돌 해결 방법은 다음과 같습니다.</p>
<ol>
<li>충돌 파일 수정.</li>
<li>변경 사항 스테이징.</li>
<li>커밋하여 충돌 해결.</li>
</ol>
<p><strong>Git과 GitHub을 활용하여 효율적인 협업을 진행해 보세요! 💻</strong></p>
<hr>
<h3 id="10-마무리">10. 마무리</h3>
<h4 id="gitgithub을-통한-협업의-장점">Git/GitHub을 통한 협업의 장점</h4>
<ol>
<li><p><strong>버전 관리</strong>: Git은 코드의 모든 변경 이력을 기록하여, 언제든지 이전 상태로 돌아갈 수 있습니다. 이는 실수로 인한 손실을 방지합니다.</p>
</li>
<li><p><strong>협업 용이성</strong>: 여러 개발자가 동시에 작업할 수 있어, 팀원 간의 효율적인 협업이 가능합니다. 브랜치 기능을 통해 각자 독립적으로 작업할 수 있습니다.</p>
</li>
<li><p><strong>코드 리뷰</strong>: GitHub의 Pull Request 기능을 통해 팀원 간의 코드 리뷰가 가능하며, 이는 코드 품질을 높이는 데 기여합니다.</p>
</li>
<li><p><strong>문서화</strong>: 프로젝트의 변경 이력과 논의 내용을 기록할 수 있어, 프로젝트의 진행 상황을 쉽게 파악할 수 있습니다.</p>
</li>
</ol>
<h4 id="좋은-커밋-메시지-작성법">좋은 커밋 메시지 작성법</h4>
<p>좋은 커밋 메시지는 협업의 효율성을 높이고, 코드 이력을 이해하는 데 도움을 줍니다. 다음은 좋은 커밋 메시지를 작성하기 위한 몇 가지 팁입니다:</p>
<ol>
<li><p><strong>명확하고 간결하게</strong>: 커밋 메시지는 변경 사항을 간단명료하게 설명해야 합니다. 예를 들어, &quot;Fix bug in user authentication&quot;처럼 구체적으로 작성합니다.</p>
</li>
<li><p><strong>현재형 사용</strong>: 커밋 메시지는 현재형으로 작성합니다. &quot;Add feature&quot; 대신 &quot;Added feature&quot;라고 하지 않습니다.</p>
</li>
<li><p><strong>커밋 목적 강조</strong>: 무엇을 변경했는지뿐만 아니라 왜 변경했는지도 설명합니다. 예를 들어, &quot;Improve performance of data processing&quot;처럼 작성합니다.</p>
</li>
<li><p><strong>문장 구조</strong>: 메시지는 제목과 본문으로 나누어 작성할 수 있습니다. 제목은 50자 이내로, 본문은 72자 이내로 작성하는 것이 좋습니다.</p>
</li>
</ol>
<h4 id="추가-리소스-링크">추가 리소스 링크</h4>
<p>다음은 Git과 GitHub을 더 깊이 이해하고 활용하기 위한 유용한 리소스입니다:</p>
<ul>
<li><a href="https://git-scm.com/book/en/v2">Git 공식 문서</a></li>
<li><a href="https://learngitbranching.js.org/?locale=ko">Git 실습</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>