<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ly-ra.log</title>
        <link>https://velog.io/</link>
        <description>one setp</description>
        <lastBuildDate>Fri, 28 Mar 2025 03:39:53 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ly-ra.log</title>
            <url>https://velog.velcdn.com/images/ly-ra/profile/8d1369a0-7974-47c8-b24e-1e1a8fb4a813/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ly-ra.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ly-ra" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[최종 팀플 ID~어~?]]></title>
            <link>https://velog.io/@ly-ra/%EC%B5%9C%EC%A2%85-%ED%8C%80%ED%94%8C-ID%EC%96%B4</link>
            <guid>https://velog.io/@ly-ra/%EC%B5%9C%EC%A2%85-%ED%8C%80%ED%94%8C-ID%EC%96%B4</guid>
            <pubDate>Fri, 28 Mar 2025 03:39:53 GMT</pubDate>
            <description><![CDATA[<p>[2025.03.28 금요일]</p>
<h2 id="personal-focus-pomodoro">Personal Focus Pomodoro</h2>
<h3 id="💡-서비스-컨셉">💡 서비스 컨셉</h3>
<blockquote>
<p>목표:</p>
<p>단순한 타이머가 아니라</p>
<p>→ &quot;내가 언제 집중을 잘하는 사람인지&quot;
→ &quot;어떤 시간대에 순공/업무 시간이 많은지&quot;
→ &quot;장기적으로 개선/멘토링 자료로 활용할 수 있는&quot;</p>
<p>개인 분석용 타이머</p>
</blockquote>
<h3 id="🎯-사용자의-핵심-니즈">🎯 사용자의 핵심 니즈</h3>
<ul>
<li>순수 업무 시간(순공) 체크</li>
<li>하루, 주, 월 단위 통계</li>
<li>시간대별 집중 패턴 분석</li>
<li>달력 기반 기록</li>
<li>목표 기반 작업 (단순히 시간만 채우지 않고, 무엇을 했는지도 기록)</li>
<li>결과 공유 (멘토에게, 팀원에게, 코치에게)</li>
</ul>
<h3 id="🚩-구성">🚩 구성</h3>
<table>
<thead>
<tr>
<th>기능</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>타이머</td>
<td>업무 집중 타이머 (ex: 25/5, 커스텀 가능)</td>
</tr>
<tr>
<td>목표 입력</td>
<td>타이머 시작 시, 목표(할 일, 작업 내용) 기록</td>
</tr>
<tr>
<td>순공 시간 기록</td>
<td>중간에 멈춤, 완료 여부 기록 (순수 집중 시간)</td>
</tr>
<tr>
<td>달력 표시</td>
<td>하루 순공시간 기록, 목표, 성취 여부 표시</td>
</tr>
<tr>
<td>집중 시간 분석</td>
<td>&quot;몇 시 ~ 몇 시&quot; 구간에 집중 많이 했는지 시각화 (Bar or Heatmap)</td>
</tr>
<tr>
<td>통계</td>
<td>일별, 주별, 월별 총 순공 시간, 완료 목표 개수, 집중 시간대 분석</td>
</tr>
<tr>
<td>결과 공유</td>
<td>이미지, 링크, PDF로 오늘/이번 주 통계 공유</td>
</tr>
<tr>
<td>로컬 시간 기반</td>
<td>실제 환경의 Local Time 기록 (타임존 정확히 기록)</td>
</tr>
<tr>
<td>모바일 / 데스크탑</td>
<td>둘 다 대응 (Responsive)</td>
</tr>
</tbody></table>
<h3 id="🟣-사용자의-핵심-가치-체험">🟣 사용자의 핵심 가치 체험</h3>
<ol>
<li><p>그냥 타이머 쓰는 게 아니라,</p>
<pre><code> → **내 집중 습관을 기록하고 분석**할 수 있다.</code></pre></li>
<li><p><strong>개선</strong>할 수 있다.</p>
<pre><code> → 아침형? 밤형? 내가 언제 잘하는지 알 수 있다.</code></pre></li>
<li><p><strong>멘토링</strong>에 활용 가능</p>
<pre><code> → 캡처해서 멘토, 팀원에게 &quot;이번 주 이렇게 집중했어요&quot; 제출 가능</code></pre></li>
<li><p><strong>스스로 동기부여</strong></p>
<pre><code> → 오늘 하루 순공 4시간 했네? 내일도 해보자</code></pre></li>
</ol>
<hr>
<h2 id="1-서비스-개요">1. 서비스 개요</h2>
<p>FocusMate(Personal Focus Pomodoro)는 사용자의 집중 업무 시간을 기록하고, 이를 바탕으로
개인화된 집중 패턴 분석, 시간대별 통계, 목표 달성 기록을 제공하는
개인 생산성 향상 도구입니다.</p>
<p>단순한 업무 타이머를 넘어서,
언제, 어떻게 집중했는지를 체계적으로 기록하고
이를 통해 자기 분석, 멘토링, 업무 습관 개선에 활용할 수 있는 서비스입니다.</p>
<hr>
<h2 id="2-서비스-개발-배경-및-목적">2. 서비스 개발 배경 및 목적</h2>
<h3 id="배경">배경</h3>
<p>재택근무, 자율학습, 프리랜서, 창작자, 수험생, 취준생 등
스스로 시간을 관리해야 하는 사람들이 늘어남.</p>
<p>기존 타이머 서비스는 단순히 시간을 기록할 뿐,
사용자가 집중 습관을 분석하거나 개선할 수 있는 피드백을 제공하지 않음.</p>
<h3 id="목적">목적</h3>
<ul>
<li>사용자가 실제 집중 습관을 파악할 수 있도록 도움</li>
<li>자신의 집중 패턴을 바탕으로 시간 관리 능력 향상</li>
<li>멘토링, 코칭, 자기 피드백에 활용할 수 있는 객관적 기록 제공</li>
</ul>
<hr>
<h2 id="3-주요-타겟-사용자">3. 주요 타겟 사용자</h2>
<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>멘토 / 코치</td>
<td>피드백 자료로 활용</td>
</tr>
<tr>
<td>자기계발 관심자</td>
<td>스스로 습관 개선 및 동기부여</td>
</tr>
</tbody></table>
<hr>
<h2 id="4-핵심-기능-mvp">4. 핵심 기능 (MVP)</h2>
<table>
<thead>
<tr>
<th>기능</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>🟣 목표 입력</td>
<td>타이머 시작 전 오늘의 목표 or 작업 내용을 기록</td>
</tr>
<tr>
<td>🟣 집중 타이머</td>
<td>순공 타이머 (Pomodoro or Custom 가능)</td>
</tr>
<tr>
<td>🟣 집중 시간 기록</td>
<td>종료 시 기록 → 목표 + 실제 순공 시간 저장</td>
</tr>
<tr>
<td>🟣 달력 기반 기록</td>
<td>달력에 하루 순공시간, 목표, 완료 여부 표시</td>
</tr>
<tr>
<td>🟣 시간대별 집중 분석</td>
<td>하루 내 집중이 잘된 시간대 시각화 (Bar or Heatmap)</td>
</tr>
<tr>
<td>🟣 주간 / 월간 통계</td>
<td>누적 순공 시간, 목표 완료 수, 집중 시간대 분석</td>
</tr>
<tr>
<td>🟣 결과 공유</td>
<td>PDF, 이미지, URL로 결과 내보내기</td>
</tr>
<tr>
<td>🟣 모바일 / 데스크탑 대응</td>
<td>Responsive UI/UX</td>
</tr>
</tbody></table>
<hr>
<h2 id="5-서비스-기대-효과">5. 서비스 기대 효과</h2>
<ul>
<li><p><strong>자기 인식</strong>
자신이 언제 집중을 잘 하는지 알게 됨</p>
</li>
<li><p><strong>습관 개선</strong>
결과를 기반으로 학습 / 업무 습관 개선</p>
</li>
<li><p><strong>멘토링</strong>
결과를 멘토, 팀원에게 제출 → 피드백</p>
</li>
<li><p><strong>지속 동기 부여</strong>
시각화된 성과로 동기 강화</p>
</li>
</ul>
<hr>
<h2 id="6-페이지-및-기능-구조-예시">6. 페이지 및 기능 구조 (예시)</h2>
<ol>
<li><p>홈
오늘의 목표, 타이머 시작</p>
</li>
<li><p>타이머
진행 중, 일시정지, 종료</p>
</li>
<li><p>결과 기록
목표 + 순공시간 + 시간대 기록</p>
</li>
<li><p>달력
일별 기록 표시</p>
</li>
<li><p>통계
주간 / 월간 분석, 시간대별 분석</p>
</li>
<li><p>공유
결과 이미지 or PDF 다운로드</p>
</li>
</ol>
<hr>
<h2 id="7-서비스-흐름도-간단-버전">7. 서비스 흐름도 (간단 버전)</h2>
<blockquote>
<p>목표 입력 → 타이머 시작 → 타이머 종료 → 기록 저장 → 달력 표시 → 통계 분석 → 결과 공유</p>
</blockquote>
<hr>
<h2 id="8-기술-스택-예정">8. 기술 스택 (예정)</h2>
<ul>
<li>Frontend: Next.js 14, React, TailwindCSS</li>
<li>State: Zustand</li>
<li>Data: IndexedDB + Supabase (or 로컬 우선)</li>
<li>Chart: Chart.js</li>
<li>Deployment: Vercel</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[하이드레이션 불일치요?]]></title>
            <link>https://velog.io/@ly-ra/%ED%95%98%EC%9D%B4%EB%93%9C%EB%A0%88%EC%9D%B4%EC%85%98-%EB%B6%88%EC%9D%BC%EC%B9%98%EC%9A%94</link>
            <guid>https://velog.io/@ly-ra/%ED%95%98%EC%9D%B4%EB%93%9C%EB%A0%88%EC%9D%B4%EC%85%98-%EB%B6%88%EC%9D%BC%EC%B9%98%EC%9A%94</guid>
            <pubDate>Wed, 19 Mar 2025 01:47:29 GMT</pubDate>
            <description><![CDATA[<p>[2025.03.19 수요일]</p>
<p>[Next.js 개인과제 - 트러블슈팅]</p>
<h2 id="🚨-문제-발생">🚨 문제 발생</h2>
<ul>
<li>개인과제 도전기능중 다크모드 구현이 있었다. 찾아보다가 <code>next-themes</code> 라이브러리를 사용하여 구현하였다. 오~ 생각보다 쉽게 모드구현이 되서 라이브러리가 좋구만! 하고있었는데, 새로고침을 하고 나니까 화면 가능 에러가 표출!
<img src="https://velog.velcdn.com/images/ly-ra/post/8f65b465-c448-470f-933b-29a5307a8502/image.png" alt=""></li>
</ul>
<p>Hydration failed 라고나오면서, 콘솔창에도 오류가 발생하였다.
<img src="https://velog.velcdn.com/images/ly-ra/post/5d91aa6c-c5f2-4700-bffc-34cc90e66927/image.png" alt=""></p>
<p>이게 대체 무슨 오류이지?
에러 관련 하여 <a href="https://nextjs.org/docs/messages/react-hydration-error">next.js 공식문서</a>에서 제공하는 부분이 있어서 먼저 살펴보았다.</p>
<hr>
<h2 id="원인-추론">원인 추론</h2>
<ul>
<li>서버에서 렌더링한 HTML과 클라이언트에서 렌더링한 결과가 달라서 발생하는 <strong>&quot;Hydration mismatch&quot;</strong> 라고 한다.</li>
<li>콘솔창의 경고에서도 html과 RootLayout을 지목하고 있는데, 아마도 테마모드를 바꾸면서 html의 class 값이 next.js에서 미리 렌더링한 부분과 맞지 않아서 생기는 오류인것 같았다.</li>
</ul>
<h2 id="해결-과정">해결 과정</h2>
<h3 id="테마의-기본값-주기-❌">테마의 기본값 주기 ❌</h3>
<ul>
<li>테마의 기본값을 명시적으로 주어서 렌더링 시점의 테마와 이후의 테마를 같게 만들어 주면 되지 않을까? 생각을 하고 <code>&lt;ThemeProvider attribute=&quot;class&quot; defaultTheme=&quot;light&quot;&gt;</code> 코드를 수정해 주었으나, 동일한 오류가 발생하였다.</li>
</ul>
<h3 id="suppresshydrationwarning-추가-❌">suppressHydrationWarning 추가 ❌</h3>
<ul>
<li>이처럼 하이드레이션 불일치 오류를 표기하지 않도록 공식문서에서 제시한 대로<code>suppressHydrationWarning={true}</code> 속성을 주었지만, 여전히 오류 발생.</li>
</ul>
<h3 id="icon-대신-html로만-작성-❌">icon 대신 HTML로만 작성 ❌</h3>
<ul>
<li>구글링해보니 잘못된 html, 또는 icon등으로 인해서 발생한 경우도 있어서 icon을 지우고 html만 사용했을 경우에도 동일한 오류 발생.</li>
</ul>
<h3 id="mounted-값-설정해주기-❌">mounted 값 설정해주기 ❌</h3>
<ul>
<li>문제를 해결하다보니 처음 마운트되었을 때는 모드변경이 전혀 되지 않다가, 다른 페이지로 이동한 후에 모드를 변경하면 변경이 된점.</li>
<li>새로고침을 한 이후에만 해당 오류가 발생한점.</li>
</ul>
<p>위와같은 오류가 발생하는 환경을 확인할 수 있게 되었다.
그래서 마운트값을 적용해보기로 하였다.</p>
<p>해당 테마를 설정중인 컴포넌트에 아래와 같은 코드를 추가</p>
<pre><code class="language-javascript"> const [mounted, setMounted] = useState(false);

  useEffect(() =&gt; {
    setMounted(true);
  }, []);</code></pre>
<p>이 경우에도 여전히 결과가 같았다.
강력새로고침, 캐시를 비우고 빌드.. 모두 오류가 표출되었다.</p>
<p>라이브러리를 버려야 하나? 좀더 방법을 찾아봐야하나?</p>
<h2 id="결과">결과</h2>
<h3 id="해결방안">해결방안</h3>
<ul>
<li>새로고침시에만 오류 표출이되고 있어서, mounted 코드를 버릴수가 없었다. 그 상태에서 경고 무시를 하는 <code>suppressHydrationWarning</code> 속성도 추가해보았다. 혹시!? 하면서..여전히 동일한 오류가 발생하였다.</li>
<li>그리고 또 다른 방법이 있을까? 하고 구글링도 하고 다시 공식문서도 보았다.
공식문서 아래쪽에 <code>suppressHydrationWarning</code> 는 one level deep에서만 작동한다는 문구가 있어서 해당 속성의 위치를 바꿔 보았다.</li>
<li>테마프로바이더 컴포넌트 상단에 해당속성을 주었더니!! 됐다! 오류가 생성되지 않고 잘 작동되었다!!</li>
<li>그래서 html에 주었던 <code>suppressHydrationWarning</code> 속성을 지웠는데, 왜 안되죠?</li>
<li>결과적으로 내 코드는 html과 body에 각가 <code>suppressHydrationWarning</code> 속성을 주어서 해결이 되었다. 이게 맞는건가? 좀 찜찜한 해결이 되었다.</li>
</ul>
<pre><code class="language-html">&lt;html lang=&quot;ko&quot; suppressHydrationWarning&gt;
      &lt;body className={`${pretendard.variable} font-pretendard`} suppressHydrationWarning&gt;
          &lt;ThemeProvider attribute=&quot;class&quot; defaultTheme=&quot;light&quot;&gt;

              {children}

          &lt;/ThemeProvider&gt;
      &lt;/body&gt;
    &lt;/html&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/ly-ra/post/847b97f9-e56c-4dfa-9cfd-5ee361dc7217/image.gif" alt=""></p>
<hr>
<h2 id="느낀점">느낀점</h2>
<ul>
<li>리액트만 할때는 서버렌더링을 접하지 않아서 이런오류를 마주칠 일이 없었다. 이론으로는 알고있었지만 차이를 체감하기는 어려웠는데 이번 오류로 인해서 SSR과 CSR의 차이를 체감한 좋은 기회가 되었다.
앞으로도 next.js를 사용하면서 종종 만나게 될 오류일 것 같다.
컬러모드 다크모드 같은 테마들이 여기저기 많이 사용되는데 이렇게 클라이언트 상태를 확인하는 경우는 서버렌더링과의 상호작용(?) 등도 고려해야 하겠구나도 느꼈다.
이게 서버와 클라이언트의 렌더링 차이구나~</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[이건 브라우저가 잘못한건가?]]></title>
            <link>https://velog.io/@ly-ra/%EC%9D%B4%EA%B1%B4-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EA%B0%80-%EC%9E%98%EB%AA%BB%ED%95%9C%EA%B1%B4%EA%B0%80</link>
            <guid>https://velog.io/@ly-ra/%EC%9D%B4%EA%B1%B4-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EA%B0%80-%EC%9E%98%EB%AA%BB%ED%95%9C%EA%B1%B4%EA%B0%80</guid>
            <pubDate>Fri, 14 Mar 2025 18:10:44 GMT</pubDate>
            <description><![CDATA[<p>[2025.03.14 금요일]</p>
<p>[Next.js 개인과제 - 트러블슈팅]</p>
<h2 id="api-요청시-지속적으로-발생하는-500번과-400번">API 요청시 지속적으로 발생하는 500번과, 400번</h2>
<ul>
<li>Next.js 개인과제를 진행하면서 필수구현 요소중 마지막 Route Handlers를 이용한 CSR 페이지를 구현중이었다.
SSG, SSR, ISR 페이지들은 생각보다 어렵지않게 구현을 했어서, 이번에도 여럽지 않게 가겠지... 하고 지레 짐작을 한것이 화근이었다.
2개의 api를 같이 처리해 버려야지~하고 일단 코드부터 작성하고 제대로 확인을 하지 않았다. 그렇게 확인도 하지 않고 코드부터 냅다 작성한 후에 페이지를 띄워보니.. pending...화면이 나타나고 이제 자료값이 나오면되! 라고생각했는데 시간이 오래걸렸다..이상한데? 하는 순간 500번 등장.
<img src="https://velog.velcdn.com/images/ly-ra/post/ce43bd79-2a2d-40f7-86a5-385327c63399/image.png" alt=""></li>
</ul>
<h3 id="원인-파악하기">원인 파악하기</h3>
<ul>
<li>2개의 api를 모두 한 로직에 사용했기 때문에 어떤게 문제인지 확인 하기 위해서 console.log도 확인해 보았다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/ly-ra/post/d823acbd-3d9c-4c14-b87b-c9407115b1ed/image.png" alt=""></p>
<p>잘 받아오던 championList는 왜 403이 뜨는건지 모르겠지만, 지금 먼저 해결해야하는 건 rotation이었다.</p>
<h4 id="주소와-api-키-다시-확인">주소와 API 키 다시 확인</h4>
<ul>
<li><p>400이면 Bad request인데 통신로직이다 보니 주소가 맞는지, 혹시 오타가 있는지 확인부터했다. 혹시나 못찾을 수도 있기 때문에 api를 제공하는 공식페이지로가서 주소를 복사 붙여넣기! ❌ </p>
</li>
<li><p>내가 발급 받은 API키도 다시 복사해서 붙여넣기! ❌</p>
</li>
</ul>
<h4 id="환경변수가-잘-적용되는지-확인">환경변수가 잘 적용되는지 확인</h4>
<ul>
<li>Next.js에서 환경변수는 처음 사용하다 보니 혹시나 하는 마음에 환경변수가 잘 받아오고있는지도 console.log로 확인, 아주 잘 받아오고있었다.</li>
</ul>
<h4 id="postman에서도-확인해보기">Postman에서도 확인해보기</h4>
<p>전날 튜터님이 이야기해주신 부분이 생각나서 Postman을 사용하여 직접 API 요청을 해봤지만 역시 같은 400번 에러가 발생했다.
왜 Unknown apikey인가요????
<img src="https://velog.velcdn.com/images/ly-ra/post/8a1a89b6-9c5c-42d0-b2fd-637334a3a974/image.png" alt=""></p>
<h4 id="api-key-재발급-받기">API KEY 재발급 받기</h4>
<p><img src="https://velog.velcdn.com/images/ly-ra/post/36c43573-056c-4ef6-942c-b3eb5b34a706/image.png" alt="">
API Key를 재발급 받고 다시한번 Postman으로 요청을 시도해보았다.
어? 그래도 동일하게 400번 Unknown apikey가 확인되었다.</p>
<p>이상해서 숨겨진 key를 다시한번 확인해 보니 이전과 동일한 key였다. 
<img src="https://velog.velcdn.com/images/ly-ra/post/5710fd23-c4ae-4580-858c-5eb3b0e74bc7/image.png" alt=""></p>
<p>재발급을 받고, 재발급성공했다고 메시지도 확인했는데!? API Key가 실제로는 재발급되지 않은것이다 😱
다시한번 재발급해서 갱신한 후에 Key를 확인해 보니 여전히 동일한 Key였다. </p>
<h3 id="원인은-브라우저">원인은 브라우저!?</h3>
<p>순간 스쳐지나간 부분이 있었다. 오늘 내 브라우저 상태가 좋지 않았던 게 생각났다. 업데이트 이후 브라우저가 뭔가 불안정한 상태였기 때문이다. 설마 브라우저 문제가 아닐까 하는 마음으로 다른 브라우저에서 시도해보았다.</p>
<h4 id="결과는-성공이었다">결과는 성공이었다.</h4>
<p>이번에는 API 키가 제대로 재발급되었고 아까와는 다르게 재발급한 키 하단에 만료 일자와 시간까지 갱신된 것이 확인되었다.
<img src="https://velog.velcdn.com/images/ly-ra/post/aa21444d-7bd9-4216-b0cf-5feb3802b25c/image.png" alt="">
재발급한 키를 사용하니 API 요청이 정상적으로 잘 작동했다.</p>
<p><img src="https://velog.velcdn.com/images/ly-ra/post/4dcf2752-faf8-44d4-99ea-936785575a28/image.png" alt=""></p>
<p>이번 문제의 원인은 브라우저의 일시적인 오류나 캐싱 문제로 인해 API 키 재발급 과정이 정상적으로 이루어지지 않았던 것으로 보인다.</p>
<p>처음 시도하는 라우터 핸들러였기 때문에 처음엔 내가 뭔가 코드를 잘못 작성한 건 아닐까 걱정하며 강의 자료와 강의를 다시 살펴보기도 했지만, 알고 보니 브라우저의 사소한 문제가 원인이었다는 게 참 허탈하면서도 기억에 남는 트러블 슈팅 경험이 되었다.</p>
<p>그동안 나도 모르게 계속 한 종류의 브라우저만을 사용해 왔는데 이제는 다른 브라우저로도 크로스체크를 해봐야 겠다는 생각이 들었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL | 잊기 전에 다시한번! useQuery의 흐름]]></title>
            <link>https://velog.io/@ly-ra/TIL</link>
            <guid>https://velog.io/@ly-ra/TIL</guid>
            <pubDate>Thu, 13 Mar 2025 18:27:13 GMT</pubDate>
            <description><![CDATA[<p>Next.js에서 TQ복습을 하다가 나온 내용이다.
useQuery를 이해하는 중요한 내용이라서 다시한번 정리해본다.</p>
<h3 id="예시-코드">예시 코드</h3>
<pre><code class="language-javascript">&quot;use client&quot;

import { useQuery } from &quot;@tanstack/react-query&quot;;
import React from &quot;react&quot;;

const TodoListPage = () =&gt; {
    const { data: todos } = useQuery({
      queryKey: [&quot;todos&quot;],
      queryFn: () =&gt; fetch(&quot;http://localhost:4000/todos&quot;).then((res) =&gt; res.json()),
    });

      console.log(todos); // 주목

    return &lt;div&gt;&lt;/div&gt;
};

export default TodoListPage</code></pre>
<p>여러 공식 문서들에 등장하는 예시인 투두리스트 만들기다.
위와 같이 useQuery의 가장 기본적인 사용방법이다. 그리고 나서 콘솔로그를 확인해 보면 아래와 같은 결과를 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/ly-ra/post/d20bcde6-0a10-40d6-93e5-3d36190b4168/image.png" alt=""></p>
<p>db.json에 이미 4개의 데이터가 기록되어 있기기에 4개의 값이 잘 나타나고 있다.</p>
<p> <strong>여기서 useQuery를 이해하기위해서 확인해야할 부분은 자료값 위의 <code>undefined</code>이다.</strong></p>
<p>콘솔창에 보이는것처럼 처음부터 db의 자료값이 나타나는게 아니고 undefined 후에 자료가 나타난다.</p>
<p><strong>useQuery</strong>는 서버에서 데이터를 가져오는 역할을 하는데, 처음부터 무조건적으로 db의 자료를 가져오는 것이 아니다.</p>
<ol>
<li><p>캐시컨텍스트에 queryKey로 지정한 데이터가 있는지부터 확인한다.
 새로고침하면 당연히 캐시컨텍스트에 데이터가 없다. 그래서 undefined가 먼저 나타나는 것이다.</p>
</li>
<li><p>캐시컨텍스트에 데이터가 없는 것을 확인했기에 queryFn이 실행되는 것이다.</p>
</li>
<li><p>db의 데이터를 캐시컨텍스트에 저장하고 리렌더링이 된다.</p>
<ul>
<li>캐시컨텍스트의 값이 바꼇다는 것은 전역상태가 바꼈다는 것.</li>
<li>전역상태가 바꼈다는 것은 state가 바꼈다는 것.</li>
<li>state가 바꼈다는 것은 리렌더링이 된다는 것.</li>
</ul>
</li>
<li><p>리렌더링이 되었기 때문에 로직이을 다시 시작한다. 
이번에는 캐시컨텍스트에 queryKey 저장된 데이터가 있기에 이 데이터들을 불러오는 것이다.</p>
</li>
</ol>
<p>콘솔창에 출력되는 값들에 대해서는 그렇구나~ 하고 넘어갈때도 있지만, 이렇게 로직의 흐름을 보여주기 때문에 꼼꼼하게 확인하고 해석해볼 필요가 있다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL | 난 FE니까 Next.js]]></title>
            <link>https://velog.io/@ly-ra/TIL-%EB%82%9C-FE%EB%8B%88%EA%B9%8C-Next.js</link>
            <guid>https://velog.io/@ly-ra/TIL-%EB%82%9C-FE%EB%8B%88%EA%B9%8C-Next.js</guid>
            <pubDate>Wed, 12 Mar 2025 16:57:18 GMT</pubDate>
            <description><![CDATA[<p>[2025. 03. 12 수요일]</p>
<blockquote>
<p>Next.js를 배우는 과정에서 알고있어야 할 개념들에 대해서 정리해본다.</p>
</blockquote>
<h2 id="1-기존-리액트의-단점과-spa-코드-스플리팅">1. 기존 리액트의 단점과 SPA, 코드 스플리팅</h2>
<ul>
<li><p>기존에 사용하던 리액트는 <strong>SPA(Single Page Application)</strong> 방식을 사용한다. 
애플리케이션의 모든 뷰가 하나의 HTML 파일 내에서 JS로 관리되고, 클라이언트(브라우저)에서 동적으로 페이지(컴포넌트) 전환이 이루어진다.</p>
</li>
<li><p>JS의 비중이 크기 때문에 전체 애플리케이션 코드가 한꺼번에 로드될 경우 초기 로딩 속도가 느려질 수 있다. 또한 JS가 모두 로드되고 온전한 렌더링이된 후에 실행되기 때문에 초기에 사용자에게 빈 화면이 보여질 수도 있다.</p>
</li>
<li><p>클라이언트에서 렌더링되기 때문에, 검색 엔진 봇이 JS 실행 전에 빈 HTML만 보게 될 가능성이 있어 SEO 최적화에 불리하다.</p>
</li>
<li><p>위와 같은 사항들을 개선하기 위해 JS를 쪼개서 필요한 부분만 먼저 로드하는 <strong>코드 스플리팅이 필요</strong> 하다.</p>
<ul>
<li><code>React.lazy</code>, <code>next/dynamic</code> </li>
</ul>
</li>
</ul>
<hr>
<h2 id="2-hydration-ttv-tti">2. Hydration, TTV, TTI</h2>
<blockquote>
<p><strong>Hydration</strong>
서버에서 렌더링된 정적 HTML을 클라이언트(브라우저)의 JS가 가져와서, 이벤트 핸들러를 붙이는 등의 작업을 수행해 페이지를 동적으로 만드는 과정.</p>
</blockquote>
<ul>
<li>수업에서는 북어포에 물을 적신다고 설명을 했다.</li>
<li><em>정적 HTML(TTV)에 JS(TTI)를 채워*</em>, TTV와 TTI의 간극을 채우는 개념을 수(水)화라고 한다.</li>
<li>SSR로 빠르게 보여진 정적 콘텐츠를, 사용자가 상호작용할 수 있도록 전환하는 단계다.</li>
</ul>
<p><strong>TTV (Time to View)</strong></p>
<ul>
<li>사용자가 페이지를 요청한 후, 시각적으로 콘텐츠가 브라우저에 표시되기까지 걸리는 시간으로, 사용자가 콘텐츠를 인지할 수 있는 순간이다.</li>
</ul>
<p><strong>TTI (Time to Interaction)</strong></p>
<ul>
<li>페이지가 사용자가 인터렉션할수 있는 상태(클릭, 스크롤 등 사용자의 상호작용에 반응할 수 있는 상태)가 되기까지 걸리는 시간을 의미한다.</li>
</ul>
<hr>
<h2 id="3-js의-런타임-환경-2가지">3. JS의 런타임 환경 2가지</h2>
<p><strong>1) 브라우저 환경 (Client-side)</strong></p>
<ul>
<li>사용자 디바이스(브라우저)에서 실행</li>
<li>DOM, 브라우저 API 등을 이용해 UI를 조작</li>
<li>사용자와의 직접 상호작용, 이벤트 처리, 동적 페이지 업데이트</li>
</ul>
<p><strong>2) Node.js 환경 (Server-side)</strong></p>
<ul>
<li>서버에서 실행되는 자바스크립트 런타임</li>
<li>파일 시스템, 네트워크 요청 등 서버 관련 작업에 적합</li>
<li>SSR, API 서버, 빌드 도구 등</li>
</ul>
<p>Next.js는 이 두 환경을 모두 활용하여, 서버에서 초기 HTML을 렌더링하고 클라이언트에서 인터랙션을 관리합니다. 라고 한다.
두 가지 환경을 모두 활용하기 때문에 Next.js 공식문서에서 Web Server 프레임워크라고 소개하고 있다.</p>
<hr>
<h2 id="5-ssg-isr-ssr-csr의-차이점">5. SSG, ISR, SSR, CSR의 차이점</h2>
<p><strong>SSG (Static Site Generation)</strong></p>
<ul>
<li><strong>개념:</strong>
빌드 시 미리 HTML을 생성하여 정적 파일로 배포하는 방식.</li>
<li><strong>장점:</strong>
빠른 로딩 속도, 낮은 서버 부하, 높은 보안성</li>
<li><strong>적합한 경우:</strong>
콘텐츠 업데이트가 자주 일어나지 않는 페이지</li>
</ul>
<p><strong>ISR (Incremental Static Regeneration)</strong></p>
<ul>
<li><strong>개념:</strong>
SSG의 장점을 유지하면서, 특정 간격이나 조건에 따라 정적 페이지를 업데이트할 수 있는 방식.</li>
<li><strong>장점:</strong>
정적 사이트의 빠른 로딩과 함께, 최신 데이터를 제공할 수 있음.</li>
<li><strong>적합한 경우:</strong>
일부 페이지는 정적이지만, 주기적인 업데이트가 필요한 경우</li>
</ul>
<p><strong>SSR (Server-Side Rendering)</strong></p>
<ul>
<li><strong>개념:</strong>
사용자의 요청마다 서버에서 HTML을 렌더링해 제공하는 방식.</li>
<li><strong>장점:</strong>
항상 최신 데이터를 반영할 수 있고, SEO에 유리</li>
<li><strong>단점:</strong>
서버 부하 증가, 응답 시간이 빌드된 정적 파일보다 느릴 수 있음</li>
</ul>
<p><strong>CSR (Client-Side Rendering)</strong></p>
<ul>
<li><strong>개념:</strong>
브라우저에서 자바스크립트를 통해 동적으로 콘텐츠를 렌더링하는 방식.</li>
<li><strong>장점:</strong>
풍부한 인터랙티브 기능과 동적 업데이트가 용이함</li>
<li><strong>단점:</strong>
초기 로딩 시 빈 화면 또는 로딩 상태가 길어질 수 있으며, SEO 최적화가 어려울 수 있음</li>
</ul>
<hr>
<p><strong>Next.js</strong> TIP</p>
<ul>
<li>기본적으로 Server Component이며, 렌더링단위 역시 Component라는것을 인지하면 조금은 Next.js를 이해하는데 도움이 된다!!</li>
<li>Client와 Server는 상대적인 개념이라는 것도 유의하자!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL | SSR, CSR, SSG, ISR]]></title>
            <link>https://velog.io/@ly-ra/TIL-SSR-CSR-SSG-ISR</link>
            <guid>https://velog.io/@ly-ra/TIL-SSR-CSR-SSG-ISR</guid>
            <pubDate>Tue, 11 Mar 2025 14:55:06 GMT</pubDate>
            <description><![CDATA[<p>[2025. 03. 11 화요일]</p>
<p>next.js를 배우면서 렌더링에 대해서만 한참을 배우고 구분했다.</p>
<h3 id="ssr-csr-ssg-isr은-웹페이자-렌더링html이-생성되는-시점과-방식에-따라-구분된다">SSR, CSR, SSG, ISR은 웹페이자 렌더링(HTML이 생성)되는 시점과 방식에 따라 구분된다.</h3>
<p>뭐가 이렇게 많죠..?</p>
<h2 id="ssr--server-side-rendering">SSR : Server-Side Rendering</h2>
<ul>
<li>클라이언트가 요청할 때마다 서버에서 데이터를 가져와 페이지의 HTML을 생성하여 응답하는 방식이다.</li>
<li>최신 데이터가 항상 보장되나, 매 요청마다 서버에서 데이터를 가져오기 때문에 서버부담이 크다.</li>
<li>검색 엔진이 완성된 HTML을 받아들이므로, 콘텐츠가 자주 변하는 SEO를 기대할 수 있다.</li>
</ul>
<h3 id="언제-사용하면-좋을까">언제 사용하면 좋을까?</h3>
<ul>
<li><p>사용자 요청시 마다 최신 데이터를 렌더링하기 때문에 실시간 정보를 제공할 때 유리하다.</p>
<ul>
<li>은행이나 금융 서비스의 대시보드처럼 로그인한 사용자에게 개인화된 정보를 제공해야할때.</li>
<li>뉴스처럼 매번 최신 기사를 보여줘야하는 사이트, 날씨 정보 등</li>
</ul>
</li>
</ul>
<h3 id="예시-코드">예시 코드</h3>
<pre><code class="language-javascript">export default async function SSRPage() {
    const res = await fetch(&#39;https://api.example.com/data&#39;, {
    cache: &#39;no-store&#39;,
    });
    const data = await res.json();

  return &lt;div&gt;{data.message}&lt;/div&gt;
}</code></pre>
<hr>
<h2 id="csr--client-side-rendering">CSR : Client-Side Rendering)</h2>
<ul>
<li>페이지의 정적인 부분만 서버에서 HTML로 내보내고, 데이터는 브라우저에서 JS가 실행되면서 동적으로 페이지를 렌더링한다.</li>
<li>초기 HTML파일 크기가 작아서 초기 로디잉 빠르며, 이후 필요한 데이터만 가져오므로 효율적이다.</li>
<li>사용자와의 상호작용이 많은 애플리케이션에서 사용된다.</li>
<li>SEO최적화는 부족.</li>
</ul>
<h3 id="언제-사용하면-좋을까-1">언제 사용하면 좋을까?</h3>
<ul>
<li>사용자와의 상호작용이 많은 애플리케이션</li>
<li>데이터카 클라이언트에서 API호출로 가져오는 경우</li>
<li>SEO가 크게 중요하지 않은 내부 애플리케이션, 관리자 도구</li>
<li>SPA처럼 페이지 이동없이 동적으로 콘텐츠를 변경하는 웹앱.</li>
</ul>
<h3 id="예시-코드-1">예시 코드</h3>
<pre><code class="language-javascript">&#39;use client&#39;;
import { useEffect, useState } from &#39;react&#39;;

export default function CSRPage() {
  const [data, setData] = useState(null);

  useEffect(() =&gt; {
    const fetchData = async () =&gt; {
      const res = await fetch(&#39;https://api.example.com/data&#39;);
      const data = await res.json();
      setData(data);
    }

    fetchData();
  }, []);

  return &lt;div&gt;{data ? data.message : &#39;Loading...&#39;}&lt;/div&gt;;
}
</code></pre>
<hr>
<h2 id="ssg--static-site-generation">SSG : Static Site Generation</h2>
<p><em><del>쓱</del></em></p>
<ul>
<li>빌드시점에 미리 HTML파일을 생성하여 정적 파일로 배포하는 방식이다.</li>
<li>빠른 로딩 속도와 높은 성능이 중요한 정적웹사이트에서 사용.</li>
<li>단 콘텐츠 변경 빈도가 낮아야 한다.</li>
<li>SEO 효과가 좋다.</li>
</ul>
<h3 id="언제-사용하면-좋을까-2">언제 사용하면 좋을까?</h3>
<ul>
<li>개인 블로그나 포트폴리오, 회사 소개 페이지 처럼 변경이 잦지 않은 사이트.</li>
<li>React나 Next.js 등의 공식문서 처럼 정적인 문서 페이지.</li>
</ul>
<h3 id="예시-코드-2">예시 코드</h3>
<pre><code class="language-javascript">export default async function Page() {
  const res = await fetch(&#39;https://api.example.com/data&#39;, {
   cache: &#39;force-cache&#39; 
   });
  const data = await res.json();

  return &lt;div&gt;{data.message}&lt;/div&gt;;
}
</code></pre>
<hr>
<h2 id="isr--incremental-static-regeneration증분-정적-재생성">ISR : Incremental Static REgeneration(증분 정적 재생성)</h2>
<ul>
<li>SSG의 장점을 유지하면서, 일정 주기마다 또는 특정 조거에 따라 페이지를 재생성 하여 최신 콘텐츠를 반영하는 방식</li>
<li>SSG와 SSR의 중간 상태.</li>
</ul>
<h3 id="언제-사용하면-좋을까-3">언제 사용하면 좋을까?</h3>
<ul>
<li>상품 상세 페이지 : 대부분 정적 페이지로 제공되지만, 가격이나 재고정보등은 주기적으로 업데이트되어야 할 때</li>
<li>특정 시점에 정보를 갱신해야하는 사이트 (주간랭킹, 일간랭킹 등)</li>
</ul>
<h3 id="예시-코드-3">예시 코드</h3>
<pre><code class="language-javascript">const ONE_MINUTE_SECONDS = 60;

export default async function Page() {
  const res = await fetch(&#39;https://api.example.com/data&#39;, {
   next: { 
       revalidate: ONE_MINUTE_SECONDS, 
       } 
   });
  const data = await res.json();

  return &lt;div&gt;{data.message}&lt;/div&gt;;
}
</code></pre>
<hr>
<h3 id="정리">정리</h3>
<ul>
<li>각 렌던링 방식은 프로젝트의 요구사항과 데이터 업데이트 빈도, SEO 필요성 등에 따라 선택하면 될것 같다.</li>
</ul>
<table>
<thead>
<tr>
<th>렌더링 방식</th>
<th>데이터 최신성</th>
<th>성능</th>
<th>추천 사용 사례</th>
<th>SEO 필요성</th>
</tr>
</thead>
<tbody><tr>
<td>SSR</td>
<td>높음</td>
<td>중간</td>
<td>자주 바뀌는 실시간 데이터</td>
<td>SEO 유리</td>
</tr>
<tr>
<td>CSR</td>
<td>중간</td>
<td>높음</td>
<td>사용자 상호작용이 많은 앱</td>
<td>x</td>
</tr>
<tr>
<td>SSG</td>
<td>낮음</td>
<td>매우 높음</td>
<td>거의 바뀌지 않는 마케팅 페이지</td>
<td>SEO 유리</td>
</tr>
<tr>
<td>ISR</td>
<td>중간</td>
<td>높음</td>
<td>블로그나 뉴스 사이트</td>
<td></td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL | 제네릭 타입]]></title>
            <link>https://velog.io/@ly-ra/TIL-%EC%A0%9C%EB%84%A4%EB%A6%AD-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@ly-ra/TIL-%EC%A0%9C%EB%84%A4%EB%A6%AD-%ED%83%80%EC%9E%85</guid>
            <pubDate>Mon, 10 Mar 2025 14:00:31 GMT</pubDate>
            <description><![CDATA[<p>[2025. 03. 10. 월요일]</p>
<p>ts에서 <strong>타입을 변수처럼 사용하는 제네릭타입</strong>에 대해서
React에서 많이 사용하는 useState를 예시로 이해하기!!</p>
<h3 id="usestate로-이해해보기">useState로 이해해보기!</h3>
<ul>
<li>useState에 제네릭타입 사용방법<pre><code class="language-javascript">const [state, setState] = useState&lt;T&gt;([])</code></pre>
</li>
</ul>
<blockquote>
<ol>
<li>useState는 <code>(초기값)</code>을 받아서 <code>[state, setState]</code>를 반환한다.</li>
<li>_초기값은 state에 할당_되고, <strong>setState함수</strong>는 <code>(newState)</code>를 받아서 _state에 newState를 재할당_한다.</li>
</ol>
</blockquote>
<ul>
<li><p>useState훅이 내부적으로 아래와 같다고 예상을 하고 보면(실제 내부 로직 X)</p>
<pre><code class="language-javascript">const useState&lt;T&gt; = (initialState: T): [T, (newState: T)=&gt;void] =&gt; {
  let state = initialState // 초기값을 state에 할당

  const setStste = (newState: T) =&gt; {
      // 리렌더링
      state = [...state, newState]  // newState를 state에 재할당

      // 추가 로직들...

  }

  return [state, setState] // state배열을 반환
}</code></pre>
</li>
<li><p>이 과정중에서 내부 로직을 보면 <code>초기값의 타입</code>과 <code>내부 로직의 타입</code>들이 일치해야 타입스크립트에서 오류없이 useState훅이 동작할 수있다.</p>
</li>
<li><p>타입스크립트에서 각 타입을 명시해야하는 곳들을 보면 여러곳이다. 매번 타입을 작성해주는 것도 번거롭지만, 처음에 <code>&lt;number&gt;</code>로 타입을 명시 했다가 <code>&lt;string&gt;</code>으로 바꾼다고 생각을 해보면 이과정이 더 번거롭게 된다.
타입이 명시될 위치에 <strong><code>&lt;T&gt;</code></strong>라고 <strong>제네릭타입</strong>으로 적어주면 내부 로직 모든 곳에서 변수처럼 동일한 타입을 받을 수 있다.</p>
</li>
</ul>
<h3 id="제네릭타입-특징">제네릭타입 특징</h3>
<blockquote>
<ul>
<li>로직 내부적으로 <code>&lt;T&gt;</code> <strong>재사용가능</strong>하며 중복코드를 줄일 수있다.</li>
</ul>
</blockquote>
<ul>
<li>아직 결정되지 않은 Type으로 <strong>어떤타입도 받을 수 있는 변수</strong>처럼 취급할 수 있기 때문에 유연하다.</li>
<li><strong>함수가 호출될 때 타입이 결정</strong>되며 <strong>반환값도 인자타입과 동일</strong>하기 때문에 안정성도 있다.</li>
</ul>
<h3 id="제네릭타입을-사용한-유틸리티타입들도있다">제네릭타입을 사용한 유틸리티타입들도있다.</h3>
<ul>
<li>Pick&lt;T, K&gt; : T에서 K를 선택해 타입을 구성.</li>
</ul>
<pre><code class="language-javascript">interface Todo {
    title: string;
    description: string;
    completed: boolean;
}

type TodoPreview = Pick&lt;Todo, &#39;title&#39; | &#39;completed&#39;&gt;;

const todo: TodoPreview = {
    title: &#39;Clean room&#39;,
    completed: false,
};</code></pre>
<ul>
<li>Omit&lt;T, K&gt; : T에서 K를 제외하고 타입을 구성.</li>
</ul>
<pre><code class="language-javascript">interface Todo {
    title: string;
    description: string;
    completed: boolean;
}

type TodoPreview = Omit&lt;Todo, &#39;description&#39;&gt;;

const todo: TodoPreview = {
    title: &#39;Clean room&#39;,
    completed: false,
};</code></pre>
<ul>
<li>Record&lt;K, T&gt; : 특정 키 타입(K)과 값 타입(T)을 가지는 객체 타입을 구성.</li>
</ul>
<pre><code class="language-javascript">type PageInfo = {
  title: string;
};

type Page = &#39;home&#39; | &#39;about&#39; | &#39;contact&#39;;

const pages: Record&lt;Page, PageInfo&gt; = {
  home: { title: &quot;Home Page&quot; },
  about: { title: &quot;About Page&quot; },
  contact: { title: &quot;Contact Page&quot; },
};
</code></pre>
<hr>
<p>타입을 명시해줘야하는 타입스크립트를 배우면서 굳이 타입을 작성해 줘야 하나? 하는 의문도 많고 부담이 됐는데, 어려웠던 제네릭을 어느정도 이해하니 조금은 타입을 명시하는 이유를 알것같기도 하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[낙관적업데이트, 내코드도 낙관적으로 봐줄순 없었니?]]></title>
            <link>https://velog.io/@ly-ra/%EB%82%99%EA%B4%80%EC%A0%81%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EB%82%B4%EC%BD%94%EB%93%9C%EB%8F%84-%EB%82%99%EA%B4%80%EC%A0%81%EC%9C%BC%EB%A1%9C-%EB%B4%90%EC%A4%84%EC%88%9C-%EC%97%86%EC%97%88%EB%8B%88</link>
            <guid>https://velog.io/@ly-ra/%EB%82%99%EA%B4%80%EC%A0%81%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EB%82%B4%EC%BD%94%EB%93%9C%EB%8F%84-%EB%82%99%EA%B4%80%EC%A0%81%EC%9C%BC%EB%A1%9C-%EB%B4%90%EC%A4%84%EC%88%9C-%EC%97%86%EC%97%88%EB%8B%88</guid>
            <pubDate>Tue, 04 Mar 2025 17:07:50 GMT</pubDate>
            <description><![CDATA[<p>[2025.03.04 화요일]</p>
<p>[아웃소싱 프로젝트 - 트러블슈팅]</p>
<h2 id="1-문제-발생">1. 문제 발생</h2>
<h3 id="찜은-되는데-해제는-못합니다">찜은 되는데 해제는 못합니다...</h3>
<p><a href="https://tanstack.com/query/latest/docs/framework/react/guides/optimistic-updates">TanStack Query</a> 공식문서와 강의를 참고하며 찜하기(북마크) optimistic updates(낙관적 업데이트)로 리팩토링.
차근차근 따라가며 코드를 작성후 실행되는지 확인!!
오! 찜하기 기능은 정상 작동되었다.</p>
<p>자신감을 얻어서 찜해제 기능도 코드를 작성하고 실행되는지 확인!?
네트워크를 확인해 보니 400번에러 확인. 내탓, 내 코드가 문제라고 알려주고있다.
<img src="https://velog.velcdn.com/images/ly-ra/post/5a4d3336-1981-492b-aba7-876af0b5f393/image.png" alt=""></p>
<h3 id="문제-코드">문제 코드</h3>
<pre><code class="language-javascript">export const useRemoveWish = (productId) =&gt; {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: ({ wishId }) =&gt; removeWish(wishId),
    onMutate: async ({ wishId }) =&gt; {
      // 기존 쿼리 취소
      await queryClient.cancelQueries([QUERY_KEYS.WISH.LIST, productId]);
      // 현재 상태 저장 (Snapshot)
      const prevWish = queryClient.getQueryData([
        QUERY_KEYS.WISH.LIST,
        productId,
      ]);
      // 취소 UI 즉시 반영
      queryClient.setQueryData([QUERY_KEYS.WISH.LIST, productId], (old) =&gt;
        old.filter((wish) =&gt; wish.id !== wishId),
      );
      return { prevWish };
    },
    // 에러시 롤백
    onError: (err, wishId, context) =&gt; {
      queryClient.setQueryData(
        [QUERY_KEYS.WISH.LIST, productId],
        context.prevWish,
      );
    },
    onSettled: () =&gt; {
   queryClient.invalidateQueries([QUERY_KEYS.WISH.LIST, productId]);
    },
  });
};
</code></pre>
<hr>
<h2 id="2-원인-추론">2. 원인 추론</h2>
<h3 id="wishid가-undefined">wishId가 undefined</h3>
<p><code>addWish</code>는 가능하지만 <code>removeWish</code>는 되지 않는다면, 인자값을 제대로 못받고있을 가능성이 높았다.
코드를 다시 한번 검토!</p>
<hr>
<h2 id="3-해결-방안">3. 해결 방안</h2>
<h3 id="인자-구조-확인-및-정확한-값-전달">인자 구조 확인 및 정확한 값 전달</h3>
<ul>
<li><code>addWish</code> </li>
</ul>
<pre><code class="language-javascript">mutationFn: ({ userId }) =&gt; addWish({ productId, userId }),</code></pre>
<ul>
<li><code>removeWish</code></li>
</ul>
<pre><code class="language-javascript">mutationFn: ({ wishId }) =&gt; removeWish(wishId)</code></pre>
<p>두 함수의 구조를 보았을때 코드의 문제는 찾을 수가 없었다.</p>
<p>addWish는 가능하다 removeWish가 실행되지 않아 두 함수 구조만 계속 확인하고 다시 작성하고 시도해 보고 했는데...</p>
<p>여러 방법을 시도해 보다가,<code>removeWish</code>의 혹시나 하여 객채구조를 풀어주었다.</p>
<pre><code class="language-javascript">mutationFn: (wishId) =&gt; removeWish(wishId)</code></pre>
<p>정상적으로 찜해제가 가능해졌다!??
왜지??</p>
<hr>
<h2 id="4-해결-결과">4. 해결 결과</h2>
<p>코드를 작성할 당시에는 Optimistic Updates를 처음 시도하는 부분이라서, 그 부분에만 초점이 맞춰져 있었다.</p>
<p>실제 사용처를 확인해볼 생각을 못했던 것이다.
실제 함수가 사용되는 곳에서 보면</p>
<ul>
<li><p><code>addWishMutation.mutate({ productId, userId: currentUser.id })</code> 객체 형태이고</p>
</li>
<li><p><code>removeWishMutation.mutate(wishToRemove.id)</code> 단일값을 직접 전해주고 있었다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/ly-ra/post/c416db57-00cb-43ad-9039-0912f0499149/image.gif" alt=""></p>
<hr>
<h2 id="느낀점">느낀점</h2>
<p>로직을 분리했기 때문에 연관된 모든 코드들을 확인해 보았어야 했다.</p>
<p>앞으로 로직을 분리해서 관리할텐데 시야가 좁아지지 않도록 관련된 부분은 모두 훑어보는 습관을 들여야겠다.</p>
<p>전에도 그랬지만 오류가 생기면 그 코드만 보게 된다. 그리고 시야가 좁아진다. 다른 사람의 코드를 볼때보다 시야가 확 좁아진다는 것이 느껴진다. 왜 그런걸까?</p>
<p>또한 나중에 확인해보니 add와 remove를 따로 두면서 중복 검사를 하지 않아 동일한 물품도 계속 북마크에 추가가 되었다.
코드를 작성하면서도 토글로 만들었어야 했는데.. 다른페이지에서 동일한 함수를 사용하는 분이 계셔서 일단은 진행해! 라고 했던 부분이 역시나~!!
앞으로 이런 on/off 만 있는 기능들은 모두 토글로 만들어 버릴테다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이건 어쩔 수 없는 거였어요..]]></title>
            <link>https://velog.io/@ly-ra/%EC%9D%B4%EA%B1%B4-%EC%96%B4%EC%A9%94-%EC%88%98-%EC%97%86%EB%8A%94-%EA%B1%B0%EC%98%80%EC%96%B4%EC%9A%94</link>
            <guid>https://velog.io/@ly-ra/%EC%9D%B4%EA%B1%B4-%EC%96%B4%EC%A9%94-%EC%88%98-%EC%97%86%EB%8A%94-%EA%B1%B0%EC%98%80%EC%96%B4%EC%9A%94</guid>
            <pubDate>Mon, 24 Feb 2025 13:46:16 GMT</pubDate>
            <description><![CDATA[<p>[25.02.24 월요일]</p>
<p>[MBTI 테스트 - 트러블슈팅]</p>
<hr>
<h2 id="1-문제-발생"><strong>1. 문제 발생</strong></h2>
<h3 id="1-유저-정보가-업데이트되지-않는-문제">1) 유저 정보가 업데이트되지 않는 문제</h3>
<ul>
<li>유저 정보를 받아와 닉네임을 수정하는 로직을 확인하던 중, 닉네임 변경이 정상적으로 동작하지 않는 것을 발견.  </li>
</ul>
<h3 id="2-router가-작동하지-않는-문제">2) <code>Router</code>가 작동하지 않는 문제</h3>
<ul>
<li><code>PrivateRouter</code> 설정 후 정상 작동을 확인했으나, 코드 정리 과정에서 일부 파일을 <code>layout</code> 폴더로 이동.  </li>
<li>이후 <code>pathname</code>을 읽어올 수 없다는 오류가 발생.
<img src="https://velog.velcdn.com/images/ly-ra/post/2940fd7c-208d-49b1-a9f0-9fa296600247/image.png" alt=""></li>
</ul>
<hr>
<h2 id="2-원인-추론"><strong>2. 원인 추론</strong></h2>
<h3 id="1-유저-정보-업데이트-문제">1) 유저 정보 업데이트 문제</h3>
<ul>
<li><code>axios</code>를 활용한 API 서버 통신 과정에서 발생한 문제일 가능성이 높음.  </li>
<li>익숙하지 않은 로직이 포함되어 있어 놓친 부분이 있을 수 있다고 판단하여 전체 로직을 검토함.  </li>
</ul>
<h3 id="2-router-오류-발생-원인">2) <code>Router</code> 오류 발생 원인</h3>
<ul>
<li>파일 이동 과정에서 <code>import</code> 경로가 제대로 반영되지않았을 가능성이 있음.  </li>
<li><code>BrowserRouter</code>, <code>Routes</code>, <code>Route</code> 설정이 올바르게 유지되었는지 확인이 필요함.  </li>
<li><code>Link</code>, <code>Navigate</code>, <code>useNavigate</code> 등의 경로 설정이 제대로 반영되었는지도 점검해야 함.  </li>
</ul>
<hr>
<h2 id="3-해결-방안"><strong>3. 해결 방안</strong></h2>
<h3 id="1-유저-정보-업데이트-문제-1">1) 유저 정보 업데이트 문제</h3>
<ul>
<li>로직을 다시 점검하고, 강의 시간에 배운 코드로 변경하여 테스트함. 그러나 여전히 업데이트가 되지 않음.  </li>
<li>데이터 통신 과정에서 놓친 부분이 있을 것으로 판단하여, <code>console.log</code>를 이용해 요청 및 응답 데이터를 전반적으로 확인함.  </li>
</ul>
<h3 id="2-router-오류-해결">2) <code>Router</code> 오류 해결</h3>
<ul>
<li><code>path</code> 경로를 다시 확인하고, <code>Link</code>, <code>Navigate</code>, <code>useNavigate</code> 등의 경로 설정을 모두 점검함.  </li>
<li>라우터 설정이 올바르게 동작하는지 확인하기 위해 기존의 코드와 비교 검토함.  </li>
</ul>
<hr>
<h2 id="4-해결-결과"><strong>4. 해결 결과</strong></h2>
<h3 id="1-유저-정보-업데이트-문제-해결">1) 유저 정보 업데이트 문제 해결</h3>
<ul>
<li><p>업데이트 로직에서 데이터를 받아오지 못하는 것을 확인함.  </p>
</li>
<li><p>업데이트 버튼을 클릭한 직후 일시적으로 오류가 발생했다가 사라지는 현상을 발견함.  </p>
</li>
<li><p>URL 뒤에 설정하지 않은 <code>?</code> 문자가 붙어있는 것을 확인.
위 상황들을 토대로 업데이트 로직이 정상적으로 실행되지 않았음을 인지함.</p>
</li>
<li><p>업데이트 로직 시작 전 부터 전체적으로 재검토.</p>
</li>
<li><p><code>e.preventDefault()</code> 오타가 있었던 것을 확인!  </p>
</li>
<li><p>올바르게 수정한 후 테스트해보니 정상적으로 작동함.<br><strong>&quot;설마… 너 하나 때문이었니?&quot;</strong> 😂  </p>
</li>
</ul>
<h3 id="2-router-문제-해결">2) <code>Router</code> 문제 해결</h3>
<ul>
<li>원인을 찾지 못하여 구글링해보았으나 <code>react-router-dom</code> 버전 오류관련 내용이며 내가 사용하는 버전에 관해서는 찾아볼수 없었음.  </li>
<li>다행히 <code>GitHub</code>에 정상 작동하는 코드를 푸시해둔 상태였기 때문에, 현재 작업 중인 폴더를 삭제하고 새롭게 <code>clone</code>하여 다시 확인함.  </li>
<li>결과적으로 <strong>정상적으로 동작하는 것을 확인</strong>…  
<strong>&quot;화난다&quot;</strong> 😤  </li>
</ul>
<hr>
<h2 id="느낀-점"><strong>느낀 점</strong></h2>
<p>이번 프로젝트를 진행하면서, 몇몇 확장 프로그램 설치 문제로 인해 <code>VSCode</code>를 업데이트해야 했고, 이 과정에서 이전에는 마주치지 않았던 <code>config</code> 파일이나 <code>eslint</code> 설정 문제를 겪었다. 또한, <code>Router</code>처럼 리액트 내부 로직이 예상치 않게 꼬이는 경험을 하며 많은 어려움을 느꼈다.  </p>
<p>최근 발생한 오류들을 되돌아보면, 로직 자체의 문제보다는 <strong>공백, 오타 같은 사소한 실수</strong>가 더 많았다. 그리고 오늘 <code>Router</code> 문제처럼 <strong>리액트 내부 로직이 꼬이는 상황</strong>에서는, 정상적으로 동작하는 코드가 있다면 <strong>망설이지 말고 빠르게 <code>add → commit → push</code> 하자!</strong><br>역시 직접 겪어봐야 진짜 깨닫는다. 😅  </p>
<p>서버 통신 개념은 처음엔 굉장히 어렵게 느껴졌지만, 개인 과제를 진행하면서 점차 이해되는 부분도 있고, 여전히 어려운 부분도 있다. 개인 과제가 끝나면 바로 팀 프로젝트가 시작되는데, <strong>그때까지도 흐름이 어렵다고 느껴진다면 기초를 탄탄하게 다지기 위해 분반 변경 신청도 고려해볼 생각</strong>이다.  </p>
<p>🔥 <strong>이제야 정상 작동되지만, 개발은 역시 쉽지 않다. 그래도 차근차근 쌓아가면 분명 더 나아질 거라 믿는다!</strong> 🚀  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[저도 Tailwind 써보고 싶습니다!]]></title>
            <link>https://velog.io/@ly-ra/%EC%A0%80%EB%8F%84-Tailwind-%EC%8D%A8%EB%B3%B4%EA%B3%A0-%EC%8B%B6%EC%8A%B5%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@ly-ra/%EC%A0%80%EB%8F%84-Tailwind-%EC%8D%A8%EB%B3%B4%EA%B3%A0-%EC%8B%B6%EC%8A%B5%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Thu, 20 Feb 2025 15:02:21 GMT</pubDate>
            <description><![CDATA[<p>[25.02.20 목요일]   <br />
[MBTI 테스트 - 트러블슈팅]</p>
<p>리액트 심화주차 수강을 힘들게 마치고 개인과제를 셋팅하던 중에 문제가 발생!!
셋팅과정부터 문제가 발생하다니..🤦‍♀️</p>
<h2 id="🚨-문제-발생">🚨 문제 발생</h2>
<p>Tailwind CSS를 설정한 후 정상적으로 적용되었는지 확인하는 과정에서 <code>unknown at rule @tailwind</code>라는 경고 메시지를 확인했다. 이는 오류가 아닌 경고였기 때문에 혹시나 하는 마음에 Tailwind를 적용해 보았으나, 스타일이 정상적으로 반영되지 않았다.
<img src="https://velog.velcdn.com/images/ly-ra/post/3cc49fe3-662c-41f6-9446-65d719914004/image.png" alt=""></p>
<p>팀원들에게 동일한 현상이 있었는지 확인하고, 구글링을 통해 <code>unknown at rule @tailwind</code> 문제를 해결했다. 해결 방법 중 하나로 <strong>PostCSS Language Support</strong> 익스텐션을 설치했으나 
 <img src="https://velog.velcdn.com/images/ly-ra/post/9db7a111-352b-4ab2-a3f7-05a956cc73be/image.png" alt=""></p>
<p>여전히 Tailwind가 적용되지 않았다.
<img src="https://velog.velcdn.com/images/ly-ra/post/81780fb7-915b-4315-a4e6-b87763b187f3/image.png" alt=""></p>
<hr>
<h2 id="1-원인-추론">1. 원인 추론</h2>
<ul>
<li>처음 사용하는 CSS 프레임워크이므로 설치 과정과 설정에서 오류가 발생했을 가능성이 높았다. 그래서 다시 한 번 설치 과정을 점검하고, 설정한 파일들에서 오타가 있는지 확인했다.</li>
</ul>
<h3 id="2-해결-방안">2. <strong>해결 방안</strong></h3>
<ul>
<li><code>package.json</code>에 설치된 Tailwind CSS의 버전을 확인했다.<ul>
<li>버전은 발제에서 제시한 버전과 같았다.</li>
</ul>
</li>
<li><code>tailwind.config.js</code> 파일과 <code>postcss.config.js</code> 파일의 설정을 점검했다.</li>
<li><code>main.jsx</code>에서 <code>index.css</code>가 올바르게 import되었는지 확인했다.<ul>
<li>모든 항목을 점검하고 다시 import까지 확인했으나 문제는 해결되지 않았다.</li>
</ul>
</li>
</ul>
<h3 id="3-결과">3. <strong>결과</strong></h3>
<p>결국 <strong>강의에서 제공한 코드를 그대로 복사하여 붙여넣었더니 정상적으로 작동</strong>했다.
&quot;왜 갑자기 되는 거지?&quot; 하고 깃(Git) 변경 사항을 확인해 보니, <strong>tailwind.config.js 파일에서 파일명 앞뒤의 공백 차이</strong>가 문제였다.</p>
<pre><code class="language-js">// 문제 발생 코드
content: [&quot; ./src/**/*.{js, jsx,ts,tsx} &quot;]

// 정상 작동 코드
content: [&quot;./src/**/*.{js,jsx,ts,tsx}&quot;]</code></pre>
<table>
<thead>
<tr>
<th align="center">문제 발생 코드</th>
<th align="center">정상 작동 코드</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><img src="https://velog.velcdn.com/images/ly-ra/post/54e561c3-d607-4f5a-a8b4-c98fca5019ce/image.png" alt=""></td>
<td align="center"><img src="https://velog.velcdn.com/images/ly-ra/post/11fc5c05-fd61-46eb-b849-833528826f2b/image.png" alt=""></td>
</tr>
</tbody></table>
<p>🪄 <strong>공백을 제거한 후</strong> Dev 서버를 다시 실행하니 매우 정상적으로 작동하는 것을 확인했다. 
<img src="https://velog.velcdn.com/images/ly-ra/post/4fcf982b-ce9d-4373-9c5b-02b4849d62a1/image.png" alt=""></p>
<p><strong>Config 파일 내 공백이 이렇게 큰 영향을 미칠 줄은 몰랐다!</strong> 튜터님들조차 새롭게 알게 된 사실이었다.</p>
<hr>
<h3 id="느낀-점"><strong>느낀 점</strong></h3>
<p>해당 부분은 Prettier에서도 자동으로 수정되지 않는 영역이었다. 파일을 점검하는 과정에서 나도 모르게 공백을 입력했을 가능성이 높았다.</p>
<p>Tailwind가 적용되지 않는 원인을 찾겠다고 여러 개의 config 파일을 열어둔 채 다른 작업을 진행하면서, 키보드를 실수로 눌러 의미 없는 글자가 입력되었고, 그로 인해 추가적인 오류 메시지를 마주치기도 했기 때문이다.</p>
<p><strong>Config 파일을 수정하거나 확인할 때는 반드시 원본을 저장하거나, 가급적 건드리지 않는 것이 중요하다...! 😭</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[뉴스피드 프로젝트 KPT]]></title>
            <link>https://velog.io/@ly-ra/%EB%89%B4%EC%8A%A4%ED%94%BC%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-KPT</link>
            <guid>https://velog.io/@ly-ra/%EB%89%B4%EC%8A%A4%ED%94%BC%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-KPT</guid>
            <pubDate>Tue, 18 Feb 2025 12:02:09 GMT</pubDate>
            <description><![CDATA[<p>[25.02.18 화요일]</p>
<h2 id="1-keep-잘했던-점--유지하고-싶은-것">1. Keep (잘했던 점 / 유지하고 싶은 것)</h2>
<p>✅ 개인적인 성과</p>
<ul>
<li>회원가입 자료를 받아서 Supabase에 기록</li>
<li>협업을 위한 공통 양식 제시</li>
<li>필수 구현 사항 완성</li>
</ul>
<p>✅ 팀의 성과</p>
<ul>
<li>유연하고 원활한 소통과 수용, 방향성&amp;레퍼런스 제시 (비판, 비난 없이)</li>
<li>필수 구현 사항 완성</li>
<li>발표 준비부터 발표까지 완성도 높았음</li>
</ul>
<h2 id="2-problem-개선이-필요하거나-아쉬웠던-점">2. Problem (개선이 필요하거나 아쉬웠던 점)</h2>
<p>⚠️ 개인적인 아쉬움</p>
<ul>
<li>확인이 필요한 부분은 시간이 소요되더라도 정확하게 진행해야 함</li>
<li>내가 맡은 부분을 명확하게 이해하지 못한 점이 아쉬움</li>
</ul>
<p>⚠️ 팀 차원의 문제</p>
<ul>
<li>팀 기획 단계에서 디테일을 신경 쓰지 못함 (첫 진행에 따른 시행착오)</li>
<li>스타일, 컴포넌트, 자료 조사 부족 → 레퍼런스 부족</li>
<li>GitHub 사용 시, 각자 맡은 1개의 브랜치만 사용하도록 개선 필요</li>
<li>여러 사람이 하나의 파일을 동시에 수정하는 문제 발생 방지</li>
</ul>
<h2 id="3-try-앞으로-시도해보고-싶은-것">3. Try (앞으로 시도해보고 싶은 것)</h2>
<p>🌟 새로운 도전</p>
<ul>
<li>SNS 가입 기능 추가</li>
<li>회원가입 유효성 검사 강화</li>
<li>게시글 검색 기능</li>
<li>하드코딩 및 매직넘버 리팩토링</li>
<li>컴포넌트화 진행</li>
<li>댓글 개수 표시 기능</li>
<li>게시글에 이미지 추가</li>
<li>마크다운 작성 기능 추가 (GitHub처럼)</li>
<li>피드 읽기 시 발생하는 문제 해결</li>
<li>Supabase를 활용한 개인 프로젝트 진행</li>
<li>트러블슈팅 기록 남기기</li>
<li>관리자 페이지 제작</li>
</ul>
<p>🌟 문제 해결 방안</p>
<ul>
<li>Supabase 공식 문서 참고하며 실습 진행</li>
<li>React 마크다운 라이브러리 활용</li>
<li>사용자 개선을 위한 Validation 레퍼런스 확인 및 명확한 적용</li>
</ul>
<h2 id="4-자유-메모">4. 자유 메모</h2>
<p>📌 프로젝트와 관련하여 추가로 남기고 싶은 생각</p>
<ul>
<li>프로젝트 시작 시 초기 세팅 담당자 지정</li>
<li>90% 이상 완료 후 클론 받아 진행</li>
<li>플러그인 버전 맞추기, 환경 변수 설정, .gitignore, 개발 환경 버전 정리, Prettier 설정, ERD, 컨벤션 체크</li>
<li>Vercel 배포 제한(하루 100번) 관련 안내 필요</li>
<li>기획 단계에서 충분한 레퍼런스 조사 및 기록</li>
<li>Notion &amp; GitHub의 Project, Milestone 기능 활용</li>
<li>기획 단계의 기록이 README 및 발표 자료로 활용 가능하여 후작업 절감</li>
<li>코드 유지보수성 및 재사용성 고려</li>
<li>하드코딩 및 매직넘버 최소화</li>
<li>재사용성이 높은 부분은 커스텀 훅 적극 활용</li>
<li>프론트엔드 개발자로서 서버 통신 고려</li>
<li>사용자 경험(UX)을 고려한 인터페이스 설계</li>
<li>주석 처리 공통화</li>
<li>특정 기호 사용 또는 주석 작성 기준 통일</li>
<li>CSS 스타일 가이드라인 마련</li>
<li>공통적으로 사용할 모드, 폰트 크기, 색상, 마진, 패딩 등 사전 정의</li>
<li>컴포넌트 담당자 지정</li>
<li>추가적으로 필요한 라이브러리 사전 조사 및 설치</li>
<li>버그 발생 가능성이 높은 부분에 대해 극단적 테스트 진행</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Team Project] 뉴스피드 제작기 day3]]></title>
            <link>https://velog.io/@ly-ra/Team-Project-%EB%89%B4%EC%8A%A4%ED%94%BC%EB%93%9C-%EC%A0%9C%EC%9E%91%EA%B8%B0-day3</link>
            <guid>https://velog.io/@ly-ra/Team-Project-%EB%89%B4%EC%8A%A4%ED%94%BC%EB%93%9C-%EC%A0%9C%EC%9E%91%EA%B8%B0-day3</guid>
            <pubDate>Fri, 14 Feb 2025 22:24:04 GMT</pubDate>
            <description><![CDATA[<p>[25.02.14 금요일]</p>
<h2 id="🤝-정말-협업">🤝 정말 협업</h2>
<ul>
<li>혼자서 사용하던 깃허브를 공통으로 사용하면서 브랜치전략에 대해서 체감하는 중.</li>
<li>협업 과정중의 pull, push, Conflict, rebase, merge 방법등을 실제 사용해보고 겪어보니 진짜협업이구나.</li>
<li>내가 제작하지도 않은 페이지가완성되고 있다.</li>
<li>나에게 코드리뷰와 merge권한이 생기다.</li>
<li>작업 과정중의 의사 소통.</li>
<li>공통셋팅 룰은 계속해서 새로새로 생긴다. 처음에 잘 정해두자.</li>
<li>supabase 그녀석!</li>
</ul>
<h3 id="🌿-첫-db활용-프로젝트">🌿 첫 DB활용 프로젝트</h3>
<ul>
<li>팀플 같은 팀플을 처음하는 중인데, DB를 사용하라는 지령이 내려졌다.</li>
<li>수파베이스를 사용하여 DB기반으로 CRUD구성.</li>
<li>DB도 처음 사용해 보고, 셋팅하고, P.K와 F.K구분 잘해서 정의하기.</li>
<li>속성값 설정.</li>
<li>auth.users -&gt; public.users 저장하기(트리거 활용)</li>
</ul>
<h4 id="트리거-함수sql">트리거 함수(sql)</h4>
<p>가장 큰 고비중 하나였던 트리거!
회원가입할때 기본 email, password는 auth 스키마의 users로 자동으로 저장되지만, public스키마에 설정한 우리의 커스텀 테이블과는 관계형으로 이용하기가 힘들다. 개인정보가 저장되는 부분이기 때문이다.
그래서 public 스키마에 users 테이블을 추가하여 auth.users의 값을 일부 넣어줘야 하는데 생각대로 되지 않아았다.
어제 만든 회원가입 페이지에서 테스트중인데 auth.users에만 값이 저장이 되고 public.users에는 저장이 되지 않아 한참을 찾았다. 
강의 도중에 &quot;트리거&quot; 함수를 추가해야 한다고해서 찾아보았는데, ?? 내가 생각한 리액트 함수가 아닌 sql함수였던 것이다.
당황했다. 튜터님들도 구체적으로 알려주지 않고 찾아서 해보라고만 이야기해서.. 일단 진행..20분, 30분... 과제 진행 기간이 짧기 때문에 더이상은 고민하지 못하고 튜터님을 찾아가려했는데!!
다행히도 팀에 DB를 유경험자가 있어서 여기서 진행이 거의 막힐 상황이었는데 구세주 처럼 등장!! 생각보다는 수월하게 해결!</p>
<p>추가하는 트리거에 대해서만 참고용으로 기록</p>
<pre><code class="language-sql">DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
DROP FUNCTION IF EXISTS public.handle_new_user;

CREATE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO public.users(user_id, email, name, nickname, created_at)
  VALUES (
    NEW.id,
    NEW.email,
    COALESCE(NEW.raw_user_meta_data-&gt;&gt;&#39;name&#39;, &#39;default_name&#39;), -- NULL 방지
    COALESCE(NEW.raw_user_meta_data-&gt;&gt;&#39;nickname&#39;, &#39;default_nickname&#39;),
    NEW.created_at
  )
  ON CONFLICT (user_id) DO NOTHING;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW
EXECUTE FUNCTION public.handle_new_user();</code></pre>
<p>테스트해서 정상적으로 자료가 저장이 되는 것을 확인했다.
나중에 이걸 다시 사용하려고 할때 수정해서 사용해봐야지.
그리고 공식문서도 계속 읽어면서 앞으로의 코드를 짜야겠다.
공식문서를 참조하지 않으면 js에서 사용이 매우 힘들겠다 라고 느꼈다.
내일은 공식문서 확인하면서 코딩해보자!!</p>
<p><a href="https://supabase.com/docs/reference/javascript/start?queryGroups=platform&amp;platform=yarn">https://supabase.com/docs/reference/javascript/start?queryGroups=platform&amp;platform=yarn</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Team Project] 뉴스피드 제작기 day2]]></title>
            <link>https://velog.io/@ly-ra/Team-Project-%EB%89%B4%EC%8A%A4%ED%94%BC%EB%93%9C-%EC%A0%9C%EC%9E%91%EA%B8%B0-day2</link>
            <guid>https://velog.io/@ly-ra/Team-Project-%EB%89%B4%EC%8A%A4%ED%94%BC%EB%93%9C-%EC%A0%9C%EC%9E%91%EA%B8%B0-day2</guid>
            <pubDate>Thu, 13 Feb 2025 19:16:07 GMT</pubDate>
            <description><![CDATA[<p>[25.02.13 목요일]</p>
<p>이번 팀프로젝트의 주요 목적은 <strong>협업</strong>과 <strong>Git &amp; GitHub</strong> 사용 방법에 익숙해지는 것이다.
어제 협업을 위한 여러 규칙들을 정했지만, 추가로 정해야 할 규칙들이 예상보다 많았다.</p>
<h2 id="깃허브-컨벤션">깃허브 컨벤션</h2>
<p>항상 개인과제를 하면서 &#39;나&#39; 혼자 사용하던 레포지토리를 공동으로 사용하는 부분에서는 모두 익숙하지 않았다.</p>
<p>팀원들이 같이 코드를 작성하지만 누가 작성한 코드인지 특정하기 힘들게 하는 것이 목적! 그러기 위해 정할 룰이 생각보다도 많이 남아 있었다.</p>
<h3 id="커밋-컨벤션">커밋 컨벤션</h3>
<ul>
<li>커밋 타입을 세부적으로 나누기보다는 비슷한 범주끼리 묶어서 혼란과 고민을 줄이는 방향으로 규칙을 정했다.</li>
<li>우리가 만든 규칙이 일반적으로 많이 사용되는 부분이 아닌것도 있다. 라는 튜터님의 피드백이 있었지만, 각자 사용하던 방식과 사용빈도가 달랐기 때문에 최대한 혼란과 고민을 줄이는 방향으로 정했다는 이유를 설명했다. 튜터님은 이에 대해서 그렇게 팀원들과 정했다면 그대로 진행하면 된다고 답변을 주셨다.</li>
</ul>
<h4 id="우리조의-커밋-컨벤션">우리조의 커밋 컨벤션</h4>
<table>
<thead>
<tr>
<th>커밋타입</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>feat</td>
<td>새로운 기능 추가 (변수, 함수 선언+호출)</td>
<td>feat: 회원가입 로직 추가</td>
</tr>
<tr>
<td>fix</td>
<td>버그수정</td>
<td>fix: 로그인 시 비밀번호 검증 오류 해결</td>
</tr>
<tr>
<td>docs</td>
<td>문서수정</td>
<td>docs: README에 프로젝트 개요 추가</td>
</tr>
<tr>
<td>style</td>
<td>UI(css등) 디자인 추가/수정</td>
<td>style: 버튼 추가</td>
</tr>
<tr>
<td>refactor</td>
<td>코드 리팩토링, 성능개선(기능변경없이 구조 개선)</td>
<td>refactor: 홈 화면 UI 로직 개선</td>
</tr>
<tr>
<td>test</td>
<td>테스트 관련 코드(누락된 테스트 추가, 리팩토링 테스트 등)</td>
<td>test: 유닛 테스트 추가</td>
</tr>
<tr>
<td>chore</td>
<td>빌드 업무, 패키지 매니저 설정, 기타 자잘한 수정</td>
<td>chore: package.json 버전 업그레이드</td>
</tr>
<tr>
<td>add</td>
<td>없던 파일을 생성함, 초기 세팅 (기능제외 rafce)</td>
<td>add: 컴포넌트.jsx 생성</td>
</tr>
</tbody></table>
<h3 id="브랜치-컨벤션">브랜치 컨벤션</h3>
<ul>
<li>브랜치명 kebab-case로 작성</li>
</ul>
<table>
<thead>
<tr>
<th>브랜치명</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>main</td>
<td>배포용 브랜치</td>
<td>main</td>
</tr>
<tr>
<td>dev</td>
<td>통합(개발)용 브랜치</td>
<td>develop</td>
</tr>
<tr>
<td>feature/</td>
<td>기능 개발 브랜치</td>
<td>feature/login</td>
</tr>
<tr>
<td>bugfix</td>
<td>버그 수정 브랜치</td>
<td>bugfix/nav-bar</td>
</tr>
<tr>
<td>chore/</td>
<td>문서, 설정파일 변경 브랜치</td>
<td>chore/update-readme</td>
</tr>
<tr>
<td>docs/</td>
<td>문서 작업 브랜치</td>
<td>docs/api-guide</td>
</tr>
<tr>
<td>refactor/</td>
<td>리팩토링 브랜치</td>
<td>refactor/homepage-ui</td>
</tr>
</tbody></table>
<h2 id="각종-코드-컨벤션">각종 코드 컨벤션</h2>
<ul>
<li>변수명, 함수명, 상수명, boolean</li>
<li>파일/폴더명</li>
<li>컴포넌트/styled-components</li>
<li>이벤트 핸들러 prefix</li>
<li>styled-components profix, 사용방식</li>
<li>주석 작성 방식</li>
</ul>
<p>위내용들 처럼 여러가지 컨벤션을 정했더라도, 작업을 하다보니 여러가지 예외사항들이나 추가사항들이 생기게 되어 그때 그때 규칙을 추가 설정 하기도 하였다.</p>
<p>반복사용되는 issues등은 템플릿으로 등록하여 사용하기도 하였다.</p>
<hr>
<h3 id="급하다-급해">급하다 급해</h3>
<p>작업을 할수 있는 물리적인 기간이 많지않은데, 협업을 위해 생각보다 많은 것들을 정해야하고 시간소요도 많이 되다 보니 슬슬 다들 마음이 급해졌다.</p>
<p>어느정도 룰이 정해지고난 후 각자 담당한 페이지의 작업을 시작했다.</p>
<p>내가 담당한 login 페이지와 sign up 페이지는 다른 팀원과 작업 내용이 많이 겹쳐서 함께 작업하기로 했다.</p>
<p>회원가입을 통해 users DB로 로그인 여부를 확인하고 커뮤니티 활동 권한이 주어지기 때문에, 다른 팀원들의 페이지에도 영향을 미친다. 따라서 이 부분을 최우선으로 작업하여 전달하기로 했다.</p>
<p>기본 input과 button 구성을 먼저 만들어 팀원에게 전달하면, 팀원이 UI 스타일링을 맡고 나는 기본 로직의 틀을 구성했다.</p>
<p>데이터베이스 간의 속성값 정의가 아직 완료되지 않아서, 기본 로직의 틀과 Supabase 연동까지만 마무리하고 나머지 기능 구현은 내일 완성하기로 했다.
<img src="https://velog.velcdn.com/images/ly-ra/post/41a416e5-b88c-432d-a2a9-7cbdcd96dcca/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Team Project] 뉴스피드 제작기 day1]]></title>
            <link>https://velog.io/@ly-ra/Team-Project-%EB%89%B4%EC%8A%A4%ED%94%BC%EB%93%9C-%EC%A0%9C%EC%9E%91%EA%B8%B0-day1</link>
            <guid>https://velog.io/@ly-ra/Team-Project-%EB%89%B4%EC%8A%A4%ED%94%BC%EB%93%9C-%EC%A0%9C%EC%9E%91%EA%B8%B0-day1</guid>
            <pubDate>Wed, 12 Feb 2025 18:19:47 GMT</pubDate>
            <description><![CDATA[<p>[25.02.12 수요일]</p>
<p><strong>BasS를 활용한 프론트엔드 개발 팀프로젝트</strong></p>
<ul>
<li>React와 Supabase를 활용한 CRUD 기능구현</li>
<li>github 협업</li>
</ul>
<hr>
<h3 id="1-주제-선정-및-아이디어-도출">1. <strong>주제 선정 및 아이디어 도출</strong></h3>
<p><strong>King&#39;s Ear is (a.k.a  KEI)</strong>👂👑
꿀팁, 질문, 코드리뷰, 잡담등 내배캠 대원들의 대나무숲 커뮤니티 🎋
<img src="https://velog.velcdn.com/images/ly-ra/post/ad9f298a-c85d-4f15-a087-0e4d2203a9ed/image.png" alt=""></p>
<p>처음에는 각자가 생각한 아이디어들을 자유롭게 기록한 후, 그 중 겹치는 부분들을 추려내어 공통된 기능과 아이디어를 뽑았다.</p>
<p>우리가 논의한 주제들 중에는 학습 질문, 꿀팁 모음, 목표 설정 공유, 콘텐츠 플랫폼 등이 있었다. 여러 아이디어들 중에서도 &#39;임금님 귀는 당나귀 귀&#39;와 &#39;대나무숲&#39; 커뮤니티 페이지 제작 아이디어가 흥미롭다고 판단되어 이를 기반으로 프로젝트를 진행하기로 결정!
비슷한 다른 커뮤니티 페이지들을 레퍼런스로 참고하여 필요한 기능들을 추출하고, 우리가 구현할 기능들을 세부적으로 계획했다.</p>
<h3 id="2-기능-구성과-역할-분담">2. <strong>기능 구성과 역할 분담</strong></h3>
<p>필요한 페이지들을 우선적으로 나누고, 각 페이지에서 제공해야 할 주요 기능들을 나열하며 기획을 시작했다. 
그중에서 <strong>회원가입</strong>에 필요한 정보를 제시하던중 평소 다른사이트 등에서 예상보다 많은 정보를 요구해서 왜 이렇게 많은 정보가 필요한지 의문이 들었으나, 우리가 직접 구현을 진행하려다보니 그 이유를 명확하게 알게 되었다. 
특정 조건에 맞는 유저를 찾아야 할 경우, 유저가 제공한 정보와 데이터베이스에서 대조할 수 있는 정보가 많아야 하며, 이를 통해 정확한 유저 관리가 가능하다는 점을 이해하게 되었다.</p>
<p>또한, 뉴스피드 페이지가 <strong>커뮤니티</strong>와 <strong>소셜 기능</strong>을 중심으로 다루기 때문에, user DB를 제외하고도 생각보다 많은 DB가 필요하다는 것을 진행하며 알게 되었다.</p>
<h3 id="3-기획-페이지-작성-및-ui-설계">3. <strong>기획 페이지 작성 및 UI 설계</strong></h3>
<p>프로젝트 진행 초기에는 <strong>Notion</strong>을 활용하여 기획 페이지를 작성하고, 필요한 모든 기능과 내용을 정리했습니다. 이를 바탕으로 각자 파트를 나누어 Login전, 후를 기준으로 <strong>Figma</strong>로 UI 디자인을 구현했습니다. 처음 Figma를 사용해보는 팀원들도 있었지만, 서로 협력하며 배워가면서 즐겁게 진행할 수 있었습니다. 각자 한 페이지씩을 맡고, 피드백을 주고받으며 디자인을 완성해 나갔습니다.</p>
<p>&quot;템플릿 퍼가요<del>&quot;, &quot;로고있으신분</del>&quot; 재밌는 과정이었다.
UI디자인을 끝내고 나서도 무언가 하나씩 놓친부분들이 보여서 나중에 보완 작업을 진행했고, 은근히 세세한 부분들이 많아서 페이지 연동등에 혼동이 올 수도 있단 생각에 흐름도를 나중에 추가 하기도 했다.
<img src="https://velog.velcdn.com/images/ly-ra/post/cf21b1d3-2a17-4475-8e61-e45d45724447/image.png" alt=""></p>
<h3 id="4-각자의-역할-분담과-진행-과정">4. <strong>각자의 역할 분담과 진행 과정</strong></h3>
<p>이 외에도 팀으로 진행을 하다보니 생각보다 정해야하는 것들이 많았다. 
깃허브, supabase 협력자등록, 기본 셋팅, 데이터베이스 셋팅, 코드컨벤션, 깃허브 룰 등...</p>
<p>초반에는 팀장이 진행을 해서 초대를 해야하는 초기 작업들이 많기는 했으나, 화면공유를 하면서 서로조율해가면서 진행하였다.
사공이 많으면 배가 산으로 간다고하는데 우리팀원들은 서로 의견을 내면 수용하고 이해하면서 절충안은 매너있게 잘 제시하기도 하고 좋은 분위기에서 진행되었다.
BasS를 활용한 프로젝트가 처음이다 보니 업무 분담을 나눌지 명확한 기준을 제시할 정보가 없어서 페이지별로 역할분담을 나누기로 했다.
담당할 페이지는 다들 지원을 하지 않아서 원활한 진행과 형평성(?)을 고려하여 사다리타기로 정했다.
나는 로그인페이지와 기능을 담당하게 되었는데, 회원가입페이지와 기능을 담당한 팀원과 같이 진행하기로 하였다.</p>
<p>관계형 DB를 다뤄본 경험이 없어, DB 설계에 어려움이 있었지만 팀원들의 집단지성과 마지막 튜터님의 확인과정을 거쳐서 작업환경이 만들어졌다.
깃허브 초기 셋팅에 보완점이 확인되어 이부분은 다시한번 셋팅을하고 작업에 들어가야 한다.</p>
<hr>
<h3 id="느낀점">느낀점</h3>
<p>팀으로 진행한다는것이 혼자서 할때도 거치는 과정이긴 하지만 해야하고 정해야할 것들이 훨씬 많다는 것을 느꼈다.
이전 팀 프로젝트를 할 때도 느낀부분이긴 하나, 다른사람과 협력을 한다는 것은 생각보다 상대방과 많은 대화와 의견을 주고받아야 하는 부분이라 이번 팀프로젝트에서도 &quot;소통&quot;을 어떻게 하느냐에 따라 진척도가 많이 달라지겠다고 생각된다.
다행히 기획단계에서 우리팀은 우리 팀은 소통을 잘 하고 서로를 많이 배려하며, 기분나쁘지 않게 의견을 표현하고 수용하는점이 너무 좋았다.
평소에는 마이크활용을 잘 하지 않더라도, 소통이 중요한 이 시점에서 마이크를 켜고 목소리를 내는 것에 대한 용기에 정말 감사하다!
발표준비도 해야하기 때문에 제작기간이 빠듯하긴 하지만 시작이 느낌이 좋은팀이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL | Link 와 Navigate]]></title>
            <link>https://velog.io/@ly-ra/TIL-Link-%EC%99%80-Navigate</link>
            <guid>https://velog.io/@ly-ra/TIL-Link-%EC%99%80-Navigate</guid>
            <pubDate>Tue, 11 Feb 2025 14:32:36 GMT</pubDate>
            <description><![CDATA[<p>[25.02.11 화요일]</p>
<p>개인과제 제출 마감기한이 지나고 해설 영상을 확인 하던중, 내가 정확하게 모르고 그냥 사용한 부분이 있음을 알게 되었다.</p>
<h4 id="link와-navigate">Link와 Navigate</h4>
<p>코딩을 하면서 후반부에 의문이 생기긴 했지만, 일단 구현이 목적이었기 때문에 넘어갔던 부분이다.</p>
<p>리액트에서 사용하는 Link와 Navigate에 대해 다시 한 번 확인하고 넘어가도록 하자!</p>
<h2 id="🔗-link">🔗 Link</h2>
<ul>
<li><code>React-Router-Dom</code> 에서 제공하는 컴포넌트로, 설치가 필요하다.</li>
<li><code>Link</code> 컴포넌트를 사용하여 사용자가 클릭하면 다른 페이지(컴포넌트)로 이동할 수 있게 해준다.</li>
<li><strong>클릭만 하면 이동</strong>하기 때문에, 다른 연산과정 없이 페이지를 이동할 때 사용된다.</li>
<li>개발자 도구에서는 <code>&lt;a&gt;</code>태그로 보인다.</li>
<li>페이지 이동시 전체 페이지를 새로고침하지 않고 필요한 부분만 리랜더링한다.</li>
</ul>
<h4 id="✅-예-페이지-이동">✅ 예: 페이지 이동</h4>
<pre><code class="language-javascript">import { Link } from &#39;react-router-dom&#39;;

function Navigation() {
  return (
    &lt;nav&gt;
      &lt;Link to=&quot;/home&quot;&gt;Home&lt;/Link&gt;
      &lt;Link to=&quot;/about&quot;&gt;About&lt;/Link&gt;
    &lt;/nav&gt;
  );
}
</code></pre>
<h3 id="📌link와-a-의-차이점">📌<code>&lt;Link&gt;</code>와 <code>&lt;a&gt;</code> 의 차이점</h3>
<h4 id="✅-a">✅ <code>&lt;a&gt;</code></h4>
<ul>
<li>브라우저 주소를 이동시키고 페이지 자체를 새로고침한다.(전체 페이지를 리랜더링)</li>
<li>외부 페이지와 연결할 때 주로 사용.</li>
</ul>
<h4 id="✅-link">✅ <code>&lt;Link&gt;</code></h4>
<ul>
<li>SPA구현시에 사용되며, React Router가 필요한 부분만 리랜더링한다.</li>
<li>프로젝트내에서 페이지(컴포넌트) 전환 시 주로 사용.</li>
</ul>
<br />

<h2 id="📍-usenavigate">📍 useNavigate</h2>
<ul>
<li><code>React-Router-Dom</code> 에서 제공하는 훅으로, 설치가 필요하다.</li>
<li><strong>특정 로직을 처리한 후</strong> 자동으로 페이지를 이동시키거나, 사용자가 특정 행동을 했을 때 페이지를 변경하는 경우 사용한다.<ul>
<li><strong>조건이 필요한 곳에서 navigate 함수를 호출해서 페이지 이동</strong>. </li>
</ul>
</li>
</ul>
<h4 id="✅-예-로그인-폼-제출시-특정-페이지로-이동">✅ 예: 로그인 폼 제출시 특정 페이지로 이동</h4>
<pre><code class="language-javascript">import { useNavigate } from &#39;react-router-dom&#39;;

function Login() {
  const navigate = useNavigate();

  function handleSubmit(event) {
    event.preventDefault();
    // 로그인 처리 후 대시보드 페이지로 이동
    navigate(&#39;/dashboard&#39;);
  }

  return (
    &lt;form onSubmit={handleSubmit}&gt;
      &lt;input type=&quot;text&quot; placeholder=&quot;Username&quot; /&gt;
      &lt;input type=&quot;password&quot; placeholder=&quot;Password&quot; /&gt;
      &lt;button type=&quot;submit&quot;&gt;Log In&lt;/button&gt;
    &lt;/form&gt;
  );
}</code></pre>
<hr>
<ul>
<li>Link는 사용자가 클릭시 바로 페이지를 이동하는 로직구현에 사용.</li>
<li>useNavigate는 페이지 전환 시 처리해야하는 조건 로직이 있을 경우 사용.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL | vercel 배포 딸깍! 주의 사항!]]></title>
            <link>https://velog.io/@ly-ra/TIL-vercel-%EB%B0%B0%ED%8F%AC-%EB%94%B8%EA%B9%8D-%EC%A3%BC%EC%9D%98-%EC%82%AC%ED%95%AD</link>
            <guid>https://velog.io/@ly-ra/TIL-vercel-%EB%B0%B0%ED%8F%AC-%EB%94%B8%EA%B9%8D-%EC%A3%BC%EC%9D%98-%EC%82%AC%ED%95%AD</guid>
            <pubDate>Mon, 10 Feb 2025 15:40:21 GMT</pubDate>
            <description><![CDATA[<p>[25.02.10 월요일]</p>
<p>개인프로젝트 6일차 배포!</p>
<p>튜터님이 정말 옵션 하나하나 설명하며 친절하고 상세하게 알려주셨는데...
마치 수업에 나온 코드를 그대로 따라 쳤는데도 제대로 구동이 안되는 것 처럼..
뭔가 하나 부족한 상태가 되는 걸까??</p>
<h2 id="vercel-배포-후-이미지가-사라졌다">vercel 배포 후 이미지가 사라졌다?</h2>
<p>처음해보는 부족하지만 그래도 잘 구동되는 내 프로젝트를 배포했다.
두근두근~ 아뉘 근데..뭔가 미리보이는 배포상태가 쪼~ 끔 이상하다..?</p>
<p>메인페이지 로고가 어디갔지??
<img src="https://velog.velcdn.com/images/ly-ra/post/6dfc0f17-520d-4d17-b034-19755bd08290/image.png" alt="">
메인페이지 뿐만이 아니었다.  내부의 다른 페이지도 로컬에서 직접 지정한 이미지파일들은 모두 배포하면 보이지 않았다.
엑박을 내 사이트에서 보게될줄이야..?</p>
<h3 id="로컬에서-작업할-때는-문제가-없었는데요">로컬에서 작업할 때는 문제가 없었는데요.</h3>
<p>찾아보니 환경변수를 설정해주고, 파일경로를 절대경로로 지정을하고..여러방법이 있었으나.. 내상황과는 맞지 않는 부분이었다.</p>
<p>vite를 활용하여 프로젝트를 진행중이었기에 공식문서를 보니...
<a href="https://ko.vitejs.dev/guide/assets.html">https://ko.vitejs.dev/guide/assets.html</a></p>
<p>아..또, <strong>import</strong>였다. 
이번 프로젝트를 진행하며 import 주의보가 열심히 울린다..ㅠ</p>
<p>✅ 이미지 파일 import, 파일 경로에는 <code>{}</code>묶어서 파일명만</p>
<pre><code class="language-javascript">// 파일 import
import pokemonlogo from &quot;../assets/pokemonlogo.png&quot;;
.
.
.
&lt;LogoImage src={pokemonlogo} alt=&quot;PekemonDex&quot; /&gt;</code></pre>
<p>이제 배포 후에도 정상적으로 이미지가 표출 된다!
<img src="https://velog.velcdn.com/images/ly-ra/post/a566500f-c0cb-4947-a0b5-cb98dab02f8b/image.png" alt=""></p>
<h2 id="404를-여기서-보게된다구요-새로고침했을-뿐인데요">404를 여기서 보게된다구요? 새로고침했을 뿐인데요????</h2>
<p>네?? 
<img src="https://velog.velcdn.com/images/ly-ra/post/ccd23b37-2715-48d1-bf04-00c21fbf3d33/image.png" alt=""></p>
<p>아뉘..이건 전혀 예상 밖입니다!?
로컬에서 작업할 때 처럼 습관적으로 새로고침을 하였는데 마주한 페이지가 Not_Found 🫠</p>
<p>뭐가 문제인지 정확히 모르겠지만, vercel<a href="https://vercel.com/docs/projects/project-configuration#rewrites">공식문서</a>에 있는 내용은 더 모르겠고..
구글링을 토대로 시도!</p>
<ul>
<li>최상위 디렉토리에 vercel.json 파일을 생성</li>
<li>아래 코드를 작성 해준다.</li>
</ul>
<pre><code class="language-javascript">{
  &quot;routes&quot;: [{ &quot;src&quot;: &quot;/[^.]+&quot;, &quot;dest&quot;: &quot;/&quot;, &quot;status&quot;: 200 }]
}</code></pre>
<ul>
<li>깃헙에 push</li>
<li>재배포된 확인!</li>
</ul>
<p>새로고침을 열심히 눌러도 404 Not_found는 마주치지 않은 것을 확인했다!</p>
<hr>
<p>정말 이제 다 했구나~ 라고 생각했지만 끝까지 방심할 수 없다!!
배포 과정에서도 이런 난관이 생길 줄이야.. 나만 그런가 했으나 처음 배포를 진행하는 다른 팀원들도 비슷한 과정을 겪었다.
끝날 때까지 끝난 게 아닌 점!! 조금 일찍 과제를 마쳐서 풀어져 있었는데 다시 긴장했다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL | 버블버블~ e.stopPropagation() vs e.preventDefault()]]></title>
            <link>https://velog.io/@ly-ra/TIL-%EB%B2%84%EB%B8%94%EB%B2%84%EB%B8%94-e.stopPropagation-vs-e.preventDefault</link>
            <guid>https://velog.io/@ly-ra/TIL-%EB%B2%84%EB%B8%94%EB%B2%84%EB%B8%94-e.stopPropagation-vs-e.preventDefault</guid>
            <pubDate>Fri, 07 Feb 2025 18:21:56 GMT</pubDate>
            <description><![CDATA[<p>[25.02.07 금요일]</p>
<p>개인과제 5일차.</p>
<p>기능 구현들은 다 완성을 하였다!!
세부사항들을 다듬고, 코드를 다시 확인하고 스타일정리를 하면서
예상하지 않은 부분이 발생!</p>
<h2 id="🫧-버블버블">🫧 버블버블?</h2>
<p>ui요소를 클릭하면 다른페이지로 넘어가도록 구현을 한 기능 중에서 <code>array.map()</code>을 사용하여 그려주는 부분이 있어 그 묶음 들을 <code>li</code>태그로 묶어 줬는데, 시멘틱 요소를 고려하면 <code>&lt;li&gt;</code> → <code>&lt;a&gt;</code>로 구현해주는게 좋겠다는 조언으로 수정하였다.</p>
<p>리액트를 이용하여 구현을 하는 중이었기 때문에 <code>&lt;Link&gt;</code>로 변경해주었다.
기존에 있던 코드에서 태그명만 바꿔주었기 때문에 다른 문제는 생기지 않을 것이라 예상했으나, 버블링이 발생한것이다.
구조 자체가 버블링이 생길수 밖에 없는 구조이긴 하나, 이미 그 전에 이벤트 버블링을 방지용으로 <code>event.stopPropagation()</code>를 넣어준 상태였다. 🤔</p>
<ul>
<li><p>이벤트 버블링 발생한 코드</p>
<pre><code class="language-javascript">const PokemonCard = ({ data }) =&gt; {
const dispatch = useDispatch();

const navigate = useNavigate();
const goToPokemonDetail = () =&gt; {
  navigate(`/dex/detail?id=${data.id}`);
};

const handleAddPokemon = (e) =&gt; {
  e.stopPropagation();
  dispatch(addMyPokemon(data));
};

return (
&lt;CardContainerLink to={`/dex/detail?id=${data.id}`} onClick={goToPokemonDetail}&gt;
  &lt;PokemonCardImage src={data.img_url} alt={data.korean_name} /&gt;
  &lt;PokemonCardName&gt;{data.korean_name}&lt;/PokemonCardName&gt;
  &lt;PokemonCardNum&gt;no.{data.id.toString().padStart(3, &quot;0&quot;)}&lt;/PokemonCardNum&gt;
  &lt;ToggleButton onClick={handleAddPokemon}&gt;+ add&lt;/ToggleButton&gt;
&lt;/CardContainerLink&gt;</code></pre>
</li>
</ul>
<br />

<h2 id="💡-코드를-뜯어보면-나온다">💡 코드를 뜯어보면 나온다!!</h2>
<p><code>Link</code>는 어딘가로 연결시켜주는 <code>to</code> 속성을 기본으로 가지고 있다. 그런데  기존의 코드에서 수정을 해주지 않아서 동일한 기능을 하는 클릭 이벤트 함수도 그대로 들어가 있다.</p>
<p>중복되는 2개의 기능은 필요 없으므로 click 이벤트를 삭제했다!
그래도 문제는 생긴다. 
내가 넣어준 <code>event.stopPropagation()</code>은 캡처링/버블링 방지를 하는 것은 맞으나 <code>Link</code>가 가지고있는 기본 동작인 <code>to</code> 속성은 그대로 실행이 되기 때문이다.
이런 기본 동작을 방지하는 목적으로 사용할 때는 <code>event prevnetDfault()</code> 메서드를 사용해 줘야 한다!</p>
<p>✅ 수정한 코드</p>
<pre><code class="language-javascript">const PokemonCard = ({ data }) =&gt; {
  const dispatch = useDispatch();

  const handleAddPokemon = (e) =&gt; {
    e.preventDefault();
    dispatch(addMyPokemon(data));
  };

  return (
&lt;CardContainerLink to={`/dex/detail?id=${data.id}`} &gt;
    &lt;PokemonCardImage src={data.img_url} alt={data.korean_name} /&gt;
    &lt;PokemonCardName&gt;{data.korean_name}&lt;/PokemonCardName&gt;
    &lt;PokemonCardNum&gt;no.{data.id.toString().padStart(3, &quot;0&quot;)}&lt;/PokemonCardNum&gt;
    &lt;ToggleButton onClick={handleAddPokemon}&gt;+ add&lt;/ToggleButton&gt;
&lt;/CardContainerLink&gt;</code></pre>
<ul>
<li><code>Link</code> 자체에 페이지를 연결할수 있는 <code>to</code> 속성이 있으므로, <del><code>onClick={goToPokemonDetail}</code></del> 이벤트 삭제.</li>
<li>해당 이벤트가 삭제되어 <del><code>useNavigate</code></del> 도 필요가 없어졌으므로 삭제.</li>
<li>버튼 이벤트로 인해 전파되는 버블링은 <strong><code>Link</code>의 기본 요소</strong>이기 때문에 <code>event.stopPropagation()</code> → <code>e.preventDefault()</code> 변경해 주었다.</li>
</ul>
<p>원하는 대로 정상적으로 잘 작동된다!</p>
<p>각 이벤트 메소드의 차이점을 알았으니, 다음부터는 구분해서 사용할수 있겠지!!?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL | RTK refactoring]]></title>
            <link>https://velog.io/@ly-ra/TIL-RTK-refactoring</link>
            <guid>https://velog.io/@ly-ra/TIL-RTK-refactoring</guid>
            <pubDate>Thu, 06 Feb 2025 16:03:19 GMT</pubDate>
            <description><![CDATA[<p>[25.02.06 목요일]</p>
<p>개인과제 4일차. 
어제까지 Context API 리팩토링을 마무리하고, Redux-ToolKit을 활용한 리팩토링을 시작했다.</p>
<p>Context API를 사용하면서 어느정도 코드는 정리를 해둔 상태여서 바~로 진행하면 되는 사항이었는데, createSlice 파일을 만들고 침묵...
다시 rtk를 활용하라고 하면 할수는 있겠지만, 진행 과정을 기록해 둬야 나중에라도 다시 찾아보고 활용하지 않겠나?</p>
<h2 id="rtk는"><a href="https://redux-toolkit.js.org/">RTK</a>는</h2>
<p>전역 상태관리 라이브러리인 <a href="https://ko.redux.js.org/">Redux</a>를 더 편하게 사용할 수 있게 만든 툴키트다.</p>
<p>내가 프로젝트를 진행하며 작업했던 과정을 기록해 둔다.
yarn + vite를 기반으로 작업을 진행했다.</p>
<h3 id="1-rtk-설치">1. RTK 설치</h3>
<pre><code class="language-bash">yarn add react-redux @reduxjs/toolkit</code></pre>
<h3 id="2-redux-폴더-구조-생성">2. redux 폴더 구조 생성</h3>
<p>src/redux/ 폴더 안에 <code>configureStore.js</code> 파일과 <code>&lt;작업명&gt;Slice.js</code> 파일을 생성해준다.</p>
<h3 id="3-createslice-생성-후-export">3. createSlice 생성 후 export</h3>
<details>
  <summary>pokemonSlice.js</summary>

<pre><code class="language-javascript">import { createSlice } from &quot;@reduxjs/toolkit&quot;;
import MOCK_DATA from &quot;../data/MOCK_DATA&quot;;

// 초기값 설정
const initialState = {
  pokemonData: MOCK_DATA,
  selectedPokemon: localStorage.getItem(&quot;myPokemon&quot;)
    ? JSON.parse(localStorage.getItem(&quot;myPokemon&quot;))
    : [],
};

// ⭐️슬라이스 만들기 &amp; createSlice import 확인
const pokemonSlice = createSlice({
  name: &quot;pokemon&quot;,  // 이 슬라이스의 이름
  initialState,  // 초기값
  reducers: {  // 리듀서들
    // ✅ 포켓몬 대쉬보드에 추가
    addMyPokemon: (state, action) =&gt; {
      const addPokemon = [
        ...state.selectedPokemon,
        { ...action.payload, isSelected: true },
      ];

      // 이미 대시보드에 등록한 포켓몬인지 확인
      if (state.selectedPokemon.find((item) =&gt; item.id === action.payload.id)) {
        alert(`${action.payload.korean_name} 이미 보유한 포켓몬입니다.`)
        return;
      }

      // 6마리 초과시 알림
      if (state.selectedPokemon.length &gt;= 6) {
         alert(`최대 6마리 까지만 등록 가능합니다.`)
        return;
      }

      state.selectedPokemon = addPokemon;
      localStorage.setItem(&quot;myPokemon&quot;, JSON.stringify(addPokemon));
      alert(`${action.payload.korean_name} 컬렉션에 추가되었습니다.`)

    // ✅ 포켓몬 대쉬보드에서 삭제
    removeMyPokemon: (state, action) =&gt; {
      const removePokemon = state.selectedPokemon.filter(
        (item) =&gt; item.id !== action.payload.id
      );

      state.selectedPokemon = removePokemon;
      localStorage.setItem(&quot;myPokemon&quot;, JSON.stringify(removePokemon));
      alert(`${action.payload.korean_name} 컬렉션에서 제외되었습니다.`)
    },
  },
});

// ⭐️현재 작업중인 파일명의 reducer를 내보내준다.
export default pokemonSlice.reducer;
// ⭐️현재 작업 파일의 action들을 내보내준다.
export const { addMyPokemon, removeMyPokemon } = pokemonSlice.actions;
</code></pre>
</details>

<br />

<h3 id="4-store-생성-후-export">4. store 생성 후 export</h3>
<details>
  <summary>configureStore.js</summary>

<pre><code class="language-javascript">import { configureStore } from &quot;@reduxjs/toolkit&quot;;
import pokemonSlice from &quot;./pokemonSlice&quot;;

// ⭐️configureStore 생성, import 확인
const store = configureStore({
  reducer: {
    pokemon: pokemonSlice, // pokemonSlice.js에서 생성한 리듀서객체 이름과 경로 지정, import 확인
  },
});

// 다른곳에서 사용할 수 있도록 내보내 주기
export default store;</code></pre>
</details>

<br />

<h3 id="5-전역관리할-최상위-컴포넌트에-provider">5. 전역관리할 최상위 컴포넌트에 Provider</h3>
<p>나의 작업 환경에서는 App.jsx가 최상위 컴포넌트상태.</p>
<details>
  <summary>App.jsx</summary>

<pre><code class="language-javascript">import { Provider } from &quot;react-redux&quot;;
import store from &quot;./redux/store&quot;;
import Router from &quot;./shared/Router&quot;;

function App() {
  return (
    &lt;&gt;
    {/* 생성한 store 연결, Provider와 store import 확인! */}
      &lt;Provider store={store}&gt;
        &lt;Router /&gt;
      &lt;/Provider&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
  </details>

<br />

<h3 id="6-필요한-곳에서-사용">6. 필요한 곳에서 사용</h3>
<h4 id="useselector">useSelector</h4>
<ul>
<li>기존 리액트 State에 저장된 데이터를 조회할때(R) 사용한다.</li>
</ul>
<details>
  <summary>PokemonList.jsx</summary>

<pre><code class="language-javascript">import React from &quot;react&quot;;
import { useSelector } from &quot;react-redux&quot;;
import styled from &quot;styled-components&quot;;
import PokemonCard from &quot;./PokemonCard&quot;;

const PokemonList = () =&gt; {
  // ⭐️useSelector, import 확인
  const pokemonData = useSelector((state) =&gt; state.pokemon.pokemonData);

  return (
    &lt;PokemonListWrapper&gt;
      {pokemonData.map((data) =&gt; (
        &lt;PokemonCard key={data.id} data={data} /&gt;
      ))}
    &lt;/PokemonListWrapper&gt;
  );
};

export default PokemonList;
</code></pre>
  </details>


<h4 id="usedispatch">useDispatch</h4>
<ul>
<li>기존 리액트 setState위치에서 하던 역할을 한다.</li>
<li>C, U, D 데이터 변화가 있는 곳에서 사용.</li>
</ul>
<details>
  <summary>PokemonList.jsx</summary>

<pre><code class="language-javascript">import React from &quot;react&quot;;
import { useDispatch, useSelector } from &quot;react-redux&quot;;
import { useNavigate, useSearchParams } from &quot;react-router-dom&quot;;
import styled from &quot;styled-components&quot;;
import { removeMyPokemon, addMyPokemon } from &quot;../redux/pokemonSlice&quot;;

const Detail = () =&gt; {
  const pokemonData = useSelector((state) =&gt; state.pokemon.pokemonData);
  const selectedPokemon = useSelector((state) =&gt; state.pokemon.selectedPokemon);

  // ⭐️ dispatch를 거쳐 action을 store로 전달, import확인
  const dispatch = useDispatch();

  const navigate = useNavigate();
  const [params, setParams] = useSearchParams();
  const pokemonId = params.get(&quot;id&quot;);

  const selectPokemon = pokemonData.find((data) =&gt; {
    return data.id === +pokemonId;
  });

  // ⭐️ addMyPokemon, removeMyPokemon함수도 slice생성하고
  // actions로 export했기 때문에 사용을 하려면 import 해줘야 한다!!

  const handleAddPokemon = (selectPokemon) =&gt; {
    dispatch(addMyPokemon(selectPokemon));
  };

  const handleRemovePokemon = (selectPokemon) =&gt; {
    dispatch(removeMyPokemon(selectPokemon));
  };

  // 생략
</code></pre>
  </details>


<hr>
<p>이 과정중에서 Read는 잘 되었는데 C,U,D가 제대로 작동을 하지 않아서 로직에 문제가 있는 줄알았다. 몇번 확인하니 잘못작성한 부분도 있긴 했으나 금방 확인하고 수정하였다.</p>
<p>그런데도 이상하게 함수가 실행되지가 않는것이다. 콘솔에러는 해당 함수가 정의되지 않았다고 하면서 dispatch부터 읽지를 못하는 것이다.
그래서 오타와 useDispatch가 import되었는지 확인했으나, 오타도 없고 import도 잘 되어 있는 상태였다.</p>
<p>찾다 찾다 못찾겠어서 튜터님께 가니, 오류 확인 하시고는 함수도 import했는지 체크해 보라는 것.
아... 하도 import한게 많다 보니 해당함수들도 import한줄 알았다..!</p>
<pre><code class="language-javascript">import { removeMyPokemon, addMyPokemon } from &quot;../redux/pokemonSlice&quot;;</code></pre>
<p>import하니 바로 해결...🫠</p>
<p>props 지옥에서 해방되었다~ 했더니, import지옥이...?
확실히 전역상태관리라는 것이 편하긴 했다만... 종종 마주칠것만 같은 import 이슈.</p>
<h4 id="export한-녀석들은-사용할-때-import-확인-필수">export한 녀석들은 사용할 때 import 확인 필수!!</h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[props 지옥, props-drilling]]></title>
            <link>https://velog.io/@ly-ra/props-%EC%A7%80%EC%98%A5-props-drilling</link>
            <guid>https://velog.io/@ly-ra/props-%EC%A7%80%EC%98%A5-props-drilling</guid>
            <pubDate>Wed, 05 Feb 2025 18:30:05 GMT</pubDate>
            <description><![CDATA[<p>[25.02.05 수요일] : 포켓몬dex - 트러블슈팅</p>
<p>개인과제 3일차!!
어제 2단 변신 중 베이스를 마무리 한 줄 알았는데...하나를 빼먹었네? 추가해야지~ 하고 시작했는데....</p>
<p>아니... 왜.. 안됨!?
아니... 왜 작동되던 녀석도 안됨!?
아니... 새로 구현한 녀석 너만 안되면 안 되니!?
아니<del>이</del>!!!!💢</p>
<h2 id="1-문제-발생">1. 문제 발생</h2>
<p>포켓몬 카드를 선택하면 상세페이지로 넘어가게 되는데, 상세페이지에서도 포켓몬을 내 포켓몬에 추가하거나 삭제하는 버튼을 넣어줬다.</p>
<table>
<thead>
<tr>
<th>Dex.jsx</th>
<th>Detail.jsx</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/ly-ra/post/6b7fd1b8-1138-46d2-8ab9-eb95925ea5b4/image.png" alt=""> 여기서도 포켓몬 추가, 삭제가 가능하고!</td>
<td><img src="https://velog.velcdn.com/images/ly-ra/post/e449c3b6-4357-493e-9f2f-947afc5473e1/image.png" alt=""> 여기서도 추가, 삭제가 가능하도록!</td>
</tr>
</tbody></table>
<p>그.런.데. 버튼은 들어가 있지만 기능이 작동하지 않았다...
Dex.jsx에 있던 버튼은 잘 작동하였으나, Detail.jsx에 있는 버튼이 정상작동하지 않았다.
그래서 조건, props 등을 확인하며 다시 수정!!
그래도 안되는데...난..무슨짓을 한 것인가?</p>
<hr>
<h2 id="2-원인-추론">2. 원인 추론</h2>
<p>&lt;나만의 포켓몬 - 카드&gt;에 있는 버튼도,
&lt;포켓몬 목록 - 카드&gt;에 있는 버튼도,
&lt;포켓몬 상세페이지&gt;에 있는 버튼도
모두 1개의 버튼에서 추가(add)로직, 삭제(remove)로직을 조건에 맞춰서 사용하고 있는 중이다.</p>
<p>가장 유력한 원인은 props를 전달하고 전달하고 전달하고... 하다 보니 분명 어디선가 꼬인 것이고, 그러면서 로직에서 몇 가지 수정 사항도 있었다.</p>
<p>props를 계속 전달하다 보니 헷갈려서 로직에서도 뭔가를 착각한 것 같은 느낌이 강하게 들었다.</p>
<hr>
<h2 id="3-해결방안">3. 해결방안</h2>
<h3 id="🪄-props-정리">🪄 props 정리</h3>
<p>지금 작업 중인 깃허브 브랜치 이름이 &#39;props-drillig&#39;이다.
분명 작업하다 어디선가 props를 잘못 전달하거나 못 받아온 것이다.
코드에디터에서만 확인을 하니 혼동이 생기고 정리가 안됐다.
그렇다면! <strong>시각화!</strong>
<img src="https://velog.velcdn.com/images/ly-ra/post/c723a316-c4fe-4560-8ea1-24624e281e30/image.jpg" alt=""></p>
<p>각 페이지/컴포넌트 별로 필요한 props만 정리하고 나니 또 한 가지 원인이 보였다.</p>
<h3 id="🪄-props-공통-부모를-찾아라">🪄 props 공통 부모를 찾아라!</h3>
<p>작업할 때 내 로직은 모두 Dex.jsx 페이지에서부터 시작을 했다.
그렇다면 내려줄 수 있는 자식 컴포넌트는 &lt;Dash.jsx, List.jsx, Card.jsx&gt;까지 인 것이다. 
Detail.jsx는 Card컴포넌트를 누르면 보이는 화면이기 때문에 Dex의 하위 컴포넌트가 아닌 것이다. 즉, props를 전달할 수 없는 것이다.</p>
<p>그렇다면 props를 내려줄 수 있도록 Dex와 Detail의 공통 부모를 찾아 올라가자!!
그랬더니 Router.jsx가 나왔다. 여기서는 라우터 설정만 해주고 끝냈는데, 지금보 니 상태 관리부터 모든 로직이 여기서부터 시작을 했어야 했던 것이다.</p>
<p>과제 발제 페이지에서 추가 기능 구현 부분을 제대로 확인하지 않았던 결과🥲
props-drilling을 제대로 경험하고 말았다🫠</p>
<hr>
<h2 id="4-결과">4. 결과</h2>
<p>상위 컴포넌트에서부터 필요한 props를 정리해서 전달, 전달, 전달, 전달해 주니 정상적으로 잘 작동한다!
컴포넌트 관계와 props를 정리하고 나니 일부 수정했던 로직에서 필요한 부분도 다시 채워 넣었다.
정상적으로 잘 작동하는 것을 확인 한 후에, 바로 context API로 리팩토링진행!</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/ly-ra/post/06d0a29e-946d-4ca8-a8b9-d0089ad6078e/image.png" alt=""></th>
<th><img src="https://velog.velcdn.com/images/ly-ra/post/c60266cb-d20a-4700-be27-7c3cbf5245bf/image.png" alt=""></th>
</tr>
</thead>
<tbody><tr>
<td>import useEffect 하면 해결</td>
<td>import useState 하면 해결</td>
</tr>
</tbody></table>
<p>와.. props 정리를 제대로 해두니 context API로 리팩토링할 땐 거의 10분 컷으로 해결...
극과 극, 천국과 지옥을 체험해버렸다. 😳</p>
<hr>
<h3 id="기록">기록</h3>
<p>다른 팀원은 context API 리팩토링하면서도 오류가 발생해서 시간이 오래 걸렸다고 했는데, props 정리를 잘 해둬서 금방 끝난 것인가?
너무 쉽게 마무리가 되어서 &quot;이게 되네?&quot;, &quot;이게 맞아?&quot;, &quot;찜찜하네&quot; 하면서 튜터님 확인 방문까지 했다. 물론 다른 부분들도 확인하고 질문도 하면서 겸사겸사 방문한 것이지만..</p>
<p>스탠다드반 수업에서 튜터님이 설명을 정말 잘 해주셔서, 실제 코딩을 진행할 때 그 부분만 다시 확인하고 작업할 수 있었다. 어제 특강도 있었고, 반복을 이만큼 하다 보니 그만큼 결과가 나온 것 같다고 생각한다.</p>
<p>“이거 쉬워요, 정말 쉬우니까 여기서 고생하지 말고 스무스하게 가세요~” 하는 튜터님의 무언의 메시지랄까... 튜터님들이 정말 많이 고민하고 애써주신다는 것이 느껴졌다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL | 동적라우팅 - 패스파라미터 &  쿼리스트링]]></title>
            <link>https://velog.io/@ly-ra/TIL-%EB%8F%99%EC%A0%81%EB%9D%BC%EC%9A%B0%ED%8C%85-%ED%8C%A8%EC%8A%A4%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%EC%BF%BC%EB%A6%AC%EC%8A%A4%ED%8A%B8%EB%A7%81</link>
            <guid>https://velog.io/@ly-ra/TIL-%EB%8F%99%EC%A0%81%EB%9D%BC%EC%9A%B0%ED%8C%85-%ED%8C%A8%EC%8A%A4%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%EC%BF%BC%EB%A6%AC%EC%8A%A4%ED%8A%B8%EB%A7%81</guid>
            <pubDate>Tue, 04 Feb 2025 16:24:18 GMT</pubDate>
            <description><![CDATA[<p>[25.02.04 화요일]</p>
<p>개인과제 2일차, 2단 변신 과정중 베이스 단계를 마무리 했다!
이 전 보다는 리액트에 조금 익숙해진 것 같은 느낌적인 느낌!?</p>
<p>쪼끔 친해졌다고 금방 또 사이 멀어질뻔 했다.</p>
<p>동적라우팅에 대해서 패스파라미터만 배운것 같은데...
쿼리스트링으로 구현하라는 미션이 주어졌다..🤔</p>
<h2 id="📌-동적라우팅">📌 동적라우팅</h2>
<blockquote>
</blockquote>
<p>Route를 설정할 때 URL의 전체 형태를 미리 정의하는 것이 아니라, 특정 규칙을 정의한 후 규칙에 부합하는 URL의 경우에는 해당 element를 보여주게 설정한다.
즉, 임의의 동적으로 변화하는 url이 뒤에 추가로 붙어도 라우팅 해준다는 것.</p>
<pre><code class="language-javascript">//Router.jsx- 동적라우팅

import React from &#39;react&#39;;
import { BrowserRouter, Route, Routes } from &#39;react-router-dom&#39;;

export default function Router() {
  return (
    &lt;BrowserRouter&gt;
      &lt;Routes&gt;
        &lt;Route path=&quot;/&quot; element={&lt;Home /&gt;} /&gt; // 정적 라우팅
        &lt;Route path=&quot;/detail/:id&quot; element={&lt;Detail /&gt;} /&gt; //동적 라우팅
      &lt;/Routes&gt;
    &lt;/BrowserRouter&gt;
  );
}</code></pre>
<h3 id="🔗-path-parameter">🔗 Path Parameter</h3>
<p><code>경로/:문자열</code> 형태로 <strong>path</strong>를 설정하면 URL에서 경로/ 뒤에 글자가 오면 이 Route로 연결해준다.</p>
<p><code>/detail/${data.id}</code>
위 처럼 특정페이지 id(path parameter)값을 가져올때는</p>
<h4 id="useparams-hook을-이용한다">useParams hook을 이용한다</h4>
<pre><code class="language-javascript">import { useParams } from &#39;react-router-dom&#39;;

const param = useParams();
const detailId = params.id;

//

&lt;Link to={`/detail/${detailId}`}&gt;</code></pre>
<h3 id="🔗-query-string">🔗 Query String</h3>
<p>쿼리는 질문이라는 뜻이다.
쿼리스트링은 URL에서 물음표 뒤 모든 문자열을 의미한다.</p>
<p><code>/products?sort=popular&amp;color=red</code></p>
<p>쿼리스트링: <code>key=value</code> 형태의 문자열로 표현 (key=value 페어의 개수 제한은 없음)
<code>?</code> : 쿼리스트링의 시작 표시 (start of parameters)
<code>&amp;</code> : key=value 페어 구분 표시 (separator)</p>
<h4 id="usesearchparams-hook">useSearchParams hook</h4>
<pre><code>const [searchParams, setSearchParams] = useSearchParams();</code></pre><p><strong>searchParams</strong>: URLSearchParams 객체
<strong>setSearchParams 함수</strong>: 인자로 객체 또는 문자열을 넣어서 호출하면 현재 URL의 쿼리 스트링을 변경하는 기능을 제공 (컴포넌트 안에서 쿼리 스트링을 변경하고자 할 때 사용)</p>
<h4 id="searchparams에-정의된-메서드">searchParams에 정의된 메서드</h4>
<p><strong>1. 값을 읽어오는 메서드</strong></p>
<ul>
<li><code>searchParams.get(key)</code> : 특정한 key의 value를 가져오는 메서드
원하는 쿼리 스트링의 key를 인자로 넣어서 메서드를 호출하면 해당 key에 부합하는 value가 리턴된다.</li>
<li><code>searchParams.getAll(key)</code> : 특정한 key에 부합하는 value가 두 개 이상일 경우 get 메서드는 제일 먼저 나온 value만 리턴해준다. getAll 메서드는 해당 key에 해당하는 모든 value 값들을 배열의 형태로 리턴해준다.</li>
</ul>
<p><strong>2. 값을 변경하는 메서드</strong>
searchParams의 값을 변경하더라도 실제 URL의 쿼리 스트링은 변경되지 않는다. 실제 쿼리 스트링을 변경시키려면 setSearchParams 함수에 searchParams를 인자로 전달하면서 호출해야 한다.</p>
<ul>
<li><code>searchParams.set(key, value)</code> : 메서드를 호출하면서 인자로 전달한 key 값을 value로 설정하는 메서드
만약 동일한 key에 여러 value가 이미 존재하고 있었다면, set 메서드를 호출하면서 설정한 값 외에는 삭제된다.</li>
<li><code>searchParams.append(key, value)</code> : 메서드를 호출하면서 인자로 전달한 key 값을 value로 추가하는 메서드
기존의 값들을 변경하거나 삭제하지 않고 추가하는 방식으로 동작한다.</li>
</ul>
<hr>
<ul>
<li>또한 라우터에서 어떤방식으로 설정을 해주었느냐에 따라 파라미터는 잘 입력되었어도 라우팅 되지 않을 수있으니 꼭! 확인!!<pre><code>&lt;Routes&gt;
&lt;Route path=&quot;/&quot; element= { Home } /&gt;
&lt;Route path=&quot;/a&quot; element= { A } /&gt;
&lt;Route path=&quot;/a/*&quot; element= { A ALL SUB } /&gt;
&lt;Route path=&quot;/a/:id&quot; element= { A ID } /&gt;
&lt;Route path=&quot;/b&quot; element= { B } /&gt;
&lt;Route path=&quot;*&quot; element= { ErrorPage } /&gt;
&lt;/Routes&gt;

</code></pre></li>
</ul>
<p>// 단순 &#39;/a/&#39; url 뒤에 어느 값이 오든 특정 페이지 렌더링
&lt;Route path=&quot;/a/*&quot; element= { A ALL SUB } /&gt;</p>
<p>// &#39;/a/&#39; url 뒤에 오는 값을 url 파라미터로 사용
&lt;Route path=&quot;/a/:id&quot; element= { A ID } /&gt;</p>
<h2 id="">```</h2>
<blockquote>
<p>참고 : <a href="https://velog.io/@leah1225/React-%EC%BF%BC%EB%A6%AC-%EC%8A%A4%ED%8A%B8%EB%A7%81Query-String">[React] 쿼리 스트링(Query String)</a></p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>