<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>devstory</title>
        <link>https://velog.io/</link>
        <description>프론트엔드 공부 기록 아카이빙🍁</description>
        <lastBuildDate>Wed, 03 Sep 2025 02:00:35 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>devstory</title>
            <url>https://velog.velcdn.com/images/yoon_ji/profile/d91d7a39-d87a-417e-a5f0-9d38e82a8692/image.PNG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. devstory. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/yoon_ji" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[기능구현] 이미지 프리뷰 방식 비교 URL.createObjectURL vs FileReader.readAsDataURL]]></title>
            <link>https://velog.io/@yoon_ji/%EA%B8%B0%EB%8A%A5%EA%B5%AC%ED%98%84-%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%94%84%EB%A6%AC%EB%B7%B0-%EB%B0%A9%EC%8B%9D-%EB%B9%84%EA%B5%90-URL.createObjectURL-vs-FileReader.readAsDataURL</link>
            <guid>https://velog.io/@yoon_ji/%EA%B8%B0%EB%8A%A5%EA%B5%AC%ED%98%84-%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%94%84%EB%A6%AC%EB%B7%B0-%EB%B0%A9%EC%8B%9D-%EB%B9%84%EA%B5%90-URL.createObjectURL-vs-FileReader.readAsDataURL</guid>
            <pubDate>Wed, 03 Sep 2025 02:00:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>글 업로드 할 때 이미지 프리뷰를 보여주기위해서는 <strong><code>URL.createObjectURL</code> vs <code>FileReader.readAsDataURL</code></strong> 두 가지 방법을 활용할 수 있다. 이걸 비교해 보고자 한다</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yoon_ji/post/05aa4aad-6aec-4f08-b541-d50693ad6f94/image.png" alt=""></p>
<h2 id="1-비교하기">1. 비교하기</h2>
<h3 id="1-1-데이터-형식">1-1. 데이터 형식</h3>
<ul>
<li><strong><code>URL.createObjectURL</code></strong>: 브라우저 메모리 내 객체를 참조하는 <strong>임시 blob URL</strong> 생성
ex) blob:<a href="https://example.com/550e8400">https://example.com/550e8400</a>...<ul>
<li>이 URL은 <strong>현재 브라우저 탭/세션 내에서만 유효</strong>, 새로고침하거나 탭 닫으면 사라짐</li>
<li><strong>장점:</strong> base64 변환이 필요 없어서 빠르고 메모리 효율적.</li>
<li><strong>단점:</strong> <strong>외부 저장 불가</strong>. 서버로 보내거나 LocalStorage에 그대로 보관할 수 없음. 
(서버에 보내려면 원본 Blob/File을 직접 FormData로 append해서 보내야 함)<ul>
<li><strong>주의:</strong> 사용 끝나면 URL.revokeObjectURL(url)로 <strong>직접 해제</strong> 필요(이후 예제 참고)</li>
</ul>
</li>
</ul>
</li>
<li><strong><code>FileReader.readAsDataURL</code></strong>: 파일 내용을 <strong>base64 문자열</strong>로 변환해 &quot;data:...&quot; 형식으로 생성
ex) data:image/jpeg;base64,/9j/4AAQSkZJRgAB...<ul>
<li><strong>장점:</strong> 브라우저의 메모리 <strong>외부에서도 사용 가능</strong>. ⇒ 로컬 스토리지에 저장, 서버 전송, 다른 컨텍스트(다른 탭, 다른 브라우저)에서도 그대로 사용 가능</li>
<li><strong>단점:</strong> base64 인코딩이라 용량이 33% 정도 커지고 메모리 소모도 큼.</li>
</ul>
</li>
</ul>
<h3 id="1-2-시간">1-2. 시간</h3>
<ul>
<li><strong><code>URL.createObjectURL</code></strong>: 동기적으로 실행(즉시 실행)</li>
<li><strong><code>FileReader.readAsDataURL</code></strong>은 비동기적으로 실행(시간이 조금 지체된 후 실행)</li>
</ul>
<h3 id="1-3-메모리">1-3. 메모리</h3>
<ul>
<li><strong><code>URL.createObjectURL</code></strong> : <strong>직접 revokeObjectURL로 해제 필요</strong>(또는 문서 종료 시 해제)</li>
<li><strong><code>FileReader.readAsDataURL</code></strong> : 결과가 긴 문자열이라(base64) Blob URL(createObjectURL)에 비해 메모리를 많이 잡아먹지만, 사용하지 않으면 자동으로 <strong>가비지 컬렉터에 의해 자동 제거</strong></li>
</ul>
<h3 id="1-4-지원">1-4. 지원</h3>
<ul>
<li>두 방식 모두 <strong>IE10+ 및 모던 브라우저</strong> OK.</li>
</ul>
<h3 id="1-5-결론">1-5. 결론</h3>
<ul>
<li><strong>미리보기(Preview)</strong> 용도라면 createObjectURL이 <strong>가볍고 빠름.
단,</strong> 사용하지 않을 때 일일이 <strong>revokeObjectURL</strong>로 release시켜주어야 하는 번거로움이 있다.</li>
<li><strong>Base64(data URL)</strong> 은 “문자열이 필요할 때” 쓰면 좋음. (예: LocalStorage 임시 저장/복사-붙여넣기/서버가 Base64만 받을 때)</li>
<li><em>이미지 크기 커질수록 손해(33%↑)*</em> 이고, <strong>인코딩 CPU/메모리 비용</strong>도 들기 때문에 <strong>createObjectURL</strong>을 권장한다고 함. gpt왈</li>
</ul>
<h2 id="2-예제-코드">2. 예제 코드</h2>
<h4 id="urlcreateobjecturl-urlrevokeobjecturl"><strong><code>URL.createObjectURL</code>, <code>URL.revokeObjectURL</code></strong></h4>
<pre><code class="language-tsx">import { useEffect, useRef, useState } from &quot;react&quot;;

export default function ImagePreview() {
  const [previewUrl, setPreviewUrl] = useState&lt;string | null&gt;(null);

  const handleChangeImage = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    const file = e.target.files?.[0];
    if (!file) return;

    // 새 URL 만들기 전에 이전 URL 해제
    setPreviewUrl((prev) =&gt; {
      if (prev) URL.revokeObjectURL(prev);
      return URL.createObjectURL(file);
    });
  };

  // 컴포넌트 언마운트 시 마지막 URL 해제
  useEffect(() =&gt; {
    return () =&gt; {
      if (previewUrl) URL.revokeObjectURL(previewUrl);
    };
  }, [previewUrl]);

  return (
    &lt;&gt;
      &lt;input type=&quot;file&quot; accept=&quot;image/*&quot; onChange={handleChangeImage} /&gt;
      {previewUrl &amp;&amp; (
        &lt;img
          src={previewUrl}
          alt=&quot;preview&quot;
          // 이미 로딩이 끝났다면 바로 해제하는 패턴도 가능(선택)
          onLoad={() =&gt; {
            // URL.revokeObjectURL(previewUrl); // 선택사항
          }}
        /&gt;
      )}
    &lt;/&gt;
  );
}</code></pre>
<h4 id="filereaderreadasdataurl"><strong><code>FileReader.readAsDataURL</code></strong></h4>
<pre><code class="language-tsx">import { useState } from &quot;react&quot;;

export default function ImagePreviewBase64() {
  const [previewDataUrl, setPreviewDataUrl] = useState&lt;string | null&gt;(null);

  const handleChangeImage = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    const file = e.target.files?.[0];
    if (!file) return;

    const reader = new FileReader(); //파일 리더 객체 지정
    reader.readAsDataURL(file);//파일 객체를 읽어서 이해 가능한 문자열로 변경
    reader.onload = () =&gt; {
      // onload 시점에만 result가 채워짐(비동기)
      setPreviewDataUrl(reader.result as string);
    };
  };

  return (
    &lt;&gt;
      &lt;input type=&quot;file&quot; accept=&quot;image/*&quot; onChange={handleChangeImage} /&gt;
      {previewDataUrl &amp;&amp; &lt;img src={previewDataUrl} alt=&quot;preview&quot; /&gt;}
    &lt;/&gt;
  );
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[FE 기술 면접] SSR, CSR]]></title>
            <link>https://velog.io/@yoon_ji/FE-%EA%B8%B0%EC%88%A0-%EB%A9%B4%EC%A0%91-SSR-CSR</link>
            <guid>https://velog.io/@yoon_ji/FE-%EA%B8%B0%EC%88%A0-%EB%A9%B4%EC%A0%91-SSR-CSR</guid>
            <pubDate>Sat, 11 Jan 2025 08:19:17 GMT</pubDate>
            <description><![CDATA[<h2 id="1-csrclient-side-rendering이란"><strong>1. CSR(Client-Side Rendering)이란?</strong></h2>
<h3 id="개념"><strong>개념</strong></h3>
<p>브라우저가 서버로부터 <strong>비어있는 뼈대 HTML</strong>을 받아온 후, 필요한 자바스크립트 번들을 다운로드하고 번들을 실행하여 동적으로 컨텐츠를 채우는 렌더링 방식</p>
<h3 id="작동-방식"><strong>작동 방식</strong></h3>
<ol>
<li><strong>브라우저</strong>가 서버에 빈 HTML과 JS 파일 요청</li>
<li>브라우저가 JS 파일을 다운로드하고 실행</li>
<li>실행된 JS가 HTML의 빈 공간을 채우며 화면을 생성</li>
</ol>
<h3 id="장점"><strong>장점</strong></h3>
<ol>
<li><strong>빠른 상호작용</strong> 서버에서 필요한 파일을 초기에 로드받은 후에는 동적으로 빠르게 렌더링이 가능함</li>
<li><strong>서버 부담 감소</strong> 페이지 렌더링 후에는 추가 서버 요청이 필요 없어 서버 부하가 적음</li>
</ol>
<h3 id="단점"><strong>단점</strong></h3>
<ol>
<li><strong>초기 렌더링 속도 느림</strong> 브라우저가 JS 파일을 모두 다운로드하고 실행해야 화면 표시가 가능하여 초기 렌더링이 느림</li>
<li><strong>SEO 취약</strong> JS로 생성된 콘텐츠는 검색 엔진 크롤러가 읽지 못할 가능성이 높음</li>
</ol>
<h2 id="2-ssrserver-side-rendering이란"><strong>2. SSR(Server-Side Rendering)이란?</strong></h2>
<h3 id="개념-1"><strong>개념</strong></h3>
<p>SSR은 <strong>서버에서 HTML 페이지를 완성</strong>한 뒤 클라이언트(브라우저)에 전달하는 렌더링 방식</p>
<h3 id="작동-방식-1"><strong>작동 방식</strong></h3>
<ol>
<li>사용자가 브라우저에서 페이지 요청</li>
<li>서버는 요청에 따라 HTML 페이지 생성</li>
<li>생성된 HTML을 브라우저로 전송</li>
<li>브라우저는 HTML을 즉시 화면에 렌더링</li>
</ol>
<h3 id="장점-1"><strong>장점</strong></h3>
<ol>
<li><strong>빠른 초기 렌더링</strong> 서버에서 완성된 HTML을 제공하여 사용자에게 첫 화면을 빠르게 표시 가능.</li>
</ol>
<blockquote>
<p>💡 SSR은 서버에서 페이지를 구성하는 시간은 CSR보다 더 걸리지만, 브라우저가 JavaScript를 다운로드하고 실행할 필요가 없어 사용자가 실제로 콘텐츠를 보게 되는 시점은 더 빠르다</p>
</blockquote>
<ol>
<li><strong>SEO에 유리</strong> 검색 엔진 크롤러가 HTML을 쉽게 읽을 수 있어, 검색 결과 상위 노출 가능성이 높음</li>
</ol>
<h3 id="단점-1"><strong>단점</strong></h3>
<ol>
<li><strong>서버 부하 증가</strong> 모든 페이지 요청마다 서버가 HTML을 새로 만들어야 하기 때문</li>
<li><strong>복잡한 구현</strong> 서버와 클라이언트 로직의 분리 관리로 인한 개발과 유지보수의 복잡성 증가</li>
</ol>
<p><strong>🤔 Q. CSR은 왜 검색엔진에 불리하고 SSR은 왜 유리한가요?</strong></p>
<p>CSR의 경우, 아래 이미지처럼 초기 HTML이 거의 비어있는 상태(<code>div id=&quot;root&quot;</code>)로 전달되고 JavaScript가 실행된 후에야 실제 콘텐츠가 생성되는데, 대부분의 검색엔진 크롤러는 이 JavaScript 실행을 기다리지 않고 빈 HTML만 읽고 지나가기 때문</p>
<p><img src="https://velog.velcdn.com/images/yoon_ji/post/1913fbab-dc19-4646-b054-1993412bd8b5/image.png" alt=""></p>
<p>반면 SSR은 서버에서 이미 모든 콘텐츠가 포함된 완성된 HTML을 제공하므로, 크롤러가 JavaScript 실행 없이도 모든 콘텐츠를 즉시 읽을 수 있어 검색 엔진 최적화에 유리함</p>
<h3 id="3-ssr과-csr-비교"><strong>3. SSR과 CSR 비교</strong></h3>
<table>
<thead>
<tr>
<th><strong>특징</strong></th>
<th><strong>SSR</strong></th>
<th><strong>CSR</strong></th>
</tr>
</thead>
<tbody><tr>
<td>초기 로딩 속도</td>
<td>빠름 (완성된 HTML 제공)</td>
<td>느림 (JS 다운로드 및 실행 필요)</td>
</tr>
<tr>
<td>상호작용 속도</td>
<td>느림 (Hydration 필요)</td>
<td>빠름 (JS 중심 렌더링)</td>
</tr>
<tr>
<td>SEO</td>
<td>유리 (HTML 제공)</td>
<td>불리 (JS 의존)</td>
</tr>
<tr>
<td>서버 리소스</td>
<td>높음 (매 요청마다 HTML 생성)</td>
<td>낮음 (정적 리소스 제공)</td>
</tr>
<tr>
<td>구현 복잡도</td>
<td>복잡 (서버-클라이언트 로직 분리 필요)</td>
<td>상대적으로 단순 (JS 중심)</td>
</tr>
</tbody></table>
<hr>
<h3 id="4-면접-질문과-예상-답변"><strong>4. 면접 질문과 예상 답변</strong></h3>
<p><strong>Q. SSR과 CSR의 주요 차이점은?</strong></p>
<p><strong>A.</strong> SSR은 서버에서 완성된 HTML을 생성하여 제공하고, CSR은 브라우저가 JavaScript로 화면을 동적 생성합니다.</p>
<p>각각의 특징</p>
<ul>
<li><strong>SSR</strong>: 초기 로딩 빠름, SEO 유리, 서버 부하 큼</li>
<li><strong>CSR</strong>: 동적 상호작용 빠름, 서버 부담 적음, SEO 취약</li>
</ul>
<p><strong>Q. 어떤 상황에서 각각을 사용해야 하나요?</strong></p>
<p><strong>SSR 적합</strong></p>
<ul>
<li>블로그, 쇼핑몰 등 검색엔진 최적화가 중요한 서비스</li>
<li>빠른 초기 로딩이 필수인 경우</li>
</ul>
<p><strong>CSR 적합</strong></p>
<ul>
<li>관리자 페이지처럼 상호작용이 많은 서비스</li>
<li>실시간 데이터 업데이트가 잦은 경우</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] ‘오운완’ 팀프로젝트 진행과정]]></title>
            <link>https://velog.io/@yoon_ji/%ED%9A%8C%EA%B3%A0-%EC%98%A4%EC%9A%B4%EC%99%84-%ED%8C%80%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A7%84%ED%96%89%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@yoon_ji/%ED%9A%8C%EA%B3%A0-%EC%98%A4%EC%9A%B4%EC%99%84-%ED%8C%80%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A7%84%ED%96%89%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Fri, 03 Jan 2025 13:31:12 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-시작하며">📌 시작하며</h2>
<p> 프로그래머스 데브코스에서 진행한 2차 팀 프로젝트 회고를 진행해 보려 한다 </p>
<p>처음 경험하는 규모의 프로젝트는 처음이기도 했고 2024년 마지막 프로젝트였기 때문에 더 욕심이 났다.</p>
<p>그리하여 우리 팀이 만든 서비스는 두둥
<a href="https://github.com/Out-of-Type-O/Today_Workout_Done">🔗깃허브 주소</a>
<img src="https://velog.velcdn.com/images/yoon_ji/post/e77108f6-b34a-45e3-9b24-5e883a141b69/image.png" alt=""></p>
<h3 id="서비스-소개">서비스 소개</h3>
<blockquote>
<p><strong><code>!O운완?O운완</code></strong> 서비스는  <strong>현대인의 건강한 라이프스타일</strong>을 위한 사진 기반 운동 인증 커뮤니티로, 사용자들에게 동기부여와 정보를 제공하는 것을 목표로 삼았다.</p>
</blockquote>
<p>주요기능</p>
<ul>
<li><strong>4가지 주요 채널 제공</strong> : 오운완 인증, 프로틴 추천, 식단 공유, 헬스장 리뷰<ul>
<li>게시글 작성으로 건강 및 운동 정보를 공유</li>
<li>좋아요, 댓글, 팔로우 기능으로 <strong>활발한 커뮤니티 형성</strong></li>
</ul>
</li>
<li><strong>운동 동기 부여 기능</strong>:<ul>
<li>BMI 계산기</li>
<li>운동 인증 캘린더 및 운동 달성도 그래프</li>
</ul>
</li>
</ul>
<h3 id="프로젝트-명세서">프로젝트 명세서</h3>
<blockquote>
<p>주제: 제공되는 api를 이용한 회원 인증 기반 커뮤니티
기간: 2024.12.05 ~ 2024.12.23</p>
</blockquote>
<p><strong>기본 요구사항</strong></p>
<ul>
<li><input disabled="" type="checkbox"> 사용자는 <strong>회원가입과 로그인</strong>을 할 수 있습니다.</li>
<li><input disabled="" type="checkbox"> 사용자는 <strong>채널에 올라온 글</strong>을 볼 수 있습니다.</li>
<li><input disabled="" type="checkbox"> 사용자는 <strong>가입자 목록</strong>을 볼 수 있습니다.</li>
<li><input disabled="" type="checkbox"> 사용자는 <strong>가입자를 이름</strong>으로 <strong>검색</strong>을 할 수 있습니다.</li>
<li><input disabled="" type="checkbox"> 사용자는 <strong>가입자의 정보</strong>를 볼 수 있습니다.</li>
<li><input disabled="" type="checkbox"> 사용자는 <strong>포스트 혹은 가입자를 검색</strong>할 수 있습니다.</li>
<li><input disabled="" type="checkbox"> 인증된 사용자는 <strong>자신의 정보를 변경</strong>할 수 있습니다.</li>
<li><input disabled="" type="checkbox"> 인증된 사용자는 <strong>채널에 올라온 글</strong>을 볼 수 있습니다.</li>
<li><input disabled="" type="checkbox"> 인증된 사용자는 <strong>채널에 포스트를 남길 수</strong> 있습니다.</li>
<li><input disabled="" type="checkbox"> 인증된 사용자는 <strong>포스트를 좋아요</strong> 할 수 있습니다.</li>
<li><input disabled="" type="checkbox"> 인증된 사용자는 <strong>포스트에 댓글</strong>을 남길 수 있습니다.</li>
<li><input disabled="" type="checkbox"> 인증된 사용자는 <strong>자신의 알림 목록을 확인</strong> 할 수 있습니다.</li>
<li><input disabled="" type="checkbox"> <strong>SPA 형태</strong>로 만들어주세요.</li>
<li><input disabled="" type="checkbox"> 엉뚱한 페이지에 접속하면 <strong>404 페이지</strong>를 보여주세요.</li>
</ul>
<p><strong>보너스 요구사항</strong></p>
<ul>
<li><input disabled="" type="checkbox"> 사용자는 현재 접속 중인 사용자를 볼 수 있습니다.</li>
<li><input disabled="" type="checkbox"> 인증된 사용자는 다른 가입자에게 메시지를 보낼 수 있습니다.</li>
<li><input disabled="" type="checkbox"> 인증된 사용자는 자신에게 온 메시지 목록을 확인 할 수 있습니다.</li>
<li><input disabled="" type="checkbox"> 인증된 사용자는 특정 사용자와의 메시지 대화 내역을 확인 할 수 있습니다.</li>
<li><input disabled="" type="checkbox"> 인증된 사용자는 프로필 이미지 변경</li>
<li><input disabled="" type="checkbox"> 인증된 사용자는 포스트를 작성할 때 이미지를 첨부할 수 있습니다. 파일 업로드를 구현해보세요.</li>
<li><input disabled="" type="checkbox"> 다크 모드를 적용해보세요.</li>
</ul>
<h2 id="📅-프로젝트-진행-과정">📅 프로젝트 진행 과정</h2>
<h3 id="기획">기획</h3>
<p><strong>주제 선정</strong>
<img src="https://velog.velcdn.com/images/yoon_ji/post/292b815e-4d9b-4526-9ad7-287159b3b38f/image.png" alt=""></p>
<p>주제는 요구사항에 맞춰서 팀원들이 각자 의견을 던지고 투표를 진행하였다</p>
<p>투표 결과 top3 주제들로 요구사항에 충족시킬 수 있는지 확인 후 특이사항을 점검하고 투표로 진행되었다</p>
<p>그 결과 최종적으로 내가 제안한 <strong><code>오운완</code></strong>이 선정됐다 🎉</p>
<p><strong>🗣️ 오운완 제안 이유</strong></p>
<ul>
<li><p>운동인이라는 <strong>구체적인 타겟층을 설정</strong>하여 그들의 니즈(운동 성과 공유, 경험과 노하우 공유)에 효과적으로 대응할 수 있다</p>
</li>
<li><p>인스타그램이나 핀터레스트같은 <strong>사진 기반 커뮤니티 서비스</strong>를 구현하고자 했다</p>
<p>  ⇒ <code>운동인증</code>이라는 주제는 <strong>시각적 콘텐츠</strong>와 어울린다</p>
</li>
<li><p>제공된 API를 활용해 <strong>주요 기능을 구현</strong>하는 데 있어 현실적인 접근이 가능하다</p>
</li>
</ul>
<h3 id="유저플로우">유저플로우</h3>
<p>디자인에 들어가기 전 유저플로우를 작성했다</p>
<p>유저플로우를 작성함으로써 다음과 같은 이점을 얻을 수 있었다</p>
<ul>
<li>서비스의 전체적인 흐름을 시각화하여 팀원들과 효과적으로 소통할 수 있었다</li>
<li>사용자 관점에서 서비스를 바라보며 불필요한 단계나 개선이 필요한 부분을 미리 파악할 수 있었다</li>
<li>기능 개발의 우선순위를 정하고 작업 범위를 명확히 할 수 있었다</li>
<li>인증/비인증 사용자의 접근 권한을 명확히 구분하여 보안 설계에 도움이 되었다
<img src="https://velog.velcdn.com/images/yoon_ji/post/8afa5882-c1b9-4b96-8afb-24325fee729a/image.png" alt=""></li>
</ul>
<h3 id="디자인">디자인</h3>
<p>디자인은 피그마를 통해 진행되었다. 반응형 웹을 전제로 디자인을 진행했으며, 피그마에서 공통 컴포넌트들은 별도 분리해 관리하며 디자인 시스템을 효율적으로 구축하고자 했다. 이를 통해 디자인의 일관성을 유지하고 수정 사항 발생 시 빠른 대응이 가능했다.</p>
<ul>
<li><p><strong>메인 컬러</strong>로 포카리스웨트를 연상시키는 #3B6CB4를 선택했다.
   ⇒ 운동과 관련된 서비스의 정체성을 잘 표현할 수 있는 색상이라 생각했다.</p>
</li>
<li><p><strong>폰트</strong>는 다국어 지원이 우수한 프리텐다드를 사용했다.</p>
</li>
<li><p>이미지가 주요 콘텐츠인 만큼, 불필요한 디자인 요소를 최소화하고 <strong>시각적 콘텐츠를 돋보이게 하는 미니멀한 디자인</strong>을 추구했다.</p>
</li>
<li><p>사용자의 집중도를 높이기 위해 게시물에는 최소한의 정보(프로필 이미지, 닉네임)만 표시하고, 호버 시 제목과 내용 일부를 보여주는 인터랙션으로 클릭을 유도했다.</p>
</li>
<li><p>디자인의 일관성을 위해 버튼과 같은 상호작용 요소에는 메인 컬러를 적용하여 시각적 통일감을 주었다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/yoon_ji/post/d05c0a39-d8f9-408e-ac51-44b6fb3f3d03/image.png" alt=""></p>
<h3 id="기능명세서">기능명세서</h3>
<p>프로젝트 초기에는 기능명세서를 어떻게 정리해야 할지 막막했지만, 여러 블로그 글을 참고하며 <strong>페이지별로 나누어 작성</strong>하는 방식을 선택했다. 그 결과 다음과 같은 이점이 있었다.</p>
<ol>
<li><strong>기능 가시성 향상</strong></li>
</ol>
<ul>
<li>각 페이지마다 필요한 기능을 한눈에 확인할 수 있어 <strong>중복 기능</strong>을 쉽게 식별할 수 있었다</li>
<li>구현해야 할 기능들이 <strong>명확히 정리</strong>되어 개발 방향이 분명해졌다</li>
</ul>
<ol start="2">
<li><strong>효율적인 역할 분담</strong></li>
</ol>
<ul>
<li>기능별로 나눠서 팀원들과의 역할 분담이 수월해졌다</li>
<li>각자 담당 기능이 명확히 정해지면서 <strong>책임감</strong>이 상승했다</li>
</ul>
<ol start="3">
<li><strong>실시간 진행 상황 공유</strong></li>
</ol>
<ul>
<li>진행 상황을 <strong>실시간으로 업데이트</strong>하여, 완료되지 않은 기능들을 파악하고 우선순위에 따라 작업을 조율할 수 있었다.</li>
<li>작업이 완료된 팀원들이 다른 팀원들을 도와주며 협업할 수 있었다.
<img src="https://velog.velcdn.com/images/yoon_ji/post/7794d370-6dc2-4da3-b06a-0951f899a5ff/image.png" alt="">
🤔 다만 컴포넌트별로 분리가 되어 있지 않아서, 디자인 단계에서 중복된 컴포넌트가 많았는데도 개발 시에는 개별 컴포넌트로 나누어 작업하게 되었다. 초반에 중복되는 요소들은 한 명이 전담해서 진행했다면 더 효율적이지 않았을까 싶다!</li>
</ul>
<h2 id="💻-개발문화">💻 개발문화</h2>
<p><img src="https://velog.velcdn.com/images/yoon_ji/post/94e53785-4ea9-4ec5-8721-d909264e27a0/image.png" alt=""><img src="https://velog.velcdn.com/images/yoon_ji/post/42824663-9167-41ee-8a2d-9d4395711d6f/image.png" alt=""></p>
<h3 id="1-깃허브-이슈와-pr-활용">1. 깃허브 이슈와 PR 활용</h3>
<p>깃허브에서는 이슈와 PR 템플릿을 활용하여 일관된 형식을 유지할 수 있었다.</p>
<p>라벨을 통하여 작업 내용을 한눈에 파악할 수 있었고, 각 PR에서는 구현한 로직에 대한 간단한 설명을 포함하여, 다른 팀원들이 코드를 리뷰하고 이해하는데 도움이 되도록 했다. </p>
<h3 id="2-브랜치-관리">2. 브랜치 관리</h3>
<p><strong>브랜치와 이슈를 연</strong>동하여 PR 머지 시 해당 이슈가 자동으로 closed 되도록 설정했다. 이를 통해 작업 진행 상황을 효율적으로 추적할 수 있었다.</p>
<p>브랜치 네이밍 규칙은 &#39;dev_task_#issuenumber&#39; 형식을 따랐다. (예: feat_header_#2)</p>
<p>각 팀원이 한 번에 1개의 태스크만 담당하도록 하여 코드 병합 과정에서 발생할 수 있는 문제를 사전에 방지할 수 있었다. </p>
<p>🔥 그럼에도 발생한 충돌… 원인은 개인 개발에 몰두하여 pull을 받지 않고 작업을 진행하는 경우가 있었기 때문.</p>
<p>이를 해결하기 위해 Zoom 채팅과 카카오톡 등 모든 연락망으로 PR과 머지 상황을 실시간 공유했고, 이를 통해 팀원들이 즉시 pull을 받아 충돌을 최소화하며 작업을 이어갈 수 있었다! 👍</p>
<h2 id="👥-협업방식">👥 협업방식</h2>
<p><img src="https://velog.velcdn.com/images/yoon_ji/post/9cd08381-6374-403e-9805-7158dcd8147b/image.png" alt=""></p>
<ul>
<li><strong>슬랙을 통한 일일 스크럼:</strong> 매일 아침 팀원들과 컨디션을 공유하고 진행 상황을 점검했다</li>
<li><strong>노션을 활용한 일정 관리:</strong> 프로젝트 전체 일정과 개인 일정을 공유하고 관리했다</li>
<li><strong>깃허브 기반 협업:</strong> 이슈와 PR 활용했다</li>
<li><strong>피그마를 통한 디자인 협업:</strong> 유저플로우와 디자인 작업을 실시간으로 공유하고 피드백을 주고받았다</li>
</ul>
<h2 id="💫-트러블슈팅-qa">💫 트러블슈팅 QA</h2>
<p>개발시 지속적으로 테스트를 진행했다. 발견된 버그들은 즉시 수정하거나 노션에 기록하여 추후 해결할 수 있도록 했다. 이런 방식으로 모든 팀원이 버그 발견과 수정에 적극적으로 참여하여 서비스의 안정성을 높일 수 있었다.</p>
<p>테스트와 품질 관리를 위해 다음과 같은 방식으로 진행했다</p>
<ul>
<li><strong>실시간 버그 트래킹:</strong> 발견된 모든 오류사항을 즉시 기록하고 담당자를 지정했다</li>
<li><strong>체계적인 이슈 관리:</strong> 각 버그에 대해 완료 여부를 체크리스트로 관리하여 진행 상황을 명확히 했다</li>
<li><strong>우선순위 기반 해결:</strong> 사용자 경험에 직접적인 영향을 미치는 문제부터 우선적으로 해결했다
<img src="https://velog.velcdn.com/images/yoon_ji/post/dc40858d-9e67-4f5d-8111-39f28d2f2e27/image.png" alt=""></li>
</ul>
<p>🔥 개발하는 시간보다 오류를 잡는 데 더 많은 시간을 할애했던 것 같다. 기능 구현이 끝났다고 생각했지만 QA를 진행할수록 계속해서 예상치 못한 오류들이 발견되었고, 이를 해결하기 위해 많은 시간과 노력을 들였다. 다음 프로젝트에서는 QA 시간을 충분히 확보해야겠다.</p>
<h2 id="⌛️마치며">⌛️마치며</h2>
<p>이번 프로젝트의 1차 스프린트에서는 보너스 요구사항을 제외한 모든 기능 구현을 목표로 삼고, 수업 시간뿐 아니라 주말과 저녁 시간까지 팀원들이 함께 슬랙 허들에 모여 개발에 매진했다. 팀원 모두 적극적인 참여로 기한 내에 보너스 요구사항까지 도전할 수 있었고, 결과적으로 전체 21개 중 메시지 기능을 제외한 18개의 요구사항을 성공적으로 구현했다! </p>
<p>팀내 에이스 분들(양대산맥)이 화면을 공유하며 어려운 부분을 설명해 주신 덕에 수업 시간에 완벽히 이해하지 못했던 axios나 zustand와 같은 기술들도 프로젝트에서 활용해 볼 수 있었다.</p>
<p>개발 과정에 집중하다보니 나머지 버그 수정과 QA에 충분히 시간을 할애하지 못한 점이 아쉽지만, 3주라는 짧은 시간동안 정말 열심히 했다는 점에서 만족한다! 중간에 어려운 부분도 있었지만 팀원들의 응원 덕분에 프로젝트를 끝까지 마무리할 수 있었다. 굿굿 👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 24.12.16 - 24.12.22]]></title>
            <link>https://velog.io/@yoon_ji/TIL-24.12.16-24.12.22</link>
            <guid>https://velog.io/@yoon_ji/TIL-24.12.16-24.12.22</guid>
            <pubDate>Sun, 22 Dec 2024 14:49:10 GMT</pubDate>
            <description><![CDATA[<h3 id="241216">24.12.16</h3>
<p>📚 오늘 한 일
팔로우 기능 추가
메인페이지 디자인 및 퍼블리싱</p>
<p>💬 마치며
스와이퍼 커스텀 만만치 않았다 리액트에선 css 모듈 불러오는 오류가 계속 나서 link로 넣었다.</p>
<h3 id="241217">24.12.17</h3>
<p>📚 오늘 한 일
메인 페이지 bmi 계산기 추가
헤더 반응형 구현</p>
<h3 id="241218">24.12.18</h3>
<p>📚 오늘 한 일
메인 페이지 데이터 필터링
메인 페이지 로딩창 추가
그외 자잘한 로직, css 수정</p>
<p>💬 마치며
슬슬 기능은 마무리돼서 다른 분들 코드 보면서 자잘하게 수정했다. 중복되는 코드들이 보였는데 어떻게 손을 대야 할지...</p>
<h3 id="241219">24.12.19</h3>
<p>📚 오늘 한 일
사이드바 상태 로컬스토리지 저장
자잘한 스타일, 로직 수정</p>
<p>💬 마치며
무한 스크롤 적용해야 하는 페이지가 하나 남아서 리액트쿼리를 공부해 보기로 했다</p>
<h3 id="241220">24.12.20</h3>
<p>📚 오늘 한 일
무한스크롤 구현 - intersection observer
자잘한 스타일, 오류 수정
알림창 css 수정</p>
<p>💬 마치며
별도의 라이브러리 설치가 필요없는 intersection로 무한스크롤을 구현했다. 사실 팀원분 코드를 가져다 썼다고 보는 게 맞음. 다음엔 혼자서 해 봐야겠다.</p>
<h3 id="241221">24.12.21</h3>
<p>📚 오늘 한 일
리액트 강의 - todolist 만들기</p>
<h3 id="241222">24.12.22</h3>
<p>📚 오늘 한 일
아는 만큼 보이는 프런트엔드 개발 도서 읽기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 24.12.07 - 24.12.15]]></title>
            <link>https://velog.io/@yoon_ji/TIL-24.12.07-24.12.09</link>
            <guid>https://velog.io/@yoon_ji/TIL-24.12.07-24.12.09</guid>
            <pubDate>Mon, 16 Dec 2024 05:45:23 GMT</pubDate>
            <description><![CDATA[<h3 id="241207">24.12.07</h3>
<p>📚 오늘 한 일</p>
<ul>
<li>프로젝트 디자인작업 수정</li>
<li>프로젝트 맡은 부분 퍼블리싱</li>
</ul>
<p>💬 마치며
고려해야 할 부분이 추가적으로 계속 생긴다. 그래도 프로젝트 기간이 짧다보니 애자일하게 가는 게 맞을 것 같다.</p>
<h3 id="241208">24.12.08</h3>
<p>📚 오늘 한 일</p>
<ul>
<li>프로젝트 디자인작업 수정</li>
<li>프로젝트 맡은 부분 퍼블리싱 마무리</li>
<li>기능명세서 작성
페이지별 기능을 정리하고 우선순위를 정리했다 비고엔 고려사항을 적었다</li>
</ul>
<p>💬 마치며
기능명세서를 작성하니 구현해야 할 부분들이 한눈에 들어와서 일정 조율이 수월해졌다</p>
<h3 id="241209">24.12.09</h3>
<p>📚 오늘 한 일</p>
<ul>
<li><p>데브코스 역량평가(알고리즘, 과제테스트)진행(5h)</p>
</li>
<li><p>특강 진행: 올바르게 프론트엔드 내 지식으로 만들기</p>
<blockquote>
<p>강사: 김태희 강사님 - 당근 엔지니어
일시: 2024년 12월 9일(월) 15:00-19:00(4시간)</p>
</blockquote>
</li>
<li><p>기능명세서를 기반으로 팀원들과 일정조율
1차, 2차로 나누어 개발을 진행하기로 하고 기능별로 담당자를 지정했다</p>
</li>
</ul>
<p>💬 마치며</p>
<p>현재의 기술이나 라이브러리가 등장하게 된 배경과 발전 과정을 이해하면 앞으로의 발전 방향을 예측할 수 있고, 새로운 기술을 더 효과적으로 학습(가성비 학습)할 수 있다고 하셨다
그냥 기술을 사용하는 것이 아닌 &quot;이 기술이 왜 필요했을까?&quot;, &quot;어떤 문제를 해결하려고 했을까?&quot;와 같은 질문을 자주 던지며  공부해야겠다</p>
<p>+) 강사님 밴드 하신다고 했는데 내가 너무 되고 싶은 모습이었다... 직장인밴드</p>
<h3 id="241210">24.12.10</h3>
<p>📚 오늘 한 일</p>
<ul>
<li>프로젝트 사이드바 토글 기능 구현</li>
<li>사이드바 현재 path에 따라 채널리스트 활성화 처리</li>
<li>자잘한 ui 수정</li>
<li>게시판 페이지 디자인</li>
</ul>
<p>💬 마치며
깃허브 이슈를 이용하니 팀원들이 작업 중인 부분을 확인할 수 있어서 충돌을 (그나마) 피할 수 있었다.
프로젝트를 하면서 사이드바 토글 상태를 zustand로 관리하게 됐다. 수업 시간에 애매하게 이해했는데 직접 필요를 느끼고 하니 zustand 사용법을 확실히 익힐 수 있었다</p>
<h3 id="241211">24.12.11</h3>
<p>📚 오늘 한 일</p>
<ul>
<li>댓글 불러오기, 삭제, 추가 기능 구현</li>
</ul>
<p>💬 마치며
전역 폰트 설정을 하지 않은 상태로 진행했는데 맥, 윈도우 환경에 따라 기본 폰트가 다르기 때문에 깨져 보일 수도 있다는 것을 알게 됐다.
댓글 기능 구현을 하는 중인데 api가 익숙하지 않아서 시간을 엄청 잡아먹었다. 내일 퍼블리싱 된 곳에 옮겨서 마무리해야지</p>
<h3 id="241212">24.12.12</h3>
<p>📚 오늘 한 일</p>
<ul>
<li>퍼블리싱 완료된 댓글창에 기능 입히기</li>
<li>좋아요 기능 구현 중</li>
<li>git complete 해결해 봤다 </li>
</ul>
<p>💬 마치며
기능 구현 후에도 자잘하게 손댈 게 많았다
게시글 불러오면서 댓글수도 같이 불러오기 때문에 댓글 입력시 댓글수가 늘어나지 않아서 상태 관리를 이용한다거나,, 회원만 댓글 입력 가능하게 표시를 해 줘야 되는데 이거 내일 추가해야겠다
좋아요도 한 명이 여러번 하지 못하게 막아야 할 것 같다</p>
<h3 id="241213">24.12.13</h3>
<p>📚 오늘 한 일</p>
<ul>
<li>좋아요 기능 완료</li>
</ul>
<p>💬 마치며
좋아요 기능이 생각보다 어려워서 한참 잡고 있었다
코드는 엉망이겠지만... 일단 현재까지 기능 이상 없음 뿌듯해
한 컴포넌트에서 댓글, 좋아요 로직을 모두 작성하다보니 엄청 길어졌는데 이것도 정리해 봐야겠다</p>
<h3 id="241214">24.12.14</h3>
<p>📚 오늘 한 일</p>
<ul>
<li>로딩창 추가</li>
</ul>
<p>💬 마치며</p>
<ul>
<li>로딩창 은근 생각할 게 많았다. 로딩 화면은 어디에 띄워야 하는가, 데이터 로딩 중인 곳에만 띄웠을 때 css 처리는?</li>
<li>레이아웃 파일에서 띄우고 싶었는데 일단 css 이슈로... 데이터 로딩이 필요한 각 컴포넌트에서 불러올 수 있게 처리했다</li>
</ul>
<h3 id="241215">24.12.15</h3>
<p>📚 오늘 한 일</p>
<ul>
<li>사이드바, 유저모달, 마이페이지 ui 수정</li>
</ul>
<p>💬 마치며
아르바이트 때문에 css 위주로 손봤다
폰트 사이즈 날잡고 전체적으로 수정해야겠다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 24.12.06]]></title>
            <link>https://velog.io/@yoon_ji/TIL-24.12.06</link>
            <guid>https://velog.io/@yoon_ji/TIL-24.12.06</guid>
            <pubDate>Fri, 06 Dec 2024 15:06:54 GMT</pubDate>
            <description><![CDATA[<h3 id="💬-마치며">💬 마치며</h3>
<p>프로젝트를 본격적으로 시작하는 첫날</p>
<p><strong>오늘 한 것</strong></p>
<ul>
<li>플로우차트</li>
<li>디자인</li>
<li>디자인리뷰</li>
<li>퍼블리싱 역할분배</li>
</ul>
<p>수정할 부분은 많지만 일단 이렇게 하는 것만 종일 걸렸다
아직 완전하진 않지만 조금씩 의견이 좁혀지고 있는 것 같음!
이번 프로젝트로 팀원들 보두 각자 부족한 부분을 채울 수 있었으면 좋겠다.(나포함)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 24.12.05]]></title>
            <link>https://velog.io/@yoon_ji/TIL-24.12.05</link>
            <guid>https://velog.io/@yoon_ji/TIL-24.12.05</guid>
            <pubDate>Thu, 05 Dec 2024 14:31:43 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-til">📚 TIL</h2>
<ul>
<li>JSON Server를 활용 API 통신<ul>
<li>JSON Server 라이브러리 설치 및 설정</li>
<li>db.json 파일 생성하여 더미 데이터 구성</li>
<li>GET, POST, PUT, DELETE 메서드 테스트</li>
</ul>
</li>
<li>Fetch API를 사용한 데이터 통신<ul>
<li>fetch 함수를 통한 HTTP 요청 구현</li>
<li>async/await를 활용한 비동기 처리</li>
<li>try-catch 문으로 에러 핸들링</li>
<li>커스텀 훅으로 디벨롭<ul>
<li>API 호출 로직을 커스텀 훅으로 분리</li>
</ul>
</li>
</ul>
</li>
<li>TMDB API를 활용한 영화 데이터 통신 실습</li>
<li>React Router</li>
<li>JWT 인증 로그인 로그아웃(복습…)</li>
<li>2차 프로젝트 설명(회원 인증 기반 커뮤니티)</li>
</ul>
<h2 id="💬-마치며">💬 마치며</h2>
<p>JWT 로그인 로그아웃 기능이 이해가 잘 안 된다… 손이 바빴던 하루</p>
<p>프로젝트 시작인데 1차에 비해 규모가 배로 커진 느낌이라 기한내에 끝낼 수 있을까 의문이지만…</p>
<p>해피크리스마스를 위해 1팀 파이팅~!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 24.12.04]]></title>
            <link>https://velog.io/@yoon_ji/TIL-24.12.04</link>
            <guid>https://velog.io/@yoon_ji/TIL-24.12.04</guid>
            <pubDate>Wed, 04 Dec 2024 14:57:57 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-til">📚 TIL</h2>
<ol>
<li><strong>전역 상태 관리 Context API, Redux, zustand</strong></li>
</ol>
<ul>
<li><strong>전역 상태 관리</strong>를 활용한 카운터 앱 구현<ul>
<li>전역 상태로 카운터 값 관리하기</li>
<li>Context API: 메모리 최적화를 위한 상태와 함수 분리</li>
<li>zustand(디벨롭): 로그아웃시 reset 함수 호출</li>
</ul>
</li>
<li><strong>Context 중첩</strong><ul>
<li>Auth Context 생성 및 설정</li>
<li>isLogin 상태값으로 조건부 렌더링한 카운터 앱 구현</li>
</ul>
</li>
</ul>
<ol>
<li><strong>API와 HTTP 통신</strong><ul>
<li>fetch, async/await를 활용한 비동기 처리</li>
<li>axios 라이브러리 사용</li>
</ul>
</li>
</ol>
<ul>
<li><strong>TMDB API 과제</strong><ul>
<li>TMDB API 회원가입 및 인증키 발급</li>
<li>API 사용 가이드 학습</li>
<li><strong>과제: 영화 데이터를 활용한 리스트 페이지 구현</strong></li>
</ul>
</li>
</ul>
<h2 id="💬-마치며">💬 마치며</h2>
<p>Context API를 사용하면서 Context를 중첩해서 적용할 수 있다는 것을 알게 되었다. 권한에 따라 컴포넌트를 보여주는 인증 기반 서비스에 적용할 수 있다는 점을 배웠다. 전역 상태 관리가 헷갈려서 오늘은 Context API와 Zustand를 다시 학습했다. 여전히 헷갈리지만 조금은 정리된 것 같다...(아마도) Redux는 내일 다시 봐야지. 😭
우선은 프로젝트에서 사용하기로 한 Zustand 한 놈에게 집중해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 24.12.03]]></title>
            <link>https://velog.io/@yoon_ji/TIL-24.12.03</link>
            <guid>https://velog.io/@yoon_ji/TIL-24.12.03</guid>
            <pubDate>Tue, 03 Dec 2024 14:27:48 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-til">📚 TIL</h2>
<ol>
<li>useEffect</li>
</ol>
<ul>
<li>함수형 컴포넌트의 생명주기를 관리하는 React Hook</li>
<li>컴포넌트의 마운트, 업데이트, 언마운트 시점에 특정 작업 수행</li>
<li>실습: Todo 리스트에서 로컬스토리지를 활용한 데이터 영속성 구현</li>
</ul>
<ol start="2">
<li>메모이제이션</li>
</ol>
<ul>
<li><p>불필요한 리렌더링을 방지</p>
<ul>
<li>React.memo(): 컴포넌트 자체를 메모이제이션</li>
<li>useCallback(): 함수를 메모이제이션</li>
<li>useMemo(): 계산값을 메모이제이션</li>
</ul>
<p><strong>최적화 적용 순서</strong></p>
<ol>
<li>컴포넌트 메모이제이션 (React.memo)</li>
<li>참조형 props 메모이제이션 (useCallback, useMemo)</li>
</ol>
<ul>
<li>실습: Todo 리스트 컴포넌트 최적화 구현</li>
</ul>
</li>
</ul>
<ol start="3">
<li>전역상태관리</li>
</ol>
<ul>
<li>Context API를 통한 전역 상태 관리<ul>
<li>전역 상태를 여러 컴포넌트에서 공유할 수 있음</li>
<li>불필요한 props drilling 방지</li>
</ul>
</li>
</ul>
<ol start="4">
<li>useId</li>
</ol>
<ul>
<li>서버사이드 렌더링 환경에서도 안정적인 고유 ID 생성</li>
<li>접근성 향상을 위한 라벨-폼 요소 연결에 활용</li>
<li>실습: Todo 리스트의 체크박스-라벨 연결에 useId 활용</li>
</ul>
<h2 id="💬-마치며">💬 마치며</h2>
<p>useEffect, 메모이제이션 응용 연습이 필요할 것 같다. 뭔가 사용해 보려고 하면 감이 안 잡힘... 포스팅은 머리에서 정리가 되는대로 해봐야지 ^.^</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 24.12.02]]></title>
            <link>https://velog.io/@yoon_ji/TIL-24.12.02</link>
            <guid>https://velog.io/@yoon_ji/TIL-24.12.02</guid>
            <pubDate>Mon, 02 Dec 2024 15:05:43 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-til">📚 TIL</h2>
<p>useReducer (별도 포스팅 예정)</p>
<ul>
<li>useReducer 예제 복습</li>
<li>당일 과제 TODO 만들기(등록, 추가, 삭제)</li>
</ul>
<h2 id="💬-마치며">💬 마치며</h2>
<p><strong>TODO 과제 회고</strong></p>
<p>id 값을 설정할 때 초기에는 배열의 마지막 인덱스를 기준으로 id를 설정했는데, 배열 변경이 일어날 경우 예상치 못한 충돌이나 변경 문제가 발생할 가능성이 있었다. 그래서 useRef를 활용해 id 값을 관리하는 방식으로 변경</p>
<p>기존 코드</p>
<pre><code class="language-tsx">// 마지막 배열 인덱스 번호로 id 설정
 const lastIndex = todos[todos.length - 1].id + 1;</code></pre>
<p>변경 코드</p>
<pre><code class="language-tsx"> //id
  const idRef = useRef(0);

  // 투두리스트 추가 함수
  const addTodo = (value: string) =&gt; {
    if (value.trim() === &quot;&quot;) {
      alert(&quot;내용을 입력해 주세요&quot;);
      inputRef.current?.focus();
    } else
      setTodos((todos) =&gt; [
        ...todos,
        { id: idRef.current++, content: value, completed: false },
      ]);
    setInput(&quot;&quot;);
    inputRef.current?.focus();
  };
</code></pre>
<p>분명 더 좋은 코드가 있겠지만... 이번 과제를 통해 useRef가 DOM 조작 외에도 다양한 상황에서 활용될 수 있다는 것을 알게 됐다 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] Lv.1 나누어 떨어지는 숫자 배열 - JavaScript]]></title>
            <link>https://velog.io/@yoon_ji/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Lv.1-%EB%82%98%EB%88%84%EC%96%B4-%EB%96%A8%EC%96%B4%EC%A7%80%EB%8A%94-%EC%88%AB%EC%9E%90-%EB%B0%B0%EC%97%B4-JavaScript</link>
            <guid>https://velog.io/@yoon_ji/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Lv.1-%EB%82%98%EB%88%84%EC%96%B4-%EB%96%A8%EC%96%B4%EC%A7%80%EB%8A%94-%EC%88%AB%EC%9E%90-%EB%B0%B0%EC%97%B4-JavaScript</guid>
            <pubDate>Mon, 02 Dec 2024 14:15:43 GMT</pubDate>
            <description><![CDATA[<h2 id="📍-문제">📍 문제</h2>
<p>array의 각 element 중 divisor로 나누어 떨어지는 값을 오름차순으로 정렬한 배열을 반환하는 함수, solution을 작성해주세요.</p>
<p>divisor로 나누어 떨어지는 element가 하나도 없다면 배열에 -1을 담아 반환하세요.</p>
<h3 id="제한사항">제한사항</h3>
<ul>
<li>arr은 자연수를 담은 배열입니다.</li>
<li>정수 i, j에 대해 i ≠ j 이면 arr[i] ≠ arr[j] 입니다.</li>
<li>divisor는 자연수입니다.</li>
<li>array는 길이 1 이상인 배열입니다.</li>
</ul>
<h3 id="입출력-예">입출력 예</h3>
<table>
<thead>
<tr>
<th>arr</th>
<th>divisor</th>
<th>return</th>
</tr>
</thead>
<tbody><tr>
<td>[5, 9, 7, 10]</td>
<td>5</td>
<td>[5, 10]</td>
</tr>
<tr>
<td>[2, 36, 1, 3]</td>
<td>1</td>
<td>[1, 2, 3, 36]</td>
</tr>
<tr>
<td>[3,2,6]</td>
<td>10</td>
<td>[-1]</td>
</tr>
</tbody></table>
<h3 id="입출력-예-설명">입출력 예 설명</h3>
<p>입출력 예#1</p>
<p>arr의 원소 중 5로 나누어 떨어지는 원소는 5와 10입니다. 따라서 [5, 10]을 리턴합니다.</p>
<p>입출력 예#2</p>
<p>arr의 모든 원소는 1으로 나누어 떨어집니다. 원소를 오름차순으로 정렬해 [1, 2, 3, 36]을 리턴합니다.</p>
<p>입출력 예#3</p>
<p>3, 2, 6은 10으로 나누어 떨어지지 않습니다. 나누어 떨어지는 원소가 없으므로 [-1]을 리턴합니다.</p>
<h2 id="🥔-내-코드">🥔 내 코드</h2>
<p>1차 </p>
<pre><code class="language-jsx">const solution = (arr, divisor) =&gt; {
  const result = arr.filter((num) =&gt; num % divisor === 0).sort((a, b) =&gt; a - b);
  if (!result[0]) return [-1];
  return result;
};</code></pre>
<p>2차</p>
<pre><code class="language-bash">const solution = (arr, divisor) =&gt; {
    const result = arr.filter((num)=&gt; num % divisor === 0).sort((a, b) =&gt; a - b)
    return result[0] ? result : [- 1]
    }</code></pre>
<h2 id="✅-풀이-과정">✅ 풀이 과정</h2>
<ol>
<li>filter로 조건에 맞는 요소 필터링: arr.filter((num) =&gt; num % divisor === 0)</li>
<li>sort로 배열 오름차순 정렬: sort((a, b) =&gt; a - b)</li>
<li>빈 배열 처리: 삼항 연산자(result[0] ? result : [-1]) 사용</li>
</ol>
<ul>
<li>result[0]이 truthy면 result 반환, falsy면 [-1] 반환</li>
<li>빈 배열의 첫 요소는 undefined로, falsy 값</li>
</ul>
<h2 id="💡-다른-사람의-코드">💡 다른 사람의 코드</h2>
<pre><code class="language-jsx">function solution(arr, divisor) {
    var answer = arr.filter(v =&gt; v%divisor == 0);
    return answer.length == 0 ? [-1] : answer.sort((a,b) =&gt; a-b);
}</code></pre>
<h2 id="💬-마치며">💬 마치며</h2>
<p>1차 방식대로 풀었다가 다른 분의 풀이를 보고 2차 삼항연산자를 이용해서 값을 리턴하도록 했다</p>
<p>삼항연산자 꽤나 유용하게 쓰이는 것 같다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] Lv.1 직사각형 별찍기 - JavaScript]]></title>
            <link>https://velog.io/@yoon_ji/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Lv.1-%EC%A7%81%EC%82%AC%EA%B0%81%ED%98%95-%EB%B3%84%EC%B0%8D%EA%B8%B0-JavaScript</link>
            <guid>https://velog.io/@yoon_ji/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Lv.1-%EC%A7%81%EC%82%AC%EA%B0%81%ED%98%95-%EB%B3%84%EC%B0%8D%EA%B8%B0-JavaScript</guid>
            <pubDate>Mon, 02 Dec 2024 14:14:26 GMT</pubDate>
            <description><![CDATA[<h2 id="📍-문제">📍 문제</h2>
<h3 id="문제-설명"><strong>문제 설명</strong></h3>
<p>이 문제에는 표준 입력으로 두 개의 정수 n과 m이 주어집니다.</p>
<p>별(*) 문자를 이용해 가로의 길이가 n, 세로의 길이가 m인 직사각형 형태를 출력해보세요.</p>
<hr>
<h3 id="제한-조건">제한 조건</h3>
<ul>
<li>n과 m은 각각 1000 이하인 자연수입니다.</li>
</ul>
<hr>
<h3 id="예시">예시</h3>
<p>입력</p>
<p><code>5 3</code></p>
<p>출력</p>
<pre><code>*****
*****
*****</code></pre><h2 id="🥔-내-코드">🥔 내 코드</h2>
<pre><code class="language-jsx">process.stdin.setEncoding(&#39;utf8&#39;);
process.stdin.on(&#39;data&#39;, data =&gt; {
    const n = data.split(&quot; &quot;);
    const a = Number(n[0]), b = Number(n[1]);

    let rectangle = &quot;&quot;;
    for (let i = 0; i &lt; b; i++) {
        rectangle += &quot;*&quot;.repeat(a) + &quot;\n&quot;; // 가로 길이만큼 별 반복 후 줄바꿈
    }
    console.log(rectangle)
});</code></pre>
<h2 id="✅-풀이-과정">✅ 풀이 과정</h2>
<ol>
<li>rectangle 빈 변수 선언</li>
<li>for문 이용해서 가로 길이 b만큼 반복</li>
<li>세로는 repeat(a)로 반복 후 “\n”으로 줄바꿈</li>
</ol>
<h2 id="💡-다른-사람의-코드">💡 다른 사람의 코드</h2>
<pre><code class="language-jsx">process.stdin.setEncoding(&#39;utf8&#39;);
process.stdin.on(&#39;data&#39;, data =&gt; {
    const n = data.split(&quot; &quot;);
    const a = Number(n[0]), b = Number(n[1]);
    console.log(((&#39;*&#39;).repeat(a)+`\n`).repeat(b))
});</code></pre>
<h2 id="💬-마치며">💬 마치며</h2>
<p>오… repeat() 두 번 사용하는 방법도 있구나</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[FE 기술 면접] Virtual DOM]]></title>
            <link>https://velog.io/@yoon_ji/FE-%EA%B8%B0%EC%88%A0-%EB%A9%B4%EC%A0%91-Virtual-DOM</link>
            <guid>https://velog.io/@yoon_ji/FE-%EA%B8%B0%EC%88%A0-%EB%A9%B4%EC%A0%91-Virtual-DOM</guid>
            <pubDate>Sun, 01 Dec 2024 14:55:03 GMT</pubDate>
            <description><![CDATA[<h3 id="📌-virtual-dom-개념-정리">📌 <a href="https://velog.io/@yoon_ji/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B0%80%EC%83%81-%EB%8F%94Virtual-DOM">Virtual DOM 개념 정리</a></h3>
<hr>
<h3 id="q1-virtual-dom이란-무엇인가요">Q1: Virtual DOM이란 무엇인가요?</h3>
<p>A: Virtual DOM은 실제 DOM을 추상화한 가벼운 복사본으로, 메모리에 객체 형태로 존재합니다. 변경 사항이 발생했을 때 실제 DOM에 직접 반영하지 않고, Virtual DOM에서 먼저 처리한 후 필요한 부분만 실제 DOM에 업데이트하여 효율적인 렌더링을 가능하게 합니다.</p>
<h3 id="q2-virtual-dom을-사용하는-이유는-무엇인가요">Q2: Virtual DOM을 사용하는 이유는 무엇인가요?</h3>
<p>A: DOM을 직접 조작하면 브라우저 렌더링이 빈번하게 발생하여 성능이 저하될 수 있습니다. Virtual DOM을 사용하면 실제 DOM 조작을 최소화하여 성능을 개선할 수 있습니다.</p>
<h3 id="q3-virtual-dom이-항상-더-나은-성능을-보장하나요">Q3: Virtual DOM이 항상 더 나은 성능을 보장하나요?</h3>
<p>A: 반드시 그렇지는 않습니다. Virtual DOM을 생성하고 비교하는 과정에서도 연산 비용이 발생하기 때문입니다. 상황에 따라 성능 차이가 있을 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 리액트 이미지 렌더링]]></title>
            <link>https://velog.io/@yoon_ji/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A0%8C%EB%8D%94%EB%A7%81</link>
            <guid>https://velog.io/@yoon_ji/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A0%8C%EB%8D%94%EB%A7%81</guid>
            <pubDate>Sun, 01 Dec 2024 14:47:30 GMT</pubDate>
            <description><![CDATA[<h1 id="1-에셋-폴더">1. <strong>에셋 폴더</strong></h1>
<p><strong>특징</strong></p>
<ul>
<li>빌드 도구가 이미지를 <strong>최적화</strong>하고 관리함</li>
<li>사용하지 않는 이미지는 최종 번들에 포함되지 않는 <strong>트리쉐이킹</strong> 적용</li>
<li>빌드 시 이미지 이름이 해시값이 포함된 새로운 파일명으로 변환</li>
<li><strong>임포트(import)</strong>를 사용하여 이미지 추가</li>
</ul>
<hr>
<h2 id="에셋-폴더-사용법">에셋 폴더 사용법</h2>
<h3 id="1-이미지-임포트-및-렌더링">1) 이미지 임포트 및 렌더링</h3>
<pre><code class="language-tsx">import tree from &quot;./assets/images/tree.jpg&quot;;

export default function App() {
  return (
    &lt;&gt;
      {/* 이미지 렌더링 */}
      &lt;img src={tree} alt=&quot;tree&quot; /&gt;
    &lt;/&gt;
  );
}</code></pre>
<ul>
<li><strong>장점</strong>: 빌드 도구가 경로를 자동으로 관리하고, 이미지 최적화 및 캐싱 처리</li>
<li><strong>결과</strong>: 개발 모드에서 <code>&#39;tree.jpg&#39;</code>였던 경로가 <code>&#39;assets/tree.123abc.jpg&#39;</code>처럼 최적화된 경로로 변경</li>
</ul>
<blockquote>
<p>⚠️ <strong>주의: img src에 직접 경로 입력 시 빌드 후 <code>npm run preview</code> 로 확인시 이미지 깨짐</strong>
<code>&lt;img src=&quot;../src/assets/images/tree.jpg&quot; alt=&quot;&quot; /&gt;</code></p>
<ul>
<li>JSX는 JavaScript로 상대 경로 파싱 불가</li>
<li>해결: <code>import</code>로 이미지 불러옴</li>
</ul>
</blockquote>
<h3 id="✅-빌드와-프리뷰">✅ 빌드와 프리뷰</h3>
<p><code>npm run build</code> 명령어로 프로젝트를 빌드하면 최적화된 배포 파일 생성</p>
<p><code>npm run preview</code> 명령어로 빌드된 파일을 로컬에서 미리 확인 가능</p>
<p><strong>개발 모드와 빌드/프리뷰 모드의 차이</strong></p>
<ul>
<li>개발 모드: <code>index.html</code> 직접 사용</li>
<li>프리뷰 모드: <code>dist</code> 폴더의 최적화된 <code>index.html</code> 사용</li>
<li>프리뷰는 실제 배포 환경과 유사한 조건에서 테스트 가능</li>
</ul>
<hr>
<h3 id="2-백그라운드-이미지로-사용">2) 백그라운드 이미지로 사용</h3>
<pre><code class="language-tsx">&lt;div
  className=&quot;w-60 h-60&quot;
  style={{ backgroundImage: `url(${tree})` }}
&gt;&lt;/div&gt;;</code></pre>
<hr>
<h3 id="4-이미지-관리-파일-생성-💡">4) 이미지 관리 파일 생성 💡</h3>
<blockquote>
<p>🤔 이미지가 100개가 되면 코드가 너무 지저분해지지 않을까?</p>
</blockquote>
<p>이미지 개별 임포트가 번거롭다면 <strong>이미지 관리 파일</strong>로 한 곳에서 관리하여 코드를 깔끔하게 유지할 수 있음</p>
<p><strong>예시: 이미지 관리 파일 생성</strong></p>
<p><code>assets/images/images.ts</code></p>
<pre><code class="language-tsx">import tree from &quot;./tree.jpg&quot;;
import flower from &quot;./flower.jpg&quot;;

export const images = {
  tree,
  flower,
};</code></pre>
<p><strong>사용법</strong></p>
<pre><code class="language-tsx">import { images } from &quot;./assets/images/images&quot;;

export default function App() {
  return (
    &lt;&gt;
      &lt;img src={images.tree} alt=&quot;tree&quot; /&gt;
      &lt;img src={images.flower} alt=&quot;flower&quot; /&gt;
    &lt;/&gt;
  );
}</code></pre>
<hr>
<h1 id="2-퍼블릭-폴더">2. <strong>퍼블릭 폴더</strong></h1>
<p><strong>특징</strong></p>
<ul>
<li>빌드 도구가 <strong>관여하지 않고</strong> 이미지를 있는 그대로 복사함</li>
<li>모든 파일이 최종 번들에 포함되며, <strong>사용하지 않아도 번들 크기에 영향</strong>을 줌</li>
<li>파일 이름이 그대로 유지되며, <strong>절대 경로</strong>로 접근 필요</li>
<li><strong>정적 자산</strong>(예: 파비콘, robots.txt 등) 관리에 적합</li>
</ul>
<hr>
<h2 id="퍼블릭-폴더-사용법">퍼블릭 폴더 사용법</h2>
<h3 id="1-이미지-렌더링">1) 이미지 렌더링</h3>
<pre><code class="language-tsx">export default function App() {
  return (
    &lt;&gt;
      &lt;img src=&quot;/tree_p.jpg&quot; alt=&quot;tree&quot; /&gt;
    &lt;/&gt;
  );
}</code></pre>
<ul>
<li><strong>절대 경로</strong>(<code>/</code>)를 사용해야 이미지가 올바르게 표시됨</li>
<li>빌드 후에도 경로가 변하지 않음</li>
</ul>
<hr>
<h3 id="2-백그라운드-이미지로-사용-1">2) 백그라운드 이미지로 사용</h3>
<pre><code class="language-tsx">&lt;div
  className=&quot;w-60 h-60&quot;
  style={{ backgroundImage: &quot;url(&#39;/tree_p.jpg&#39;)&quot; }}
&gt;&lt;/div&gt;;
</code></pre>
<hr>
<h2 id="3-에셋-폴더와-퍼블릭-폴더-비교">3. <strong>에셋 폴더와 퍼블릭 폴더 비교</strong></h2>
<table>
<thead>
<tr>
<th><strong>특징</strong></th>
<th><strong>에셋 폴더</strong></th>
<th><strong>퍼블릭 폴더</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>빌드 도구 관리</strong></td>
<td>✅ 빌드 도구가 이미지 최적화 및 경로 관리</td>
<td>❌ 빌드 도구가 관여하지 않음</td>
</tr>
<tr>
<td><strong>최적화</strong></td>
<td>✅ <strong><code>트리쉐이킹</code></strong> 적용</td>
<td>❌ 모든 이미지가 번들에 포함됨</td>
</tr>
<tr>
<td><strong>파일 이름</strong></td>
<td>✅ 해시값 포함 (캐싱 및 버전 관리)</td>
<td>❌ 원본 파일 이름 그대로 유지</td>
</tr>
<tr>
<td><strong>사용 방법</strong></td>
<td><code>import</code>로 이미지 추가</td>
<td>절대 경로(<code>/</code>)로 이미지 추가</td>
</tr>
<tr>
<td><strong>사용 사례</strong></td>
<td>고정된 이미지를 프로젝트 내부에서 관리할 때</td>
<td>파비콘, robots.txt 등 정적 자산 관리</td>
</tr>
</tbody></table>
<hr>
<h2 id="4-css-이미지-사용">4. <strong>CSS 이미지 사용</strong></h2>
<p>CSS 파일에서 이미지를 사용할 때는 경로 작성</p>
<pre><code class="language-css">.bg {
  background-image: url(&quot;../assets/images/tree.jpg&quot;);
}</code></pre>
<blockquote>
<p>출처: <a href="https://www.sucoding.kr">수코딩</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] useRef]]></title>
            <link>https://velog.io/@yoon_ji/React-useRef</link>
            <guid>https://velog.io/@yoon_ji/React-useRef</guid>
            <pubDate>Sat, 30 Nov 2024 14:29:35 GMT</pubDate>
            <description><![CDATA[<h2 id="useref-비제어-컨트롤러로-dom-직접-조작"><strong>useRef: 비제어 컨트롤러로 DOM 직접 조작</strong></h2>
<h3 id="비제어-컨트롤러란"><strong>비제어 컨트롤러란?</strong></h3>
<p>비제어 컨트롤러는 DOM 요소에 직접 접근하여 값을 관리하는 방식으로, 실시간 값 변화 감지 불가</p>
<p>useRef를 사용해 DOM 요소 참조</p>
<h3 id="useref란"><strong>useRef란?</strong></h3>
<p>React에서 제공하는 Hook으로, 값을 보관하고 관리할 수 있는 저장소</p>
<p><strong>주요 특징:</strong></p>
<ul>
<li>컴포넌트가 리렌더링되어도 동일한 참조값 유지</li>
<li>값이 변경되어도 컴포넌트가 리렌더링되지 않음</li>
<li>DOM 요소에 직접 접근할 때 사용되며, current 프로퍼티를 통해 값을 안전하게 관리</li>
</ul>
<blockquote>
<p><strong>🤔 DOM 조작에 querySelector 대신 useRef를 사용하는 이유</strong>
querySelector는 실제 DOM을 직접 조작하여 React의 가상 DOM 시스템과 충돌 가능
컴포넌트가 리렌더링될 때마다 참조가 깨질 수 있고, React의 단방향 데이터 흐름을 방해할 수 있음
❌ React에서도 직접적인 DOM 조작을 권장하지 않음</p>
</blockquote>
<p>예시코드</p>
<pre><code class="language-tsx">import { useRef } from &quot;react&quot;;

export default function App() {
  const inputRef = useRef&lt;HTMLInputElement | null&gt;(null);

  const handleSubmit = () =&gt; {
    console.log(inputRef.current?.value); // input 값 가져오기
  };

  return (
    &lt;&gt;
      &lt;input type=&quot;text&quot; ref={inputRef} placeholder=&quot;Enter your name&quot; /&gt;
      &lt;button onClick={handleSubmit}&gt;Submit&lt;/button&gt;
    &lt;/&gt;
  );
}</code></pre>
<hr>
<h2 id="useref와-dom-접근"><strong>useRef와 DOM 접근</strong></h2>
<p><code>useRef</code>로 생성된 객체는 <code>current</code> 프로퍼티를 통해 DOM 요소에 접근 가능</p>
<p>이를 통해 값이나 메서드를 호출 가능</p>
<pre><code class="language-tsx">console.log(inputRef.current?.value); // input의 값
inputRef.current?.focus(); // input에 포커스</code></pre>
<hr>
<h2 id="폼-데이터"><strong>폼 데이터</strong></h2>
<p><code>useRef</code>를 여러 개 사용하여 폼 입력값 가져오기 가능</p>
<p><strong>예제: 다중 입력값 처리</strong></p>
<pre><code class="language-tsx">import { useRef } from &quot;react&quot;;

export default function App() {
  const idRef = useRef&lt;HTMLInputElement | null&gt;(null); //ref 객체
  const pwRef = useRef&lt;HTMLInputElement | null&gt;(null);

  const handleSubmit = (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
    e.preventDefault();
    console.log(&quot;ID:&quot;, idRef.current?.value);
    console.log(&quot;Password:&quot;, pwRef.current?.value);
  };

  return (
    &lt;form onSubmit={handleSubmit}&gt;
      &lt;input type=&quot;text&quot; ref={idRef} placeholder=&quot;아이디 입력&quot; /&gt;
      &lt;input type=&quot;password&quot; ref={pwRef} placeholder=&quot;비밀번호  입력&quot; /&gt;
      &lt;button type=&quot;submit&quot;&gt;로그인&lt;/button&gt;
    &lt;/form&gt;
  );
}</code></pre>
<hr>
<h2 id="라디오-버튼과-체크박스-처리"><strong>라디오 버튼과 체크박스 처리</strong></h2>
<h3 id="라디오-버튼"><strong>라디오 버튼</strong></h3>
<p><code>useRef</code>와 배열을 활용해 선택된 라디오 버튼의 값을 가져옴</p>
<p>라디오 버튼 처리 시 필요한 특별 처리:</p>
<ul>
<li>배열로 참조 관리 (<code>useRef&lt;HTMLInputElement[]&gt;([])</code>)</li>
<li>각 라디오 버튼에 <code>ref</code> 콜백 함수로 배열에 요소 할당</li>
<li><code>find()</code> 메서드로 선택된 라디오 버튼 탐색</li>
</ul>
<p>예시 코드:</p>
<pre><code class="language-tsx">const radioRef = useRef&lt;HTMLInputElement[]&gt;([]);

// 선택된 라디오 버튼 찾기
const selectedRadio = radioRef.current.find((radio) =&gt; radio.checked);
console.log(selectedRadio?.value);

// JSX에서 ref 설정
&lt;input
  type=&quot;radio&quot;
  ref={(el) =&gt; {
    if (el) radioRef.current[0] = el;
  }}
  name=&quot;gender&quot;
  value=&quot;female&quot;
  defaultChecked
/&gt;
&lt;input
  type=&quot;radio&quot;
  ref={(el) =&gt; {
    if (el) radioRef.current[1] = el;
  }}
  name=&quot;gender&quot;
  value=&quot;male&quot;
/&gt;</code></pre>
<hr>
<h3 id="체크박스"><strong>체크박스</strong></h3>
<p><code>checked</code> 속성으로 체크 여부 확인</p>
<pre><code class="language-tsx">const checkboxRef = useRef&lt;HTMLInputElement | null&gt;(null);

console.log(&quot;Checked:&quot;, checkboxRef.current?.checked);</code></pre>
<hr>
<h2 id="제어-컨트롤러-vs-비제어-컨트롤러"><strong>제어 컨트롤러 vs 비제어 컨트롤러</strong></h2>
<table>
<thead>
<tr>
<th><strong>특징</strong></th>
<th><strong>제어 컨트롤러</strong></th>
<th><strong>비제어 컨트롤러</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>기본 원리</strong></td>
<td>상태(state)로 값 관리</td>
<td>DOM 요소에 직접 접근</td>
</tr>
<tr>
<td><strong>실시간 렌더링</strong></td>
<td>✅ 지원</td>
<td>❌ 지원하지 않음</td>
</tr>
<tr>
<td><strong>사용 사례</strong></td>
<td>실시간 상태 반영이 필요한 경우</td>
<td>간단한 DOM 접근, 초기화 작업 등</td>
</tr>
</tbody></table>
<blockquote>
<p>출처: <a href="https://www.sucoding.kr">수코딩</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] useState]]></title>
            <link>https://velog.io/@yoon_ji/React-useState</link>
            <guid>https://velog.io/@yoon_ji/React-useState</guid>
            <pubDate>Sat, 30 Nov 2024 14:28:05 GMT</pubDate>
            <description><![CDATA[<h2 id="1-리액트에서-변수-선언-방식">1. 리액트에서 변수 선언 방식</h2>
<p><strong>1. 일반 변수 선언 (화면 렌더링에 영향 없음)</strong></p>
<ul>
<li>let, const, var로 선언된 변수들은 리액트가 변화 추적 불가</li>
</ul>
<p><strong>2. 상태 변수 선언 (화면 렌더링에 영향 있음)</strong></p>
<ul>
<li>useState와 같은 리액트 훅으로 선언</li>
<li>상태 변경 시 화면 자동 리렌더링</li>
</ul>
<hr>
<h2 id="2-usestate-제어-컨트롤러-상태-관리">2. useState: 제어 컨트롤러 상태 관리</h2>
<p>React에서 상태를 사용해 폼 요소나 화면 UI를 <strong>제어 컨트롤러</strong> 방식으로 관리 가능</p>
<h3 id="제어-컨트롤러란"><strong>제어 컨트롤러란?</strong></h3>
<ul>
<li><strong>제어 컨트롤러</strong>는 React 상태를 사용해 폼 요소나 UI를 <strong><code>실시간</code></strong>으로 제어하는 방식임</li>
</ul>
<h3 id="usestate란"><strong>useState란?</strong></h3>
<ul>
<li>React 16.8에 도입된 <strong>훅(hook)</strong></li>
<li>상태와 상태를 변경하는 함수를 배열로 반환</li>
</ul>
<p><strong>기본 문법</strong></p>
<pre><code class="language-tsx">// 비구조화할당
const [state, setState] = useState(초기값);</code></pre>
<ul>
<li><code>state</code>: 현재 상태 값</li>
<li><code>setState</code>: 상태 값을 변경하는 함수</li>
<li><code>초기값</code>: 상태의 초기값</li>
</ul>
<hr>
<h2 id="3-usestate-사용-예제">3. <strong>useState 사용 예제</strong></h2>
<h3 id="상태-관리로-화면-렌더링-반영"><strong>상태 관리로 화면 렌더링 반영</strong></h3>
<pre><code class="language-tsx">import { useState } from &quot;react&quot;;

export default function App() {
  const [count, setCount] = useState(0);

  const increment = () =&gt; {
    setCount(count + 1); //값 전달 시 count의 최신값으로 설정
  };

  return (
    &lt;div&gt;
      &lt;h1&gt;count: {count}&lt;/h1&gt;
      &lt;button onClick={increment}&gt;증가&lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre>
<blockquote>
<p>✅ 관례</p>
<ul>
<li>상태 이름이 <code>count</code>면 업데이트 함수는 <code>setCount</code>로 작명</li>
<li>예: <code>isVisible</code> → <code>setIsVisible</code>, <code>userName</code> → <code>setUserName</code></li>
</ul>
</blockquote>
<hr>
<h2 id="4-setstate의-두-가지-사용-방식">4. <strong>setState의 두 가지 사용 방식</strong></h2>
<h3 id="1-값-전달-방식"><strong>1. 값 전달 방식</strong></h3>
<pre><code class="language-tsx">setCount(count + 1); //값 전달 시 count의 최신값으로 설정</code></pre>
<ul>
<li>이전 상태를 참조하지 않을 때 사용</li>
</ul>
<h3 id="2-콜백-함수-방식-setstate현재상태-⇒-값"><strong>2. 콜백 함수 방식</strong> <code>setState((현재상태) ⇒ 값)</code></h3>
<pre><code class="language-tsx">setCount((count) =&gt; count + 1); //콜백함수 반환값이 count의 최신값으로 설정</code></pre>
<ul>
<li><code>이전 상태를 참조</code>해야 할 때 사용</li>
</ul>
<h3 id="3-두-방식-차이점">3. 두 방식 차이점</h3>
<p>값 전달 방식과 콜백 함수 방식의 주요 차이점은 상태 업데이트의 동작 방식에 있음</p>
<p>예시: 3번의 상태 업데이트 시도</p>
<pre><code class="language-tsx">const increment = () =&gt; {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
};</code></pre>
<p>count는 1만 증가함. 각 setCount가 동일한 count 값을 참조하기 때문(비동기)</p>
<p>하지만 콜백 함수 사용 시:</p>
<pre><code class="language-tsx">const increment = () =&gt; {
  setCount(prev =&gt; prev + 1);
  setCount(prev =&gt; prev + 1);
  setCount(prev =&gt; prev + 1);
};</code></pre>
<p>count가 3씩 증가함. 각 콜백이 이전 상태의 최신값을 참조하여 업데이트하기 때문</p>
<hr>
<h2 id="5-리액트에서의-불변성">5. <strong>리액트에서의 불변성</strong></h2>
<p>React는 <strong>불변성</strong>을 기반으로 상태 변화를 감지</p>
<p>✅ 즉, 상태를 직접 변경하지 않고 새로운 상태를 생성해야 함</p>
<h3 id="배열-상태-예제"><strong>배열 상태 예제</strong></h3>
<pre><code class="language-tsx">const [students, setStudents] = useState([&quot;james&quot;, &quot;john&quot;]);

const addStudent = () =&gt; {
  // ❌ 잘못된 방식 (원본 수정)
  // students.push(&quot;smith&quot;);
  // setStudents(students);

  // ✅ 올바른 방식 (새 배열 생성)
  setStudents((prevStudents) =&gt; [...prevStudents, &quot;smith&quot;]);
};</code></pre>
<blockquote>
<p>💡 올바른 방식: spread 연산자(...), map(), filter()를 사용해 새로운 상태를 생성</p>
</blockquote>
<hr>
<h2 id="6-제네릭-타입으로-상태-관리">6. <strong>제네릭 타입으로 상태 관리</strong></h2>
<h3 id="타입스크립트와-usestate"><strong>타입스크립트와 useState</strong></h3>
<pre><code class="language-tsx">interface User {
  name: string;
  age: number;
  gender?: string; // 선택적(optional) 속성
}

const [user, setUser] = useState&lt;User&gt;({ name: &quot;john&quot;, age: 20 });

// gender는 선택적 속성으로 추가 가능
const updateUser = () =&gt; {
  setUser(prev =&gt; ({ ...prev, gender: &quot;male&quot; }));
};</code></pre>
<h2 id="7-폼-요소-상태-관리">7. <strong>폼 요소 상태 관리</strong></h2>
<h3 id="inputtypetext">input[type=&quot;text&quot;]</h3>
<pre><code class="language-tsx">const [input, setInput] = useState(&quot;&quot;);

const handleChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
  setInput(e.target.value);
};

return &lt;input type=&quot;text&quot; value={input} onChange={handleChange} /&gt;;

// 혹은 onChange 내에서 직접 호출
&lt;input type=&quot;text&quot; value={input} onChange={(e) =&gt; setInput(e.target.value)} /&gt;</code></pre>
<blockquote>
<p>💡 <strong><code>value</code></strong>와 <strong><code>onChange</code></strong>는 폼 요소 제어 시 세트로 사용</p>
<ul>
<li>value 속성 없으면 리셋 기능 추가해도 인풋 내부 값 제어 불가</li>
<li>폼 요소 완전 제어를 위해 value와 onChange 함께 사용 필수</li>
</ul>
</blockquote>
<h3 id="inputtypedate"><strong>input[type=&#39;date&#39;]</strong></h3>
<pre><code class="language-jsx">// date input
const [dateInput, setDateInput] = useState(&quot;&quot;);
&lt;input type=&quot;date&quot; value={dateInput} onChange={(e) =&gt; setDateInput(e.target.value)} /&gt;</code></pre>
<h3 id="textarea">textarea</h3>
<pre><code class="language-jsx">// textarea
const [textInput, setTextInput] = useState(&quot;초기값&quot;);
&lt;textarea value={textInput} onChange={(e) =&gt; setTextInput(e.target.value)} /&gt;</code></pre>
<h3 id="체크박스"><strong>체크박스</strong></h3>
<pre><code class="language-tsx">const [checked, setChecked] = useState(false);
&lt;input type=&quot;checkbox&quot; checked={checked} onChange={() =&gt; setChecked(!checked)} /&gt;;</code></pre>
<h3 id="radio">radio</h3>
<p><strong><code>초기값</code></strong> 설정 필수 ⇒ defaultChecked 설정</p>
<pre><code class="language-jsx">const [radioValue, setRadioValue] = useState(&quot;male&quot;);
      &lt;div&gt;
        &lt;input
          type=&quot;radio&quot;
          name=&quot;gender&quot;
          className=&quot;border border-slate-500&quot;
          value={&quot;male&quot;}
          defaultChecked
          onChange={() =&gt; setRadioValue(&quot;male&quot;)}
        /&gt;
        male
      &lt;/div&gt;
      &lt;div&gt;
        &lt;input
          type=&quot;radio&quot;
          name=&quot;gender&quot;
          className=&quot;border border-slate-500&quot;
          value={&quot;female&quot;}
          onChange={() =&gt; setRadioValue(&quot;female&quot;)}
        /&gt;
        female
      &lt;/div&gt;</code></pre>
<h3 id="셀렉트-박스"><strong>셀렉트 박스</strong></h3>
<p>select 요소 초기값 지정 시 defaultValue 속성 사용 가능</p>
<p><strong>value와 defaultValue 차이점</strong></p>
<ul>
<li>value: <strong>컴포넌트가 제어되는 상태</strong>로, React에 의해 값이 관리됨</li>
<li>defaultValue: 컴포넌트의 초기값만 설정하고 이후 React의 제어를 받지 않음</li>
</ul>
<pre><code class="language-tsx">const [selected, setSelected] = useState(&quot;apple&quot;);
&lt;select value={selected} onChange={(e) =&gt; setSelected(e.target.value)}&gt;
  &lt;option value=&quot;apple&quot;&gt;Apple&lt;/option&gt;
  &lt;option value=&quot;banana&quot;&gt;Banana&lt;/option&gt;
&lt;/select&gt;;</code></pre>
<blockquote>
<p>출처: <a href="https://www.sucoding.kr">수코딩</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 24.11.29]]></title>
            <link>https://velog.io/@yoon_ji/TIL-24.11.29</link>
            <guid>https://velog.io/@yoon_ji/TIL-24.11.29</guid>
            <pubDate>Fri, 29 Nov 2024 14:55:11 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-til">📚 TIL</h2>
<p><a href="https://velog.io/@yoon_ji/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A0%8C%EB%8D%94%EB%A7%81">리액트 이미지 렌더링</a>
<a href="https://velog.io/@yoon_ji/React-useState">useState</a>
<a href="https://velog.io/@yoon_ji/React-useRef">useRef</a></p>
<h2 id="💬-마치며">💬 마치며</h2>
<p><img src="https://velog.velcdn.com/images/yoon_ji/post/fc7f060c-0fa5-45c6-b067-aab3c8db7123/image.png" alt=""></p>
<p>주말 과제 한다고 포스팅 할 글 정리를 못해서 주말 이용해서 해 봐야겠다... 요즘 매일 새로운 걸 궤짝으로 배우는데 소화할 수 있는 양은 소주잔 정도라(술 안 좋아함) 수업 없는 주말이 소중해졌다. 폼 요소 제어하는 부분도 헷갈려서 다시 봐야겠다</p>
<p>유튜브 영상 찾아보면서도 이해가 안 됐던 리액트 훅 진도를 나갔는데 실습하면서 좀 감이 잡힌 것 같다. 실습 좋긴 한데 시간내에 못끝내면 좀 화나. 나만 느려. 주말 과제로 내주신 것도 타입 에러 뜨긴 하는데 일단 구현은 했다... 돌아가긴 함. 내일 다시 봐야지</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL 24.11.28]]></title>
            <link>https://velog.io/@yoon_ji/TIL-24.11.28</link>
            <guid>https://velog.io/@yoon_ji/TIL-24.11.28</guid>
            <pubDate>Thu, 28 Nov 2024 15:12:56 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-til">📚 TIL</h2>
<p><a href="https://velog.io/@yoon_ji/React-%ED%94%84%EB%A1%AD%EC%8A%A4-%EB%93%9C%EB%A6%B4%EB%A7%81">프롭스 드릴링</a>
<a href="https://velog.io/@yoon_ji/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EB%A0%8C%EB%8D%94%EB%A7%81-xwz3jurs">리액트 조건부 렌더링</a>
<a href="https://velog.io/@yoon_ji/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%B0%98%EB%B3%B5-%EB%A0%8C%EB%8D%94%EB%A7%81">리액트 반복 렌더링</a>
재사용 가능한 컴포넌트
Tailwind Css</p>
<h2 id="💬-마치며">💬 마치며</h2>
<p>오늘 실습이 많았다 확실히 개념만 배우는 것보다 내 눈에 UI가 보이는 게 재미있는 것 같다. 피그마 디자인을 컴포넌트로 만들어 보고 props를 구조분해할당과 나머지매개변수를 이용해서 재사용 가능한 컴포넌트로 리팩토링하는 과정을 보여주셨는데, 나름 props 하나 전달했다고 재사용 가능한 컴포넌트 완성! 이러고 있었는데 역시나 쉬운 건 없었음. 고려할 점이 백만개(아님)였다. 많은 경우의수를 생각하면서 개발해야 되는구나 느꼈다.</p>
<p>+) 부모님 결혼기념일이라 수업 마치고 식당 노래방 풀코스로 즐기고 왔다...(공부 못했단소리)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 리액트 반복 렌더링]]></title>
            <link>https://velog.io/@yoon_ji/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%B0%98%EB%B3%B5-%EB%A0%8C%EB%8D%94%EB%A7%81</link>
            <guid>https://velog.io/@yoon_ji/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%B0%98%EB%B3%B5-%EB%A0%8C%EB%8D%94%EB%A7%81</guid>
            <pubDate>Thu, 28 Nov 2024 14:49:35 GMT</pubDate>
            <description><![CDATA[<h3 id="map-메서드와-반복-렌더링"><code>map</code> 메서드와 반복 렌더링</h3>
<p><code>map</code> 메서드는 <strong>배열의 요소를 순회하며 새로운 배열을 반환</strong>하는 역할을 함</p>
<p>리액트에서 자주 사용되는 이유는 <strong>JSX 요소를 배열 데이터로부터 생성</strong>하는 작업에 매우 적합하기 때문임</p>
<hr>
<h3 id="map-메서드의-특징"><code>map</code> 메서드의 특징</h3>
<ol>
<li><strong>콜백 함수 실행</strong>: 배열의 각 요소를 순회하며 콜백 함수 실행</li>
<li><strong>새로운 배열 생성</strong>: 콜백 함수의 반환값으로 구성된 <strong>새로운 배열</strong> 생성</li>
<li><strong>원본 배열 불변</strong>: 원본 배열 유지</li>
</ol>
<hr>
<h3 id="리액트에서의-활용">리액트에서의 활용</h3>
<p>리액트에서는 <code>map</code> 메서드를 사용해 <strong><code>배열</code></strong> 데이터를 <strong>JSX 요소로 변환</strong>하여 <strong>반복 렌더링</strong>이 가능함</p>
<blockquote>
<p>💡 표현식({})에서 객체는 직접 출력이 불가능하며, JSON.stringify()를 사용하여 문자열로 변환해야 함</p>
</blockquote>
<p><strong>예시</strong></p>
<pre><code class="language-tsx">export default function Loop() {
  // 배열 데이터 생성
  const items = [&quot;apple&quot;, &quot;banana&quot;, &quot;cherry&quot;];
  return (
    &lt;ul&gt;
      {items.map((item) =&gt; {
        return &lt;li&gt;{item}&lt;/li&gt;;
      })}
    &lt;/ul&gt;
  );
}</code></pre>
<p><strong>실행 결과</strong></p>
<pre><code class="language-html">&lt;ul&gt;
  &lt;li&gt;apple&lt;/li&gt;
  &lt;li&gt;banana&lt;/li&gt;
  &lt;li&gt;cherry&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<hr>
<h3 id="map-메서드의-매개변수"><code>map</code> 메서드의 매개변수</h3>
<p><code>map</code> 메서드는 3개의 매개변수를 받을 수 있음:</p>
<ol>
<li><code>item</code>: 현재 처리 중인 요소 (필수)</li>
<li><code>index</code>: 현재 처리 중인 요소의 인덱스 (선택)</li>
<li><code>array</code>: <code>map</code>을 호출한 배열 자체 (선택)</li>
</ol>
<p><strong>예시: <code>item</code>과 <code>index</code> 사용</strong></p>
<pre><code class="language-tsx">export default function LoopWithIndex() {
  const items = [&quot;apple&quot;, &quot;banana&quot;, &quot;cherry&quot;];
  return (
    &lt;ul&gt;
      {items.map((item, index) =&gt; (
        &lt;li key={index}&gt;{`${index + 1}. ${item}`}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}</code></pre>
<p><strong>실행 결과</strong></p>
<pre><code class="language-html">&lt;ul&gt;
  &lt;li&gt;1. apple&lt;/li&gt;
  &lt;li&gt;2. banana&lt;/li&gt;
  &lt;li&gt;3. cherry&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<hr>
<h3 id="key-속성"><code>key</code> 속성</h3>
<p>리액트에서 반복 렌더링할 때는 각 요소에 <strong>고유한 <code>key</code> 속성</strong>을 지정해야 함</p>
<p><strong>key 역할</strong></p>
<ol>
<li><strong>렌더링 성능 최적화</strong>: 변경된 요소를 효율적으로 업데이트</li>
<li><strong>요소 추적</strong>: 요소를 정확히 식별</li>
</ol>
<h3 id="key-설정-예시"><code>key</code> 설정 예시</h3>
<pre><code class="language-tsx">export default function LoopWithKey() {
  const items = [&quot;apple&quot;, &quot;banana&quot;, &quot;cherry&quot;];
  return (
    &lt;ul&gt;
      {items.map((item, index) =&gt; (
        &lt;li key={index}&gt;{item}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}</code></pre>
<blockquote>
<p>💡 주의: 배열의 인덱스를 key로 사용하는 것은 권장되지 않음. 데이터에 고유한 값(ID)이 있다면 이를 사용할 것</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 리액트 조건부 렌더링]]></title>
            <link>https://velog.io/@yoon_ji/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EB%A0%8C%EB%8D%94%EB%A7%81-xwz3jurs</link>
            <guid>https://velog.io/@yoon_ji/React-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EB%A0%8C%EB%8D%94%EB%A7%81-xwz3jurs</guid>
            <pubDate>Thu, 28 Nov 2024 14:48:30 GMT</pubDate>
            <description><![CDATA[<h3 id="react에서의-조건부-렌더링">React에서의 조건부 렌더링</h3>
<p>React에서 조건부 렌더링은 특정 조건에 따라 컴포넌트를 표시하거나 숨길 때 사용</p>
<hr>
<h2 id="1-if문-사용">1. <strong>if문 사용</strong></h2>
<p><code>JSX 외부</code>에서 조건에 따라 전체 컴포넌트 또는 JSX 블록을 처리할 때 유용함</p>
<h3 id="예제-컴포넌트-렌더링"><strong>예제: 컴포넌트 렌더링</strong></h3>
<pre><code class="language-tsx">import Login from &quot;../Login&quot;;
import Logout from &quot;../Logout&quot;;

export default function IfExample() {
  const isLoggedIn = true;

  if (isLoggedIn) {
    return &lt;Login /&gt;;
  }
  return &lt;Logout /&gt;;
}</code></pre>
<h3 id="예제-간단한-메시지-출력">예제: 간단한 메시지 출력</h3>
<pre><code class="language-tsx">export default function IfExample() {
  const isLoggedIn = false;

  if (isLoggedIn) {
    return &lt;h2&gt;환영합니다!&lt;/h2&gt;;
  }
  return &lt;h2&gt;로그인이 필요합니다.&lt;/h2&gt;;
}</code></pre>
<hr>
<h2 id="2-삼항-연산자">2. <strong>삼항 연산자</strong></h2>
<p><code>JSX 내부</code>에서 <code>참과 거짓</code>에 따라 다른 요소를 렌더링할 때 사용</p>
<h3 id="1-단순-텍스트-조건부-렌더링">1. 단순 텍스트 조건부 렌더링</h3>
<pre><code class="language-tsx">export default function TernaryExample() {
  const isLoggedIn = true;

  return (
    &lt;&gt;
      &lt;h1&gt;{isLoggedIn ? &quot;로그인 되었습니다&quot; : &quot;로그인이 필요합니다&quot;}&lt;/h1&gt;
    &lt;/&gt;
  );
}</code></pre>
<h3 id="2-jsx-요소-조건부-렌더링">2. JSX 요소 조건부 렌더링</h3>
<pre><code class="language-tsx">{isLoggedIn ? &lt;h1&gt;로그인 되었습니다&lt;/h1&gt; : &lt;h1&gt;로그인 되지 않았습니다&lt;/h1&gt;}</code></pre>
<h3 id="3-컴포넌트-조건부-렌더링">3. 컴포넌트 조건부 렌더링</h3>
<pre><code class="language-tsx">{isLoggedIn ? &lt;Login /&gt; : &lt;Logout /&gt;}</code></pre>
<h3 id="4-인라인-스타일-조건부-적용">4. 인라인 스타일 조건부 적용</h3>
<pre><code class="language-tsx">&lt;h1 style={{ color: isLoggedIn ? &quot;red&quot; : &quot;blue&quot; }}&gt;로그인&lt;/h1&gt;</code></pre>
<h3 id="5-클래스명-조건부-적용">5. 클래스명 조건부 적용</h3>
<pre><code class="language-tsx">&lt;h1 className={`${isLoggedIn ? &quot;text-rose-400&quot; : &quot;text-green-600&quot;}`}&gt;로그인&lt;/h1&gt;</code></pre>
<h3 id="6-tailwind-merge를-활용한-조건부-스타일링">6. Tailwind Merge를 활용한 조건부 스타일링</h3>
<pre><code class="language-tsx">&lt;h1 className={twMerge(isLoggedIn ? &quot;text-rose-400&quot; : &quot;text-gray-500&quot;)}&gt;로그인&lt;/h1&gt;</code></pre>
<hr>
<h2 id="3--연산자">3. <strong>&amp;&amp; 연산자</strong></h2>
<p>JSX 범위 안에서 <code>참</code>에 따른 분기 처리를 하고 싶을 때</p>
<h3 id="예제-특정-컴포넌트-렌더링">예제: 특정 컴포넌트 렌더링</h3>
<pre><code class="language-tsx">import Login from &quot;../Login&quot;;
// 내가 true일 때만 렌더링하고 싶을 때 사용
// flase는 신경 안 쓰고 싶다! 하면 이렇게
export default function AndExample() {
  const isLoggedIn = true;

  return (
    &lt;&gt;
      {isLoggedIn &amp;&amp; &lt;Login /&gt;}
      &lt;h2&gt;이 메시지는 항상 보입니다.&lt;/h2&gt;
    &lt;/&gt;
  );
}</code></pre>
<h3 id="예제-클래스-이름-적용">예제: 클래스 이름 적용</h3>
<pre><code class="language-tsx">import { twMerge } from &quot;tailwind-merge&quot;;

export default function AndExample() {
  const isLoggedIn = true;

  return (
     {/* 클래스네임 조건부 */}
      {/* style로는 불가능 */}
    &lt;h1 className={twMerge(isLoggedIn &amp;&amp; &quot;text-green-600&quot;)}&gt;
      조건부 클래스 적용
    &lt;/h1&gt;
  );
}
</code></pre>
<hr>
<h2 id="4-즉시-실행-함수-iife">4. <strong>즉시 실행 함수 (IIFE)</strong></h2>
<p>🤔 <strong>JSX 표현식에서 if문 사용 불가?</strong> 놉, IIFE(즉시실행함수)로 사용 가능</p>
<p><strong>IIFE를 사용하여 JSX 표현식 내에서 if문을 사용하는 방법</strong></p>
<ol>
<li>함수를 소괄호 ()로 감싸기</li>
<li>함수를 즉시 실행하기 위해 끝에 ()를 추가</li>
<li>함수 내부에서 일반적인 if-else 문 사용 가능</li>
</ol>
<h3 id="예제-if문으로-조건부-렌더링">예제: if문으로 조건부 렌더링</h3>
<pre><code class="language-tsx">export default function IIFE() {
  const isLoggedIn = true;

  return (
    &lt;&gt;
      {(() =&gt; {
        if (isLoggedIn) {
          return &lt;h2&gt;로그인 되었습니다&lt;/h2&gt;;
        } else {
          return &lt;h2&gt;로그인이 필요합니다&lt;/h2&gt;;
        }
      })()}
    &lt;/&gt;
  );
}
</code></pre>
<blockquote>
<p>💡 참고: IIFE 활용 시 JSX 표현식 내부에서도 if문 사용 가능. 단, 가독성을 위해 외부 처리나 삼항/&amp;&amp; 연산자 사용이 일반적임</p>
</blockquote>
<hr>
<h2 id="조건부-렌더링의-선택-기준"><strong>조건부 렌더링의 선택 기준</strong></h2>
<table>
<thead>
<tr>
<th><strong>방법</strong></th>
<th><strong>사용 상황</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>if문</strong></td>
<td>JSX <strong>외부</strong>에서 전체 블록 또는 컴포넌트를 조건부로 처리할 때</td>
</tr>
<tr>
<td><strong>삼항 연산자</strong></td>
<td>JSX <strong>내부</strong>에서 <code>참</code>과 <code>거짓</code>에 따라 다른 결과를 렌더링할 때</td>
</tr>
<tr>
<td><strong>&amp;&amp; 연산자</strong></td>
<td>JSX <strong>내부</strong>에서 ****조건이 <code>참</code>일 때만 특정 요소를 렌더링할 때</td>
</tr>
</tbody></table>
]]></description>
        </item>
    </channel>
</rss>