<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>obebe_00.log</title>
        <link>https://velog.io/</link>
        <description>다른 건 노력의 시간</description>
        <lastBuildDate>Sat, 09 May 2026 08:41:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>obebe_00.log</title>
            <url>https://velog.velcdn.com/images/obebe_00/profile/5ddf4649-3ee5-4ab6-908b-f1cf5d1c146f/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. obebe_00.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/obebe_00" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Tailwind CSS]]></title>
            <link>https://velog.io/@obebe_00/Tailwind-CSS</link>
            <guid>https://velog.io/@obebe_00/Tailwind-CSS</guid>
            <pubDate>Sat, 09 May 2026 08:41:22 GMT</pubDate>
            <description><![CDATA[<p>React를 공부하며 Next.js와 같은 여러 프레임워크를 접해보고 있다.
그러며 <code>Tailwind CSS</code>도 접하게 되었는데, 여러 시니어분들도 추천을 하시고 실제 현업에서도 사용 비율이 점점 높아지고 있다는 이야기를 들으며 더 알아보고자 정리해보려고 한다.</p>
<blockquote>
<p>이 글은 Tailwind CSS v4 기준으로 작성되었다.</p>
</blockquote>
<hr>
<h3 id="기존-css-작성-방식">기존 CSS 작성 방식</h3>
<p><img src="https://velog.velcdn.com/images/obebe_00/post/30205478-3063-4edb-8fc6-b402f3f04d29/image.png" alt=""></p>
<p>처음 React를 공부할 때는 일반 CSS 방식으로 스타일을 작성했다.</p>
<pre><code class="language-js">&lt;button className=&quot;btn-primary&quot;&gt;버튼&lt;/button&gt;</code></pre>
<pre><code class="language-css">.btn-primary {
  background-color: blue;
  padding: 8px 16px;
  border-radius: 8px;
}</code></pre>
<p>이 방식은 가장 익숙하고 이해하기에는 쉬웠다.</p>
<p>하지만 프로젝트가 커질수록 몇 가지 불편한 점이 있다</p>
<ul>
<li>클래스 이름을 계속 고민해야 함</li>
<li>css 파일 이동이 많아짐</li>
<li>스타일 충돌 가능성 존재</li>
<li>어디에서 사용되는 클래스인지 찾기 어려움</li>
</ul>
<p>그래서 이후에는 CSS Module이나 styled-components 방식도 사용해보게 되었다.</p>
<hr>
<h3 id="tailwind를-처음-접하였을-때">Tailwind를 처음 접하였을 때</h3>
<p>솔직히 처음에는 코드가 오히려 복잡해보였다.</p>
<pre><code class="language-jsx">&lt;button className=&quot;bg-blue-500 px-4 py-2 rounded text-white&quot;&gt; 버튼 &lt;/button&gt;</code></pre>
<p>스타일이 전부 className 안에 들어가 있다 보니 HTML이 너무 길어 보인다는 느낌도 있었다.
&quot;오히려 더 불편하지않은가?&quot; 라는 생각도 들었지만 현업에서 많이 사용하는 이유를 찾아보고자 더 공부해보려고 마음먹었다.</p>
<hr>
<h1 id="tailwind의-장점">Tailwind의 장점</h1>
<h2 id="빠른-ui-개발">빠른 UI 개발</h2>
<p>Tailwind는 미리 정의된 utility class를 조합해서 스타일을 작성하는 방식이다.</p>
<pre><code class="language-jsx">&lt;div className=&quot;p-4 bg-white rounded-lg shadow-md&quot;&gt;</code></pre>
<p>기존 CSS처럼</p>
<ul>
<li>CSS 파일 생성</li>
<li>클래스 이름 작성</li>
<li>다시 컴포넌트로 이동</li>
</ul>
<p>과정을 반복하지 않아도 되기 때문에 빠르게 화면을 구성할 수 있다는 장점이 있다고 한다.</p>
<p>특히 프로토타입 제작이나 초기 UI 작업 속도가 빠르다는 의견이 많았다.</p>
<hr>
<h2 id="class명-고민-❌">class명 고민 ❌</h2>
<p>기존 CSS에서는 스타일을 만들 때마다 이름을 계속 지어야 했다.</p>
<p>.container {}
.wrapper {}
.box {}
.card {}</p>
<p>프로젝트가 커질수록 이름이 겹치거나, 어떤 역할인지 애매해지는 경우도 있었다.</p>
<p>하지만 Tailwind는 이미 정의된 utility class를 조합하는 방식이라 이런 고민이 줄어든다고 한다.</p>
<hr>
<h2 id="전역-클래스-명-충돌-걱정-감소">전역 클래스 명 충돌 걱정 감소</h2>
<p>기존 CSS는 전역 스타일 충돌 문제가 자주 발생한다.
특히 여러 사람이 함께 작업하는 프로젝트에서는 같은 클래스 이름을 사용하는 상황도 생길 수 있다.</p>
<p>Tailwind는 필요한 utility class를 직접 조합해서 사용하는 방식이기 때문에, 전역 클래스 이름이 겹칠 걱정이 상대적으로 줄어든다는 장점이 있다.
(CSS Module이나 styled-components처럼 스코프를 완전히 격리해주는 방식은 아니지만, 클래스 이름 충돌로 인한 문제는 훨씬 덜 발생한다.)</p>
<hr>
<h2 id="반응형-작업에-용이">반응형 작업에 용이</h2>
<p>Tailwind는 반응형 스타일을 쉽게 작성할 수 있도록 breakpoint 문법을 제공한다.</p>
<pre><code class="language-jsx">&lt;div className=&quot;text-sm md:text-lg lg:text-2xl&quot;&gt;</code></pre>
<p>별도의 media query를 작성하지 않아도 되기 때문에 반응형 UI를 빠르게 적용할 수 있다는 점이 많이 언급되었다.</p>
<hr>
<h2 id="react--nextjs-생태계와-좋은-궁합">React / Next.js 생태계와 좋은 궁합</h2>
<p>최근 React 기반 프로젝트에서는 Tailwind를 사용하는 경우가 많다고 한다.</p>
<p>특히</p>
<ul>
<li>Next.js</li>
<li>shadcn/ui</li>
<li>Vercel 기반 프로젝트</li>
</ul>
<p>등 최신 React 생태계와 함께 자주 사용된다는 점도 특징이라고 느꼈다.</p>
<p>컴포넌트 기반 개발 방식과 utility class 방식이 잘 어울린다는 의견도 많았다.</p>
<hr>
<h2 id="디자인-규칙-유지">디자인 규칙 유지</h2>
<p>Tailwind는 spacing, color, size 등이 일정한 규칙 안에서 제공된다.</p>
<p>그래서 프로젝트 전체에서 디자인 스타일을 통일하기 쉽다는 장점이 있다고 한다.</p>
<p>여러 명이 함께 작업할 때 UI 일관성을 유지하는 데 도움이 된다는 글들도 많이 볼 수 있었다.</p>
<hr>
<h2 id="현업에서-자주-쓰이는-cn-패턴">현업에서 자주 쓰이는 <code>cn()</code> 패턴</h2>
<p>현업 React + Tailwind 코드를 보면 아래와 같은 패턴이 자주 등장한다.</p>
<pre><code class="language-ts">import { clsx } from &quot;clsx&quot;;
import { twMerge } from &quot;tailwind-merge&quot;;

export function cn(...inputs) {
  return twMerge(clsx(inputs));
}</code></pre>
<pre><code class="language-jsx">&lt;button className={cn(&quot;px-4 py-2 rounded&quot;, isActive &amp;&amp; &quot;bg-blue-500&quot;, &quot;bg-gray-200&quot;)}&gt;
  버튼&lt;/button&gt;</code></pre>
<ul>
<li><code>clsx</code>는 조건부 클래스를 깔끔하게 처리해주고</li>
<li><code>tailwind-merge</code>는 충돌하는 클래스를 자동으로 합쳐준다</li>
</ul>
<p>shadcn/ui 같은 라이브러리에서도 이 패턴을 기본으로 사용하고 있어서, Tailwind를 실제로 사용하게 된다면 함께 알아두면 좋을 것 같다.</p>
<hr>
<h1 id="tailwind-v4에서-달라진-점">Tailwind v4에서 달라진 점</h1>
<p>2025년 초에 Tailwind CSS v4가 정식 릴리즈되면서 설정 방식이 꽤 달라졌다.
공식 문서를 보면서 따라할 때 버전 차이로 혼란스러울 수 있으니 알아두면 좋다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>v3</th>
<th>v4</th>
</tr>
</thead>
<tbody><tr>
<td>설정 파일</td>
<td><code>tailwind.config.js</code></td>
<td>CSS 파일 내부에서 직접 설정</td>
</tr>
<tr>
<td>임포트 방식</td>
<td><code>@tailwind base/components/utilities</code></td>
<td><code>@import &quot;tailwindcss&quot;</code></td>
</tr>
<tr>
<td>content 경로</td>
<td>직접 지정 필요</td>
<td>자동 감지</td>
</tr>
<tr>
<td>빌드 속도</td>
<td>빠름</td>
<td>훨씬 더 빠름</td>
</tr>
</tbody></table>
<hr>
<h1 id="tailwind가-무조건-정답일까">Tailwind가 무조건 정답일까?</h1>
<p>여러 자료를 찾아보면서 Tailwind가 무조건 모든 프로젝트의 정답은 아니라는 의견도 많았다.</p>
<p>대표적인 단점과 함께, 현업에서 어떻게 대응하는지도 정리해보았다.</p>
<table>
<thead>
<tr>
<th>단점</th>
<th>현업에서의 대응</th>
</tr>
</thead>
<tbody><tr>
<td>className이 길어진다</td>
<td>React 컴포넌트로 추상화하거나 <code>cn()</code> 패턴 활용</td>
</tr>
<tr>
<td>처음에는 가독성이 떨어진다</td>
<td>IDE 플러그인(Tailwind CSS IntelliSense)으로 자동완성 지원</td>
</tr>
<tr>
<td>HTML이 복잡해 보인다</td>
<td>컴포넌트 단위로 분리하면 어느 정도 해소됨</td>
</tr>
</tbody></table>
<p>그래서 프로젝트 규모나 팀 스타일에 따라</p>
<ul>
<li>일반 CSS</li>
<li>CSS Module</li>
<li>styled-components</li>
<li>Tailwind</li>
</ul>
<p>중 적절한 방식을 선택하는 것이 중요하다고 느꼈다.</p>
<hr>
<p>처음에는 단순히 “요즘 많이 사용하는 CSS 방식” 정도로 생각했다.</p>
<p>하지만 여러 자료를 찾아보면서 Tailwind는 단순 유행이라기보다 빠른 UI 개발과 유지보수를 고려한 방식이라는 점을 조금 이해할 수 있었다.</p>
<p>아직 직접 많이 사용해보지는 않았지만, 앞으로 React와 Next.js 프로젝트를 진행하면서 Tailwind를 직접 사용해보고 장단점을 더 체감해보고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Soft Delete, 언제 그리고 어떻게 사용할까?]]></title>
            <link>https://velog.io/@obebe_00/Soft-Delete-%EC%96%B8%EC%A0%9C-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@obebe_00/Soft-Delete-%EC%96%B8%EC%A0%9C-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Wed, 29 Apr 2026 11:08:30 GMT</pubDate>
            <description><![CDATA[<h3 id="왜-이-글을-쓰게-됐나">왜 이 글을 쓰게 됐나</h3>
<p>습관 관리 프로젝트에서 &quot;습관 삭제&quot; 기능을 구현하다가 뜻밖의 고민에 빠졌다. 처음엔 당연히 <code>DELETE</code> 쿼리를 쓰면 된다고 생각했는데, 막상 구현하려고 보니 두 가지 문제가 걸렸다.</p>
<p>첫 번째. 습관을 삭제하면 연결된 체크 기록(<code>habitLogs</code>)이 cascade로 함께 날아간다. 사용자가 한 달 동안 쌓아온 기록이 습관 하나 삭제로 전부 사라지는 건 말이 안 됐다.</p>
<p>두 번째. UX 문제였다. 오늘 아침에 이미 체크한 습관을 낮에 삭제했는데 목록에서 즉시 사라지면 어색하다. &quot;삭제했으니 오늘은 유지되고, 내일부터 안 보이는&quot; 동작이 훨씬 자연스럽다.</p>
<p>이 두 가지 이유로 Soft Delete를 선택했다.</p>
<hr>
<h1 id="hard-delete-vs-soft-delete">Hard Delete vs Soft Delete</h1>
<p>먼저 개념부터 정리하자.</p>
<p><strong>Hard Delete</strong>는 <code>DELETE</code> 쿼리로 DB에서 행을 완전히 제거하는 방식이다. 구현이 단순하고 DB를 깔끔하게 유지할 수 있다. 하지만 한 번 지우면 복구가 어렵고, 연관 데이터까지 함께 사라질 수 있다.</p>
<p>Soft Delete는 실제로 삭제하지 않고 &quot;삭제됐다&quot;는 표시만 남기는 방식이다. 행은 DB에 그대로 있고, <code>deletedAt</code>이나 <code>isDeleted</code> 같은 필드로 상태를 관리한다.</p>
<hr>
<h3 id="스키마-설계-isdeleted가-아니라-endat을-선택한-이유">스키마 설계: isDeleted가 아니라 endAt을 선택한 이유</h3>
<p><strong>Soft Delete</strong>를 구현할 때 가장 먼저 결정해야 할 건 어떤 필드로 상태를 표현할 것인가다.
흔히 <code>isDeleted: Boolean</code> 방식을 많이 쓴다. 직관적이고 간단하다. 그런데 이 프로젝트에선 <code>endAt: DateTime?</code>을 선택했다.</p>
<p>이유는 하나다. 삭제 여부뿐만 아니라 삭제 시점도 함께 남길 수 있기 때문이다.</p>
<ul>
<li><code>isDeleted: true</code> → &quot;지워졌다&quot;는 사실만 알 수 있다</li>
<li><code>endAt: 2024-11-15</code> → &quot;언제 지워졌는지&quot;까지 알 수 있다</li>
</ul>
<p>게다가 이 프로젝트에서 핵심 요구사항이 &quot;삭제 당일은 목록에 유지되고, 다음 날부터 제외&quot;였기 때문에, 날짜 기반 필드가 훨씬 다루기 쉬웠다.</p>
<pre><code class="language-prisma">model Habit {
  id        Int       @id @default(autoincrement())
  studyId   Int       @map(&quot;study_id&quot;)
  habitName String    @map(&quot;habit_name&quot;)
  startAt   DateTime  @map(&quot;start_at&quot;)
  endAt     DateTime? @map(&quot;end_at&quot;)  // null이면 활성, 값이 있으면 종료
  // ...
}</code></pre>
<p><code>null</code>이면 현재 활성 상태, 값이 있으면 해당 날짜에 종료된 습관이다.</p>
<hr>
<h1 id="삭제-처리-delete-대신-update">삭제 처리: DELETE 대신 UPDATE</h1>
<p>실제 삭제 로직은 단순하다. DELETE 대신 endAt을 오늘 날짜로 업데이트한다.</p>
<pre><code class="language-jsx">jsexport const deleteHabit = async (studyId, habitId) =&gt; {
  const habit = await prisma.habit.findFirst({
    where: { id: habitId, studyId },
  });

  if (!habit) throw new HabitNotFoundError();

  // 이미 종료된 습관은 다시 삭제 불가
  if (habit.endAt !== null) {
    throw new BadRequestError(&quot;이미 종료된 습관은 삭제할 수 없습니다.&quot;);
  }

  // 실제 삭제 대신 endAt을 오늘 날짜로 설정
  await prisma.habit.update({
    where: { id: habitId },
    data: { endAt: getTodayKST() },
  });
};</code></pre>
<p>한 가지 주의할 점은 <code>if (habit.endAt !== null)</code> 체크다. 이미 종료된 습관에 다시 삭제 요청이 들어왔을 때 멱등하게 처리하거나(아무것도 안 함), 아니면 에러를 던질 수 있다. 이 프로젝트에서는 &quot;이미 종료된 습관&quot;이라는 상태를 명확히 알려주는 게 낫다고 판단해서 에러를 던졌다.</p>
<p><code>getTodayKST()</code>는 <strong>UTC 기준 날짜 밀림 문제를 방지하기 위해 KST 기준으로 오늘 날짜를 반환하는 유틸 함수</strong>다. 한국 서비스라면 이 부분이 꽤 중요하다. (별도 포스팅으로 정리 예정)</p>
<hr>
<h3 id="조회-필터링-삭제-당일을-목록에-유지하는-법">조회 필터링: 삭제 당일을 목록에 유지하는 법</h3>
<p>Soft Delete에서 가장 신경 써야 할 부분이 조회 쿼리다. 조건을 빠뜨리면 삭제된 데이터가 그대로 노출된다.
이 프로젝트의 요구사항은 이랬다:</p>
<blockquote>
<p>삭제 당일은 목록에 유지되고, 다음 날부터 자동으로 제외된다.</p>
</blockquote>
<p>이를 구현한 조회 조건은 다음과 같다.</p>
<pre><code class="language-jsx">const habits = await prisma.habit.findMany({
  where: {
    studyId,
    startAt: { lte: now },
    OR: [
      { endAt: null },           // 종료일이 없는 활성 습관
      { endAt: { gt: today } },  // 종료일이 오늘보다 미래인 습관 (삭제 당일 포함)
    ],
  },
  include: {
    habitLogs: {
      where: { logDate: today },
    },
  },
});</code></pre>
<p><code>endAt: { gt: today }</code> 조건이 핵심이다. <code>endAt</code>이 오늘과 같은 날짜라면 이미 <code>gt</code> 조건에서 걸러지기 때문에, 삭제 당일은 목록에 포함되고 그 다음 날부터 제외된다.</p>
<p>조건을 정리하면 이렇다:</p>
<table>
<thead>
<tr>
<th>상태</th>
<th>endAt 값</th>
<th>오늘 목록에 포함?</th>
</tr>
</thead>
<tbody><tr>
<td>활성 중</td>
<td>null</td>
<td>✅</td>
</tr>
<tr>
<td>오늘 삭제</td>
<td>오늘 날짜</td>
<td>✅ (삭제 당일 유지)</td>
</tr>
<tr>
<td>이전에 삭제</td>
<td>과거 날짜</td>
<td>❌</td>
</tr>
</tbody></table>
<h3 id="실제로-cascade는-어떻게-됐을까">실제로 cascade는 어떻게 됐을까?</h3>
<p>처음 고민의 시작이었던 cascade 문제로 돌아가보자.</p>
<p>Soft Delete를 쓰면 부모 행(<code>Habit</code>)이 실제로 삭제되지 않기 때문에, DB에 설정된 <code>onDelete: Cascade</code>가 동작하지 않는다. 덕분에 <code>habitLogs</code>는 안전하게 보존된다.
다만 여기서 한 가지 확인해야 할 게 있다. </p>
<p><strong>Soft Delete</strong> 상태의 부모에 연결된 자식 데이터를 조회할 때, 자식 쿼리에서도 부모의 soft delete 상태를 체크하는지 확인해야 한다. 이 프로젝트에서는 항상 <code>habitId</code>를 기준으로 조회하고, 습관 자체의 활성 여부는 습관 목록 조회 단계에서 필터링하기 때문에 문제가 없었다.</p>
<hr>
<h3 id="soft-delete의-단점도-솔직하게">Soft Delete의 단점도 솔직하게</h3>
<p>장점만 있는 건 아니다. Soft Delete를 선택할 때 감수해야 할 것들도 있다.</p>
<p><strong>DB</strong>에 데이터가 계속 쌓인다. 삭제해도 행이 남아있으니 테이블이 계속 커진다. 서비스 규모가 커지면 일정 기간이 지난 종료 데이터를 주기적으로 정리하는 배치 작업이 필요하다.</p>
<p>조회 쿼리마다 조건을 추가해야 한다. 깜빡하고 <code>endAt</code> 조건을 빠뜨리면 삭제된 데이터가 그대로 노출된다. 팀 전체가 이 규칙을 공유하고 있어야 하고, <strong>ORM</strong> 레벨에서 글로벌 스코프로 자동 적용하는 방법도 고려해볼 수 있다. Prisma에서는 middleware 또는 <code>$extends</code>를 활용할 수 있다.</p>
<hr>
<h3 id="언제-써야-할까">언제 써야 할까?</h3>
<p>Soft Delete가 항상 정답은 아니다.</p>
<table>
<thead>
<tr>
<th>상황</th>
<th>선택</th>
</tr>
</thead>
<tbody><tr>
<td>삭제 후 복구 가능성이 있어야 할 때</td>
<td>Soft Delete</td>
</tr>
<tr>
<td>연관 기록(로그, 히스토리)을 보존해야 할 때</td>
<td>Soft Delete</td>
</tr>
<tr>
<td>삭제 시점을 추적해야 할 때</td>
<td>Soft Delete</td>
</tr>
<tr>
<td>단순 임시 데이터, 복구 불필요</td>
<td>Hard Delete</td>
</tr>
<tr>
<td>DB 용량이 민감한 환경</td>
<td>Hard Delete</td>
</tr>
</tbody></table>
<p>이 프로젝트처럼 사용자의 행동 기록이 핵심 가치인 서비스라면 Soft Delete가 더 안전한 선택이다. 데이터는 복구할 수 있지만, 한 번 날린 신뢰는 복구하기 어렵다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React - 다중 편집 버그 해결 과정]]></title>
            <link>https://velog.io/@obebe_00/React-%EB%8B%A4%EC%A4%91-%ED%8E%B8%EC%A7%91-%EB%B2%84%EA%B7%B8-%ED%95%B4%EA%B2%B0-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@obebe_00/React-%EB%8B%A4%EC%A4%91-%ED%8E%B8%EC%A7%91-%EB%B2%84%EA%B7%B8-%ED%95%B4%EA%B2%B0-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Wed, 22 Apr 2026 17:30:37 GMT</pubDate>
            <description><![CDATA[<p>스터디 습관 관리 앱을 개발하는 프로젝트를 진행 중 모달 안에서 여러 습관 항목의 이름을 자유롭게 편집하고, &quot;수정 완료&quot; 버튼 하나로 한 번에 저장하는 기능을 구현해야 했다.
<img src="https://velog.velcdn.com/images/obebe_00/post/9b917503-a43d-46da-9910-e29022cee249/image.png" alt="">
항목을 클릭하면 인풋으로 전환되어 이름을 수정할 수 있고, &quot;수정 완료&quot;를 누르면 변경사항이 서버에 반영되는 구조였다.</p>
<p>언뜻 단순해 보이는 기능이었지만 테스트하다가 이상한 점을 발견했다.</p>
<hr>
<h1 id="버그-발견">버그 발견</h1>
<p>두 가지 이상의 항목을 수정한 뒤 <code>수정 완료</code>를 눌렀더니, 마지막 항목만 저장되고 이전에 수정한 내역들은 반영이 되지 않았다.</p>
<hr>
<h1 id="원인-분석">원인 분석</h1>
<p>코드에 이유가 있었다.</p>
<pre><code class="language-jsx">// 기존 코드
const [editingHabitId, setEditingHabitId] = useState(null);
const [editingValue, setEditingValue] = useState(&quot;&quot;);</code></pre>
<p><code>editingHabitId</code>는 현재 포커스된 항목 하나의 ID만 기억한다. 그렇기에 수정 후 다른 항목을 수정하면서 포커싱이 옮겨지면 <code>editingHabitId</code>가 덮어씌워지고, 이전 변경사항은 상태에 남아있지 않게 되는 것이다.</p>
<p>&quot;수정 완료&quot;로직도 마찬가지였다.</p>
<pre><code class="language-jsx">// editingHabitId 하나만 저장 → 마지막으로 편집한 항목만 API 호출
if (editingHabitId !== null &amp;&amp; editingValue.trim() !== originalHabitName) {
  await editHabit(editingHabitId, editingValue.trim());
}</code></pre>
<p>결국 저장할 수 있는 건 <code>editingHabitId</code> 하나뿐이니, 마지막으로 클릭한 항목만 전송됐던 것이다.</p>
<hr>
<h1 id="해결-방향">해결 방향</h1>
<p>이것을 해결하기 위해 여러 방안을 모색해봤지만 별다른 수가 떠오르지 않았고 생각해낸 방법은 &quot;수정 완료를 누르기 전까지 모든 변경사항을 어딘가에 쌓아두면 좋겠다&quot;였다.</p>
<p>항목 하나를 추적하는 <code>editingHabitId</code>대신, 변경된 모든 항목을 담는 객체를 도입해보기로 했다.</p>
<pre><code class="language-jsx">// { [habitId]: 수정된 이름 } 형태로 누적
stagedEdits = {
  1: &quot;운동하기 (수정됨)&quot;,
  3: &quot;물 마시기 (수정됨)&quot;,
}</code></pre>
<p>스테이징의 의미처럼 아직 서버에는 반영되지 않은, 저장 직전 상태를 나타내기 위해 <code>stagedEdits</code>로 변수명을 정했다.</p>
<p>API가 항목 하나씩만 받더라도 문제없었다. <code>Promiss.all</code>로 병렬 호출하면 여러 항목을 동시에 전송할 수 있다.</p>
<pre><code class="language-jsx">// PATCH /habits/1
// PATCH /habits/3   → 세 요청이 동시에 전송됨
// PATCH /habits/5
await Promise.all(editPromises);</code></pre>
<hr>
<h1 id="구현">구현</h1>
<p>① stagedEdits 상태 추가</p>
<pre><code class="language-jsx">const [stagedEdits, setStagedEdits] = useState({});</code></pre>
<p>② 편집값이 바뀔 때마다 누적</p>
<pre><code class="language-jsx">jsconst handleEditChange = (habitId, value) =&gt; {
  setEditingValue(value);  // 인풋 표시용 (기존 유지)
  setStagedEdits((prev) =&gt; ({ ...prev, [habitId]: value }));  // 변경사항 누적
};</code></pre>
<p>③ 수정 완료 시 stagedEdits 전체를 API로 전송</p>
<pre><code class="language-jsx">jsconst handleConfirm = async () =&gt; {
  const editPromises = Object.entries(stagedEdits)
    .filter(([, name]) =&gt; name.trim())
    .map(([id, name]) =&gt; editHabit(Number(id), name.trim()));

  await Promise.all(editPromises);
  setStagedEdits({});  // 저장 후 초기화
};</code></pre>
<hr>
<h1 id="파생-버그---수정한-내용이-화면에서-사라진다">파생 버그 - 수정한 내용이 화면에서 사라진다</h1>
<p>이렇게 수정 후 테스트를 진행해보니 먼저 수정한 습관 이후에 다른 습관으로 포커싱을 옮겨 수정하려고하면 화면에는 이전 이름이 다시 나타났다.</p>
<p><code>stagedEdits</code>에는 잘 저장되어있는데, 화면만 초기화된 것이다.</p>
<hr>
<p>원인은 각 항목이 편집 모드에서 벗어날 때 <code>habit.habitName</code>(서버의 원본값)을 그대로 표시하기 때문이었다.</p>
<pre><code class="language-jsx">// 편집 모드가 아닐 때 원본값만 표시 → staged된 값이 무시됨
&lt;span&gt;{habit.habitName}&lt;/span&gt;</code></pre>
<p>서버 데이터는 아직 바뀌지 않았으니 어쩌면 당연한 결과였다. <code>stagedEdits</code>에 값이 있으면 그걸 우선 보여주도록 수정했다.</p>
<pre><code class="language-jsx">// 부모 컴포넌트 — stagedName prop 추가
&lt;HabitItem
  stagedName={stagedEdits[habit.id]}
  ...
/&gt;

// HabitItem — staged 값이 있으면 우선 표시, 없으면 원본
&lt;span&gt;{stagedName ?? habit.habitName}&lt;/span&gt;</code></pre>
<p>항목으로 다시 돌아올 때도 이전 편집값이 복원되어야 한다.</p>
<pre><code class="language-jsx">const handleEditStart = (habit) =&gt; {
  setEditingHabitId(habit.id);
  // stagedEdits에 값이 있으면 복원, 없으면 원본값으로 시작
  setEditingValue(stagedEdits[habit.id] ?? habit.habitName);
};</code></pre>
<hr>
<h1 id="파생-버그---삭제한-항목에-api가-호출된다">파생 버그 - 삭제한 항목에 API가 호출된다</h1>
<p>항목을 삭제한 뒤 &quot;수정 완료&quot;를 눌렀더니 에러가 발생했다. 삭제한 항목의 ID가 stagedEdits에 그대로 남아있어, 이미 존재하지 않는 항목에 PATCH 요청이 날아갔기 때문이었다.</p>
<p>삭제 시 stagedEdits도 함께 정리하면 해결된다.</p>
<pre><code class="language-jsx">jsconst handleDelete = async (habitId) =&gt; {
  await removeHabit(habitId);

  setStagedEdits((prev) =&gt; {
    const next = { ...prev };
    delete next[habitId];  // 삭제한 항목은 stagedEdits에서도 제거
    return next;
  });
};</code></pre>
<hr>
<h1 id="최종-흐름">최종 흐름</h1>
<pre><code class="language-md">&quot;운동하기&quot; 클릭 → &quot;헬스&quot; 입력
→ stagedEdits = { 1: &quot;헬스&quot; }

&quot;물 마시기&quot; 클릭 → &quot;운동하기&quot;는 화면에 &quot;헬스&quot;로 유지
→ &quot;물 2L&quot; 입력
→ stagedEdits = { 1: &quot;헬스&quot;, 3: &quot;물 2L&quot; }

수정 완료 클릭
→ PATCH /habits/1, PATCH /habits/3 동시 호출
→ 둘 다 저장 ✅</code></pre>
<hr>
<p>처음에는 단순한 버그처럼 보였지만, 고치는 과정에서 파생 버그가 두 번이나 더 나왔다. 공통된 원인은 하나였다. &quot;저장 전 클라이언트 상태&quot;를 명확하게 설계하지 않으면 화면과 실제 데이터가 어긋난다.</p>
<p><code>stagedEdits</code> 패턴은 할 일 목록, 설정 화면, 인라인 편집이 있는 어떤 UI에서라도 응용할 수 있다고 한다. 다음에 비슷한 UX를 구현하게 된다면 이 구조를 떠올려봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[로그인 없이 비밀번호 기반 접근 제어 (Session 활용)]]></title>
            <link>https://velog.io/@obebe_00/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%97%86%EC%9D%B4-%EB%B9%84%EB%B0%80%EB%B2%88%ED%98%B8-%EA%B8%B0%EB%B0%98-%EC%A0%91%EA%B7%BC-%EC%A0%9C%EC%96%B4-Session-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@obebe_00/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%97%86%EC%9D%B4-%EB%B9%84%EB%B0%80%EB%B2%88%ED%98%B8-%EA%B8%B0%EB%B0%98-%EC%A0%91%EA%B7%BC-%EC%A0%9C%EC%96%B4-Session-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Sat, 11 Apr 2026 10:51:22 GMT</pubDate>
            <description><![CDATA[<p>프로젝트에서 &#39;오늘의 습관&#39; 기능을 구현하며 가장 고민됐던 부분은 <strong>접근 제어 방식</strong>이었다.</p>
<p>페이지의 흐름은 아래와 같다.</p>
<blockquote>
<p>생성된 스터디(studyId)에 대해 비밀번호를 입력하고 일치하면 &#39;오늘의 습관&#39;페이지로 이동할 수 있다.</p>
</blockquote>
<p>하지만 로그인 기능이 없는 상태였기에 다음과 같은 문제가 발생했다.</p>
<ul>
<li>URL 직접 접근</li>
<li>새로고침 시 비밀번호 재입력 여부</li>
</ul>
<p>이 글에서는 위 문제를 해결하기 위해 어떤 구조를 설계했고, 그 과정에서 어떤 시행착오를 겪었는지 정리해보겠다.</p>
<hr>
<h1 id="구조">구조</h1>
<p>우선 연관된 페이지의 기능 구조는 아래와 같다.</p>
<h2 id="study-페이지">Study 페이지</h2>
<ul>
<li>스터디 단위의 페이지</li>
<li>각 스터디는 <code>studyId(PK)</code>와 비밀번호(<code>password</code>)를 가진다.</li>
</ul>
<h2 id="오늘의-습관-페이지">오늘의 습관 페이지</h2>
<ul>
<li>각 스터디에 속한 &quot;오늘의 습관&quot; 관리 페이지</li>
</ul>
<pre><code class="language-md">스터디 페이지에서 비밀번호 인증 후 오늘의 습관 페이지로 이동하는 구조</code></pre>
<hr>
<h1 id="문제-해결-접근-방법">문제 해결 접근 방법</h1>
<h2 id="1-비밀번호를-계속-사용">1. 비밀번호를 계속 사용</h2>
<p>가장 먼저 떠올린 방법은 비밀번호를 계속 들고 다니면서 요청마다 검증하는 방식이었다.
하지만 매 요청마다 비밀번호를 처리해야하는 것은 상당히 비효율적이라 생각해 다른 방식을 찾아보았다.</p>
<hr>
<h2 id="2-단순-인증-여부만-저장">2. 단순 인증 여부만 저장</h2>
<p>다음으로 생각한 방법은 비밀번호를 입력하여 입장에 성공하였을 때, 인증 여부만 저장하는 것이었다.
그런데 이렇게 하면 어떤 스터디에 대한 인증인지 구분할 수 없어 제대로 관리가 되지 않을 수 있다.</p>
<hr>
<h2 id="3-studyid를-저장하는-방식">3. studyId를 저장하는 방식</h2>
<p>비밀번호 인증 후 studyId를 세션에 담아 url과 일치하는지를 비교하여 인증 여부를 판단하는 것을 떠올렸다.</p>
<p>이 경우 다른 스터디로 이동하면 어떻게 해야할 지 해결이 되지 않았다.</p>
<hr>
<h1 id="최정-해결-방법-session을-활용한-study-단위-인증-상태를-관리">최정 해결 방법: Session을 활용한 study 단위 인증 상태를 관리</h1>
<p>앞서 고민했던 방식들의 공통 문제는 두 가지였다.</p>
<pre><code>1. 인증 상태를 어떻게 유지할 것인지 (새로고침 시 인증 유지)
2. 인증되지 않은 사용자가 URL을 직접 입력했을 때, 이를 어떻게 막을 것인지(url 직접 접근)</code></pre><hr>
<h2 id="session-사용">Session 사용</h2>
<p>인증 상태를 유지하는 방법으로</p>
<ul>
<li>localStorage에 저장</li>
<li>매 요청마다 비밀번호 입력</li>
<li>Session 사용</li>
</ul>
<p>이렇게 세 가지를 떠올렸고, session을 선택한 이유는 다음과 같다.</p>
<h3 id="인증-상태를-서버에서-안전하게-관리-가능">인증 상태를 서버에서 안전하게 관리 가능</h3>
<ul>
<li>비밀번호를 저장하지 않아도 됨</li>
<li>인증 결과만 서버에 담아둠</li>
</ul>
<p>보안 측면에서 유리</p>
<h3 id="새로고침-시에도-상태-유지-가능">새로고침 시에도 상태 유지 가능</h3>
<ul>
<li>session은 서버에 저장되기에 브라우저는 sessionId를 쿠키로 유지</li>
</ul>
<h3 id="추가-요청-없이-인증-상태-확인-가능">추가 요청 없이 인증 상태 확인 가능</h3>
<ul>
<li>매 요청마다 비밀번호를 다시 확인할 필요가 없음</li>
</ul>
<hr>
<h1 id="session-구조">Session 구조</h1>
<pre><code class="language-jsx">req.session.auth = {
    [studyId]: true
};</code></pre>
<p>: 해당 스터디에 대해 인증이 완료 됨</p>
<hr>
<h2 id="인증-흐름">인증 흐름</h2>
<h3 id="1️⃣-비밀번호-인증">1️⃣ 비밀번호 인증</h3>
<pre><code>if (password가 일치하면) { req.session.auth[studyId] = true; }</code></pre><hr>
<h3 id="2️⃣-이후-요청-처리">2️⃣ 이후 요청 처리</h3>
<pre><code>if (!req.session.auth?.[studyId]) { return res.status(403); }</code></pre><p>이후 매 요청에서 session을 통하여 인증 상태를 확인하고
인증 상태는 스터디 단위로 관리한다</p>
<hr>
<p>session을 처음 사용해보기에 session에 담는 값을 통하여 인증 상태를 관리하는 구조를 처음 접해보고 이해해보며 글을 정리해본다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DB - 관계형 데이터베이스 개념 정리]]></title>
            <link>https://velog.io/@obebe_00/DB-%EA%B4%80%EA%B3%84%ED%98%95-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@obebe_00/DB-%EA%B4%80%EA%B3%84%ED%98%95-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 07 Apr 2026 09:40:56 GMT</pubDate>
            <description><![CDATA[<p>Prisma에 대해 <a href="https://velog.io/@obebe_00/Prisma">지난 포스팅</a>에서 살펴보며 느낀 점은  DB 개념을 알아야 이것이 효과가 있다는 것이었다.</p>
<p>그래서! 다시 정리해서 기록해두고자 한다.</p>
<hr>
<h1 id="db">DB</h1>
<p>관계형 데이터베이스</p>
<blockquote>
<p>데이터를 테이블(표) 형태로 저장하는 구조</p>
</blockquote>
<p>like Excel</p>
<ul>
<li>테이블 -&gt; 시트</li>
<li>행(Row) -&gt; 데이터 한 줄</li>
<li>열(Column) -&gt; 데이터 항목</li>
</ul>
<p>이것들이 서로 연관을 지어있기에 이를 관계형 데이터베이스라 한다.</p>
<hr>
<h2 id="primary-key-pk">Primary Key (PK)</h2>
<p><strong>각 데이터를 구분하는 고유한 값</strong></p>
<pre><code>📍 중복 불가능
📍 NULL 불가능
❗ 테이블당 하나 존재</code></pre><p>으로 이것은 <strong>데이터를 식별하는 기준</strong>이 된다.</p>
<hr>
<h2 id="foreign-key-fk">Foreign Key (FK)</h2>
<p>Foreign key는 다른 테이블의 PK를 참조하는 값이다.</p>
<p>예를 들어 하나의 게시글을 두고, 이 게시글이 어떤 사용자의 게시글인지는 <code>users.id</code>를 보면 알 수 있다.</p>
<p>테이블 간 연결을 해주며 데이터의 일관성을 유지하는 역할을 한다.</p>
<h3 id="참조-무결성">참조 무결성</h3>
<p>이런 Foreign Key에서 중요한 개념으로 <strong>참조 무결성</strong>이 있다.</p>
<pre><code class="language-md">FK는 반드시 존재하는 PK만 참조할 수 있다</code></pre>
<p>이것은 존재하는 값들만 사용이 가능하다는 의미이고, 잘못된 데이터 연결을 막아준다</p>
<hr>
<h2 id="관계-relationship">관계 (Relationship)</h2>
<p>관계는 <strong>테이블과 테이블 사이의 연결 구조</strong>를 의미한다.</p>
<h3 id="🔗-1--n-일대다">🔗 1 : N (일대다)</h3>
<p>가장 많이 사용하는 관계</p>
<p>&rarr; 한 명의 사용자가 여러 개의 게시글을 가질 수 있다</p>
<h3 id="🔗-n--1-다대일">🔗 N : 1 (다대일)</h3>
<p>&rarr; 여러 게시글은 하나의 사용자에 속한다</p>
<h3 id="🔗-n--n-다대다">🔗 N : N (다대다)</h3>
<p>&rarr; 여러 개가 서로 여러 개와 연결되는 구조</p>
<pre><code>사용자 ↔ 태그
학생 ↔ 수업</code></pre><p>이 경우 중간 테이블이 필요하다.</p>
<hr>
<h1 id="제약-조건-constraints">제약 조건 (Constraints)</h1>
<p>기본 개념에 대해 알아보았고 이제 제약 조건에 대해 알아보고자 한다</p>
<p>제약조건이라함은</p>
<pre><code>잘못된 데이터가 들어오는 것을 막기 위한 규칙
즉, 데이터의 정확성과 일관성을 유지하기 위한 장치</code></pre><p>대표적인 제약 조건들을 정리해서 보겠다.</p>
<h2 id="제약-조건">제약 조건</h2>
<table>
<thead>
<tr>
<th>제약 조건</th>
<th>의미</th>
<th>특징</th>
<th>사용 예시</th>
</tr>
</thead>
<tbody><tr>
<td>PRIMARY KEY</td>
<td>데이터를 구분하는 고유값</td>
<td>중복 ❌, NULL ❌</td>
<td>id</td>
</tr>
<tr>
<td>FOREIGN KEY</td>
<td>다른 테이블과 연결</td>
<td>참조 무결성 유지</td>
<td>user_id</td>
</tr>
<tr>
<td>NOT NULL</td>
<td>반드시 값 입력</td>
<td>NULL 허용 ❌</td>
<td>name</td>
</tr>
<tr>
<td>UNIQUE</td>
<td>중복 값 금지</td>
<td>같은 값 2번 ❌</td>
<td>email</td>
</tr>
<tr>
<td>DEFAULT</td>
<td>기본값 자동 설정</td>
<td>값 없으면 자동 입력</td>
<td>created_at</td>
</tr>
</tbody></table>
<p>예시를 살펴보자.</p>
<p>실습 과제로 나왔던 온라인 강의 플랫폼 테이블 구조 설계다.</p>
<pre><code>요구사항
**요구사항:**

- 수강생은 이름, 이메일, 가입일 정보를 가진다
- 강사는 이름, 이메일, 소개글, 전문 분야 정보를 가진다
- 강의는 제목, 설명, 가격, 카테고리, 생성일 정보를 가지며, 한 명의 강사가 여러 강의를 만들 수 있다
- 수강생이 강의를 결제하면 수강 등록이 되고, 등록일과 결제 금액이 기록된다
- 수강생은 수강 중인 강의에 별점(1~5)과 텍스트 리뷰를 남길 수 있다
- 한 수강생이 같은 강의에 리뷰를 중복으로 남길 수 없다

**설계 조건:**

- PK, FK 명시
- UNIQUE 제약조건이 필요한 곳을 찾아서 적용
- DEFAULT 값이 적절한 곳에 설정</code></pre><hr>
<h3 id="수강생">수강생</h3>
<table>
<thead>
<tr>
<th>수강생 ID (PK)</th>
<th>이름</th>
<th>이메일 (UNIQUE, NOT NULL)</th>
<th>가입일 (DEFAULT NOW())</th>
</tr>
</thead>
<tbody><tr>
<td>id</td>
<td>name (NOT NULL)</td>
<td>email (UNIQUE, NOT NULL)</td>
<td>created_at</td>
</tr>
</tbody></table>
<h3 id="강사">강사</h3>
<table>
<thead>
<tr>
<th>강사 ID (PK)</th>
<th>이름</th>
<th>이메일 (UNIQUE, NOT NULL)</th>
<th>소개글</th>
<th>전문 분야</th>
</tr>
</thead>
<tbody><tr>
<td>id</td>
<td>name (NOT NULL)</td>
<td>email (UNIQUE, NOT NULL)</td>
<td>bio</td>
<td>specialty</td>
</tr>
</tbody></table>
<h3 id="강의">강의</h3>
<table>
<thead>
<tr>
<th>강의 ID (PK)</th>
<th>제목</th>
<th>설명</th>
<th>가격 (NOT NULL)</th>
<th>카테고리</th>
<th>생성일 (DEFAULT NOW())</th>
<th>강사 ID (FK, NOT NULL)</th>
</tr>
</thead>
<tbody><tr>
<td>id</td>
<td>title (NOT NULL)</td>
<td>description</td>
<td>price (NOT NULL)</td>
<td>category</td>
<td>created_at</td>
<td>instructor_id</td>
</tr>
</tbody></table>
<h3 id="수강--결제">수강 / 결제</h3>
<table>
<thead>
<tr>
<th>결제 ID (PK)</th>
<th>수강생 ID (FK, NOT NULL)</th>
<th>등록일 (DEFAULT NOW())</th>
<th>결제 금액 (NOT NULL)</th>
<th>강의 ID (FK, NOT NULL)</th>
</tr>
</thead>
<tbody><tr>
<td>id</td>
<td>student_id</td>
<td>enrolled_at</td>
<td>payment_amount</td>
<td>lecture_id</td>
</tr>
</tbody></table>
<h3 id="리뷰">리뷰</h3>
<table>
<thead>
<tr>
<th>결제 ID (PK)</th>
<th>수강생 ID (FK, NOT NULL)</th>
<th>등록일 (DEFAULT NOW())</th>
<th>결제 금액 (NOT NULL)</th>
<th>강의 ID (FK, NOT NULL)</th>
</tr>
</thead>
<tbody><tr>
<td>id</td>
<td>student_id</td>
<td>enrolled_at</td>
<td>payment_amount</td>
<td>lecture_id</td>
</tr>
</tbody></table>
<hr>
<p>정답이 맞는지는 모르지만, 이런 식으로 개념과 제약 조건을 사용해보았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Prisma]]></title>
            <link>https://velog.io/@obebe_00/Prisma</link>
            <guid>https://velog.io/@obebe_00/Prisma</guid>
            <pubDate>Tue, 07 Apr 2026 05:41:39 GMT</pubDate>
            <description><![CDATA[<p>직접 DB를 다뤄야 하는 순간이 찾아왔고 그렇게 Prisma를 처음 접하게 되었다.
처음부터 무엇인지에 대한 감은 전혀 잡히지 않았기에 이렇게 정리해보며 다시 공부해보고자 한다.</p>
<h1 id="prisma">Prisma</h1>
<p>Prisma는 ORM으로, 객체를 스키마(schema)로 정의한 다음 그 객체와 내가 선택한 데이터베이스를 연결시켜주는 <strong>매개체</strong>이다.</p>
<h3 id="orm은-무엇인가">ORM은 무엇인가</h3>
<p>여기서 처음 듣는 단어인 <code>ORM</code>이 무엇인지에 대해 짚고 넘어가보자
ORM은 👉 <strong>데이터베이스를 코드(객체)처럼 다루게 해주는 개념</strong>이다.</p>
<p>기존 방식에서는 DB에 접근할 때 SQL을 직접 작성해야 했다.
&#39;어떤 데이터를 어떻게 가져올지&#39;를 쿼리로 직접 표현했던 반면 ORM을 사용하면 DB를 직접 다루는 대신 <strong>코드에서 정의한 모델을 기준으로 데이터를 다룬다.</strong></p>
<pre><code class="language-md">SQL 방식 → DB 중심
ORM 방식 → 코드(객체) 중심</code></pre>
<p>즉, ORM은 DB를 다루는 방식을 &quot;쿼리&quot;에서 &quot;객체&quot;로 바꿔주는 역할을 한다.
이로 인하여 <strong>SQL 작성 부담이 줄어들고, 코드 흐름 안에서 데이터를 다룰 수 있게 되는 것이다</strong>.</p>
<hr>
<h3 id="prisma의-역할">Prisma의 역할</h3>
<p>ORM의 개념에 대해 정리를 해보았고, 다시 돌아와 Prisma를 보자.
Prisma는 그것을 실제로 사용할 수 있게 해주는 도구이다</p>
<p>단순히 DB에 연결하는 역할이 아닌</p>
<ul>
<li><strong>DB 구조를 코드로 정의하고</strong></li>
<li><strong>그 구조를 실제 DB와 연결해주는 역할</strong>을 한다.</li>
</ul>
<p>그럼 어떻게 연결을 해줄까?</p>
<h1 id="prisma의-흐름">Prisma의 흐름</h1>
<pre><code class="language-md">1. schema로 구조 정의
2. DB에 반영
3. 코드에서 사용</code></pre>
<h3 id="1-구조-정의-schema">1. 구조 정의 (schema)</h3>
<p>먼저 <code>schema.prisma</code> 파일에서 DB 구조를 정의한다.</p>
<p>이곳에서</p>
<ul>
<li>테이블(model)</li>
<li>필드</li>
<li>관계
전부 코드로 작성한다.</li>
</ul>
<hr>
<h3 id="2-db-반영-migration">2. DB 반영 (migration)</h3>
<p>schema를 작성한 뒤에 이 내용을 실제 DB에 반영해야 한다.</p>
<p>이 과정을 migration이라고 한다.</p>
<ul>
<li>코드로 정의한 구조를 실제 DB에 적용하는 단계</li>
</ul>
<hr>
<h3 id="3-코드에서-사용-prisma-client">3. 코드에서 사용 (Prisma Client)</h3>
<p>DB가 생성되면 이제 Prisma Client를 통해 데이터를 다룬다.</p>
<p>예를 들어 User 데이터를 조회한다고 하면</p>
<pre><code class="language-jsx">import { PrismaClient } from &quot;@prisma/client&quot;;

const prisma = new PrismaClient();

async function getUsers() {
  const users = await prisma.user.findMany();
  console.log(users);
}

getUsers();</code></pre>
<ul>
<li><code>user</code>라는 모델을 기준으로 <code>findMany()</code> 메서드를 호출해서 데이터를 가져온다.</li>
</ul>
<p>또 다른 예시로 데이터를 생성할 수도 있다.</p>
<pre><code class="language-jsx">await prisma.user.create({
  data: {
    email: &quot;test@test.com&quot;,
  },
});</code></pre>
<ul>
<li>이런 식으로 SQL 없이도 데이터를 조회하고 생성할 수 있다.</li>
</ul>
<hr>
<p>Prisma를 다시 정리해보면,</p>
<pre><code class="language-md">코드로 DB 구조 정의 → DB에 반영 → 코드로 데이터 사용</code></pre>
<p>SQL을 대신하여 코드로 작업할 수 있게 해준다는 점에서 마음에 들기도 했다.</p>
<p>하지만 가장 중요한 데이터베이스 관련 개념이 정리되어있지 않으면 제대로 사용할 수 없기에 이것에 대한 복습도 필요해보인다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 정규화]]></title>
            <link>https://velog.io/@obebe_00/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%A0%95%EA%B7%9C%ED%99%94</link>
            <guid>https://velog.io/@obebe_00/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%A0%95%EA%B7%9C%ED%99%94</guid>
            <pubDate>Mon, 06 Apr 2026 00:01:14 GMT</pubDate>
            <description><![CDATA[<h1 id="데이터베이스-정규화-normalization">데이터베이스 정규화 (Normalization)</h1>
<p>데이터베이스를 설계하다 보면 &#39;데이터베이스를 나누는 기준&#39;에 대한 고민이 생긴다.
이걸 정립해주는 개념이 <strong>정규화(Normalization)</strong>이다.</p>
<p>정규화란 </p>
<pre><code class="language-md">데이터의 중복을 줄이고, 이상 현상을 방지하기 위해 테이블을 구조적으로 나누는 과정</code></pre>
<hr>
<h2 id="정규화를-해야하는-이유">정규화를 해야하는 이유</h2>
<p>정규화를 하지 않았을 경우 발생하는 문제에 대해서 살펴보자.</p>
<hr>
<p><strong>❌ 1. 중복 데이터 발생</strong></p>
<pre><code class="language-md">학생 | 과목 | 교수
------------------
A    | DB   | Kim
A    | OS   | Lee</code></pre>
<p>👉 학생 A가 여러 번 반복됨</p>
<hr>
<p><strong>❌ 2. 수정 이상 (Update Anomaly)</strong></p>
<p>교수 이름이 바뀌면?</p>
<p>👉 모든 행을 다 수정해야 한다</p>
<p>→ 하나라도 빠지면 데이터 불일치 발생</p>
<hr>
<p><strong>❌ 3. 삽입 이상 (Insert Anomaly)</strong></p>
<p>새로운 과목을 추가하려는데 학생이 없다면?</p>
<p>👉 데이터를 넣을 수 없음</p>
<hr>
<p><strong>❌ 4. 삭제 이상 (Delete Anomaly)</strong></p>
<p>학생 A를 삭제하면?</p>
<p>👉 해당 과목 정보까지 같이 사라짐</p>
<hr>
<p>이러한 문제 발생을 방지하기 위하여 정규화를 사용한다.</p>
<h2 id="정규화의-목적">정규화의 목적</h2>
<p>정규화는 단순히 테이블을 나누는 것이 아니다.</p>
<pre><code>데이터를 일관성 있고 효율적으로 관리하기 위한 구조를 만드는 과정이다.</code></pre><ul>
<li>데이터 중복 제거</li>
<li>이상 현상 방지 (Update / Insert / Delete)</li>
<li>데이터 무결성 유지</li>
<li>유지보수 용이성 향상</li>
</ul>
<hr>
<h1 id="정규화-과정">정규화 과정</h1>
<p>정규화는 단계적으로 진행되며, 일반적으로 <strong>제3정규형(3NF)</strong>까지 적용한다.</p>
<h2 id="1️⃣-제1정규형-1nf">1️⃣ 제1정규형 (1NF)</h2>
<p>👉 모든 컬럼은 원자값(Atomic Value)을 가져야 한다</p>
<hr>
<p><strong>❌ 정규화 전</strong></p>
<pre><code>학생 | 전화번호
------------------------
A    | 010-1111, 010-2222</code></pre><p>👉 하나의 컬럼에 여러 값이 존재</p>
<hr>
<p><strong>✅ 정규화 후</strong></p>
<pre><code>학생 | 전화번호
------------------------
A    | 010-1111
A    | 010-2222</code></pre><p>👉 하나의 컬럼에는 하나의 값만 존재</p>
<hr>
<h2 id="2️⃣-제2정규형-2nf">2️⃣ 제2정규형 (2NF)</h2>
<pre><code>부분 함수 종속 제거</code></pre><p>👉 기본키의 일부에만 의존하는 컬럼 제거</p>
<p><strong>❌ 정규화 전</strong></p>
<pre><code>학생ID | 과목ID | 학생이름
---------------------------
1       | DB     | Kim
1       | OS     | Kim</code></pre><p>👉 학생이름은 학생ID에만 의존</p>
<hr>
<p><strong>✅ 정규화 후</strong></p>
<pre><code>학생 테이블
학생ID | 이름
------------
1      | Kim

수강 테이블
학생ID | 과목ID
-------------
1      | DB
1      | OS</code></pre><p>👉 테이블을 분리하여 중복 제거</p>
<hr>
<h2 id="3️⃣-제3정규형-3nf">3️⃣ 제3정규형 (3NF)</h2>
<pre><code>이행 함수 종속 제거</code></pre><p>👉 기본키가 아닌 컬럼이 다른 컬럼에 의존하는 경우 제거</p>
<p><strong>❌ 정규화 전</strong></p>
<pre><code>학생ID | 학과ID | 학과명
-------------------------
1      | 10     | 컴퓨터공학</code></pre><p>👉 학과명은 학과ID에 의존</p>
<hr>
<p><strong>✅ 정규화 후</strong></p>
<pre><code>학생
학생ID | 학과ID

학과
학과ID | 학과명</code></pre><p>👉 관계를 분리하여 구조 개선</p>
<hr>
<p>하지만 이런 정규화에도 한계는 있다.
너무 많이 나누게 되면</p>
<ul>
<li>JOIN 증가</li>
<li>쿼리 성능 저하</li>
<li>구조 복잡도 증가</li>
</ul>
<p>이런 경우에는 <strong>반정규화(Denormalization)</strong>를 고려하기도 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React - key 설정 이유와 주의점]]></title>
            <link>https://velog.io/@obebe_00/React-key-%EC%84%A4%EC%A0%95-%EC%9D%B4%EC%9C%A0%EC%99%80-%EC%A3%BC%EC%9D%98%EC%A0%90</link>
            <guid>https://velog.io/@obebe_00/React-key-%EC%84%A4%EC%A0%95-%EC%9D%B4%EC%9C%A0%EC%99%80-%EC%A3%BC%EC%9D%98%EC%A0%90</guid>
            <pubDate>Sun, 05 Apr 2026 23:18:26 GMT</pubDate>
            <description><![CDATA[<p>React에서 배열을 랜더링할 때 <code>key</code>를 설정해야하는 이유 그리고 주의점에 대해서 알아보고자 한다.</p>
<p>React에서 배열을 랜더링 하는 코드는 자주 사용된다.</p>
<p>예를 들어 간단한 리스트를 출력한다고 해보자.</p>
<pre><code class="language-jsx">const items = [&quot;Apple&quot;, &quot;Banana&quot;, &quot;Cherry&quot;];

function App() {
  return (
    &lt;ul&gt;
      {items.map((item) =&gt; (
        &lt;li&gt;{item}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}</code></pre>
<p>이 코드는 정상적으로 동작하지만, 콘솔을 보면 이런 경고가 뜬다.</p>
<blockquote>
<p>Each child in a list should have a unique &quot;key&quot; prop.</p>
</blockquote>
<p>왜 이러한 경고가 발생하는 것일까?
React의 랜더링 방식을 다시 살펴보면 알 수 있다.</p>
<p>React는 상태가 변경될 때마다 이전 화면과 새로운 화면을 비교해서 <strong>바뀐 부분만 업데이트</strong>한다.</p>
<p>그런데 key가 없다면 React는 &#39;이 요소가 이전에 있던 그 요소가 맞는지&#39;에 대하여 정확히 판단할 수 없다.</p>
<hr>
<pre><code class="language-jsx">const items = [&quot;A&quot;, &quot;B&quot;, &quot;C&quot;];</code></pre>
<p>이렇게 key가 없을 때는</p>
<pre><code class="language-jsx">{items.map((item) =&gt; (
  &lt;li&gt;{item}&lt;/li&gt;
))}</code></pre>
<p>이 상태에서 <code>&quot;A&quot;</code>가 삭제되면</p>
<pre><code class="language-jsx">[&quot;B&quot;, &quot;C&quot;]</code></pre>
<p>React는 단순히 <strong>순서 기준</strong>으로 비교하기에</p>
<pre><code class="language-md">A → B (변경됨)
B → C (변경됨)
C → 삭제됨</code></pre>
<blockquote>
<p>즉, 모든 요소가 바뀐 것으로 인식</p>
</blockquote>
<p>이것은 단순한 성능 문제에 그치지 않고 실제 UI에 이러한 일이 일어날 경우 예를 들어</p>
<ul>
<li>input에 입력한 값이 다른 아이템으로 이동</li>
<li>체크박스 상태가 꼬임</li>
<li>애니메이션이 이상하게 동작</li>
</ul>
<p>과 같은 일이 일어날 수 있다</p>
<hr>
<p>그래서 key를 설정해주면</p>
<pre><code class="language-jsx">{items.map((item) =&gt; (
  &lt;li key={item}&gt;{item}&lt;/li&gt;
))}</code></pre>
<p>값이 아닌 key를 기준으로 비교하여</p>
<pre><code class="language-md">A → 삭제됨
B → 그대로 유지
C → 그대로 유지</code></pre>
<p>필요한 부분만 업데이트를 진행한다</p>
<hr>
<h1 id="주의점">주의점</h1>
<p>결론부터 말하면 <strong>아무 값이나 넣으면 안된다</strong></p>
<p>key는 단순히 값이 있으면 되는 것이 아닌</p>
<pre><code class="language-md">요소를 안정적으로 식별할 수 있는 값</code></pre>
<p>이어야 한다.</p>
<p><strong>❌ 잘못된 예</strong></p>
<pre><code class="language-jsx">&lt;li key={Math.random()}&gt;{item}&lt;/li&gt;</code></pre>
<ul>
<li>렌더링될 때마다 값이 바뀜</li>
<li>React 입장에서는 매번 새로운 요소
👉 불필요한 재렌더링 발생 (성능 문제)</li>
</ul>
<hr>
<pre><code class="language-jsx">&lt;li key={index}&gt;{item}&lt;/li&gt;</code></pre>
<ul>
<li>배열 순서가 바뀌면 key도 같이 바뀜
👉 다른 요소를 같은 요소로 착각 (버그 발생)</li>
</ul>
<hr>
<p><strong>✅ 올바른 기준</strong></p>
<pre><code class="language-jsx">&lt;li key={item.id}&gt;{item.name}&lt;/li&gt;</code></pre>
<ul>
<li>고유하다 (unique)</li>
<li>변하지 않는다 (stable)
👉 이 두 가지 조건이 가장 중요하다</li>
</ul>
<p>따라서 key는 아무 값이 아닌, 👉 “변하지 않는 고유한 식별자”를 넣어야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React - 기본 폴더 구조 정리]]></title>
            <link>https://velog.io/@obebe_00/React-%EA%B8%B0%EB%B3%B8-%ED%8F%B4%EB%8D%94-%EA%B5%AC%EC%A1%B0-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@obebe_00/React-%EA%B8%B0%EB%B3%B8-%ED%8F%B4%EB%8D%94-%EA%B5%AC%EC%A1%B0-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 01 Apr 2026 13:36:47 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 시작하면서 가장 먼저 고민했던 것 중 하나가 <strong>폴더 구조</strong>였다.</p>
<p>Flutter로 프로젝트를 진행했던 것과는 여러 구조들이 다를 것이라고 생각해서 관련된 내용을 정리하고 시작하고자 한다.</p>
<hr>
<h1 id="📦-기본-폴더-구조">📦 기본 폴더 구조</h1>
<p>React에서 가장 기본적인 폴더 구조를 알아보자.</p>
<pre><code>src/
├── components/     // 재사용 가능한 UI 컴포넌트
├── pages/          // 페이지 단위 컴포넌트
├── styles/         // 전역 스타일 관리
│   ├── colors/
│   │   └── colors.css
│   ├── fonts/
│   │   └── fonts.css
│   └── global/
│       ├── reset.css
│       └── common.css
├── api/            // API 요청 함수
├── hooks/          // custom hook
├── utils/          // 공통 함수
└── assets/         // 이미지, 아이콘
    └── images/</code></pre><h2 id="폴더별-역할-정리">폴더별 역할 정리</h2>
<h3 id="components">components</h3>
<blockquote>
<p>재사용 가능한 UI 조각</p>
</blockquote>
<ul>
<li>Button</li>
<li>Card</li>
<li>Header</li>
<li>ItemCard<pre><code>여러 페이지에서 사용할 수 있도록 만드는 것이 핵심</code></pre></li>
</ul>
<hr>
<h3 id="pages">pages</h3>
<blockquote>
<p>화면 단위</p>
</blockquote>
<ul>
<li>MainPage</li>
<li>Detailpage
등등</li>
</ul>
<pre><code>하나의 화면 = 하나의 페이지</code></pre><hr>
<h3 id="styles">styles</h3>
<blockquote>
<p>전역 스타일 관리</p>
</blockquote>
<ul>
<li>colors.css</li>
<li>fonts.css</li>
<li>reset.css</li>
<li>common.css</li>
</ul>
<pre><code>디자인과 관련된 파일들</code></pre><hr>
<h3 id="api">api</h3>
<blockquote>
<p>서버 통신</p>
</blockquote>
<pre><code>* 예시
export async function getProducts() {
  const res = await fetch(&#39;/products&#39;);
  return res.json();
}</code></pre><pre><code>API 로직을 분류하여 관리하면 컴포넌트가 깔끔해진다</code></pre><hr>
<h3 id="hooks">hooks</h3>
<pre><code class="language-js">function useProducts() {
  // 데이터 fetch 로직
}</code></pre>
<pre><code>로직 재사용을 위한 공간</code></pre><hr>
<h3 id="utils">utils</h3>
<blockquote>
<p>공통 함수</p>
</blockquote>
<hr>
<h3 id="assets">assets</h3>
<blockquote>
<p>이미지, 아이콘</p>
</blockquote>
<hr>
<h3 id="flutter와-react-폴더-구조-차이">Flutter와 React 폴더 구조 차이</h3>
<p>Flutter로 프로젝트를 진행할 때는 MVC, MVVM 같은 <strong>아키텍처 패턴 중심</strong>으로 폴더를 나누는 경우가 많았다.</p>
<p>하지만 React는 조금 다르게 접근한다는 것을 알았다.</p>
<p>⚛️**React는 &#39;역할&#39;보다 &#39;기능(UI)&#39;으로 나눈다</p>
<ul>
<li>이 파일이 어디에 사용되는가</li>
</ul>
<p>컴포넌트가 기본 단위인 React에서는 컴포넌트 하나 자체가 구조 역할을 하기에 이러한 특징이 생긴다.</p>
<p>또한 React는 공식적으로 정해진 폴더 구조가 없다고 한다.</p>
<p>그래서 작은 프로젝트는 단순 구조로 진행되지만, 커질 수록 feature 기반으로 확장되는 경우가 많다고 한다.</p>
<hr>
<p>프로젝트를 시작하기 전에 폴더 구조와 같은 여러 컨벤션들을 미리 정리하고 약속해두고 시작하면서 진행 중에 생기는 크고 작은 이슈들을 미리 방지하기에 유리함을 느꼈다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React - 디자인 시스템 정의 시 .js vs .css]]></title>
            <link>https://velog.io/@obebe_00/React-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%A0%95%EC%9D%98-%EC%8B%9C-.js-vs-.css</link>
            <guid>https://velog.io/@obebe_00/React-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%A0%95%EC%9D%98-%EC%8B%9C-.js-vs-.css</guid>
            <pubDate>Wed, 01 Apr 2026 12:11:06 GMT</pubDate>
            <description><![CDATA[<p>프로젝트 시작에 앞서 폴더 구조와 기본 파일들을 정리하며 디자인 시스템도 미리 정의해두고자 했다.</p>
<p>이 과정에서 디자인 시스템을 정리할 때 <code>.css</code>뿐만 아니라 <code>.js</code>로도 관리하는 방법이 있다는 것을 알게 되어 두 방식의 차이를 정리하고 어떤 상황에서 적합한지 비교해보고자 한다.</p>
<hr>
<p>우선 디자인 시스템이 무엇인지 짚고 넘어가자</p>
<h1 id="디자인-시스템이란">디자인 시스템이란</h1>
<blockquote>
<p>프로젝트 전반에서 일관성을 유지하기 위해 미리 정의해두는 값과 규칙들의 집합</p>
</blockquote>
<p>즉,</p>
<ul>
<li>색상</li>
<li>폰트</li>
<li>여백</li>
<li>컴포넌트 스타일
이러한 요소들을 매번 새로 정의하는 것이 아닌, <strong>한 곳에 모아두고 재사용</strong>하는 것을 말한다.</li>
</ul>
<hr>
<h1 id="css로-디자인-시스템-관리하기">.css로 디자인 시스템 관리하기</h1>
<p>가장 일반적인 방법으로 <strong>CSS 변수</strong>를 사용해서 관리하는 것이다.</p>
<pre><code class="language-css">:root {
  --color-primary: #99C08E;
  --color-gray-01: #f5f5f5;
  --font-size-base: 16px;
}</code></pre>
<p>사용 시</p>
<pre><code class="language-css">.button {
  background-color: var(--color-primary);
  font-size: var(--font-size-base);
}</code></pre>
<h4 id="특징">특징</h4>
<ul>
<li>스타일을 CSS 레벨에서 정의</li>
<li>전역적으로 사용 가능</li>
<li>브라우저가 직접 처리</li>
</ul>
<p><strong>장점</strong></p>
<ul>
<li>가장 표준적인 방식</li>
<li>스타일과 자연스럽게 연결됨</li>
<li>재사용성이 높음</li>
<li>협업에 유리 (디자이너와 공유하기 쉬움)</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>동적인 처리에 한계가 있음</li>
<li>조건에 따라 값을 바꾸기 어려움</li>
</ul>
<hr>
<h1 id="js로-디자인-시스템-관리하기">.js로 디자인 시스템 관리하기</h1>
<p>디자인 값을 JavaScript 객체로 관리하는 방법도 있다.</p>
<pre><code class="language-js">export const colors = {
  primary: &quot;#99C08E&quot;,
  gray01: &quot;#f5f5f5&quot;,
};

export const fontSize = {
  base: &quot;16px&quot;,
};</code></pre>
<p>사용 시</p>
<pre><code class="language-js">&lt;div style={{ color: colors.primary, fontSize: fontSize.base }} /&gt;</code></pre>
<h4 id="특징-1">특징</h4>
<ul>
<li>디자인 값을 데이터처럼 관리</li>
<li>필요한 곳에서 import 해서 사용</li>
</ul>
<p><strong>장점</strong></p>
<ul>
<li>조건 처리 가능 (다크모드 등)</li>
<li>JS 로직과 쉽게 결합 가능</li>
<li>자동완성, 타입 지원 가능</li>
<li><em>단점*</em></li>
<li>인라인 스타일이 많아질 수 있음</li>
<li>CSS와 분리되어 어색할 수 있음</li>
<li>스타일 재사용성이 떨어질 수 있음</li>
</ul>
<hr>
<h1 id="📃정리">📃정리</h1>
<table>
<thead>
<tr>
<th>기준</th>
<th><code>.css</code></th>
<th><code>.js</code></th>
</tr>
</thead>
<tbody><tr>
<td>역할</td>
<td>스타일 정의</td>
<td>값(데이터) 관리</td>
</tr>
<tr>
<td>사용 방식</td>
<td>class / var</td>
<td>import</td>
</tr>
<tr>
<td>동적 처리</td>
<td>제한적</td>
<td>자유로움</td>
</tr>
<tr>
<td>협업</td>
<td>유리</td>
<td>상대적으로 제한</td>
</tr>
</tbody></table>
<hr>
<h2 id="언제-무엇을-사용해야-할까">언제 무엇을 사용해야 할까</h2>
<h4 id="css가-적합한-경우">.css가 적합한 경우</h4>
<ul>
<li>색상, 폰트, spacing 같은 기본 디자인 토큰</li>
<li>공통 스타일 (버튼, 레이아웃 등)</li>
<li>프로젝트 전체에서 사용하는 값</li>
</ul>
<blockquote>
<p>👉 정적인 디자인 정의</p>
</blockquote>
<h4 id="js가-적합한-경우">.js가 적합한 경우</h4>
<ul>
<li>다크모드 같은 조건에 따라 변하는 값</li>
<li>계산이 필요한 스타일</li>
<li>컴포넌트 내부에서만 사용하는 값</li>
</ul>
<blockquote>
<p>👉 동적인 로직이 필요한 경우</p>
</blockquote>
<hr>
<p>정적인 <strong>디자인</strong> 처리는 <code>.css</code>, 동적인 처리는 <code>.js</code>가 어울린다고 정리할 수 있다.
상황에 따라 다르지만 <code>.js</code>를 컴포넌트처럼 import해서 사용하는 점에 있어 더 쉽고 다양하게 사용할 줄 알았는데 디자인 시스템을 구성하는 관점에서 <code>.css</code>가 프로젝트에 용이할 것 같다는 점이 의외였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React- useMemo, useCallback]]></title>
            <link>https://velog.io/@obebe_00/React-useMemo-useCallback</link>
            <guid>https://velog.io/@obebe_00/React-useMemo-useCallback</guid>
            <pubDate>Sun, 29 Mar 2026 09:45:47 GMT</pubDate>
            <description><![CDATA[<h1 id="usememo--usecallback">useMemo / useCallback</h1>
<p><code>useState</code>, <code>useEffect</code> 등 <code>use-</code>로 시작하는 함수들을 자주 보게 된다.
이들 모두 React에서 제공하는 <strong>Hook</strong>이다.</p>
<p>이러한 <strong>Hook</strong> 중 <code>useMemo</code>와 <code>useCallback</code>에 대해서 알아보고자 한다.</p>
<h2 id="usememo">useMemo</h2>
<p><code>useMemo</code>는 <strong>값을 기억하는 Hook</strong>이다.</p>
<pre><code class="language-jsx">const memoizedValue = useMemo(() =&gt; {
  return 계산값;
}, [의존성]);</code></pre>
<p>특정 값의 계산 결과를 저장해두고 <strong>의존성(dependency)</strong>이 변경될 때만 다시 계산한다.</p>
<blockquote>
<p>불필요한 반복 계산을 막기 위한 Hook</p>
</blockquote>
<pre><code class="language-jsx">const sortedList = useMemo(() =&gt; {
  return [...items].sort((a, b) =&gt; a - b);
}, [items]);</code></pre>
<p>위 코드를 보면 <code>items</code>가 바뀔 때만 정렬을 다시 수행한다. 그렇지 않으면 이전에 계산된 결과(sortedList)를 그대로 재사용한다.</p>
<p>여기서 헷갈리기 쉬운 부분이 있다.
의존성 배열<code>[items]</code>는 값을 저장하는 것이 아닌, 다시 계산할지 판단하는 기준이다.</p>
<p>정리하자면</p>
<ul>
<li><code>useMemo</code>는 계산 결과를 기억하고, 의존성이 변경될 때만 다시 실행된다.</li>
<li>불필요한 계산을 줄이기 위해 사용한다. </li>
</ul>
<hr>
<h3 id="남용하면-생기는-문제">남용하면 생기는 문제</h3>
<pre><code class="language-js">const sum = useMemo(() =&gt; a + b, [a, b]);</code></pre>
<p>이건 그냥</p>
<pre><code class="language-js">const sum = a + b;</code></pre>
<p>이렇게 가벼운 계산에는 그냥 쓰는 것이 좋다 -&gt; 코드의 복잡성만 향상</p>
<ul>
<li><p><strong>성능 저하</strong>
<code>useMemo</code>도 비용이 있다.
의존성 비교 비용, 메모리 사용, Hook 실행 비용이 있기에 무조건 빠르게만 해주는 도구가 아니라는 점을 인지해야한다.</p>
</li>
<li><p><strong>의존성 관리 실수</strong></p>
<pre><code class="language-jsx">const value = useMemo(() =&gt; {
return count * 2;
}, []);</code></pre>
<p><code>count</code>가 바뀌어도 값이 안바뀐다.</p>
</li>
</ul>
<p>정리하자면 <code>useMemo</code>는 </p>
<blockquote>
<p>비싼 계산을 줄이기 위한 선택적 최적화</p>
</blockquote>
<p>이기에</p>
<pre><code>- 계산 비용이 크거나
- 렌더링마다 실행이 부담되거나
- 실제 성능 문제가 발생했을 때</code></pre><p>사용하는 것이 좋다.</p>
<h2 id="usecallback">useCallback</h2>
<p><code>useCallback</code>은 <strong>함수를 기억하는 Hook</strong>이다.</p>
<pre><code class="language-jsx">const memoizedFn = useCallback(() =&gt; {
  실행할 함수
}, [의존성]);</code></pre>
<p>특정 함수를 저장해두고, 의존성(dependency)이 변경될 때만 새로운 함수를 생성한다</p>
<blockquote>
<p>불필요한 함수 재생성을 막기 위한 Hook</p>
</blockquote>
<p>천천히 알아보자.</p>
<p>❌ useCallback 없이</p>
<pre><code class="language-jsx">function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = () =&gt; {
    console.log(&quot;클릭&quot;);
  };

  return (
    &lt;div&gt;
      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;+&lt;/button&gt;
      &lt;Child onClick={handleClick} /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p>여기서 <code>handleClick</code> 함수는 <strong>랜더링이 될 때마다 새로 생성</strong>된다</p>
<pre><code class="language-md">버튼을 눌러 `count`증가 -&gt; Parent 다시 렌더링 -&gt; `handleClick` 새로 생성</code></pre>
<p>이렇게 되면 React 기본 구조상 부모 컴포넌트가 렌더링되면 자식 컴포넌트도 함께 렌더링된다.</p>
<p>사실 <code>handleClick</code>이 새로 생성되는 것이 직접적인 문제가 되는 것은 아니다.
하지만 <strong>최적화를 했을 때</strong>는 문제가 발생한다.</p>
<p>예를 들어, 자식 컴포넌트를 <code>React.memo</code>로 감싸 불필요한 렌더링을 막으려고 한다고 가정해보자.</p>
<pre><code class="language-jsx">const Child = React.memo(({ onClick }) =&gt; {
  console.log(&quot;Child 렌더링&quot;);
  return &lt;button onClick={onClick}&gt;Click&lt;/button&gt;;
});</code></pre>
<p>이 경우 props인 onClick이 변경되지 않으면 Child는 다시 렌더링되지 않아야 한다.
하지만</p>
<pre><code class="language-jsx">const handleClick = () =&gt; {
  console.log(&quot;클릭&quot;);
};</code></pre>
<p>이 함수는 렌더링이 될 때마다 새로 생성되기 때문에 props가 변경된 것으로 인식된다
결과적으로 Child 컴포넌트의 리렌더링을 막을 수 없는 것이다.
이 문제를 해결하기 위해 <code>useCallback</code>를 사용한다.</p>
<pre><code class="language-jsx">function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() =&gt; {
    console.log(&quot;클릭&quot;);
  }, []);

  return (
    &lt;div&gt;
      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;+&lt;/button&gt;
      &lt;Child onClick={handleClick} /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p>이렇게 하면 렌더링이 다시 발생해도 <code>handleClick</code>함수는 새로 생성되지 않고 유지된다.
Child 컴포넌트의 불필요한 리렌더링을 방지할 수 있는 것이다.</p>
<hr>
<h2 id="남용하면-생기는-문제-1">남용하면 생기는 문제</h2>
<blockquote>
<p><code>useCallback</code>은 기본이 아닌 최적화 도구</p>
</blockquote>
<p>▪️ <strong>코드가 오히려 복잡해진다</strong></p>
<pre><code class="language-jsx">const handleClick = useCallback(() =&gt; {
  console.log(&quot;클릭&quot;);
}, []);</code></pre>
<p>단순한 함수라면 굳이 필요가 없다</p>
<p>▪️ <strong>성능이 더 나빠질 수도 있다</strong></p>
<ul>
<li>의존성 배열을 비교하는 비용 발생</li>
<li>메모이제이션 자체도 비용이 있음
&rarr; 작은 코드에서는 오히려 손해</li>
</ul>
<p>▪️ <strong>의존성 관리 실수</strong></p>
<pre><code class="language-jsx">const handleClick = useCallback(() =&gt; {
  console.log(count);
}, []);</code></pre>
<p><code>count</code>가 바뀌어도 반영되지 않음 (버그)</p>
<hr>
<p><code>useMemo</code>와 <code>useCallback</code>에 대해 정리해보았다. React의 여러 Hook을 알게 될 때마다 어떻게 사용해야 더 좋은 코드가 나오는지 생각해보게 되는 것 같다.</p>
<p>익숙해질 수 있도록 여러 응용을 다뤄보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React - 컴포넌트, 함수형 vs 클래스 컴포넌트]]></title>
            <link>https://velog.io/@obebe_00/React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%ED%95%A8%EC%88%98%ED%98%95-vs-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</link>
            <guid>https://velog.io/@obebe_00/React-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%ED%95%A8%EC%88%98%ED%98%95-vs-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8</guid>
            <pubDate>Sun, 29 Mar 2026 08:50:09 GMT</pubDate>
            <description><![CDATA[<p>앞서 다뤘던 주제들을 다시 읽어보다 &#39;컴포넌트&#39;에 대해 짚고 넘어가는 것이 좋을 것 같아 내용을 정리하는 와중에 위클리 페이퍼 토픽으로 &#39;컴포넌트와 함수형 컴포넌트, 클래스 컴포넌트의 차이점에 대해 설명&#39;하는 것이 나와서 이 주제를 다뤄보려고 한다.</p>
<hr>
<h1 id="컴포넌트란">컴포넌트란?</h1>
<blockquote>
<p>UI를 구성하는 가장 독립적인 단위</p>
</blockquote>
<p>React에서 화면은 하나의 덩어리가 아니라,
여러 개의 작은 조각들로 나뉘어 만들어진다.</p>
<p>이 작은 조각 하나하나를 <strong>컴포넌트(Component)</strong>라고 한다.</p>
<p>Flutter에서 <code>Widget</code>과 비슷한 역할이라고 볼 수 있다.</p>
<p>다만 React에서 컴포넌트는 단순한 UI 요소뿐만 아니라
상태(state), 이벤트, 로직까지 함께 관리한다는 점에서 더 넓은 개념이다.</p>
<hr>
<h2 id="컴포넌트-구성">컴포넌트 구성</h2>
<p>앞에서 컴포넌트는 단순한 UI 조각이 아니라 <code>상태(state)</code>, <code>event</code>, <code>logic</code>을 함께 관리하는 단위라고 했다.</p>
<p>그렇다면 남은 구성요소는 무엇일까?</p>
<hr>
<h3 id="-props-데이터-전달">• Props (데이터 전달)</h3>
<blockquote>
<p>부모 컴포넌트에서 자식 컴포넌트로 전달되는 값</p>
</blockquote>
<p>앞서 <a href="https://velog.io/@obebe_00/React%EB%A5%BC-%EC%95%8C%EC%95%84%EA%B0%80%EB%B3%B4%EC%9E%90-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%B6%84%EB%A6%AC">컴포넌트 분리</a>를 다루면서 알아보았던 <code>props</code>다.</p>
<h3 id="-state-상태-관리">• State (상태 관리)</h3>
<blockquote>
<p>컴포넌트 내부에서만 관리되는 값</p>
</blockquote>
<p>이것도 <a href="https://velog.io/@obebe_00/React%EB%A5%BC-%EC%9D%B4%ED%95%B4%ED%95%B4%EB%B3%B4%EC%9E%90">State를 알아보자</a>에서 다루어보았다.</p>
<h3 id="-event-이벤트-처리">• Event (이벤트 처리)</h3>
<p>간단하게 말하자면 사용자의 행동(클릭, 입력)등을 처리하는 부분이다.</p>
<h3 id="-logic-비즈니스-로직">• Logic (비즈니스 로직)</h3>
<p>조건문, 반복문, 데이터 처리 등 실제 동작을 담당하는 부분이다.</p>
<h3 id="-ui-렌더링">• UI (렌더링)</h3>
<p>최종적으로 화면에 보여지는 부분이다.</p>
<p>컴포넌트는 이렇게 구성되어있는 <code>하나의 작은 프로그램 단위</code> 라고 볼 수 있다.</p>
<hr>
<h2 id="컴포넌트-작성">컴포넌트 작성</h2>
<p>이제 컴포넌트를 어떻게 만드는지 알아보자</p>
<p>두 가지의 방식이 존재한다</p>
<pre><code class="language-md">- 함수형 컴포넌트 (Functional Component)
- 클래스 컴포넌트 (Class Component)</code></pre>
<h3 id="-함수형-컴포넌트-functional-component">• 함수형 컴포넌트 (Functional Component)</h3>
<pre><code class="language-jsx">function Hello() {
  return &lt;h1&gt;Hello World&lt;/h1&gt;;
}</code></pre>
<p>또는</p>
<pre><code class="language-jsx">const Hello = () =&gt; {
  return &lt;h1&gt;Hello World&lt;/h1&gt;;
};</code></pre>
<p>이런 식으로 함수 형태로 작성되는 컴포넌트이다.
이러한 함수형 컴포넌트 특징으로는</p>
<ul>
<li>코드가 간결하고 직관적</li>
<li><code>useState</code>,<code>useEffect</code>와 같은 Hook을 사용할 수 있다</li>
</ul>
<hr>
<h3 id="-클래스-컴포넌트-class-component">• 클래스 컴포넌트 (Class Component)</h3>
<pre><code class="language-jsx">import React, { Component } from &quot;react&quot;;

class Hello extends Component {
  render() {
    return &lt;h1&gt;Hello World&lt;/h1&gt;;
  }
}</code></pre>
<p>클래스 문법을 사용해 만드는 컴포넌트로
-<code>this</code>를 사용해야하며</p>
<ul>
<li>lifecycle 매서드를 사용하고</li>
<li>상대적으로 코드가 복잡하다.</li>
</ul>
<hr>
<h2 id="함수형-컴포넌트-vs-클래스-컴포넌트">함수형 컴포넌트 vs 클래스 컴포넌트</h2>
<p>작성 방식을 간단하게 알아보았고, 차이점을 정리해보자</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>함수형 컴포넌트</th>
<th>클래스 컴포넌트</th>
</tr>
</thead>
<tbody><tr>
<td>문법</td>
<td>함수</td>
<td>class</td>
</tr>
<tr>
<td>상태 관리</td>
<td>useState</td>
<td>this.state</td>
</tr>
<tr>
<td>사이드 이펙트</td>
<td>useEffect</td>
<td>lifecycle 메서드</td>
</tr>
<tr>
<td>코드 길이</td>
<td>짧고 간결</td>
<td>상대적으로 김</td>
</tr>
<tr>
<td>사용 추세</td>
<td>✅ 현재 표준</td>
<td>❌ 레거시</td>
</tr>
</tbody></table>
<p>클래스 컴포넌트는 아직 친숙하지 않은 느낌이라 이해가 쉽지는 않다.
어떤 때에 어떤 것을 사용하는 것이 좋은지도 알아보자</p>
<hr>
<h2 id="어떤-것을-사용해야-할까">어떤 것을 사용해야 할까?</h2>
<p>쉽게 결론이 나왔다.</p>
<p>과거에는 <code>state</code>와 <code>lifecycle</code>을 사용하기 위해 클래스 컴포넌트를 사용해야 했지만,
<code>Hook</code>이 등장하면서 함수형 컴포넌트에도 동일한 기능을 모두 구현할 수 있게 되었다.</p>
<blockquote>
<p>코드도 더 짧고, 가독성도 좋아졌다.</p>
</blockquote>
<hr>
<p>마지막으로 내용을 정리해보자면,</p>
<pre><code class="language-md">- 컴포넌트는 UI를 구성하는 독립적인 단위이면서 props, state, event, logic, UI로 구성된다
- 함수형 / 클래스 두 가지의 방식이 존재하며 현재는 함수형 컴포넌트 방식이 많이 사용된다</code></pre>
<p>컴포넌트 분리도 공부해보며 React를 다루면서 컴포넌트라는 단어는 많이 들었지만 제대로 이해하고나니 다른 개념들도 확실하게 잡히는 것 같아 좋았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React를 알아가보자 - 컴포넌트 분리]]></title>
            <link>https://velog.io/@obebe_00/React%EB%A5%BC-%EC%95%8C%EC%95%84%EA%B0%80%EB%B3%B4%EC%9E%90-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%B6%84%EB%A6%AC</link>
            <guid>https://velog.io/@obebe_00/React%EB%A5%BC-%EC%95%8C%EC%95%84%EA%B0%80%EB%B3%B4%EC%9E%90-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%B6%84%EB%A6%AC</guid>
            <pubDate>Tue, 24 Mar 2026 12:34:16 GMT</pubDate>
            <description><![CDATA[<h1 id="react에서-컴포넌트-분리">React에서 컴포넌트 분리</h1>
<p>컴포넌트 분리, 이전에 <code>Flutter</code>에서 Widget을 따로 분리해서 사용했던 것처럼 쉬울 줄 알았다.
오만했다고 볼 수 있다. React에서 해보니 영- 감을 못잡았기 때문이다.</p>
<hr>
<h2 id="기준">기준</h2>
<p>그냥 Widget을 똑 떼어내어 사용할 곳에서 import 해오는 방식과는 무엇인가 달랐다. 그 이질감의 원인을 알아내고자 컴포넌트 분리의 기준을 찾아보았다.</p>
<blockquote>
<p>이 UI가 독립적으로 재사용 가능한가?</p>
</blockquote>
<p>이렇게 생각해도 확 와닿지 않았기에 코드로 살펴보겠다.</p>
<pre><code class="language-jsx">&lt;div&gt;
  &lt;h2&gt;할 일 목록&lt;/h2&gt;
  &lt;input /&gt;
  &lt;button&gt;추가&lt;/button&gt;
&lt;/div&gt;</code></pre>
<p>이 코드를</p>
<pre><code class="language-js">function TodoInput() {
  return (
    &lt;div&gt;
      &lt;input /&gt;
      &lt;button&gt;추가&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p>이렇게 하면 <code>TodoInput</code>을 불러와 다른 곳에서도 사용할 수 있다.
처음에 느꼈던 것과 비슷해서 반복인 느낌이 들었기에 더 찾아보니 나는 작성법에서 어려움을 느꼈던 것이었다.</p>
<p>고로 <code>분리해서 필요한 곳에서 재사용한다</code>는 것은 맞는 흐름이었고, 분리 과정에서 데이터 전달 방법과 같은 작성이 어려웠던 것이기에 이걸 다시 알아보고자 한다.</p>
<hr>
<h2 id="props">props</h2>
<blockquote>
<p>컴포넌트를 나누는 순간, 데이터의 흐름도 같이 설계해야 한다</p>
</blockquote>
<p>먼저 분리하기 전 코드를 살펴보자.</p>
<pre><code class="language-jsx">function App() {
  const [title, setTitle] = useState(&quot;&quot;);

  const handleSubmit = (e) =&gt; {
    e.preventDefault();
    if (title.trim() === &quot;&quot;) return;

    console.log(title);
    setTitle(&quot;&quot;);
  };

  return (
    &lt;form onSubmit={handleSubmit}&gt;
      &lt;input
        value={title}
        onChange={(e) =&gt; setTitle(e.target.value)}
      /&gt;
      &lt;button type=&quot;submit&quot;&gt;추가&lt;/button&gt;
    &lt;/form&gt;
  );
}</code></pre>
<p>여기서 <code>TodoForm</code>으로 입력 부분을 분리해보겠다.</p>
<pre><code class="language-jsx">function TodoForm() {
  const [title, setTitle] = useState(&quot;&quot;);

  const handleSubmit = (e) =&gt; {
    e.preventDefault();
    if (title.trim() === &quot;&quot;) return;

    console.log(title);
    setTitle(&quot;&quot;);
  };

  return (
    &lt;form onSubmit={handleSubmit}&gt;
      &lt;input
        value={title}
        onChange={(e) =&gt; setTitle(e.target.value)}
      /&gt;
      &lt;button type=&quot;submit&quot;&gt;추가&lt;/button&gt;
    &lt;/form&gt;
  );
}</code></pre>
<p>이렇게 뚝 떼어내어서 가져오면 문제가 발생한다
할 일을 추가하는 로직은 부모인 <code>App</code>에 있어야하는데 자식인 <code>TodoForm</code>으로 들어가있다.</p>
<p>이렇게되면 상태 관리와 UI 역할이 섞이게 되어 문제가 된다</p>
<p>📌<strong>여러 컴포넌트에서 사용해야 하는 state는 부모에서 관리하는 것이 더 적절하기 때문이다</strong></p>
<hr>
<p>이걸 해결하기 위해 부모에 함수를 만들고</p>
<pre><code class="language-jsx">const handleAdd = (title) =&gt; {
  ...
};</code></pre>
<p>이걸 props안 <code>onAdd</code>로 전달한다.</p>
<pre><code class="language-js">&lt;TodoForm onAdd={handleAdd} /&gt;</code></pre>
<p>그리고 자식에서 이 함수를 받아서 실행한다</p>
<pre><code class="language-js">function TodoForm({ onAdd }) {
  ...
  onAdd(title);
}</code></pre>
<p>이렇게 해서 TodoForm의 코드는</p>
<pre><code class="language-jsx">import { useState } from &#39;react&#39;;

function TodoForm({ onAdd }) {
  const [title, setTitle] = useState(&#39;&#39;);

  const handleSubmit = (e) =&gt; {
    e.preventDefault();
    if (title.trim() === &#39;&#39;) return;
    onAdd(title.trim());
    setTitle(&#39;&#39;);
  };

  return (
    &lt;form onSubmit={handleSubmit}&gt;
      &lt;input
        type=&quot;text&quot;
        value={title}
        onChange={(e) =&gt; setTitle(e.target.value)}
        placeholder=&quot;할일 입력...&quot;
      /&gt;
      &lt;button type=&quot;submit&quot;&gt;
        추가
      &lt;/button&gt;
    &lt;/form&gt;
  );
}
</code></pre>
<p>이렇게 된다.</p>
<hr>
<p>이제 추출을 해서 사용해보자</p>
<h2 id="export--import">export / import</h2>
<p>만든 <code>TodoForm</code> 마지막에</p>
<pre><code class="language-jsx">export default TodoForm;</code></pre>
<p><code>export default</code>를 넣어 외부에서 사용할 수 있게 만들고</p>
<p><code>App</code>으로 돌아와서 </p>
<pre><code class="language-jsx">import TodoForm from &#39;./components/TodoForm&#39;;</code></pre>
<p>를 통해 <code>import</code> 해준다.</p>
<p>그리고 </p>
<pre><code class="language-jsx">    &lt;form onSubmit={handleSubmit}&gt;
      &lt;input
        value={title}
        onChange={(e) =&gt; setTitle(e.target.value)}
      /&gt;
      &lt;button type=&quot;submit&quot;&gt;추가&lt;/button&gt;
    &lt;/form&gt;</code></pre>
<p>이 부분을</p>
<pre><code class="language-jsx">      {/* props로 함수 전달 */}
      &lt;TodoForm onAdd={handleAdd} /&gt;</code></pre>
<p>이렇게 변경해주면 깔끔하게 사용할 수 있다.</p>
<hr>
<h2 id="props-vs-state">props vs state</h2>
<p>마지막으로 둘의 차이를 비교해보자</p>
<h3 id="📦-props">📦 props</h3>
<blockquote>
<p>외부(부모)에서 전달받는 데이터</p>
</blockquote>
<pre><code class="language-jsx">&lt;TodoForm onAdd={handleAdd} /&gt;
function TodoForm({ onAdd }) {
  return &lt;button onClick={onAdd}&gt;추가&lt;/button&gt;;
}</code></pre>
<pre><code class="language-md">부모 → 자식으로 전달
읽기 전용 (수정 불가)
컴포넌트 입장에서는 “받아서 쓰는 값”</code></pre>
<hr>
<h3 id="⚙️-state">⚙️ state</h3>
<blockquote>
<p>컴포넌트 내부에서 관리하는 데이터</p>
</blockquote>
<pre><code class="language-jsx">const [title, setTitle] = useState(&quot;&quot;);</code></pre>
<pre><code class="language-md">컴포넌트 내부에서 생성
setState로 변경 가능
값이 바뀌면 UI가 다시 렌더링됨</code></pre>
<h3 id="🔥-가장-중요한-차이">🔥 가장 중요한 차이</h3>
<p>👉 누가 데이터를 바꾸는가?</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>props</th>
<th>state</th>
</tr>
</thead>
<tbody><tr>
<td>소유자</td>
<td>부모</td>
<td>자기 자신</td>
</tr>
<tr>
<td>수정 가능</td>
<td>❌ 불가능</td>
<td>✅ 가능</td>
</tr>
<tr>
<td>역할</td>
<td>전달</td>
<td>관리</td>
</tr>
</tbody></table>
<pre><code class="language-jsx">function App() {
  const [todos, setTodos] = useState([]);

  return &lt;TodoForm onAdd={setTodos} /&gt;;
}</code></pre>
<ul>
<li><p>todos → state (App이 관리)</p>
</li>
<li><p>onAdd → props (TodoForm에게 전달)</p>
</li>
<li><p>데이터는 <code>App</code>이 가지고 | 행동은 <code>TodoForm</code>이 실행</p>
</li>
</ul>
<hr>
<p>컴포넌트 분리에서 막혔던 작성법을 다시 공부해보았다. 컴포넌트 분리, 그리고 <code>props</code>가 조금은 이해되었다. 다음 응용에서는 잘해보자!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React를 이해해보자 - state]]></title>
            <link>https://velog.io/@obebe_00/React%EB%A5%BC-%EC%9D%B4%ED%95%B4%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@obebe_00/React%EB%A5%BC-%EC%9D%B4%ED%95%B4%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 23 Mar 2026 06:44:12 GMT</pubDate>
            <description><![CDATA[<h1 id="react에서-state는-언제-필요할까">React에서 state는 언제 필요할까?</h1>
<p>React를 처음 배우면서 가장 많이 사용하게 되는 것이 바로 <code>state</code>다.</p>
<p>그런데 사용하다보니 한 가지 의문이 들었다.</p>
<blockquote>
<p>모든 값을 state로 관리해야 할까?</p>
</blockquote>
<hr>
<h2 id="처음에-했던-생각">처음에 했던 생각</h2>
<p>처음에는 이렇게 생각했다.</p>
<ul>
<li>값이 있으면 일단 state로 만들자</li>
<li>바뀔 가능성이 있으면 state
그러다 보니, 그냥 다 state 쓰면 되는 거 아닌가?</li>
</ul>
<p>그래서 이런 코드가 자연스럽게 나왔다.</p>
<pre><code class="language-jsx">const [name, setName] = useState(&quot;Kim&quot;);
const [age, setAge] = useState(20);
const [title, setTitle] = useState(&quot;Hello&quot;);</code></pre>
<p>이해보다는 받아들여서 쓰기 시작하다보니</p>
<blockquote>
<p>“모든 값을 state로 관리해야 하는 것인가?”</p>
</blockquote>
<p>하는 궁금증이 생겼다.</p>
<hr>
<h2 id="💡-state와-일반-변수의-차이">💡 state와 일반 변수의 차이</h2>
<p>찾아보다보니 기준을 하나 잡을 수 있었다.</p>
<p>👉 <strong>“이 값이 바뀔 때 화면(UI)이 다시 그려져야 하는가?”</strong></p>
<hr>
<h2 id="react의-렌더링이란">React의 렌더링이란?</h2>
<p>이것에 대해 이해하기 위해 렌더링을 짚어보고 넘어가겠다.</p>
<p>React는 상태(state)가 변경되면 <strong>컴포넌트를 다시 실행하고 UI를 다시 그린다</strong></p>
<pre><code>state 변경 → 컴포넌트 함수 재실행 → UI 업데이트</code></pre><p>즉, state는 단순한 값이 아니라 <strong>렌더링을 발생시키는 기준이다</strong></p>
<hr>
<h3 id="✅-그냥-변수로-충분한-경우">✅ 그냥 변수로 충분한 경우</h3>
<pre><code class="language-jsx">const title = &quot;Hello&quot;;</code></pre>
<p>👉 특징:</p>
<ul>
<li>값이 바뀌지 않음</li>
<li>UI에 영향 없음</li>
</ul>
<hr>
<h3 id="✅-state가-필요한-경우">✅ state가 필요한 경우</h3>
<pre><code class="language-jsx">const [count, setCount] = useState(0);</code></pre>
<p>👉 특징:</p>
<ul>
<li>값이 변경됨</li>
<li>변경되면 UI가 다시 렌더링되어야 함</li>
</ul>
<hr>
<h2 id="📌-핵심-기준">📌 핵심 기준</h2>
<pre><code class="language-md">👉 렌더링을 발생시키지 않는 값은 state가 아니다</code></pre>
<hr>
<h2 id="⚠️-state를-남용하면-생기는-문제">⚠️ state를 남용하면 생기는 문제</h2>
<h3 id="1️⃣-불필요한-리렌더링">1️⃣ 불필요한 리렌더링</h3>
<p>state가 바뀌면 컴포넌트가 다시 렌더링된다.
👉 필요 없는 값까지 state로 만들면 성능에 영향을 줄 수 있다.</p>
<hr>
<h3 id="2️⃣-코드-복잡도-증가">2️⃣ 코드 복잡도 증가</h3>
<ul>
<li>state 많아짐</li>
<li>관리 어려움</li>
<li>읽기 힘든 코드</li>
</ul>
<hr>
<h3 id="3️⃣-의도-파악이-어려워짐">3️⃣ 의도 파악이 어려워짐</h3>
<p>👉 어떤 값이 중요한 상태인지 헷갈림</p>
<hr>
<h2 id="🔥-다시-정리해보면">🔥 다시 정리해보면</h2>
<p>처음에는 이렇게 생각했다.</p>
<blockquote>
<p>“state는 그냥 값 저장하는 도구다”</p>
</blockquote>
<p>하지만 지금은 이렇게 이해하고 있다.</p>
<pre><code class="language-md">👉 state는 “UI를 바꾸기 위한 상태”다</code></pre>
<hr>
<h2 id="🧠-정리">🧠 정리</h2>
<p>React는 단순히 값을 저장하는 방식이 아니라</p>
<p>👉 <strong>“상태 변화에 따라 UI를 다시 그리는 구조”</strong>였다</p>
<p>그래서 중요한 건</p>
<ul>
<li>값을 저장하는 것보다</li>
<li>어떤 값을 state로 관리할지 판단하는 것</li>
</ul>
<p>이었다.</p>
<pre><code class="language-md">모든 값이 state일 필요는 없다  
렌더링이 필요한 값만 state로 관리하자</code></pre>
<hr>
<h2 id="실습-적용">실습 적용</h2>
<pre><code class="language-md">이전에 TodoList를 구현해보면서 state가 언제 필요한지 더 명확하게 이해할 수 있었다.</code></pre>
<ul>
<li><strong>할 일 추가</strong><pre><code class="language-jsx">const [todos, setTodos] = useState([]);
</code></pre>
</li>
</ul>
<p>function handleAddTodo(title) {
  const newTodo = {
    id: Date.now(),
    title,
    completed: false,
  };</p>
<p>  setTodos([...todos, newTodo]);
}</p>
<pre><code></code></pre><p>할 일을 추가하면 todos 상태가 변경되고 UI도 함께 업데이트된다.
이 경우 state가 반드시 필요하다</p>
<pre><code>* **완료 처리**
```jsx
function handleToggleTodo(id) {
  const updated = todos.map((todo) =&gt;
    todo.id === id
      ? { ...todo, completed: !todo.completed }
      : todo
  );

  setTodos(updated);
}</code></pre><pre><code>completed 값이 바뀌면서 UI에서 할일/완료 목록이 이동한다.
state 변화 → UI 변화가 연결되는 구조였다

👉 결국 중요한 것은 state를 사용하는 것이 아니라 언제 사용해야 하는지를 아는 것이었다</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[React TodoList - Lv.1 (기초)]]></title>
            <link>https://velog.io/@obebe_00/React-TodoList-Lv.1-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@obebe_00/React-TodoList-Lv.1-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Thu, 19 Mar 2026 00:18:16 GMT</pubDate>
            <description><![CDATA[<p>React 과정에 들어가면서 todolist 만들기 실습이 있었다.
lv.1 ~ lv.3까지 있었는데 우선 lv.1을 정리해보고자 한다.</p>
<h1 id="핵심-개념-정리">핵심 개념 정리</h1>
<p><strong>🔹 useState</strong></p>
<p>React에서 데이터를 관리하기 위해 사용한다.</p>
<pre><code>const [title, setTitle] = useState(&quot;&quot;);
const [todos, setTodos] = useState([]);</code></pre><ul>
<li>title → input 값 저장</li>
<li>todos → 할 일 목록 저장</li>
</ul>
<hr>
<p><strong>🔹 React의 데이터 흐름</strong></p>
<p>TodoList를 만들면서 가장 중요했던 개념은 다음 흐름이었다.</p>
<pre><code>입력 → state 변경 → 화면 자동 업데이트</code></pre><p>React는 <strong>state</strong>가 바뀌면 UI가 자동으로 다시 그려진다.</p>
<hr>
<h1 id="todo-데이터-구조">Todo 데이터 구조</h1>
<p>각 todo는 다음과 같은 형태로 구성했다.</p>
<pre><code>{
  id: Date.now(),
  title: title,
  completed: false,
  createdAt: new Date().toLocaleString()
}</code></pre><ul>
<li><p><code>id</code> → 고유값</p>
</li>
<li><p><code>title</code> → 할 일 내용</p>
</li>
<li><p><code>completed</code> → 완료 여부</p>
</li>
<li><p><code>createdAt</code> → 생성 시간</p>
</li>
</ul>
<hr>
<h1 id="기능-구현-과정">기능 구현 과정</h1>
<h3 id="할-일-추가-create">할 일 추가 (Create)</h3>
<p><strong>💡 핵심</strong></p>
<ul>
<li><p>input 값 → state로 관리</p>
</li>
<li><p>버튼 클릭 → 배열에 추가</p>
<pre><code>function handleAddTodo() {
if (title === &quot;&quot;) return;

const newTodo = {
  id: Date.now(),
  title: title,
  completed: false,
  createdAt: new Date().toLocaleString(),
};

setTodos([...todos, newTodo]);
setTitle(&quot;&quot;);
}</code></pre></li>
</ul>
<p><strong>❗ 중요 포인트</strong></p>
<blockquote>
</blockquote>
<p>기존 배열을 직접 수정하지 않고
새로운 배열을 만들어야 한다</p>
<p>👉 그래서 spread 사용</p>
<hr>
<h3 id="목록-출력-read">목록 출력 (Read)</h3>
<p>배열을 화면에 출력할 때는 <code>map</code>을 사용한다.</p>
<pre><code>&lt;ul&gt;
  {todos.map((t) =&gt; (
    &lt;li key={t.id}&gt;
      &lt;div&gt;{t.title}&lt;/div&gt;
      &lt;div&gt;{t.createdAt}&lt;/div&gt;
    &lt;/li&gt;
  ))}
&lt;/ul&gt;</code></pre><p><strong>❗ 중요 포인트</strong></p>
<ul>
<li><p>map은 배열을 순회하면서 UI를 만든다</p>
</li>
<li><p>key는 반드시 필요</p>
</li>
</ul>
<hr>
<h3 id="할일-삭제-delete">할일 삭제 (Delete)</h3>
<p>삭제는 <code>filter</code>를 사용한다.</p>
<pre><code>function handleDeleteTodo(id) {
  setTodos(todos.filter((t) =&gt; t.id !== id));
}</code></pre><p><strong>💡 핵심</strong></p>
<blockquote>
</blockquote>
<p>삭제 = 특정 값을 제외하고 남긴다</p>
<hr>
<h3 id="완료-처리-update">완료 처리 (Update)</h3>
<p>완료 상태 변경은 map을 사용한다.</p>
<pre><code>function handleDoneTodo(id) {
  const newTodos = todos.map((t) =&gt; {
    if (t.id === id) {
      return {
        ...t,
        completed: !t.completed,
      };
    }
    return t;
  });

  setTodos(newTodos);
}</code></pre><p><strong>💡 핵심</strong></p>
<blockquote>
</blockquote>
<p>수정 = map
삭제 = filter</p>
<hr>
<h3 id="ui-처리">UI 처리</h3>
<p><strong>🔹 완료 상태 스타일</strong></p>
<pre><code>&lt;li className={t.completed ? &quot;completed&quot; : &quot;&quot;}&gt;</code></pre><hr>
<h1 id="회고">회고</h1>
<p>아직 제대로 TodoList를 마스터하지 못한 것 같다. <code>map</code>과 <code>filter</code>를 사용함에 있어서, 그리고 구조대로 코드를 작성함에 있어서 미숙해서 복습 겸 정리를 진행했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[구조분해할당]]></title>
            <link>https://velog.io/@obebe_00/%EA%B5%AC%EC%A1%B0%EB%B6%84%ED%95%B4%ED%95%A0%EB%8B%B9</link>
            <guid>https://velog.io/@obebe_00/%EA%B5%AC%EC%A1%B0%EB%B6%84%ED%95%B4%ED%95%A0%EB%8B%B9</guid>
            <pubDate>Tue, 17 Mar 2026 16:40:29 GMT</pubDate>
            <description><![CDATA[<p>📦 구조분해 할당 (Destructuring)</p>
<blockquote>
<p>배열이나 객체에서 값을 “꺼내서 변수에 바로 담는 문법”</p>
</blockquote>
<p>아직 제대로 구조분해 할당의 개념을 이해하지 못한 것 같아 다시 정리하면서 공부해보고자 한다.</p>
<hr>
<h1 id="1️⃣-배열-구조분해">1️⃣ 배열 구조분해</h1>
<p>기본 형태</p>
<pre><code>const arr = [10, 20, 30];

const [a, b, c] = arr;

console.log(a); // 10
console.log(b); // 20</code></pre><p>👉 순서대로 들어간다 (중요)</p>
<hr>
<p>일부만 꺼내기</p>
<pre><code>const [a, , c] = [10, 20, 30];

console.log(a); // 10
console.log(c); // 30</code></pre><p>👉 필요 없는 값은 건너뛸 수 있음</p>
<hr>
<p>기본값 설정</p>
<pre><code>const [a = 1, b = 2] = [10];

console.log(a); // 10
console.log(b); // 2</code></pre><p>👉 값이 없을 때만 기본값 사용</p>
<p>여기까지는 기본 구조로 쉽게 이해할 수 있다.</p>
<hr>
<h1 id="2️⃣-객체-구조분해">2️⃣ 객체 구조분해</h1>
<p>기본 형태</p>
<pre><code>const user = {
  name: &quot;Kim&quot;,
  age: 20,
};

const { name, age } = user;

console.log(name); // Kim</code></pre><p>👉 key 이름 기준으로 꺼냄 (순서 X)</p>
<p>이걸 조금 더 자세히 보자</p>
<p>원래 데이터는 </p>
<pre><code>const user = {
  name: &quot;Kim&quot;,
  age: 20,
};</code></pre><p>이 상태였다
여기서 <code>user</code>안에 있는 <code>name</code>을 꺼내서 쓰고싶으면</p>
<pre><code>const name = user.name;</code></pre><p>name이라는 새로운 변수에 <code>user.name</code>을 할당한다</p>
<p>이걸 구조분해로 하면</p>
<pre><code>const { name } = user;</code></pre><blockquote>
<ol>
<li>user 객체를 본다</li>
<li>그 안에서 name이라는 key를 찾는다</li>
<li>그 값을 꺼낸다</li>
<li>name이라는 변수에 넣는다</li>
</ol>
</blockquote>
<p>이렇게 된다.</p>
<p>정리하면, <strong>“user에서 name을 꺼내서 변수로 만든다”</strong></p>
<hr>
<p>변수 이름 바꾸기</p>
<pre><code>const { name: userName } = user;

console.log(userName); // Kim</code></pre><p>👉 name을 userName으로 바꿔서 사용</p>
<p>이것도 해석해보면</p>
<blockquote>
<ol>
<li>user 객체를 본다</li>
<li>name이라는 key를 찾는다</li>
<li>그 값을 꺼낸다</li>
<li>userName이라는 변수에 넣는다</li>
</ol>
</blockquote>
<p>이렇게 된다.</p>
<hr>
<h1 id="3️⃣-함수에서-구조분해">3️⃣ 함수에서 구조분해</h1>
<p>여기서부터 다시 어려워졌었기에 천천히 해보겠다.</p>
<p>먼저 구조분해 없이 코드를 보면</p>
<pre><code>function printUser(user) {
  const name = user.name;
  const age = user.age;

  console.log(name, age);
}

printUser({ name: &quot;Kim&quot;, age: 20 });</code></pre><p>printUser라는 함수에서 <code>user</code>라는 매개변수로 값을 받는다. 이후 <code>name</code>과 <code>age</code>에 값을 할당한다.</p>
<p><strong>이 과정을 구조분해로 줄여보자</strong></p>
<blockquote>
<p>매개변수 <code>user</code>로 객체를 받고 → 그 안에서 <code>name</code>, <code>age</code>를 꺼내서 → 각 변수에 값을 할당한다.</p>
</blockquote>
<p>이 과정을 줄이면 다음과 같이 작성할 수 있다.</p>
<pre><code>function printUser(user) {
  const { name, age } = user;

  console.log(name, age);
}</code></pre><p>이해가 됐다</p>
<hr>
<h1 id="4️⃣-react에서-왜-사용할까">4️⃣ React에서 왜 사용할까?</h1>
<p>React에서는 컴포넌트에 데이터를 전달할 때 props를 사용한다.</p>
<p>예를 들어 다음과 같은 객체가 있다고 하자.</p>
<pre><code>const props = { title: &quot;Hello&quot;, count: 1 };</code></pre><hr>
<p><strong>❌ 구조분해를 사용하지 않는 경우</strong></p>
<pre><code>console.log(props.title);
console.log(props.count);</code></pre><p>👉 props.를 계속 붙여야 해서 코드가 길어지고
👉 여러 번 사용할수록 가독성이 떨어진다.</p>
<hr>
<p><strong>✅ 구조분해를 사용하는 경우</strong></p>
<pre><code>const { title, count } = props;

console.log(title);
console.log(count);</code></pre><p>👉 필요한 값만 꺼내서 바로 사용할 수 있다.
👉 코드가 짧아지고, 읽기 쉬워진다.</p>
<hr>
<p>여기까지 이해했으면 React에서 실제로 사용하는 방법을 살펴보자</p>
<pre><code>const MyComponent = ({ title, count }) =&gt; {
  return &lt;div&gt;{title} - {count}&lt;/div&gt;;
};</code></pre><p>👉 props를 받은 뒤 꺼내는 것이 아니라
👉 매개변수에서 바로 구조분해를 한다</p>
<hr>
<h1 id="5️⃣-usestate에서-구조분해">5️⃣ useState에서 구조분해</h1>
<p><strong>먼저 useState란?</strong></p>
<p>React에서 <code>useState</code>는 컴포넌트의 상태(state)를 관리하는 함수다.</p>
<pre><code>const [count, setCount] = useState(0);</code></pre><p>👉 <code>count</code>는 현재 상태 값
👉 <code>setCount</code>는 상태를 변경하는 함수</p>
<p>🧠 그런데 왜 이렇게 생겼을까?</p>
<pre><code>const [count, setCount] = useState(0);</code></pre><p>이건 사실 구조분해다.</p>
<p>구조분해 없이 살펴보면, </p>
<pre><code>const result = useState(0);</code></pre><p>👉 <code>useState</code>를 호출하면 배열이 반환된다.
<code>result = [현재값, 상태변경함수];</code></p>
<hr>
<p>실제로 풀어보면</p>
<pre><code>const result = useState(0);

const count = result[0];
const setCount = result[1];</code></pre><p>이걸 줄인 것이 바로</p>
<pre><code>const [count, setCount] = useState(0);</code></pre><hr>
<h3 id="🔗-정리">🔗 정리</h3>
<p>구조분해에 대해 정리해보면서 다시 이해해보았다.
특히 React에서는 <code>props</code>, <code>useState</code>처럼 데이터를 다루는 거의 모든 과정에서 구조분해가 사용된다.
그래도 억지로 암기하고 부딪혔다가 막혀서 다시 이해해보니 훨 이해가 잘되어서 다행이다.
앞으로도 이해하고 잘 사용해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JavaScript의 변수 선언 방식: var, let, const]]></title>
            <link>https://velog.io/@obebe_00/JavaScript%EC%9D%98-%EB%B3%80%EC%88%98-%EC%84%A0%EC%96%B8-%EB%B0%A9%EC%8B%9D-var-let-const</link>
            <guid>https://velog.io/@obebe_00/JavaScript%EC%9D%98-%EB%B3%80%EC%88%98-%EC%84%A0%EC%96%B8-%EB%B0%A9%EC%8B%9D-var-let-const</guid>
            <pubDate>Mon, 16 Mar 2026 08:07:56 GMT</pubDate>
            <description><![CDATA[<p>JavaScript에서 변수를 선언하는 방법은 크게 세 가지가 있다.</p>
<pre><code>var
let
const</code></pre><p>이 세 가지는 모두 변수를 선언하는 키워드이지만 <strong>스코프(scope), 재선언 여부, 호이스팅 방식</strong> 등이 서로 다르다.
특히 ES6 이후에는 <code>let</code>과 <code>const</code>가 등장하면서 <code>var</code>의 사용이 점점 줄어들게 되었다.</p>
<p>이번 글에서는 <code>var</code>, <code>let</code>, <code>const</code>의 특징을 살펴보고 서로 어떻게 다른지 비교해 보겠다.</p>
<hr>
<h1 id="1️⃣-var">1️⃣ var</h1>
<p><code>var</code>는 JavaScript 초기부터 존재하던 변수 선언 방식이다.</p>
<p><strong>특징</strong></p>
<ol>
<li>재선언 가능<pre><code>var a = 10;
var a = 20;
</code></pre></li>
</ol>
<p>console.log(a); // 20</p>
<pre><code>같은 이름의 변수를 다시 선언해도 에러가 발생하지 않는다.

---

2. 재할당 가능</code></pre><p>var a = 10;
a = 30;</p>
<p>console.log(a); // 30</p>
<pre><code>값을 다시 할당하는 것도 가능하다.

---

3. 함수 스코프 (Function Scope)

var는 함수 단위로 스코프가 생성된다.</code></pre><p>function test() {
  var a = 10;
}</p>
<p>console.log(a); // error</p>
<pre><code>하지만 블록문에서는 스코프가 생성되지 않는다.</code></pre><p>if (true) {
  var a = 10;
}</p>
<p>console.log(a); // 10</p>
<pre><code>이 때문에 의도하지 않은 변수 접근이 발생할 수 있다.

---

4. 호이스팅 (Hoisting)

`var`로 선언된 변수는 선언이 코드의 최상단으로 끌어올려진 것처럼 동작한다.</code></pre><p>console.log(a);
var a = 10;</p>
<pre><code>실제로는 다음과 같이 동작한다.</code></pre><p>var a;
console.log(a); // undefined
a = 10;</p>
<pre><code>그래서 선언 전에 접근하면 `undefined`가 출력된다.

---

# 2️⃣ let

`let`은 ES6에서 도입된 변수 선언 방식이다.
기존 `var`의 문제점을 보완하기 위해 등장했다.

**특징**
1. 재선언 불가능</code></pre><p>let a = 10;
let a = 20; // error</p>
<pre><code>같은 스코프에서 같은 이름의 변수를 다시 선언할 수 없다.

---

2. 재할당 가능</code></pre><p>let a = 10;
a = 30;</p>
<p>console.log(a); // 30</p>
<pre><code>값 변경은 가능하다.

---

3. 블록 스코프 (Block Scope)

`let`은 블록 단위로 스코프가 생성된다.</code></pre><p>if (true) {
  let a = 10;
}</p>
<p>console.log(a); // error</p>
<pre><code>이 특징 덕분에 변수의 사용 범위를 **더 안전하게 관리**할 수 있다.

---

4. Temporal Dead Zone (TDZ)

`let`은 선언 전에 접근할 수 없다.</code></pre><p>console.log(a); // error
let a = 10;</p>
<pre><code>이 구간을 **Temporal Dead Zone (TDZ)**이라고 한다.

---

# 3️⃣ const

const는 **상수(Constant)**를 선언할 때 사용하는 키워드이다.

**특징**

1. 재선언 불가능</code></pre><p>const a = 10;
const a = 20; // error</p>
<pre><code>---

2. 재할당 불가능</code></pre><p>const a = 10;
a = 20; // error</p>
<pre><code>---

3. 선언과 동시에 초기화 필요</code></pre><p>const a; // error</p>
<pre><code>반드시 선언과 동시에 값을 할당해야 한다.

---

4. 객체와 배열은 내부 값 변경 가능</code></pre><p>const user = { name: &quot;Kim&quot; };</p>
<p>user.name = &quot;Lee&quot;;</p>
<p>console.log(user.name); // Lee</p>
<p>```
<code>const</code>는 변수의 재할당을 막는 것이지
객체 내부 값을 변경하는 것까지 막지는 않는다.</p>
<hr>
<h1 id="정리">정리</h1>
<p><code>var</code>는 호이스팅과 함수 스코프 때문에 예측하기 어려운 동작을 만들 수 있다.
그래서 ES6 이후에는 <code>let</code>과 <code>const</code>를 사용하는 것이 일반적이다.</p>
<p>보통 다음과 같은 기준으로 사용한다.</p>
<blockquote>
<p>기본적으로 const 사용
값이 변경될 가능성이 있으면 let 사용
var는 사용하지 않음</p>
</blockquote>
<p>이 방식이 변수의 변경 가능성을 명확하게 하고 코드의 안정성을 높여 준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[깊은 복사, 얕은 복사]]></title>
            <link>https://velog.io/@obebe_00/%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC</link>
            <guid>https://velog.io/@obebe_00/%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC</guid>
            <pubDate>Mon, 16 Mar 2026 06:31:08 GMT</pubDate>
            <description><![CDATA[<p>자바스크립트에서 객체나 배열을 복사할 때 단순히 값이 복사되는 것이 아니라 <strong>참조</strong>가 복사되는 경우가 있다.</p>
<p>특히 객체와 배열 같은 <strong>참조 타입(Reference Type)</strong>에서는 복사의 방식에 따라 결과가 달라진다.</p>
<p>이때 등장하는 개념이 바로</p>
<p>**- Shallow Copy (얕은 복사)</p>
<ul>
<li>Deep Copy (깊은 복사)**</li>
</ul>
<p>이다.</p>
<hr>
<h1 id="1️⃣-왜-복사-문제가-발생할까">1️⃣ 왜 복사 문제가 발생할까?</h1>
<p>먼저 JavaScript의 데이터 타입을 간단히 이해해야 한다.</p>
<p>JavaScript의 데이터는 크게 두 가지로 나뉜다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>타입</th>
</tr>
</thead>
<tbody><tr>
<td>원시 타입</td>
<td>Number, String, Boolean, null, undefined, Symbol</td>
</tr>
<tr>
<td>참조 타입</td>
<td>Object, Array, Function</td>
</tr>
</tbody></table>
<p>원시 타입은 <strong>값 자체</strong>가 복사된다.</p>
<pre><code>let a = 10;
let b = a;

b = 20;

console.log(a); // 10</code></pre><p>하지만 객체나 배열은 <strong>메모리 주소</strong>가 복사된다.</p>
<pre><code>const user1 = { name: &quot;Kim&quot; };
const user2 = user1;

user2.name = &quot;Lee&quot;;

console.log(user1.name); // Lee</code></pre><p>두 변수는 <strong>같은 객체를 가리키고 있기 때문</strong>이다.</p>
<p>이 문제를 해결하기 위해 객체를 복사하는 방법이 필요하다.</p>
<hr>
<h1 id="2️⃣-shallow-copy-얕은-복사">2️⃣ Shallow Copy (얕은 복사)</h1>
<p>Shallow Copy는 객체의 최상위 수준만 복사하는 방식이다.</p>
<p>즉 새로운 객체가 만들어지지만
<strong>내부 객체는 동일한 참조를 공유</strong>한다.</p>
<p>예제로 살펴보자</p>
<pre><code>const user = {
  name: &quot;Kim&quot;,
  address: {
    city: &quot;Seoul&quot;
  }
};

const copyUser = { ...user };

copyUser.address.city = &quot;Busan&quot;;

console.log(user.address.city); // Busan</code></pre><p><code>spread operator</code>를 사용하면 새로운 객체가 만들어지지만
<code>address</code> 객체는 같은 메모리를 참조한다.</p>
<p>그래서 내부 값을 변경하면 <strong>원본도 함께 변경</strong>된다.</p>
<hr>
<h3 id="shallow-copy-방법">Shallow Copy 방법</h3>
<pre><code>Object.assign({}, obj)</code></pre><pre><code>const copy = { ...obj };</code></pre><pre><code>const copy = array.slice();</code></pre><pre><code>const copy = Array.from(array);</code></pre><p>하지만 이러한 방식은 <strong>중첩 객체까지는 복사하지 않는다</strong>.</p>
<hr>
<h1 id="3️⃣-deep-copy-깊은-복사">3️⃣ Deep Copy (깊은 복사)</h1>
<p>Deep Copy는 객체 내부의 모든 값까지 완전히 복사하는 방식이다.</p>
<p>즉 복사된 객체는 원본 객체와 완전히 독립적인 데이터가 된다.</p>
<pre><code>const user = {
  name: &quot;Kim&quot;,
  address: {
    city: &quot;Seoul&quot;
  }
};

const copyUser = structuredClone(user);

copyUser.address.city = &quot;Busan&quot;;

console.log(user.address.city); // Seoul</code></pre><p>이번에는 <code>copyUser</code>를 수정해도 원본 객체는 변경되지 않는다.</p>
<p>왜냐하면 <strong>내부 객체까지 완전히 새로운 메모리로 복사되었기 때문</strong>이다.</p>
<hr>
<h3 id="deep-copy-방법">Deep Copy 방법</h3>
<p><strong>structuredClone</strong></p>
<p>최근 JavaScript에서 제공하는 가장 안전한 방법이다.</p>
<pre><code>const copy = structuredClone(obj);</code></pre><hr>
<h3 id="json-방식">JSON 방식</h3>
<p>예전에는 다음 방식이 많이 사용되었다.</p>
<pre><code>const copy = JSON.parse(JSON.stringify(obj));</code></pre><p>하지만 다음과 같은 단점이 있다.</p>
<ul>
<li><p>함수 복사 불가능</p>
</li>
<li><p>undefined 값 제거</p>
</li>
<li><p>Date 객체 손실</p>
</li>
</ul>
<hr>
<h1 id="정리">정리</h1>
<p>JavaScript에서 객체를 복사할 때는 단순히 <code>=</code> 연산자를 사용하면 참조만 복사된다.</p>
<p>따라서 상황에 따라 다음과 같은 방법을 사용해야 한다.</p>
<ul>
<li><p>단순 구조 → Shallow Copy</p>
</li>
<li><p>중첩 객체 존재 → Deep Copy</p>
</li>
</ul>
<p>이 차이를 이해하는 것은 <strong>예상하지 못한 데이터 변경 버그를 방지하는 데 매우 중요</strong>하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자 포트폴리오 준비 특강 & 느낀 태도]]></title>
            <link>https://velog.io/@obebe_00/%EA%B0%9C%EB%B0%9C%EC%9E%90-%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4-%EC%A4%80%EB%B9%84-%ED%8A%B9%EA%B0%95-%EB%8A%90%EB%82%80-%ED%83%9C%EB%8F%84</link>
            <guid>https://velog.io/@obebe_00/%EA%B0%9C%EB%B0%9C%EC%9E%90-%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4-%EC%A4%80%EB%B9%84-%ED%8A%B9%EA%B0%95-%EB%8A%90%EB%82%80-%ED%83%9C%EB%8F%84</guid>
            <pubDate>Sun, 15 Mar 2026 07:02:15 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>우리는 백 번을 살아도 다 쓰지 못할 잠재력을 가지고 태어났다 - 데니스 웨이틀리</p>
</blockquote>
<p>최근 읽은 책에서 마음에 와닿았던 말이다. 모두가 특출난 장점을 가지고있다고 나는 확신한다.
단지 사회와 어울리거나 어울리지 않는 것으로 재능이 인정받거나 그렇지 않거나라고 생각한다.
시대를 잘 타고났다라는 말이 적합하다고 느낀다.</p>
<p>시작에 앞서 이 말을 하는 이유는 나 자신을 믿는 것부터가 시작인 것 같다. 우리 모두 각자의 기준에서 성공하고 싶어한다(사회적 기준에서 성공하고 싶어하더라도 그 곳에 있는 &#39;나&#39;를 바라보는 것이 곧 각자의 기준이 된다고 생각한다). 그래서 &quot;나는 안돼&quot;, &quot;저 사람은 재능을 가지고 태어났어&quot;하고 낙담하지 말고 내가 잘할 수 있는 것을 어떻게 살려서 남은 시간을 보내고 능동적으로 살아갈 것인지를 고민하는 것이 자기 자신에게 좋을 것 같다.</p>
<hr>
<h1 id="포트폴리오란">포트폴리오란?</h1>
<p>포트폴리오가 무엇인지 정의해보고 들어가자</p>
<blockquote>
<p>포트폴리오란 내가 지금까지 만든 프로젝트와 경험을 정리한 문서</p>
</blockquote>
<p>이 정의를 보면 내가 해온 많을 수도 있고 적을 수도 있는 경험들을 최대한 정리해서 풍성한 문서를 작성하고 싶어진다.</p>
<p>그런데 이번 특강을 들으면서 위와 같은 생각이 취업에 유리하지 않다는 것을 느꼈다.
오늘 글의 내용에서 다룰 특강은 개발자H님의 포트폴리오 특강이다.</p>
<p>강사님의 포인트는 <code>포트폴리오는 면접을 가기 위한 문서</code>였다.
면접을 가기 위한 문서? 이 말에 거창한 포트폴리오만을 생각해오던 일종의 개념이 깨졌다.
포트폴리오의 목적은 <strong>합격 자체가 아니라 면접으로 이어지는 것</strong>이다.</p>
<p>이 관점에서 생각해보면
포트폴리오를 만드는 방식도 조금 달라진다.</p>
<ul>
<li><p>모든 내용을 설명할 필요는 없다.</p>
</li>
<li><p>면접관이 이해하기 쉽게 작성해야 한다.</p>
</li>
<li><p>면접으로 이어질 만큼만 보여주면 된다.</p>
</li>
</ul>
<p>포트폴리오 특강을 들으며 정리한 내용을 바탕으로 내가, 나아가 개발자가 어떤 방식으로 포트폴리오를 준비하고 공부해야 하는지에 대해 정리해보려고 한다.</p>
<hr>
<h1 id="면접관의-관점에서-본-포트폴리오">면접관의 관점에서 본 포트폴리오</h1>
<p>내 포트폴리오가 정말 화려하고 아무리 내 스타일대로 잘 작성되었다고해도 이것의 독자는 정해져있다. 바로 <strong>면접관</strong>이다.</p>
<blockquote>
<p>포트폴리오는 결국 면접관이 읽는 문서다</p>
</blockquote>
<p>여기까지도 우리는 쉽게 이해할 수 있다. 하지만 면접관이 과연 내 포트폴리오를 꼼꼼히 100% 이해하면서 읽어줄까? 여기서 조금 속상했지만, 결국 면접관도 상황이 있기에 제출하는 우리들의 기대와 다르게 처음부터 끝까지 꼼꼼하게 읽기는 어렵다. 한 포지션에 몇백 개의 포트폴리오가 들어오는 경우도 흔하기 때문이다. 또한 면접관은 채용 전담 업무만 진행하는 것이 아니기에 내 포트폴리오가 면접관의 컨디션이 안좋은 상태에서 읽히는 상황이 발생할 수도 있다.</p>
<p>여기서 우리는 불평을 하거나, 운이 좋지 않았다고 치부하고 넘어가기에는 이 문제를 아무도 해결해줄 수 없는 무한 반복일 것이다.
어떻게 해결할 수 있을까? 다시 말해 어떻게 만들어야 면접관이 내 포트폴리오를 다른 포트폴리오보다 조금이라도 더 읽어보거나 기억에 남게 할 수 있을까?
<strong>경쟁력</strong>을 갖추기 위해서는 두 가지를 챙겨야 한다.</p>
<ul>
<li>가독성이 좋아야한다</li>
<li>면접관이 질문하기 쉽게 만들어야한다</li>
</ul>
<p>면접관이 피로하지 않게 내 포트폴리오를 읽고, 이해하고, 질문할 수 있도록 <strong>설계</strong>된 문서여야 한다는 것이다.</p>
<hr>
<h1 id="그래서-첫-페이지가-중요하다">그래서 첫 페이지가 중요하다</h1>
<p>가독성, 그리고 면접관이 쉽게 이해할 수 있고 질문과 연관 지을 수 있어야한다는 자연스럽게 아래와 같은 말로 귀결된다.</p>
<blockquote>
<p>첫 페이지가 가장 중요하다</p>
</blockquote>
<p>면접관이 내 포트폴리오를 더 읽을 수 있게 유도해야한다. 이를 강사님은 <strong>호기심을 만드는 페이지</strong>라 표현했다. 첫 페이지를 보았을 때 <strong>&quot;이 사람 뭐지?&quot;</strong> 라는 반응이 나오면 성공이라고 볼 수 있다. 궁금해야 다음 페이지를 읽어볼 이유가 하나라도 더 생기기 때문이다.</p>
<p>그럼 무엇을 보여줘야할까?
세 가지를 정하고 들어가보자</p>
<ul>
<li>개발자 컨셉 (타이틀)</li>
<li>강점 3가지</li>
<li>이미지 기반 디자인</li>
</ul>
<h3 id="개발자-컨셉-타이틀">개발자 컨셉 (타이틀)</h3>
<p>컨셉이라함은 곧 타이틀을 말한다. &quot;안녕하세요. 풀스택 개발자 xxx입니다.&quot; 이런 타이틀은 궁금증을 불러오지 않는다.</p>
<ul>
<li>일당백 개발자 xxx입니다.</li>
<li>퍼포먼스에 진심인 개발자 xxx입니다.
이러한 타이틀은 면접관으로 하여금 <strong>궁금증</strong>을 만든다</li>
</ul>
<h3 id="강점-3가지">강점 3가지</h3>
<p>자신의 강점 3가지 정도를 압축해서 보여주자. 중요한 점은</p>
<blockquote>
<p>문장은 20자 이내 / 가능하다면 숫자를 활용하여</p>
</blockquote>
<h3 id="이미지-기반-디자인">이미지 기반 디자인</h3>
<p>텍스트보다는 이미지가 눈에 더 쉽게 들어오고 이해하기 쉽다. 이미지로 표현은 조금 생소했다. 강사님이 추천해주신 방법으로는 </p>
<ul>
<li>핀터레스트에서 레쥬메(Resume) 템플릿 참고</li>
<li>프로젝트 스크린샷을 목업 이미지로 제작</li>
<li>텍스트보단 시각적 요소 활용 텍스트보단 시각적 요소 활용</li>
</ul>
<p>여기서 <strong>숫자를 활용</strong> 이 부분이 중요하다고 느껴졌다.
추상적인 표현보다 프로젝트1_Backend개발 : 기여도 60% 와 같이 숫자를 활용하여 보여주면 훨씬 직관적으로 표현할 수 있다.</p>
<p>여기서 걱정되는 점은 &#39;어느정도 숫자가 과하지 않을까?&#39; 였는데 내가 충분히 잘 설명할 수만 있다면 숫자에 대한 걱정은 하지 않아도 된다고 하셨다.</p>
<p>또한 역할에 대한 설명을 적는 것도 좋은 방법이다. &#39;API 설계&#39;, &#39;배포 자동화&#39; 등 자신이 맡은 파트도 좋지만 역할을 정리해서 적어주는 것도 면접관에게 쉽게 나를 이해시킬 수 있는 방법이다.</p>
<hr>
<h1 id="영화의-예고편">영화의 예고편</h1>
<p>첫 페이지의 구조를 잘 작성했다면 이제 내 프로젝트들과 경험을 소개해야한다.
포인트는</p>
<blockquote>
<p>프로젝트 상세는 &#39;영화의 예고편&#39;처럼 작성한다</p>
</blockquote>
<p>영화 예고편에서는 관객들의 흥미를 돋굴 수 있도록 중요한 장면들은 보여주지만 결말이나 핵심 내용은 숨겨둔다. 이것이 핵심이다.
포트폴리오도 마찬가지로 모든 것을 문서에 상세하게 담아내기보다는 <strong>면접에서 설명할 여지를 남겨두는 것</strong>이 중요하다</p>
<blockquote>
<p>너무 많은 내용은 가독성을 저하시키고 면접관으로 하여금 흥미를 떨어트린다</p>
</blockquote>
<h3 id="어떻게-예고편을-꾸밀까">어떻게 예고편을 꾸밀까?</h3>
<p><strong>기술 키워드</strong>와 <strong>간단한 설명을 중심</strong>으로 정리해보자</p>
<p>예를 들어,</p>
<pre><code>Redis Cache 적용으로 API 응답속도 개선
JWT 기반 인증 시스템 구현
Docker 기반 배포 자동화</code></pre><p>이 방식의 장점은 <strong>명확</strong>이다.
키워드를 확실하게 적어 <strong>면접관에게 질문을 떠올리게 유도</strong>할 수 있는 것이다.</p>
<p>또 하나는 앞서 첫 페이지에서 사용했던 <strong>이미지 활용</strong>이다.</p>
<ul>
<li>프로젝트 스크린샷</li>
<li>목업 이미지</li>
<li>서비스 구조 이미지
등을 적극적으로 활용하여 프로젝트의 맥락을 텍스트로 길게 적어내지 말고 가시적으로 표현해보자.</li>
</ul>
<hr>
<h1 id="대기업-vs-스타트업-전략">대기업 vs 스타트업 전략</h1>
<p>전체적인 구조와 작성법에 대해서는 잘 알아보았다.</p>
<p>이제 내가 서류를 낼 곳에 대해 알아보자. 크게 대기업과 스타트업으로 분류하였다.
모든 회사가 같은 기준으로 개발자를 채용하는 것은 아니다. 이 둘로 구분지은 이유는 둘의 관점이 꽤 다르기 때문이다.</p>
<h3 id="대기업">대기업</h3>
<p>대기업은 보통 <strong>스페셜리스트</strong>를 선호한다.
이미 개발 조직이 충분히 크고 체계가 잡혀있기 때문에, 특정 포지션에 정확하게 맞는 사람을 찾는 경우가 많다.
그래서 아래와 같은 특징들이 있다.</p>
<ul>
<li>특정 기술 스택에 대한 깊은 경험</li>
<li>포지션에 맞는 전문성</li>
<li>즉시 투입 가능한 역량</li>
</ul>
<p>즉,</p>
<blockquote>
<p>넓은 경험보다 깊은 경험이 중요하다</p>
</blockquote>
<h3 id="스타트업--중소기업">스타트업 / 중소기업</h3>
<p>반면 스타트업을 비롯한 중소기업은 조직 규모가 작고 빠르게 움직여야 하기 때문에 문제 해결 능력과 적응력을 더 중요하게 보는 경우가 많다.</p>
<ul>
<li>다양한 경험</li>
<li>문제 해결 과정</li>
<li>협업과 커뮤니케이션</li>
<li>컬쳐핏(함께 일할 수 있는 사람인가)</li>
</ul>
<p>즉,</p>
<blockquote>
<p>제네럴리스트 성향이 더 유리할 수 있다</p>
</blockquote>
<p>이 이야기로 미루어보았을 때, 중요한 것은 결국 <strong>전략</strong>을 세워야한다는 것이다.</p>
<p>목표하는 회사에 따라</p>
<ul>
<li>강조할 경험</li>
<li>프로젝트 구성</li>
<li>기술 스택 설명</li>
</ul>
<p>이 달라질 수 있기 때문이다.</p>
<blockquote>
<p>나는 어떤 회사에 지원할 것인가</p>
</blockquote>
<p>이것을 깊게 생각해보고 포트폴리오 전략을 세워보자</p>
<hr>
<h1 id="특강-회고">특강 회고</h1>
<p>이번 특강은 포트폴리오 작성법에 대한 내용이었지만 듣다 보니 결국 <strong>개발 공부의 방향성</strong>에 대한 이야기라는 생각이 들었다.</p>
<p>본디 포트폴리오는 취업을 위한 문서이기 전에 지금까지 내가 어떤 방식으로 공부해왔는지를 보여주는 결과물이기 때문이다.</p>
<p><code>기록</code>과 <code>태도</code> 
특강을 듣고 정리하며 느낀 두 가지의 키워드다.</p>
<h3 id="기록">기록</h3>
<ul>
<li>어떤 문제를 해결했고</li>
<li>어떤 기술을 사용했고</li>
<li>왜 그런 선택을 했는지</li>
</ul>
<p>이런 내용들은 시간이 지나면서 쉽게 잊혀진다. 그래서 더욱 기록으로 남겨둬야 단순한 경험에서 설명할 수 있는 경험이 된다.</p>
<p>또한, 숫자는 설득력을 높이기에 프로젝트를 진행할 때</p>
<ul>
<li>성능 개선</li>
<li>운영 효율</li>
<li>사용자 변화
등의 내용을 숫자로 기록해두는 것도 중요하다고 하셨다.</li>
</ul>
<h3 id="태도">태도</h3>
<p>태도에 대한 내용이 많지 않았지만 내가 태도를 키워드로 뽑은 이유는 결국 내가 해온 경험을 소개하는 것이기에 개발 공부에 대한 내용이 포트폴리오의 내용일 것이다.
개발 공부는 생각보다 긴 과정이다. 그 꾸준함을 유지하는 <strong>태도</strong>가 가장 중요하다고 생각이 들었다.</p>
<p>스프린트를 진행한지도 벌써 한 달이 넘어갔다.
가장 두려운 점은 내가 합리화를 시작했거나 욕심이 사라졌을까하는 부분이다.</p>
<p>내가 나를 가장 잘 알기에 미연에 방지하고자 스터디도 진행했다.
정말 많은 도움이 되고있고, 좋은 동료도 얻었다.
이들과 함께 끝까지 가기 위해 지치더라도 꾸준히 나아갔으면 좋겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JavaScript - 비동기 자바스크립트 Part 2]]></title>
            <link>https://velog.io/@obebe_00/JavaScript-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Part-2</link>
            <guid>https://velog.io/@obebe_00/JavaScript-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Part-2</guid>
            <pubDate>Wed, 11 Mar 2026 09:13:49 GMT</pubDate>
            <description><![CDATA[<p>앞서 자바스크립트의 비동기 구조와 Event Loop에 대해 살펴보았다.
비동기 작업은 작업이 완료될 때까지 기다리지 않고 다음 코드를 실행한다는 특징이 있다.</p>
<p>그렇다면 여기서 한 가지 문제가 생긴다.</p>
<blockquote>
<p>비동기 작업이 언제 끝나는지 어떻게 알 수 있을까?</p>
</blockquote>
<p>예를 들어 서버에서 데이터를 가져오는 작업이 있다고 가정해보자.</p>
<pre><code>fetchUserData();
console.log(&quot;다음 작업 실행&quot;);</code></pre><p>만약 데이터를 가져오는 작업이 3초가 걸린다면,
데이터가 도착하기 전에 <strong>다음 코드가 먼저 실행될 수 있다</strong>.</p>
<p>이때 <strong>작업이 끝난 뒤 실행할 코드를 전달하는 방법</strong>이 필요하다.</p>
<p>그래서 등장한 것이 바로 <strong>콜백 함수(Callback)</strong> 이다.</p>
<hr>
<h1 id="콜백-함수-callback">콜백 함수 (Callback)</h1>
<p>콜백 함수는 다른 함수에 인자로 전달되는 함수이다.</p>
<p>하지만 단순히 인자로 전달된다는 의미보다 중요한 것은 다음과 같다.</p>
<blockquote>
<p>콜백 함수는 실행 시점을 다른 함수에게 맡긴다.</p>
</blockquote>
<p>말이 어려우니 예제로 살펴보자.</p>
<pre><code>function greet(name, callback) {
  console.log(&quot;안녕하세요 &quot; + name);
  callback();
}

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

greet(&quot;홍길동&quot;, sayGoodbye);</code></pre><p>결과</p>
<pre><code>안녕하세요 홍길동
안녕히 가세요</code></pre><p>여기서 <code>sayGoodbye</code>는 직접 호출되지 않았다.
대신 <code>greet</code> 함수 내부에서 실행된다.</p>
<p>즉,</p>
<blockquote>
</blockquote>
<p>일반 함수 → 내가 원할 때 실행
콜백 함수 → 다른 함수가 필요할 때 실행</p>
<p>이처럼 <strong>실행 제어권이 다른 함수에게 넘어가는 것</strong>이 콜백 함수의 특징이다.</p>
<hr>
<h1 id="콜백-지옥-callback-hell">콜백 지옥 (Callback Hell)</h1>
<p>콜백 함수는 특히 비동기 처리에 유용하여 비동기 작업에 많이 사용되지만, 작업이 많아지면 문제가 발생한다.</p>
<p><img src="https://velog.velcdn.com/images/obebe_00/post/247a0cfb-977c-466f-9e32-5d25e799e7f0/image.png" alt=""></p>
<p>이러한 구조를 <strong>콜백 지옥</strong>이라고 한다.
사진을 보면 알겠지만 <code>가독성이 매우 떨어지며</code>, <code>에러 처리가 어렵고</code>, <code>유지보수가 힘들다</code>는 단점이 있다.</p>
<p>이것을 해결하기 위해 등장한 것이 <strong>Promise</strong>다.</p>
<hr>
<h1 id="promise">Promise</h1>
<p>Promise는</p>
<blockquote>
<p>비동기 작업의 성공 또는 실패 결과를 나타내는 객체
이다.</p>
</blockquote>
<p>그러니까, &quot;작업이 끝나면 결과를 알려줄게~&quot;라는 약속이라고 이해할 수 있다.</p>
<p>이 Promise는 3가지의 상태를 가지는데</p>
<pre><code>// 1. Pending (대기)
// - 작업이 진행 중
const promise = new Promise((resolve, reject) =&gt; {
    // 작업 중...
});

// 2. Fulfilled (이행)
// - 작업 성공
const promise = new Promise((resolve, reject) =&gt; {
    resolve(&#39;성공!&#39;);
});

// 3. Rejected (거부)
// - 작업 실패
const promise = new Promise((resolve, reject) =&gt; {
    reject(&#39;실패!&#39;);
});</code></pre><p><code>Pending - 대기</code>, <code>Fulfilled - 성공</code>, <code>Rejected - 실패</code> 이 세 상태를 기억해두자.</p>
<hr>
<h2 id="promise-만들기">Promise 만들기</h2>
<p>❗Promise를 만들 때는, 항상 이 작업이 성공할 수도 있고 실패할 수도 있다는 것을 명시해줘야한다.</p>
<blockquote>
<p>성공 = reseolve
실패 = reject</p>
</blockquote>
<pre><code>const promise = new Promise((resolve, reject) =&gt; {
    // 비동기 작업
    const success = true;

    if (success) {
        resolve(&#39;성공 데이터&#39;); // 성공
    } else {
        reject(&#39;에러 메시지&#39;);  // 실패
    }
});</code></pre><ul>
<li>then() 과 catch() :<pre><code>const promise = new Promise((resolve, reject) =&gt; {
  setTimeout(() =&gt; {
      resolve(&#39;완료!&#39;);
  }, 1000);
});
</code></pre></li>
</ul>
<p>promise
    .then(result =&gt; {
        console.log(&#39;성공:&#39;, result);
    })
    .catch(error =&gt; {
        console.log(&#39;실패:&#39;, error);
    });</p>
<p>// 출력 (1초 후):
// 성공: 완료!</p>
<pre><code>* finally()
성공/실패와 상관없이 실행:
</code></pre><p>fetchUser(123)
    .then(user =&gt; {
        console.log(&#39;성공:&#39;, user);
    })
    .catch(error =&gt; {
        console.error(&#39;실패:&#39;, error);
    })
    .finally(() =&gt; {
        console.log(&#39;작업 완료 (성공이든 실패든)&#39;);
        // 로딩 스피너 숨기기 등
    });</p>
<pre><code>
---

## async/await
Promise를 더 쉽게 사용하는 문법으로 가장 중요한 포인트는 **비동기 함수의 동기적 표현**이다.

* Promise를 동기 코드처럼 :</code></pre><p>// ❌ Promise (then 체인)
fetchUser(123)
    .then(user =&gt; {
        console.log(user);
        return fetchOrders(user.id);
    })
    .then(orders =&gt; {
        console.log(orders);
    });</p>
<p>// ✅ async/await (읽기 쉬움)
async function getUser() {
    const user = await fetchUser(123);
    console.log(user);</p>
<pre><code>const orders = await fetchOrders(user.id);
console.log(orders);</code></pre><p>}</p>
<pre><code>
`async` 키워드는 함수를 **비동기 함수**로 만들어준다.
그리고 또 하나 중요한 특징이 있다.
&gt;`async` 함수는 항상 **Promise**를 반환한다.
</code></pre><p>async function hello() {
  return &quot;안녕&quot;;
}</p>
<pre><code>이 함수는 단순히 문자열을 반환하는 것처럼 보이지만 실제로는</code></pre><p>function hello() {
  return Promise.resolve(&quot;안녕&quot;);
}</p>
<pre><code>이것과 같은 의미를 가지고 있다.
그래서 `async`함수는 다음과 같이 사용 가능하다.</code></pre><p>hello().then(msg =&gt; console.log(msg));</p>
<p>// 출력
안녕</p>
<pre><code>
---
* **await**

`await` 키워드는 **Promise가 완료될 때까지 기다리는 역할**을 한다
그러기에 반드시 **async 함수 내부에서만 사용이 가능**하다.

---

## 에러처리 (try / catch)

`async / await`에서는 에러 처리를 try / catch로 할 수 있다.
Promise의 `.catch()`와 같은 역할이다.</code></pre><p>async function getUser() {
  try {
    const user = await fetchUser(123);
    console.log(&quot;사용자:&quot;, user);
  } catch (error) {
    console.error(&quot;에러 발생:&quot;, error);
  }
}</p>
<pre><code>
간단한 개념들로 정리해봤는데, 실제 코드에서 사용해봐야 감을 찾을 것 같다. 다음은 실습을 통해 진행해보겠다.</code></pre>]]></description>
        </item>
    </channel>
</rss>