<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>haxxru</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 13 Apr 2026 07:20:29 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>haxxru</title>
            <url>https://velog.velcdn.com/images/xxxhyxxk__/profile/df239db7-4e39-46ef-903a-f0e4b9266eac/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. haxxru. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/xxxhyxxk__" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Day 30 - Hard]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-30-Hard</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-30-Hard</guid>
            <pubDate>Mon, 13 Apr 2026 07:20:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 04월 13일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 Rq 파라미터 처리 개선, 예외 처리, 테스트(assertThat), 그리고
레이어드 아키텍처 구조를 정리했다.</p>
<hr>
<h2 id="1-파라미터-처리-리팩토링-hashmap--stream">1. 파라미터 처리 리팩토링 (HashMap + Stream)</h2>
<p>문자열을 매번 직접 파싱하는 대신, 생성자에서 한 번만 파싱하여<br>HashMap에 저장하도록 개선했다.</p>
<pre><code class="language-java">@Test
@DisplayName(&quot;\&quot;등록?이름=홍길동&amp;고향=남원&amp;성별=남성\&quot;&quot;)
void t7() {
    Rq rq = new Rq(&quot;등록?이름=홍길동&amp;고향=남원&amp;성별=남성&quot;);

    String paramValue = rq.getParam(&quot;성별&quot;, &quot;&quot;);

    assertEquals(&quot;남성&quot;, paramValue);
}</code></pre>
<ul>
<li>생성자에서 초기화 → 중복 파싱 제거</li>
<li>HashMap으로 빠른 조회</li>
<li>Stream으로 코드 간결화</li>
</ul>
<hr>
<h2 id="2-예외-상황-처리">2. 예외 상황 처리</h2>
<p>파라미터가 없거나 값이 비어있는 경우 기본값을 반환하도록 처리했다.</p>
<pre><code class="language-java">@Test
@DisplayName(&quot;\&quot;등록?성별=\&quot;&quot;)
void t8() {
    Rq rq = new Rq(&quot;등록?성별=&quot;);

    String paramValue = rq.getParam(&quot;성별&quot;, &quot;모름&quot;);

    assertEquals(&quot;모름&quot;, paramValue);
}</code></pre>
<pre><code class="language-java">@Test
@DisplayName(&quot;\&quot;등록?성별\&quot;&quot;)
void t9() {
    Rq rq = new Rq(&quot;등록?성별&quot;);

    String paramValue = rq.getParam(&quot;성별&quot;, &quot;모름&quot;);

    assertEquals(&quot;모름&quot;, paramValue);
}</code></pre>
<pre><code class="language-java">@Test
@DisplayName(&quot;\&quot;등록\&quot;&quot;)
void t10() {
    Rq rq = new Rq(&quot;등록&quot;);

    String paramValue = rq.getParam(&quot;성별&quot;, &quot;모름&quot;);

    assertEquals(&quot;모름&quot;, paramValue);
}</code></pre>
<ul>
<li>값이 없으면 기본값 반환</li>
<li>예외 상황에서도 안정적으로 동작</li>
</ul>
<hr>
<h2 id="3-assertthat-assertj">3. assertThat (AssertJ)</h2>
<p>기존 assertEquals 대신 assertThat을 도입했다.</p>
<pre><code class="language-java">assertThat(value).isEqualTo(2);</code></pre>
<ul>
<li>가독성이 좋고 체이닝 방식 지원</li>
<li>다양한 검증 메서드 제공</li>
</ul>
<hr>
<h2 id="4-getparamasint-구현">4. getParamAsInt 구현</h2>
<p>문자열 파라미터를 정수로 변환하는 기능을 추가했다.</p>
<pre><code class="language-java">@Test
@DisplayName(&quot;\&quot;목록?page=2\&quot;&quot;)
void t11() {
    Rq rq = new Rq(&quot;목록?page=2&quot;);

    int value = rq.getParamAsInt(&quot;page&quot;, 1);

    assertThat(value).isEqualTo(2);
}</code></pre>
<pre><code class="language-java">@Test
@DisplayName(&quot;\&quot;목록?page=2번\&quot;&quot;)
void t12() {
    Rq rq = new Rq(&quot;목록?page=2번&quot;);

    int value = rq.getParamAsInt(&quot;page&quot;, 1);

    assertThat(value).isEqualTo(1);
}</code></pre>
<ul>
<li>변환 성공 → 값 반환</li>
<li>변환 실패 → 기본값 반환</li>
</ul>
<hr>
<h2 id="5-레이어드-아키텍처">5. 레이어드 아키텍처</h2>
<p>역할별로 계층을 나누어 구조를 분리하는 방식이다.</p>
<p>  역할            계층</p>
<hr>
<p>  요청 처리       Controller
  비즈니스 로직   Service
  데이터 처리     Repository</p>
<ul>
<li>역할이 분리되어 유지보수에 유리</li>
</ul>
<hr>
<h2 id="6-구조-분리-controller--service--repository">6. 구조 분리 (Controller / Service / Repository)</h2>
<p>기능을 역할별로 분리했다.</p>
<ul>
<li>SystemController → 종료</li>
<li>WiseSayingController → CRUD</li>
<li>Service → 로직 처리</li>
<li>Repository → 데이터 처리</li>
</ul>
<hr>
<h2 id="7-appcontext">7. AppContext</h2>
<p>공통으로 사용하는 객체를 static으로 관리한다.</p>
<pre><code class="language-java">public class AppContext {
    public static WiseSayingService wiseSayingService;
}</code></pre>
<ul>
<li>여러 곳에서 공유되는 객체 관리</li>
<li>Controller / Service / Repository 등록</li>
</ul>
<hr>
<h2 id="8-static-block">8. static block</h2>
<p>클래스 로딩 시 한 번 실행되는 초기화 블록이다.</p>
<pre><code class="language-java">static {
    // 초기화 코드
}</code></pre>
<ul>
<li>복잡한 static 초기화에 사용</li>
</ul>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<ul>
<li>파라미터를 미리 파싱해두면 코드가 훨씬 단순해진다.</li>
<li>예외 상황을 처리하면 안정적인 프로그램을 만들 수 있다.</li>
<li>레이어를 나누면 구조가 깔끔해지고 유지보수가 쉬워진다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 29 - Rq, TDD]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-29-Rq-TDD</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-29-Rq-TDD</guid>
            <pubDate>Fri, 10 Apr 2026 07:04:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 04월 10일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 명언 게시판 코드에 Stream 도입과 Rq 클래스 구현, 그리고 컨트롤러 구조 개선과 TDD
개념까지 정리했다.</p>
<hr>
<h2 id="1-stream-도입-기존-코드-→-stream">1. Stream 도입 (기존 코드 → Stream)</h2>
<p>기존에는 반복문과 조건문을 사용해 데이터를 처리했다. 하지만 Stream을
사용하면 더 간결하게 표현할 수 있다.</p>
<pre><code class="language-java">list.stream()
    .filter(e -&gt; e.getId() == id)
    .findFirst();</code></pre>
<p>핵심 - 반복문 없이 데이터 처리 가능 - filter, map 등으로 선언형 코드
작성 가능</p>
<hr>
<h2 id="2-removeif를-통한-데이터-삭제">2. removeIf를 통한 데이터 삭제</h2>
<p>컬렉션에서 조건에 맞는 요소를 제거할 때 사용한다.</p>
<pre><code class="language-java">list.removeIf(e -&gt; e.getId() == id);</code></pre>
<p>핵심 - 반복문 없이 삭제 가능 - 코드가 훨씬 간결해짐</p>
<hr>
<h2 id="3-컨트롤러와-rq-클래스">3. 컨트롤러와 Rq 클래스</h2>
<p>컨트롤러는 명령어를 처리하는 역할을 한다. 식당의 점원과 같은 역할을
한다.</p>
<hr>
<h2 id="4-rq-클래스-개념">4. Rq 클래스 개념</h2>
<p>Rq는 Request의 약자로, 컨트롤러에서 자주 사용하는 로직을 모아둔
클래스이다.</p>
<pre><code class="language-java">Rq rq = new Rq(&quot;목록?page=5&amp;searchKeyword=영광&quot;);

int page = rq.getParamAsInt(&quot;page&quot;, -1);
String keyword = rq.getParam(&quot;searchKeyword&quot;, &quot;&quot;);</code></pre>
<hr>
<h2 id="5-rq-클래스-구현">5. Rq 클래스 구현</h2>
<pre><code class="language-java">public String getParam(String paramName, String defaultValue) {
    if (paramsMap.containsKey(paramName)) {
        return paramsMap.get(paramName);
    } else {
        return defaultValue;
    }
}</code></pre>
<pre><code class="language-java">public int getParamAsInt(String paramName, int defaultValue) {
    String value = getParam(paramName, &quot;&quot;);

    if (value.isEmpty()) return defaultValue;

    try {
        return Integer.parseInt(value);
    } catch (NumberFormatException e) {
        return defaultValue;
    }
}</code></pre>
<hr>
<h2 id="6-tdd-개념">6. TDD 개념</h2>
<p>TDD(Test-Driven Development)는 테스트를 먼저 작성하고, 그 테스트를
통과시키는 방식으로 개발하는 방법이다.</p>
<h3 id="tdd-3단계">TDD 3단계</h3>
<ul>
<li>Red: 실패하는 테스트 작성</li>
<li>Green: 테스트 통과 코드 작성</li>
<li>Refactor: 코드 개선</li>
</ul>
<hr>
<h2 id="7-자동-테스트-vs-수동-테스트">7. 자동 테스트 vs 수동 테스트</h2>
<p>  구분        자동 테스트   수동 테스트</p>
<hr>
<p>  실행 주체   컴퓨터        사람
  실행 속도   빠름          느림
  신뢰도      높음          낮음</p>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<ul>
<li>Stream을 사용하면 반복문 없이 데이터를 간결하게 처리할 수 있다.</li>
<li>removeIf를 사용하면 조건에 맞는 요소를 쉽게 제거할 수 있다.</li>
<li>Rq 클래스는 명령어 파싱 로직을 분리하여 컨트롤러를 단순하게 만든다.</li>
<li>getParam과 getParamAsInt를 통해 안전하게 파라미터를 처리할 수 있다.</li>
<li>TDD는 테스트를 먼저 작성하고 기능을 구현하는 개발 방식이다.</li>
<li>Red-Green-Refactor 사이클을 통해 점진적으로 코드를 개선할 수 있다.</li>
<li>자동 테스트를 사용하면 빠르고 안정적으로 코드 품질을 유지할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 28 - Java CRUD]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-28-Java-CRUD-thpyd2zo</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-28-Java-CRUD-thpyd2zo</guid>
            <pubDate>Thu, 09 Apr 2026 07:13:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 04월 09일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 콘솔 기반 명언 관리 앱을 구현하면서 CRUD 구조와 문자열 파싱, 객체 관리 방식을 정리했다.</p>
<hr>
<h2 id="1-전체-실행-흐름-run">1. 전체 실행 흐름 (run)</h2>
<p>사용자의 명령어 입력에 따라 기능을 분기하는 메인 루프이다.</p>
<pre><code class="language-java">void run () {
    System.out.println(&quot;== 명언 앱 ==&quot;);

    while (true) {
        System.out.print(&quot;명령) &quot;);
        String cmd = scanner.nextLine().trim();

        if (cmd.equals(&quot;종료&quot;)) {
            System.out.println(&quot;프로그램을 종료합니다.&quot;);
            break;
        } else  if (cmd.equals(&quot;등록&quot;)) {
            actionWrite();
        } else if (cmd.equals(&quot;목록&quot;)) {
            actionList();
        } else if (cmd.startsWith(&quot;삭제&quot;)) {
            actionDelete(cmd);
        } else if (cmd.startsWith(&quot;수정&quot;)) {
            actionModify(cmd);
        }
    }
    scanner.close();
}</code></pre>
<p>핵심</p>
<ul>
<li><code>while(true)</code>로 계속 실행</li>
<li>명령어에 따라 기능 분기</li>
<li>콘솔 앱의 전체 흐름 제어</li>
</ul>
<hr>
<h2 id="2-상태-관리-필드">2. 상태 관리 (필드)</h2>
<p>앱 실행 동안 유지되는 데이터 저장 영역이다.</p>
<pre><code class="language-java">Scanner scanner = new Scanner(System.in);
int lastId = 0;
List&lt;WiseSaying&gt; wiseSayingList = new ArrayList&lt;&gt;();</code></pre>
<p>핵심</p>
<ul>
<li><code>lastId</code> : 명언 번호 증가 관리</li>
<li><code>List</code> : 데이터 저장소 역할</li>
<li>메모리 기반 구조</li>
</ul>
<hr>
<h2 id="3-명언-등록-create">3. 명언 등록 (Create)</h2>
<p>사용자 입력을 받아 객체를 생성하고 리스트에 저장한다.</p>
<pre><code class="language-java">void actionWrite() {
    System.out.print(&quot;명언: &quot;);
    String content = scanner.nextLine().trim();

    System.out.print(&quot;작가: &quot;);
    String author = scanner.nextLine().trim();

    WiseSaying wiseSaying = write(author, content);

    System.out.println(&quot;%d번 명언이 등록되었습니다.&quot;.formatted(wiseSaying.getId()));
}

WiseSaying write (String author, String content) {
    WiseSaying wiseSaying = new WiseSaying(++lastId, author, content );
    wiseSayingList.add(wiseSaying);
    return wiseSaying;
}</code></pre>
<p>핵심</p>
<ul>
<li>입력 → 객체 생성 → 리스트 저장</li>
<li><code>++lastId</code>로 ID 증가</li>
<li>입력과 저장 로직 분리</li>
</ul>
<hr>
<h2 id="4-id-증가-방식">4. ID 증가 방식</h2>
<p>명언 등록 시마다 번호를 증가시켜 고유값을 유지한다.</p>
<pre><code class="language-java">new WiseSaying(++lastId, author, content);</code></pre>
<p>핵심</p>
<ul>
<li>선증가 연산 사용</li>
<li>삭제해도 ID 재사용 없음</li>
</ul>
<hr>
<h2 id="5-목록-조회-read">5. 목록 조회 (Read)</h2>
<p>등록된 명언을 최신순으로 출력한다.</p>
<pre><code class="language-java">void actionList() {
    System.out.println(&quot;번호 / 작가 / 명언&quot;);
    System.out.println(&quot;----------------------&quot;);

    for (int i = wiseSayingList.size() - 1; i &gt;=0; i--) {
        WiseSaying wiseSaying = wiseSayingList.get(i);
        System.out.println(&quot;%d / %s / %s&quot;.formatted(
            wiseSaying.getId(),
            wiseSaying.getAuthor(),
            wiseSaying.getContent()
        ));
    }
}</code></pre>
<p>핵심</p>
<ul>
<li>리스트 역순 순회</li>
<li>최신 데이터가 위로 출력</li>
</ul>
<hr>
<h2 id="6-삭제-delete">6. 삭제 (Delete)</h2>
<p>명령어에서 id를 추출하여 해당 데이터를 제거한다.</p>
<pre><code class="language-java">void actionDelete(String cmd) {
    String[] cmdBits = cmd.split(&quot;=&quot;);

    if (cmdBits.length &lt; 2 ||  cmdBits[1].isEmpty()) {
        System.out.println(&quot;id를 입력해주세요.&quot;);
        return;
    }

    int id = Integer.parseInt(cmdBits[1]);

    delete(id);

    System.out.println(&quot;%d번 명언이 삭제되었습니다.&quot;.formatted(id));
}</code></pre>
<pre><code class="language-java">void delete(int id) {
    WiseSaying wiseSaying = null;

    for (int i = 0; i &lt; wiseSayingList.size(); i++) {
        if (wiseSayingList.get(i).getId() == id) {
            wiseSaying = wiseSayingList.get(i);
        }
    }

    if (wiseSaying == null) {
        System.out.println(&quot;해당 아이디는 존재하지 않습니다.&quot;);
        return;
    }

    wiseSayingList.remove(wiseSaying);
}</code></pre>
<p>핵심</p>
<ul>
<li><code>split(&quot;=&quot;)</code>로 id 추출</li>
<li>리스트 탐색 후 제거</li>
<li>존재 여부 확인</li>
</ul>
<hr>
<h2 id="7-문자열-파싱">7. 문자열 파싱</h2>
<p>명령어에서 필요한 값을 분리하는 과정이다.</p>
<pre><code class="language-java">String[] cmdBits = cmd.split(&quot;=&quot;);
int id = Integer.parseInt(cmdBits[1]);</code></pre>
<p>핵심</p>
<ul>
<li>문자열 분리 (<code>split</code>)</li>
<li>숫자 변환 (<code>parseInt</code>)</li>
</ul>
<hr>
<h2 id="8-수정-update">8. 수정 (Update)</h2>
<p>기존 데이터를 조회한 뒤 새로운 값으로 변경한다.</p>
<pre><code class="language-java">void actionModify(String cmd) {
    String[] cmdBits = cmd.split(&quot;=&quot;);

    if (cmdBits.length &lt; 2 ||  cmdBits[1].isEmpty()) {
        System.out.println(&quot;id를 입력해주세요.&quot;);
        return;
    }

    int id = Integer.parseInt(cmdBits[1]);

    WiseSaying wiseSaying = findById(id);

    System.out.printf(&quot;명언(기존) : %s\n&quot;,  wiseSaying.getContent());
    System.out.print(&quot;명언 : &quot;);
    String content = scanner.nextLine().trim();

    System.out.printf(&quot;작가(기존) : %s\n&quot;,  wiseSaying.getAuthor());
    System.out.print(&quot;작가 : &quot;);
    String author = scanner.nextLine().trim();

    modify(wiseSaying, content, author);

    System.out.println(&quot;%d번 명언이 수정 되었습니다.&quot;.formatted(id));
}</code></pre>
<pre><code class="language-java">void modify(WiseSaying wiseSaying, String content, String author) {
    wiseSaying.setContent(content);
    wiseSaying.setAuthor(author);
}</code></pre>
<p>핵심</p>
<ul>
<li>기존 객체 조회 후 값 변경</li>
<li>객체 참조 기반 수정</li>
</ul>
<hr>
<h2 id="9-findbyid">9. findById</h2>
<p>id를 기준으로 명언을 조회한다.</p>
<pre><code class="language-java">WiseSaying findById(int id) {
    WiseSaying wiseSaying = null;

    for (int i = 0; i &lt; wiseSayingList.size(); i ++) {
        if (wiseSayingList.get(i).getId() == id) {
            wiseSaying = wiseSayingList.get(i);
        }
    }

    if (wiseSaying == null) {
        System.out.println(&quot;해당 아이디는 존재하지 않습니다.&quot;);
        return null;
    }

    return wiseSaying;
}</code></pre>
<p>핵심</p>
<ul>
<li>리스트 순회로 데이터 조회</li>
<li>없으면 null 반환</li>
</ul>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<ul>
<li>List를 활용하여 명언 데이터를 메모리에 저장하고 관리할 수 있다.</li>
<li>lastId를 증가시키는 방식으로 고유한 ID를 유지할 수 있다.</li>
<li>split과 parseInt를 사용하여 문자열 명령어를 처리할 수 있다.</li>
<li>CRUD 구조를 통해 등록, 조회, 삭제, 수정 기능을 구현할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 27 - Stream +]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-27-Stream</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-27-Stream</guid>
            <pubDate>Wed, 08 Apr 2026 07:50:40 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 04월 08일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 프로그래머스 자바 입문 문제를 풀면서<br>클래스, 생성자, 접근제어자 등 객체지향 개념을 복습했다.<br>또한 Stream API를 활용하여 반복문과의 차이를 비교하며 구현해봤다.</p>
<hr>
<h2 id="1-클래스와-객체">1. 클래스와 객체</h2>
<p>클래스는 객체를 만들기 위한 설계도이고<br>객체는 클래스를 기반으로 생성된 인스턴스이다.</p>
<pre><code class="language-java">class Person {
    String name;
}</code></pre>
<hr>
<h2 id="2-생성자-constructor">2. 생성자 (Constructor)</h2>
<p>객체 생성 시 초기화를 담당하는 메서드이다.</p>
<h3 id="특징">특징</h3>
<ul>
<li>클래스 이름과 동일</li>
<li>반환 타입 없음</li>
<li>객체 생성 시 자동 호출</li>
<li>필드 초기값 설정 가능</li>
</ul>
<pre><code class="language-java">public Person(int id, String name, int age, char gender) {
    this.id = id;
    this.name = name;
    this.age = age;
    this.gender = gender;
}</code></pre>
<hr>
<h2 id="3-접근제어자">3. 접근제어자</h2>
<p>클래스의 접근 범위를 제어한다.</p>
<table>
<thead>
<tr>
<th>접근제어자</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>public</td>
<td>어디서든 접근 가능</td>
</tr>
<tr>
<td>protected</td>
<td>같은 패키지 + 자식 클래스</td>
</tr>
<tr>
<td>default</td>
<td>같은 패키지</td>
</tr>
<tr>
<td>private</td>
<td>해당 클래스 내부</td>
</tr>
</tbody></table>
<hr>
<h2 id="4-캡슐화-encapsulation">4. 캡슐화 (Encapsulation)</h2>
<p>필드를 private으로 선언하고 getter/setter로 접근하는 방식이다.</p>
<h3 id="사용하는-이유">사용하는 이유</h3>
<ul>
<li>데이터 보호</li>
<li>잘못된 값 방지</li>
<li>유지보수 용이</li>
<li>내부 구현 숨김</li>
</ul>
<pre><code class="language-java">private int age;

public int getAge() {
    return age;
}</code></pre>
<hr>
<h2 id="5-stream-api-핵심">5. Stream API 핵심</h2>
<p>컬렉션 데이터를 선언형으로 처리하는 방식이다.</p>
<h3 id="대표적인-메서드">대표적인 메서드</h3>
<table>
<thead>
<tr>
<th>메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>stream()</td>
<td>컬렉션을 Stream으로 변환</td>
</tr>
<tr>
<td>filter()</td>
<td>조건에 맞는 데이터만 추출</td>
</tr>
<tr>
<td>map()</td>
<td>데이터 변환</td>
</tr>
<tr>
<td>mapToInt()</td>
<td>int 타입으로 변환</td>
</tr>
<tr>
<td>average()</td>
<td>평균 계산</td>
</tr>
<tr>
<td>sum()</td>
<td>합계 계산</td>
</tr>
<tr>
<td>findFirst()</td>
<td>첫 번째 요소 반환</td>
</tr>
</tbody></table>
<hr>
<h2 id="6-map-vs-maptoint">6. map vs mapToInt</h2>
<p>Stream에서 데이터 타입을 변환할 때 사용하는 메서드이다.</p>
<ul>
<li><code>map()</code> → 객체 타입 변환</li>
<li><code>mapToInt()</code> → int 타입으로 변환</li>
</ul>
<p>예시</p>
<pre><code class="language-java">int[] array = Arrays.stream(new String[]{&quot;1번&quot;, &quot;2번&quot;, &quot;3번&quot;})
        .map(e -&gt; e.substring(0, e.length() - 1))
        .mapToInt(Integer::parseInt)
        .toArray();</code></pre>
<hr>
<h2 id="7-반복문-vs-stream-비교">7. 반복문 vs Stream 비교</h2>
<h3 id="71-문자열-→-숫자-변환">7.1 문자열 → 숫자 변환</h3>
<h4 id="반복문">반복문</h4>
<pre><code class="language-java">String[] messages = new String[]{&quot;1번&quot;, &quot;2번&quot;, &quot;3번&quot;};
int[] numbers = new int[messages.length];

for (int i = 0; i &lt; messages.length; i++) {
    String message = messages[i];
    message = message.substring(0, message.length() - 1);
    int number = Integer.parseInt(message);

    if (number % 2 == 0) continue;

    numbers[i] = number;
}</code></pre>
<h4 id="stream">Stream</h4>
<pre><code class="language-java">int[] array = Arrays.stream(new String[]{&quot;1번&quot;, &quot;2번&quot;, &quot;3번&quot;})
        .map(e -&gt; e.substring(0, e.length() - 1))
        .mapToInt(Integer::parseInt)
        .filter(number -&gt; number % 2 != 0)
        .toArray();</code></pre>
<hr>
<h3 id="72-조건-데이터-합계">7.2 조건 데이터 합계</h3>
<h4 id="반복문-1">반복문</h4>
<pre><code class="language-java">int sum = 0;

for (Person person : people) {
    if (person.getGender() == &#39;M&#39;) {
        sum += person.getAge();
    }
}</code></pre>
<h4 id="stream-1">Stream</h4>
<pre><code class="language-java">int sum = people.stream()
        .filter(e -&gt; e.getGender() == &#39;M&#39;)
        .mapToInt(e -&gt; e.getAge())
        .sum();</code></pre>
<hr>
<h3 id="73-평균-계산">7.3 평균 계산</h3>
<h4 id="반복문-2">반복문</h4>
<pre><code class="language-java">int sum = 0;
int count = 0;

for (Person person : people) {
    if (person.getGender() == &#39;M&#39;) {
        sum += person.getAge();
        count++;
    }
}

double avg = (double) sum / count;</code></pre>
<h4 id="stream-2">Stream</h4>
<pre><code class="language-java">double avg = people.stream()
        .filter(e -&gt; e.getGender() == &#39;M&#39;)
        .mapToInt(e -&gt; e.getAge())
        .average()
        .orElse(0);</code></pre>
<hr>
<h3 id="74-데이터-추출">7.4 데이터 추출</h3>
<h4 id="반복문-3">반복문</h4>
<pre><code class="language-java">List&lt;String&gt; names = new ArrayList&lt;&gt;();

for (Person person : people) {
    if (person.getGender() == &#39;M&#39;) {
        names.add(person.getName());
    }
}

String result = String.join(&quot;, &quot;, names);</code></pre>
<h4 id="stream-3">Stream</h4>
<pre><code class="language-java">String result = people.stream()
        .filter(e -&gt; e.getGender() == &#39;M&#39;)
        .map(Person::getName)
        .collect(Collectors.joining(&quot;, &quot;));</code></pre>
<hr>
<h3 id="75-특정-데이터-찾기">7.5 특정 데이터 찾기</h3>
<h4 id="반복문-4">반복문</h4>
<pre><code class="language-java">Person found = null;

for (Person person : people) {
    if (person.getId() == 2) {
        found = person;
        break;
    }
}</code></pre>
<h4 id="stream-4">Stream</h4>
<pre><code class="language-java">Optional&lt;Person&gt; op = people.stream()
        .filter(e -&gt; e.getId() == 2)
        .findFirst();

Person found = op.orElse(null);</code></pre>
<hr>
<h2 id="8-배열-→-list-변환">8. 배열 → List 변환</h2>
<p>기본형 배열(int[])은 바로 List로 변환할 수 없기 때문에<br>boxing 과정이 필요하다.</p>
<pre><code class="language-java">List&lt;Integer&gt; list = Arrays.stream(new int[]{10, 20, 30})
        .boxed()
        .toList();</code></pre>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<ul>
<li>클래스는 객체를 생성하기 위한 설계도이다</li>
<li>생성자는 객체 초기화를 담당한다</li>
<li>캡슐화를 통해 데이터 보호와 유지보수를 쉽게 할 수 있다</li>
<li>Stream API는 반복문을 간결하게 표현할 수 있다</li>
<li>map / filter / collect 흐름이 핵심이다</li>
<li>map과 mapToInt의 차이를 이해해야 한다</li>
<li>기본형 배열은 boxing 후 List로 변환해야 한다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 26 - Stream]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-26-Stream</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-26-Stream</guid>
            <pubDate>Tue, 07 Apr 2026 12:39:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 04월 07일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 자바 객체지향 개념(추상 클래스, 다형성, 생성자)과 Stream API
기초를 간단히 정리했다.</p>
<hr>
<h2 id="1-추상-클래스와-다형성">1. 추상 클래스와 다형성</h2>
<p>전사가 사용하는 무기에 따라 공격 방식이 달라지도록 구현한다.</p>
<pre><code class="language-java">class 전사 {
    String 이름;
    int 나이;
    무기 a무기;

    void 공격() {
        a무기.작동(이름, 나이);
    }
}

abstract class 무기 {
    String 무기명;

    void 작동(String 이름, int 나이) {
        System.out.println(나이 + &quot;살 전사 &quot; + 이름 + &quot;(이)가 &quot; + 무기명 + &quot;(으)로 공격합니다.&quot;);
    }
}

class 칼 extends 무기 {
    칼() { 무기명 = &quot;칼&quot;; }
}

class 활 extends 무기 {
    활() { 무기명 = &quot;활&quot;; }
}</code></pre>
<p>핵심</p>
<ul>
<li><code>abstract class</code> : 직접 객체 생성 불가</li>
<li>자식 클래스에서 기능을 구현</li>
<li>무기 교체로 행동이 바뀌는 <strong>다형성 구조</strong></li>
</ul>
<hr>
<h2 id="2-return">2. return</h2>
<p>메서드는 <code>return</code>을 통해 값을 반환한다.</p>
<pre><code class="language-java">static int get정수() {
    return 5;
}</code></pre>
<p>특징</p>
<ul>
<li>값을 반환하며 메서드 종료</li>
<li>객체나 <code>null</code>도 반환 가능</li>
</ul>
<hr>
<h2 id="3-생성자">3. 생성자</h2>
<p>객체 생성 시 자동 실행되는 메서드.</p>
<pre><code class="language-java">class 사람 {
    String 이름;
    int 나이;

    사람() {
        이름 = &quot;홍길동&quot;;
        나이 = 22;
    }

    사람(String 이름, int 나이) {
        this.이름 = 이름;
        this.나이 = 나이;
    }
}</code></pre>
<p>특징</p>
<ul>
<li>클래스 이름과 동일</li>
<li>반환 타입 없음</li>
<li>객체 생성 시 자동 호출</li>
</ul>
<hr>
<h2 id="4-생성자-호출-구조">4. 생성자 호출 구조</h2>
<p>객체 생성 시 <strong>부모 생성자 → 자식 생성자</strong> 순서로 실행된다.</p>
<pre><code class="language-java">class 동물 {
    동물() {
        System.out.println(&quot;동물 생성&quot;);
    }
}

class 사람 extends 동물 {
    사람() {
        super();
        System.out.println(&quot;사람 생성&quot;);
    }
}</code></pre>
<hr>
<h2 id="5-this">5. this()</h2>
<p>같은 클래스의 다른 생성자를 호출할 때 사용한다.</p>
<pre><code class="language-java">class 전사 {
    String 이름;
    int 나이;

    전사() {
        this(&quot;NoName&quot;);
    }

    전사(String 이름) {
        this.이름 = 이름;
        나이 = 20;
    }
}</code></pre>
<hr>
<h2 id="6-stream-api-기초">6. Stream API 기초</h2>
<p>컬렉션 데이터를 간결하게 처리하는 기능.</p>
<h3 id="기본-사용">기본 사용</h3>
<pre><code class="language-java">IntStream.rangeClosed(1, 10)
        .forEach(System.out::println);</code></pre>
<h3 id="filter">filter</h3>
<pre><code class="language-java">IntStream.rangeClosed(1, 10)
        .filter(e -&gt; e % 2 != 0)
        .forEach(System.out::println);</code></pre>
<h3 id="map">map</h3>
<pre><code class="language-java">int[] result = Arrays.stream(numbers)
        .map(e -&gt; e * 2)
        .toArray();</code></pre>
<p>특징</p>
<ul>
<li>데이터 변환 처리에 강함</li>
<li>원본 데이터는 변경되지 않음</li>
</ul>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<ul>
<li>추상 클래스와 다형성을 이용해 유연한 구조를 만들 수 있다.</li>
<li>생성자는 객체 생성 시 자동 실행된다.</li>
<li><code>this()</code>와 <code>super()</code>로 생성자 흐름을 제어할 수 있다.</li>
<li>Stream API를 사용하면 데이터를 더 간결하게 처리할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 25 - Inheritance]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-25-Inheritance</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-25-Inheritance</guid>
            <pubDate>Mon, 06 Apr 2026 08:09:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 4월 6일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 자바 객체지향 개념 중 <strong>상속, 오버라이딩, 추상 클래스, 형변환,
객체 상태 저장</strong>에 대해 학습했다.
특히 오리 시뮬레이션 예제를 통해 <strong>상속 구조의 장점과 한계</strong>, 그리고
<strong>추상 클래스의 역할</strong>을 이해할 수 있었다.</p>
<hr>
<h2 id="1-상속과-코드-재사용">1. 상속과 코드 재사용</h2>
<p>여러 클래스에서 같은 기능을 사용할 때 부모 클래스를 만들고 상속을
사용하면 코드 중복을 줄일 수 있다.</p>
<p>예를 들어 오리 클래스가 있을 때 기본 동작을 부모 클래스에 정의한다.</p>
<pre><code class="language-java">class 오리 {
    void 날다() {
        System.out.println(&quot;오리가 날개로 날아갑니다.&quot;);
    }
}</code></pre>
<p>그리고 다른 오리들은 이를 상속받는다.</p>
<pre><code class="language-java">class 흰오리 extends 오리 { }

class 청둥오리 extends 오리 { }</code></pre>
<p>상속 구조</p>
<pre><code>오리
 ├ 흰오리
 └ 청둥오리</code></pre><p>이렇게 하면 공통 기능을 부모에서 관리할 수 있다.</p>
<hr>
<h2 id="2-메서드-오버라이딩">2. 메서드 오버라이딩</h2>
<p>상속받은 클래스는 부모의 메서드를 <strong>재정의(Override)</strong> 할 수 있다.</p>
<p>예를 들어 고무오리는 날 수 없기 때문에 날다 메서드를 수정한다.</p>
<pre><code class="language-java">class 고무오리 extends 오리 {
    void 날다() {
        System.out.println(&quot;저는 날 수 없어요.&quot;);
    }
}</code></pre>
<p>이렇게 하면 같은 메서드 이름이지만 <strong>각 객체에 맞는 동작</strong>을 수행하게
된다.</p>
<hr>
<h2 id="3-상속-구조의-한계">3. 상속 구조의 한계</h2>
<p>상속만으로는 모든 문제를 해결할 수 없다.</p>
<p>예를 들어</p>
<ul>
<li>일반 오리 → 날 수 있음</li>
<li>고무 오리 → 날 수 없음</li>
<li>로봇 오리 → 날 수 없음 + 수영 방식 다름</li>
</ul>
<p>이처럼 다양한 동작이 섞이면 상속 구조가 복잡해지고 중복 코드가 생길 수
있다.</p>
<p>그래서 객체지향에서는 <strong>구성(Composition)</strong> 이라는 방식도 사용한다.</p>
<p>구성은</p>
<pre><code>객체 안에 다른 객체를 포함하는 방식</code></pre><p>으로 더 유연한 설계를 만들 수 있다.</p>
<hr>
<h2 id="4-추상-클래스">4. 추상 클래스</h2>
<p>추상 클래스는 <strong>공통 구조만 정의하고 구현은 자식 클래스에 맡기는
클래스</strong>이다.</p>
<p>예시</p>
<pre><code class="language-java">abstract class 무기 {
    abstract void 공격();
}</code></pre>
<p>자식 클래스는 반드시 이 메서드를 구현해야 한다.</p>
<pre><code class="language-java">class 칼 extends 무기 {
    void 공격() {
        System.out.println(&quot;칼로 공격합니다.&quot;);
    }
}</code></pre>
<p>추상 클래스는 <strong>리모콘의 버튼 설계도</strong>와 비슷하다.<br>버튼은 정의되어 있지만 실제 동작은 각 기기가 구현한다.</p>
<hr>
<h2 id="5-형변환과-객체-타입">5. 형변환과 객체 타입</h2>
<p>자바에서는 부모 타입으로 자식 객체를 참조할 수 있다.</p>
<pre><code class="language-java">사람 a사람 = new 홍길동();</code></pre>
<p>이것을 <strong>업캐스팅(자동 형변환)</strong> 이라고 한다.</p>
<p>반대로 자식 타입으로 바꾸는 경우에는 <strong>명시적 형변환</strong>이 필요하다.</p>
<pre><code class="language-java">홍길동 a홍길동 = (홍길동)a사람;</code></pre>
<p>이때 실제 객체가 해당 타입이 아니면 <strong>런타임 오류</strong>가 발생할 수 있다.</p>
<hr>
<h2 id="정리">정리</h2>
<p>오늘 학습한 핵심 개념</p>
<ul>
<li>상속은 코드 재사용을 위한 중요한 기능이다</li>
<li>메서드 오버라이딩을 통해 클래스별 동작을 다르게 만들 수 있다</li>
<li>상속만으로는 구조가 복잡해질 수 있어 구성 방식도 함께 사용한다</li>
<li>추상 클래스는 공통 인터페이스 역할을 한다</li>
<li>자바의 형변환은 자동형변환과 명시적 형변환으로 나뉜다</li>
</ul>
<p>객체지향 설계에서는 <strong>중복을 줄이고 확장 가능한 구조를 만드는 것</strong>이
중요하다.</p>
<p>오늘은 컨디션이 좋지 않아 집중을 하지 못했다.
컨디션 괜찮을 때 다시 복습을 해야 할 거 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 24 - Structure : Java]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-25-Structure-Java</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-25-Structure-Java</guid>
            <pubDate>Fri, 03 Apr 2026 08:09:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 04월 03일 작성된 글입니다.</p>
</blockquote>
<p>자바 기초 핵심 개념을 간단히 정리했다. 
증감 연산자, 반복문, 메모리 구조, 객체와 클래스, 컬렉션, static, 상속까지
자바의 기본 동작 원리를 이해하는 내용이다.</p>
<hr>
<h2 id="1-증감-연산자">1. 증감 연산자</h2>
<p>변수 값을 증가 또는 감소시킬 때 사용한다.</p>
<pre><code class="language-java">i++;   // 1 증가
i--;   // 1 감소

i = i + 2;
i += 2;

i = i - 2;
i -= 2;</code></pre>
<h3 id="후위-연산자">후위 연산자</h3>
<p>값을 먼저 사용하고 증가한다.</p>
<pre><code class="language-java">int i = 10;

System.out.println(i++); // 10
System.out.println(i);   // 11</code></pre>
<h3 id="전위-연산자">전위 연산자</h3>
<p>값을 먼저 증가시키고 사용한다.</p>
<pre><code class="language-java">int i = 10;

System.out.println(++i); // 11
System.out.println(i);   // 11</code></pre>
<hr>
<h2 id="2-while-반복문">2. while 반복문</h2>
<p>조건이 참인 동안 반복 실행된다.</p>
<pre><code class="language-java">int i = 0;

while (i &lt;= 10) {
    System.out.println(i);
    i++;
}</code></pre>
<hr>
<h2 id="3-기본-자료형-8종">3. 기본 자료형 8종</h2>
<p>  구분   타입</p>
<hr>
<p>  논리   boolean
  문자   char
  정수   byte, short, int, long
  실수   float, double</p>
<pre><code class="language-java">boolean b = true;
char c = &#39;a&#39;;

int i = 3;
long l = 4L;

float f = 5.5f;
double d = 5.5;</code></pre>
<hr>
<h2 id="4-메모리-구조">4. 메모리 구조</h2>
<p>자바 메모리는 크게 스택(Stack)과 힙(Heap)으로 나뉜다.</p>
<h3 id="지역-변수">지역 변수</h3>
<ul>
<li>스택에 저장</li>
<li>함수 종료 시 자동 삭제</li>
</ul>
<h3 id="객체">객체</h3>
<ul>
<li>힙에 저장</li>
<li>참조가 없어지면 GC에 의해 삭제</li>
</ul>
<pre><code class="language-java">int a = 10;        // 스택
String name = &quot;Paul&quot;; // 객체는 힙, 변수는 스택</code></pre>
<hr>
<h2 id="5-기본형-vs-참조형">5. 기본형 vs 참조형</h2>
<p>  구분     특징</p>
<hr>
<p>  기본형   값이 직접 저장
  참조형   객체 주소 저장</p>
<pre><code class="language-java">int a = 10;
String name = &quot;Paul&quot;;</code></pre>
<p>참조형 변수에는 객체의 주소(참조)가 저장된다.</p>
<hr>
<h2 id="6-함수와-매개변수">6. 함수와 매개변수</h2>
<pre><code class="language-java">public static void plus(int num1, int num2) {
    System.out.println(num1 + num2);
}</code></pre>
<h3 id="리턴">리턴</h3>
<pre><code class="language-java">public static int plus(int num1, int num2) {
    return num1 + num2;
}</code></pre>
<hr>
<h2 id="7-컴파일-타임-vs-런타임">7. 컴파일 타임 vs 런타임</h2>
<p>  구분          의미</p>
<hr>
<p>  컴파일 타임   실행 전 코드 검사
  런타임        프로그램 실행 중</p>
<hr>
<h2 id="8-함수-호출과-스택">8. 함수 호출과 스택</h2>
<p>함수 A가 함수 B를 호출하면 B의 스택이 생성되고 B가 끝나면 해당 스택이
제거된다.</p>
<hr>
<h2 id="9-객체-생성">9. 객체 생성</h2>
<p>new 키워드는 객체를 힙에 생성한다.</p>
<pre><code class="language-java">String name = new String(&quot;홍길동&quot;);

int[] arr = new int[] {10, 20, 30};</code></pre>
<hr>
<h2 id="10-객체-참조">10. 객체 참조</h2>
<pre><code class="language-java">int[] arr1 = new int[] {10,20,30};
int[] arr2 = arr1;</code></pre>
<p>객체가 2개가 되는 것이 아니라 참조가 2개가 된다.</p>
<hr>
<h2 id="11-메모리-해제">11. 메모리 해제</h2>
<h3 id="지역-변수-1">지역 변수</h3>
<p>함수 종료 시 삭제</p>
<h3 id="객체-1">객체</h3>
<p>참조하는 변수가 없어지면 GC가 제거</p>
<pre><code class="language-java">arr1 = null;</code></pre>
<hr>
<h2 id="12-객체">12. 객체</h2>
<p>객체는 여러 데이터를 하나로 묶는 구조이다.</p>
<hr>
<h2 id="13-인스턴스-변수">13. 인스턴스 변수</h2>
<p>객체 내부에 선언된 변수.</p>
<pre><code class="language-java">int[] person = new int[3];

person[0] = 1;
person[1] = 20;
person[2] = 170;</code></pre>
<hr>
<h2 id="14-클래스">14. 클래스</h2>
<p>객체를 만들기 위한 설계도.</p>
<pre><code class="language-java">class GameCharacter {
    int no;
    int age;
    int height;
}</code></pre>
<pre><code class="language-java">GameCharacter p1 = new GameCharacter();
p1.age = 20;</code></pre>
<hr>
<h2 id="15-this">15. this</h2>
<p>객체 자기 자신을 가리키는 참조.</p>
<pre><code class="language-java">this.age</code></pre>
<p>new는 객체 생성 후 this 참조를 반환한다.</p>
<hr>
<h2 id="16-wrapper-class">16. Wrapper Class</h2>
<p>기본형 타입을 객체 형태로 만든 클래스.</p>
<p>  기본형    래퍼</p>
<hr>
<p>  int       Integer
  double    Double
  boolean   Boolean</p>
<hr>
<h2 id="17-배열-vs-리스트">17. 배열 vs 리스트</h2>
<p>  구분     특징</p>
<hr>
<p>  배열     크기 고정
  리스트   크기 변경 가능</p>
<pre><code class="language-java">List&lt;Integer&gt; list = new ArrayList&lt;&gt;();

list.add(10);
list.add(20);

System.out.println(list.size());</code></pre>
<hr>
<h2 id="18-list-vs-map">18. List vs Map</h2>
<h3 id="list">List</h3>
<p>데이터 넣기 편함</p>
<pre><code class="language-java">List&lt;Integer&gt; ages = new ArrayList&lt;&gt;();
ages.add(10);
ages.add(20);</code></pre>
<h3 id="map">Map</h3>
<p>데이터 찾기 편함</p>
<pre><code class="language-java">Map&lt;String, Integer&gt; ages = new HashMap&lt;&gt;();

ages.put(&quot;철수&quot;,10);
ages.get(&quot;철수&quot;);</code></pre>
<hr>
<h2 id="19-static">19. static</h2>
<p>static 변수는 클래스에 속한다.</p>
<pre><code class="language-java">class Person {
    static int maxSpeed;
}</code></pre>
<pre><code class="language-java">Person.maxSpeed = 100;</code></pre>
<p>특징</p>
<ul>
<li>클래스에 1개만 존재</li>
<li>모든 객체가 공유</li>
</ul>
<hr>
<h2 id="20-객체와-참조">20. 객체와 참조</h2>
<p>객체는 참조 변수로 조종한다.</p>
<pre><code class="language-java">자동차 a자동차 = new 자동차();

a자동차.달리다();</code></pre>
<hr>
<h2 id="21-상속">21. 상속</h2>
<p>중복 코드를 줄이기 위해 사용한다.</p>
<pre><code class="language-java">class 고양이 {
    void 숨쉬다() {
        System.out.println(&quot;숨쉬다&quot;);
    }
}

class 검은고양이 extends 고양이 {
}</code></pre>
<pre><code class="language-java">검은고양이 cat = new 검은고양이();
cat.숨쉬다();</code></pre>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<ul>
<li>증감 연산자는 변수 값을 증가 또는 감소시킨다.</li>
<li>자바 메모리는 스택(지역 변수)과 힙(객체)으로 구성된다.</li>
<li>기본형은 값을 직접 저장하고 참조형은 객체 주소를 저장한다.</li>
<li>클래스는 객체의 설계도이며 new로 객체를 생성한다.</li>
<li>배열은 크기가 고정이고 리스트는 가변이다.</li>
<li>static 변수는 클래스에서 하나만 존재하며 모든 객체가 공유한다.</li>
<li>상속을 사용하면 코드 중복을 줄일 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 23 - IntelliJ]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-23-IntelliJ</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-23-IntelliJ</guid>
            <pubDate>Thu, 02 Apr 2026 06:29:09 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 04월 02일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 <strong>IntelliJ 기본 개발 환경 설정</strong>을 진행하고, 이후 <strong>Java 기초
문법과 if 조건문</strong>을 학습했다. </p>
<h2 id="1-intellij-기본-설정">1. IntelliJ 기본 설정</h2>
<p>개발 환경을 편하게 사용하기 위해 몇 가지 기본 설정을 진행했다.</p>
<h3 id="jdk-설정">JDK 설정</h3>
<ul>
<li>SDK : <code>graalvm-ce-21</code></li>
<li>없으면 <strong>Download JDK</strong> 선택</li>
<li>애플 실리콘 환경은 <strong>aarch64 버전</strong> 사용</li>
</ul>
<h3 id="자주-사용하는-플러그인">자주 사용하는 플러그인</h3>
<ul>
<li>Lombok</li>
<li>Github Copilot</li>
</ul>
<hr>
<h2 id="2-프로젝트-생성-시-설정">2. 프로젝트 생성 시 설정</h2>
<p>새 프로젝트마다 필요한 설정도 있다.</p>
<h3 id="import-자동화">Import 자동화</h3>
<p>Settings → Editor → General → Auto Import</p>
<ul>
<li>Add unambiguous imports on the fly</li>
<li>Optimize imports on the fly</li>
</ul>
<h3 id="파일-인코딩">파일 인코딩</h3>
<p>Settings → Editor → File Encodings</p>
<ul>
<li>Project encoding : UTF-8</li>
<li>Default encoding : UTF-8</li>
</ul>
<h3 id="gradle-빌드-속도-개선">Gradle 빌드 속도 개선</h3>
<p>Settings → Build Tools → Gradle</p>
<ul>
<li>Build and run using : IntelliJ IDEA</li>
<li>Run tests using : IntelliJ IDEA</li>
</ul>
<hr>
<h2 id="3-java-프로그램의-기본-구조">3. Java 프로그램의 기본 구조</h2>
<p>Java 프로그램은 <strong>main 함수에서 시작된다.</strong></p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        System.out.println(&quot;Hello World&quot;);
    }
}</code></pre>
<p>프로그램은 <strong>위에서 아래로 한 줄씩 실행된다.</strong></p>
<hr>
<h2 id="4-변수와-출력">4. 변수와 출력</h2>
<pre><code class="language-java">int x = 10;

System.out.println(x);
System.out.println(&quot;x : &quot; + x);
System.out.println(&quot;x : &quot; + (x + 10));</code></pre>
<ul>
<li>변수는 값을 저장하는 공간이다.</li>
<li>문자열과 숫자를 더하면 숫자가 문자열로 변환된다.</li>
</ul>
<hr>
<h2 id="5-조건문-if">5. 조건문 if</h2>
<p>조건이 <strong>true일 때만 코드가 실행</strong>된다.</p>
<pre><code class="language-java">int age = 50;

if (age &gt;= 20) {
    System.out.println(&quot;성년&quot;);
}</code></pre>
<hr>
<h2 id="6-논리-연산자">6. 논리 연산자</h2>
<h3 id="or-">OR (||)</h3>
<p>하나라도 참이면 결과는 참</p>
<h3 id="and-">AND (&amp;&amp;)</h3>
<p>하나라도 거짓이면 결과는 거짓</p>
<hr>
<h2 id="7-클린코딩-특강">7. 클린코딩 특강</h2>
<p>오후에는 외부 강사님의 <strong>클린코딩 특강</strong>이 진행되었다.</p>
<p>강사님은
11번가 → 뱅크샐러드 → 카카오페이에서 근무하신 개발자였다.</p>
<p><strong>클래스의 응집도는 높이고, 결합도는 낮추어야 한다는 말씀이 가장 기억에 남는다.</strong></p>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<ul>
<li>IntelliJ 기본 개발 환경 설정을 진행했다.</li>
<li>JDK, UTF-8 인코딩, 폰트, Gradle 설정을 익혔다.</li>
<li>Java 프로그램의 시작점인 main 함수를 이해했다.</li>
<li>변수, 출력, 문자열 연산의 기본 개념을 학습했다.</li>
<li>if 조건문과 논리 연산자(||, &amp;&amp;)를 배웠다.</li>
<li>클린코딩 특강을 통해 읽기 좋은 코드의 중요성을 느꼈다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 22 - Evaluate]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-22-Evaluate</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-22-Evaluate</guid>
            <pubDate>Wed, 01 Apr 2026 08:31:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 04월 01일 작성된 글입니다</p>
</blockquote>
<p>프로젝트 최종 발표와 마감을 어제 마친 뒤, 오늘은 데브코스의 <strong>1차 역량 테스트</strong>를 보게 되었다.
프로젝트를 끝낸 직후라 조금 긴장도 되었지만, 지금까지 학습한 내용을 점검해볼 수 있는 시간이었다.</p>
<h2 id="1-알고리즘-테스트">1. 알고리즘 테스트</h2>
<p>오전에는 알고리즘 문제 4개가 주어졌다.
그중 <strong>3문제는 해결했고 1문제는 풀지 못했다.</strong></p>
<p>문제를 풀면서 지금까지 학습했던 자료구조와 알고리즘 개념을 다시 떠올리게 되었고,
아직 부족한 부분이 무엇인지도 어느 정도 느낄 수 있었다.</p>
<p>또한 학습 내용을 기반으로 한 <strong>객관식 시험 20문제</strong>도 함께 진행되었다.
이 시험은 전반적인 개념 이해도를 확인하는 형태였다.</p>
<h2 id="2-웹-구현-과제">2. 웹 구현 과제</h2>
<p>오후에는 테스트 페이지에 접속해 <strong>웹 구현 과제</strong>를 진행했다.</p>
<p>테스트 웹서버에 제공된 문제 파일을 확인하면서
지시사항에 맞게 기능을 구현하는 방식이었고,
주어진 과제들을 <strong>모두 구현하는 데 성공했다.</strong></p>
<p>프로젝트를 진행하면서 익혔던 흐름들이 실제로 많이 도움이 되었다고 느꼈다.</p>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<ul>
<li>오전: 알고리즘 테스트 4문제 (3문제 해결, 1문제 미해결)</li>
<li>오전: 학습 기반 객관식 시험 20문제</li>
<li>오후: 테스트 웹서버 기반 웹 구현 과제 진행 (모두 구현 성공)</li>
</ul>
<p>앞으로 데브코스 기간 동안
<strong>2번의 팀 프로젝트와 2번의 역량 테스트</strong>가 더 남아 있다.</p>
<p>이번 테스트를 통해 현재 실력을 점검할 수 있었고,
앞으로 남은 과정에서도 더 열심히 학습하며 <strong>내실을 다져가고 싶다.</strong></p>
<p>또한 1차 역량 테스트 결과가 <strong>성적표 형태로 제공될 것 같아서</strong>
어떤 결과가 나올지 조금 기대가 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 21 - Milestone]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-21-First-step</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-21-First-step</guid>
            <pubDate>Tue, 31 Mar 2026 14:05:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 03월 31일 작성된 글입니다</p>
</blockquote>
<p>오늘은 프로젝트 <strong>최종 발표회</strong>가 있어서 오전에는 기능 개선과 오류 수정,
그리고 팀원들과 함께 발표 준비를 진행했다.
발표 전 피드백을 반영해 로그인 UI를 개선했고, 발표 이후 강사님의 코드
리뷰와 피드백을 들을 수 있었다.</p>
<h2 id="1-로그인-비밀번호-보기-토글-기능-추가">1. 로그인 비밀번호 보기 토글 기능 추가</h2>
<p>오전에 FT님께서 배포된 서비스를 확인하시면서 <strong>로그인 / 회원가입
화면에서 비밀번호를 확인할 수 있는 토글 기능이 있으면 좋겠다</strong>는
피드백을 주셨다.</p>
<p>바로 <code>useState</code>를 사용하여 비밀번호 표시 여부를 제어하는 기능을
추가했다.</p>
<pre><code class="language-tsx">const [showPassword, setShowPassword] = useState(false)</code></pre>
<p>input의 <code>type</code>을 상태값에 따라 변경하도록 구현했다.</p>
<pre><code class="language-tsx">&lt;input
  type={showPassword ? &#39;text&#39; : &#39;password&#39;}
/&gt;</code></pre>
<p>버튼을 클릭하면 상태가 토글되도록 처리했다.</p>
<pre><code class="language-tsx">&lt;button
  type=&quot;button&quot;
  onClick={() =&gt; setShowPassword((prev) =&gt; !prev)}
&gt;
  &lt;EyeIcon show={showPassword} /&gt;
&lt;/button&gt;</code></pre>
<p>간단한 기능이지만 사용자 경험 측면에서 중요한 부분이라 빠르게 반영했다.</p>
<hr>
<h2 id="2-발표-준비-및-프로젝트-발표">2. 발표 준비 및 프로젝트 발표</h2>
<p>오전에는 팀원들과 함께 <strong>프로젝트 발표 자료를 준비</strong>했고, 오후에는 최종
발표를 진행했다.</p>
<p>발표 이후 강사님의 질의응답 시간에서 우리 팀 프로젝트에 대해 몇 가지
좋은 피드백을 들을 수 있었다.</p>
<p>특히 다음 부분을 좋게 봐주셨다.</p>
<ul>
<li>GitHub <strong>Issue / PR / 컨벤션을 통한 협업 관리</strong></li>
<li><strong>컴포넌트와 앱 구조를 잘 분리한 점</strong></li>
<li><strong>로직과 인터페이스(UI) 구조 분리</strong></li>
</ul>
<p>프로젝트 구조를 설계할 때 신경 썼던 부분이었는데, 그 부분을 좋게
평가해주셔서 인상 깊었다.</p>
<hr>
<h2 id="3-소셜-로그인-로직-구조화">3. 소셜 로그인 로직 구조화</h2>
<p>내가 담당했던 <strong>Auth 파트</strong>에서도 구조를 잘 나누었다는 피드백을 받을 수
있었다.</p>
<p>특히 소셜 로그인 로직을 하나의 함수로 묶어 처리한 부분을 좋게
평가해주셨다.</p>
<pre><code class="language-ts">const signInWithOAuth = async (
  provider: &#39;google&#39; | &#39;kakao&#39;,
  onSubmittingChange: (value: boolean) =&gt; void,
  failureMessage: string,
) =&gt; {
  setErrorMessage(&#39;&#39;)
  setInfoMessage(&#39;&#39;)
  onSubmittingChange(true)

  const supabase = createClient();
  const { error } = await supabase.auth.signInWithOAuth({
    provider,
    options: {
      redirectTo: `${window.location.origin}/auth/callback`,
    },
  });

  if (error) {
    setErrorMessage(failureMessage)
    onSubmittingChange(false)
  }
}</code></pre>
<p>Google, Kakao 로그인 로직을 각각 구현하지 않고
<strong>provider 값만 받아 재사용 가능한 구조로 만든 것</strong>이 좋은 평가를
받았다.</p>
<hr>
<h2 id="4-프로젝트-피드백">4. 프로젝트 피드백</h2>
<p>전반적으로 팀 프로젝트에 대해 긍정적인 피드백을 많이 들을 수 있었다.
물론 개선해야 할 부분도 있었다.</p>
<p>가장 큰 피드백은 <strong>Supabase 클라이언트 구조</strong>였다.</p>
<p>현재 프로젝트에는 다음과 같은 구조가 공존하고 있었다.</p>
<ul>
<li><code>supabase.ts</code> (레거시 방식)</li>
<li><code>supabase/client</code></li>
<li><code>supabase/server</code></li>
</ul>
<p>이렇게 여러 방식이 섞여 있기 때문에 <strong>레거시 코드를 제거하고 하나의
구조로 통일하는 것이 좋다</strong>는 피드백을 받았다.</p>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<ul>
<li>로그인 화면에 <strong>비밀번호 보기 토글 기능</strong> 추가</li>
<li>프로젝트 <strong>최종 발표 진행</strong></li>
<li>GitHub 협업 관리와 <strong>컴포넌트 구조 설계</strong>에 대해 좋은 피드백을 받았다.</li>
<li>Auth 파트에서 <strong>소셜 로그인 로직 구조화</strong> 부분이 좋은 평가를 받았다.</li>
<li>Supabase 클라이언트 구조를 <strong>레거시 제거 후 통일할 필요</strong>가 있다는 피드백을 받았다.</li>
</ul>
<p>프로젝트를 진행하며 신경 썼던 <strong>코드 구조와 협업 방식</strong>에 대해 긍정적인
평가를 받아 의미 있는 발표였다.</p>
<p>좋은 팀원들과 좋은 결과를 낸 거 같아서 기분이 좋은 하루다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 20 - Growth]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-20-Growth</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-20-Growth</guid>
            <pubDate>Mon, 30 Mar 2026 07:09:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 03월 30일 작성된 글입니다</p>
</blockquote>
<p>프로젝트를 진행하며 여행 추천 플랫폼을 개발했고, 나는 로그인 및 회원가입
기능 구현을 담당했다.
Supabase를 활용해 이메일 인증 기반 회원가입 및 로그인 기능을 구현했으며,
이후 구글과 카카오 소셜 로그인(SSO)까지 확장할 수 있는 구조를 설계했다.</p>
<p>이번 프로젝트를 통해 단순히 기능 구현뿐만 아니라 팀 협업 방식과
애플리케이션 구조 설계의 중요성을 많이 느낄 수 있었다.</p>
<hr>
<h2 id="1-로그인-및-회원가입-기능-구현">1. 로그인 및 회원가입 기능 구현</h2>
<p>이번 프로젝트에서는 Supabase Auth를 활용하여 이메일 인증 기반 회원가입과
로그인 기능을 구현했다.</p>
<p>Supabase에서 제공하는 인증 기능을 활용하면서 기본적인 로그인 흐름을
구성했고, 이후 확장성을 고려하여 구글과 카카오 소셜 로그인(SSO)을 추가할
수 있는 구조로 설계했다.</p>
<p>이를 통해 인증 기능을 단순히 구현하는 것에 그치지 않고, 이후 서비스
확장을 고려한 구조 설계의 중요성을 경험할 수 있었다.</p>
<hr>
<h2 id="2-git-협업-방식-경험">2. Git 협업 방식 경험</h2>
<p>팀 프로젝트에서는 Git을 활용한 협업 방식도 중요한 경험이었다.</p>
<p>팀원들과 <strong>Issue, Pull Request(PR), 커밋 컨벤션 등의 양식을 통일</strong>하여
작업을 진행했는데, 덕분에 작업 내용을 한눈에 파악하기 쉬웠고 전체적인
작업 흐름도 훨씬 명확해졌다.</p>
<p>특히 다음과 같은 협업 방식을 사용했다.</p>
<ul>
<li>Issue를 통해 작업 단위 관리</li>
<li>Feature 브랜치 기반 개발</li>
<li>Pull Request를 통한 코드 리뷰 및 병합</li>
</ul>
<p>이러한 방식은 프로젝트의 작업 흐름을 정리하고 협업 효율을 높이는 데 큰
도움이 되었다.</p>
<hr>
<h2 id="3-로그인-무한-리다이렉트-문제-해결">3. 로그인 무한 리다이렉트 문제 해결</h2>
<p>여러 브랜치를 병합하는 과정에서 로그인 시 <strong>무한 리다이렉트가 발생하는
문제</strong>가 발생했다.</p>
<p>처음에는 로그인 기능 자체의 문제라고 생각했지만, 원인을 분석해보니
온보딩 페이지에서 로그인 경로가 <strong>하드코딩되어 있던 라우팅 구조
문제</strong>였다.</p>
<p>이 문제를 해결하면서 단순히 기능 코드만 보는 것이 아니라 <strong>애플리케이션
전체 라우팅 흐름을 함께 확인하는 것이 중요하다</strong>는 것을 배울 수 있었다.</p>
<hr>
<h2 id="4-브랜치-병합-과정에서의-충돌-경험">4. 브랜치 병합 과정에서의 충돌 경험</h2>
<p>프로젝트를 진행하며 여러 브랜치를 병합하는 과정에서 Git 충돌이 자주
발생하기도 했다.</p>
<p>이 경험을 통해 작업 중에도 수시로 <code>git pull</code>을 통해 <strong>최신 코드 상태를
유지하는 것이 중요하다</strong>는 점을 직접 체감할 수 있었다.</p>
<p>또한 협업 환경에서는 다음과 같은 습관이 필요하다는 것을 느꼈다.</p>
<ul>
<li>작업 시작 전 최신 코드 pull</li>
<li>작은 단위로 커밋</li>
<li>PR을 통한 코드 병합</li>
</ul>
<hr>
<h2 id="5-인증-기능-구현을-통해-배운-점">5. 인증 기능 구현을 통해 배운 점</h2>
<p>이번 프로젝트를 통해 로그인 기능은 단순히 인증 API를 호출하는 것만으로
끝나는 것이 아니라 다음과 같은 요소들을 함께 고려해야 한다는 것을
배웠다.</p>
<ul>
<li>페이지 접근 제어</li>
<li>인증 상태 관리</li>
<li>라우팅 흐름 설계</li>
</ul>
<p>또한 팀 프로젝트에서는 단순히 기능 구현뿐만 아니라 <strong>코드 구조와 협업
방식이 프로젝트의 안정성과 개발 효율성에 큰 영향을 준다</strong>는 점도 느낄 수
있었다.</p>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<p>이번 프로젝트를 통해 Supabase를 활용한 인증 기능 구현뿐만 아니라 Git을
활용한 협업 방식과 애플리케이션 구조 설계의 중요성을 경험할 수 있었다.</p>
<p>특히 로그인 기능은 단순히 인증 로직만 구현하는 것이 아니라 <strong>페이지 접근
제어, 인증 상태 관리, 라우팅 흐름까지 함께 고려해야 한다는 점</strong>을 배울
수 있었다.</p>
<p>앞으로는 기능 구현에만 집중하기보다는 <strong>애플리케이션의 전체 흐름과 인증
구조를 먼저 설계한 뒤 개발을 진행하는 습관을 가져야겠다고 느꼈다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 19 - ing]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-19-ing</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-19-ing</guid>
            <pubDate>Fri, 27 Mar 2026 16:16:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 03월 28일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 Supabase Auth를 기반으로 회원 정보를 자체 user 테이블에
저장하고,
로그아웃 및 마이페이지에서 사용자 정보를 조회/수정하는 기능을 구현했다.</p>
<p>단순 인증(Auth)만 사용하는 것이 아니라,
서비스에서 활용할 사용자 데이터를 별도의 테이블로 관리하는 구조를 구성한
것이 핵심이다.</p>
<h2 id="1-supabase-auth--user-테이블-연동">1. Supabase Auth + user 테이블 연동</h2>
<p>회원가입 시 Supabase Auth에만 저장하는 것이 아니라,
추가 정보를 관리하기 위해 public.user 테이블에도 데이터를 저장하도록
구현했다.</p>
<ul>
<li>Auth 회원가입 성공 후 DB insert</li>
<li>user 테이블에 닉네임, 이메일 등 저장</li>
</ul>
<pre><code class="language-tsx">const { data, error } = await supabase.auth.signUp({
  email,
  password,
});

if (data.user) {
  await supabase.from(&quot;user&quot;).insert({
    id: data.user.id,
    email: data.user.email,
    nickname,
  });
}</code></pre>
<h2 id="2-사이드바-사용자-정보-표시">2. 사이드바 사용자 정보 표시</h2>
<p>로그인된 사용자 정보를 가져와
사이드바 하단에 닉네임과 프로필 이미지를 표시했다.</p>
<pre><code class="language-tsx">const {
  data: { user },
} = await supabase.auth.getUser();</code></pre>
<ul>
<li>닉네임 표시</li>
<li>프로필 이미지 표시</li>
</ul>
<h2 id="3-로그아웃-및-페이지-이동-기능">3. 로그아웃 및 페이지 이동 기능</h2>
<p>사이드바에서 로그아웃 및 마이페이지 이동 기능을 구현했다.</p>
<pre><code class="language-tsx">await supabase.auth.signOut();</code></pre>
<ul>
<li>로그아웃 버튼</li>
<li>마이페이지 이동 버튼</li>
</ul>
<h2 id="4-마이페이지-ui-및-사용자-정보-조회">4. 마이페이지 UI 및 사용자 정보 조회</h2>
<p>마이페이지에서 로그인된 사용자의 정보를 조회하여 표시했다.</p>
<pre><code class="language-tsx">const { data } = await supabase
  .from(&quot;user&quot;)
  .select(&quot;*&quot;)
  .eq(&quot;id&quot;, user.id)
  .single();</code></pre>
<ul>
<li>이메일</li>
<li>닉네임</li>
<li>프로필 이미지</li>
</ul>
<h2 id="5-사용자-정보-수정-기능">5. 사용자 정보 수정 기능</h2>
<p>마이페이지에서 사용자 정보를 수정할 수 있도록 구현했다.</p>
<h3 id="1-닉네임-변경">1. 닉네임 변경</h3>
<pre><code class="language-tsx">await supabase
  .from(&quot;user&quot;)
  .update({ nickname })
  .eq(&quot;id&quot;, user.id);</code></pre>
<h3 id="2-비밀번호-재설정-이메일-전송-방식">2. 비밀번호 재설정 (이메일 전송 방식)</h3>
<p>비밀번호는 직접 변경하는 것이 아니라,
Supabase에서 제공하는 <strong>비밀번호 재설정 이메일 기능</strong>을 사용했다.</p>
<pre><code class="language-tsx">await supabase.auth.resetPasswordForEmail(email, {
  redirectTo: &quot;http://localhost:3000/reset-password&quot;,
});</code></pre>
<ul>
<li>사용자가 이메일을 통해 비밀번호 재설정 진행</li>
<li>redirect 페이지에서 새로운 비밀번호 설정</li>
</ul>
<h3 id="3-이메일-변경">3. 이메일 변경</h3>
<pre><code class="language-tsx">await supabase.auth.updateUser({
  email: newEmail,
});</code></pre>
<hr>
<h2 id="🍀-금일-구현한-것">🍀 금일 구현한 것</h2>
<ul>
<li>회원가입 시 Auth → user 테이블 저장</li>
<li>사이드바 사용자 정보 UI 구현</li>
<li>로그아웃 기능 구현</li>
<li>마이페이지 UI 및 사용자 정보 조회</li>
<li>닉네임 변경 기능</li>
<li>비밀번호 재설정 (이메일 방식)</li>
<li>이메일 변경 기능</li>
</ul>
<hr>
<h2 id="🔥-주말-구현-예정">🔥 주말 구현 예정</h2>
<p>현재는 각 컴포넌트(사이드바, 마이페이지)에서<br>개별적으로 supabase.getUser()를 호출하고 있다.</p>
<p>이를 개선하여 인증 상태를 한 곳에서 관리하도록 리팩토링할 예정이다.</p>
<ul>
<li>MainAuthGate에서 유저 정보 일괄 처리</li>
<li>전역 상태 관리 (Context 또는 상태관리 라이브러리 활용)</li>
<li>불필요한 중복 호출 제거</li>
</ul>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<ul>
<li>Supabase Auth는 인증을 담당하고, 서비스 데이터는 별도 테이블로
관리해야 한다.</li>
<li>사용자 정보 수정은 Auth / DB 역할을 나누어 처리해야 한다.</li>
<li>비밀번호는 직접 변경이 아니라 이메일 기반 재설정 방식이 안전하다.</li>
<li>인증 상태를 중앙에서 관리하면 코드 구조가 훨씬 깔끔해진다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 18 - Retry]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-18-Retry</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-18-Retry</guid>
            <pubDate>Thu, 26 Mar 2026 13:40:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 03월 26일 작성된 글입니다.</p>
</blockquote>
<h2 id="개요">개요</h2>
<p>오늘은 팀 프로젝트에서 맡은 <strong>로그인 / 회원가입 기능 개발</strong>을 본격적으로 진행했다.
피그마 와이어프레임을 기반으로 로그인 및 회원가입 페이지의 UI를 구현하고, 인증 기능을 위해 <strong>Supabase, SSO 기번 이메일 로그인 및 회원가입 구조를 설계</strong>했다.</p>
<p>개발을 진행하는 과정에서 팀원들의 브랜치를 병합한 이후 <strong>온보딩 페이지에서 로그인 무한 루프가 발생하는 문제</strong>를 겪었고, 이를 해결하기 위해 <strong>라우팅 구조와 인증 흐름을 다시 정리하는 트러블슈팅 과정</strong>도 경험했다.</p>
<hr>
<h2 id="1-로그인--회원가입-기능-구현">1. 로그인 / 회원가입 기능 구현</h2>
<p>오늘은 로그인 기능의 전체적인 흐름을 고려하면서 UI와 인증 구조를 함께 구현했다.</p>
<p>먼저 피그마 와이어프레임을 기반으로 다음 페이지들을 구현했다.</p>
<ul>
<li>로그인 페이지 UI</li>
<li>회원가입 페이지 UI</li>
</ul>
<p>또한 이후 인증 기능 연동을 위해 
<strong>Supabase + SSO 로그인 / 회원가입 기능을 연결할 수 있는 구조를 준비했다.</strong></p>
<p>금일 구현 내용</p>
<ul>
<li>Supabase 이메일 회원가입</li>
<li>Supabase 이메일 로그인</li>
<li>구글 소셜 로그인</li>
<li>카카오 소셜 로그인</li>
<li>비밀번호 재설정 기능</li>
</ul>
<hr>
<h2 id="2-인증-로직을-위한-폼-이벤트-처리">2. 인증 로직을 위한 폼 이벤트 처리</h2>
<p>로그인 폼에서는 기본적으로 폼 제출 시 페이지가 새로고침되기 때문에 이벤트를 제어해야 한다.</p>
<pre><code class="language-tsx">  const handleSubmit = async (event: FormEvent&lt;HTMLFormElement&gt;) =&gt; {
    event.preventDefault()

  // 로그인 API 요청
}</code></pre>
<p><code>preventDefault()</code>를 사용하면 기본 제출 동작을 막고
로그인 요청이나 인증 로직을 직접 처리할 수 있다.</p>
<hr>
<h2 id="3-로그인-무한-루프-문제-발생">3. 로그인 무한 루프 문제 발생</h2>
<p>팀원들의 작업 브랜치를 머지한 이후 <strong>온보딩 페이지에서 로그인 루프 문제가 발생했다.</strong></p>
<h3 id="문제-증상">문제 증상</h3>
<p>첫 수정</p>
<ul>
<li>로그인은 정상적으로 완료됨</li>
<li>하지만 로그인 페이지에서 벗어나지 못하는 문제 발생</li>
</ul>
<pre><code>로그인 성공 → 다시 로그인 페이지로 이동</code></pre><p>두번째 수정</p>
<ul>
<li>로그인 페이지에서 벗어나긴 했지만</li>
<li>페이지가 계속 새로고침되는 <strong>무한 리로드 현상 발생</strong></li>
</ul>
<pre><code>페이지 진입 → redirect → reload → 반복</code></pre><hr>
<h2 id="4-문제-원인-분석">4. 문제 원인 분석</h2>
<p>온보딩 페이지의 <strong>시작하기 버튼 경로가 하드코딩 되어 있는 것이 원인이었다.</strong></p>
<p>기존 코드</p>
<pre><code class="language-javascript">router.push(&quot;/login&quot;)</code></pre>
<p>이 구조에서는 <strong>사용자가 이미 로그인되어 있어도 무조건 로그인 페이지로 이동하게 된다.</strong></p>
<p>결과적으로</p>
<pre><code>로그인 상태 → 온보딩 → 로그인 페이지 → 다시 인증 확인 → 반복</code></pre><p>과 같은 흐름이 만들어지면서 무한 루프가 발생했다.</p>
<hr>
<h2 id="5-해결-방법">5. 해결 방법</h2>
<h3 id="1-온보딩-페이지-경로-수정">1. 온보딩 페이지 경로 수정</h3>
<p>시작하기 버튼이 <strong>메인 페이지로 이동하도록 수정했다.</strong></p>
<pre><code class="language-tsx">router.push(&quot;/main&quot;)</code></pre>
<hr>
<h3 id="2-메인-페이지-로그인-보호-로직-추가">2. 메인 페이지 로그인 보호 로직 추가</h3>
<p>메인 페이지에 로그인 여부를 확인하는 로직을 추가했다.</p>
<p>로그인이 되어 있지 않은 경우 로그인 페이지로 이동하도록 처리했다.</p>
<pre><code class="language-tsx">if (!user) {
  router.push(&quot;/login&quot;)
}</code></pre>
<hr>
<h3 id="3-메인-내부-페이지-접근-보호">3. 메인 내부 페이지 접근 보호</h3>
<p><code>/main</code> 내부의 모든 페이지는 로그인 상태가 아닐 경우
온보딩 페이지로 돌아가도록 보호 로직을 추가했다.</p>
<p>라우트 흐름</p>
<pre><code>온보딩 (/)
   ↓
로그인 (/login)
   ↓
메인 (/main)
   ↓
메인 내부 페이지</code></pre><p>로그인이 되어 있지 않은 경우</p>
<pre><code>main → onboarding(/) redirect</code></pre><p>이렇게 인증 흐름을 정리하면서 <strong>로그인 무한 루프 문제를 해결할 수 있었다.</strong></p>
<p>** 금일 구현한 결과물이다 **
<img src="https://velog.velcdn.com/images/xxxhyxxk__/post/95c646ac-c80b-4f69-9409-68cbfef7b381/image.png" alt=""></p>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<p>오늘 개발을 진행하면서 로그인 기능 구현뿐만 아니라 <strong>라우팅과 인증 흐름 설계의 중요성</strong>을 다시 느꼈다.</p>
<p>특히 팀 프로젝트에서는 여러 브랜치가 병합되면서 예상치 못한 문제가 발생할 수 있기 때문에</p>
<ul>
<li>라우팅 구조</li>
<li>로그인 상태 관리</li>
<li>페이지 접근 권한</li>
</ul>
<p>명확하게 설계하는 것이 중요하다는 것을 경험했다.</p>
<p>내일 구현 목표인 작업으로는 아래와 같다.</p>
<ul>
<li>회원가입 시 Supabase Auth → users 테이블에 회원 정보 저장</li>
<li>사이드바 하단 사용자 명, 프로필 사진 구현</li>
<li>사이드바에 로그아웃, 마이페이지 바로가기 기능 구현</li>
<li>마이페이지 UI 구현, 마이페이지에서 사용자 정보 표시</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[
Day 17 - Start]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-17-Start</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-17-Start</guid>
            <pubDate>Wed, 25 Mar 2026 11:45:45 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 03월 25일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 팀 프로젝트의 <strong>본격적인 개발 시작 전 회의와 협업 환경 설정</strong>을 진행했다.
팀원들과 역할을 분담하고, ERD 설계와 화면 와이어프레임을 작성했으며, Git 협업 규칙을 정했다.</p>
<p>특히 로그인/회원가입 기능을 맡게 되어 <strong>인증(Auth) 파트 구현을 시작했다.</strong></p>
<hr>
<h2 id="1-팀-프로젝트-역할-분담">1. 팀 프로젝트 역할 분담</h2>
<p>프로젝트 회의를 통해 기능 단위로 역할을 나누었다.</p>
<p>내가 맡은 파트는 <strong>회원 관리 기능</strong>이다.</p>
<p>주요 담당 기능</p>
<ul>
<li>이메일 기반 회원가입</li>
<li>구글 / 카카오 소셜 로그인</li>
<li>회원가입 시 사용자 정보 데이터베이스 저장</li>
<li>기존 가입 이메일 검증</li>
<li>로그인</li>
<li>아이디 / 비밀번호 찾기</li>
<li>로그아웃</li>
</ul>
<p>이 중 <strong>1차적으로 이메일 기반 회원가입과 로그인 기능 구현</strong>을 진행할 예정이다.</p>
<hr>
<h2 id="2-화면-설계-와이어프레임">2. 화면 설계 (와이어프레임)</h2>
<p>로그인 / 회원가입 화면은 <strong>피그마(Figma)</strong> 를 이용해 와이어프레임을 정의했다.</p>
<p>로그인 화면에는 다음 기능들이 포함된다.</p>
<ul>
<li>이메일 로그인</li>
<li>비밀번호 입력</li>
<li>로그인 버튼</li>
<li>구글 로그인</li>
<li>카카오 로그인</li>
<li>아이디 찾기</li>
<li>비밀번호 찾기</li>
<li>회원가입 이동</li>
</ul>
<p>좌측에는 서비스 소개 영역이 있고
우측에는 로그인 및 회원가입 폼이 위치하는 구조이다.</p>
<hr>
<h2 id="3-git-브랜치-전략">3. Git 브랜치 전략</h2>
<p>팀 협업을 위해 브랜치 전략을 다음과 같이 정했다.</p>
<p>메인 브랜치 아래에 기능 단위 브랜치를 생성한다.</p>
<pre><code>main
 ├ feature/main_page
 ├ feature/sub_page
 ├ feature/auth
 └ feature/...</code></pre><p>각자 맡은 기능을 <strong>feature 브랜치에서 개발</strong>하고
작업 완료 후 <strong>Pull Request(PR)</strong> 를 통해 <code>main</code> 브랜치에 병합한다.</p>
<hr>
<h2 id="4-issue-작성-규칙">4. Issue 작성 규칙</h2>
<p>작업은 <strong>Issue 단위로 관리</strong>하기로 했다.</p>
<h3 id="issue-양식">Issue 양식</h3>
<pre><code class="language-md">## 💡개발 내용
-

## 🌿To-do
- [ ] 구현1
- [ ] 구현2</code></pre>
<h3 id="issue-규칙">Issue 규칙</h3>
<ul>
<li>기능 단위로 이슈 작성</li>
<li>이슈 단위로 PR 생성</li>
<li>PR 머지 후 Issue Close</li>
<li>담당자와 라벨 명시</li>
</ul>
<hr>
<h2 id="5-commit-메시지-규칙">5. Commit 메시지 규칙</h2>
<p>팀 내에서 커밋 메시지 컨벤션도 정했다.</p>
<pre><code class="language-bash">[분류] 내용

ex)
[Feat] Add 메서드 구현</code></pre>
<p>커밋 타입</p>
<pre><code>Feat      : 새로운 기능 추가
Fix       : 버그 수정
Docs      : 문서 수정
Style     : 코드 포맷팅, UI 수정
Refactor  : 코드 리팩토링
Test      : 테스트 코드
Chore     : 빌드, 패키지 수정
Revert    : 이전 상태로 롤백</code></pre><hr>
<h2 id="6-오늘-느낀-점">6. 오늘 느낀 점</h2>
<p>팀 프로젝트는 개인 프로젝트와 다르게 <strong>의사소통과 협업 방식이 매우 중요하다는 것을 느꼈다.</strong></p>
<p>팀원들과 의견을 나누며
서로의 생각을 맞춰가는 과정이 쉽지는 않았지만
그 과정 자체가 흥미롭고 의미 있게 느껴졌다.</p>
<p>앞으로 개발을 진행하면서 <strong>기술적인 성장뿐만 아니라 협업 경험도 많이 쌓고 싶다.</strong></p>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<p>오늘 진행한 내용</p>
<ul>
<li>팀 프로젝트 역할 분담</li>
<li>ERD 설계</li>
<li>로그인 / 회원가입 화면 와이어프레임 작성</li>
<li>Git 브랜치 전략 수립</li>
<li>Issue 작성 규칙 정리</li>
<li>Commit 메시지 컨벤션 정의</li>
</ul>
<p>앞으로는 내가 맡은 <strong>회원가입 / 로그인 기능 구현을 중심으로 개발을 진행할 예정이다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 16 - Supabase CRUD, RLS]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-16-Supabase-CRUD-RLS</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-16-Supabase-CRUD-RLS</guid>
            <pubDate>Tue, 24 Mar 2026 10:44:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 03월 24일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 <strong>Supabase를 활용한 게시글/댓글 CRUD와 RLS 권한 제어</strong>를 학습했고,
팀 프로젝트 기획 단계에서 <strong>여행지 추천 플랫폼 주제와 요구사항 명세서 작성</strong>을 진행했다.</p>
<hr>
<h2 id="1-supabase-테이블-관계-설정-fk">1. Supabase 테이블 관계 설정 (FK)</h2>
<p>게시글 작성자를 연결하기 위해 <strong>posts.user_id</strong>를
Supabase 인증 테이블인 <strong>auth.users.id</strong>와 연결했다.</p>
<pre><code>posts.user_id → auth.users.id (FK)</code></pre><p>초기에는 데이터 입력을 위해 <strong>Nullable 허용</strong>으로 설정했다.</p>
<pre><code>Is Nullable : 체크</code></pre><p>향후 실제 <strong>user_id가 저장되기 시작하면 Nullable 해제 예정</strong>.</p>
<hr>
<h2 id="2-게시글-crud-구현">2. 게시글 CRUD 구현</h2>
<h3 id="게시글-등록">게시글 등록</h3>
<pre><code class="language-ts">const { error } = await supabase
  .from(&#39;posts&#39;)
  .insert([{ title, content }])</code></pre>
<h3 id="게시글-삭제">게시글 삭제</h3>
<pre><code class="language-ts">const { error } = await supabase
  .from(&#39;posts&#39;)
  .delete()
  .eq(&#39;id&#39;, id)</code></pre>
<h3 id="게시글-수정">게시글 수정</h3>
<pre><code class="language-ts">const { error } = await supabase
  .from(&#39;posts&#39;)
  .update({ title, content })
  .eq(&#39;id&#39;, id)</code></pre>
<hr>
<h2 id="3-댓글-crud-구현">3. 댓글 CRUD 구현</h2>
<h3 id="댓글-등록">댓글 등록</h3>
<pre><code class="language-ts">const { data, error } = await supabase
  .from(&#39;comments&#39;)
  .insert([
    { some_column: &#39;someValue&#39;, other_column: &#39;otherValue&#39; },
  ])
  .select()</code></pre>
<h3 id="댓글-삭제">댓글 삭제</h3>
<pre><code class="language-ts">const { error } = await supabase
  .from(&#39;comments&#39;)
  .delete()
  .eq(&#39;some_column&#39;, &#39;someValue&#39;)</code></pre>
<hr>
<h2 id="4-supabase-rlsrow-level-security-권한-제어">4. Supabase RLS(Row Level Security) 권한 제어</h2>
<p>RLS 설정 후 권한이 없는 경우 데이터를 가져오지 못한다.</p>
<p>권한 검증 예시</p>
<pre><code class="language-ts">if (!data || data.length === 0) {
  // RLS 차단 or 해당 행이 없음
  throw new Error(&#39;수정 권한이 없거나 대상이 존재하지 않습니다.&#39;)
}</code></pre>
<p>또한 <strong>사용자 권한에 따라 UI 토글 노출</strong>을 구현할 수 있다.</p>
<p>예</p>
<pre><code>작성자 → 수정 / 삭제 버튼 노출
비작성자 → 버튼 숨김</code></pre><hr>
<h2 id="5-getsession-vs-getuser">5. getSession() vs getUser()</h2>
<p>Supabase에서 인증 정보를 가져오는 방법은 두 가지가 있다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>getSession()</th>
<th>getUser()</th>
</tr>
</thead>
<tbody><tr>
<td>데이터 출처</td>
<td>로컬 저장소 (JWT)</td>
<td>Supabase 서버</td>
</tr>
<tr>
<td>속도</td>
<td>빠름</td>
<td>느림 (네트워크 요청)</td>
</tr>
<tr>
<td>신뢰도</td>
<td>위조 가능성 있음</td>
<td>서버 검증 완료</td>
</tr>
<tr>
<td>반환값</td>
<td>session (토큰 + 유저)</td>
<td>user (유저 정보만)</td>
</tr>
<tr>
<td>용도</td>
<td>UI 분기, 버튼 표시/숨기기</td>
<td>서버 권한 체크, 보안 로직</td>
</tr>
<tr>
<td>권장 환경</td>
<td>클라이언트 컴포넌트</td>
<td>서버 컴포넌트 / API</td>
</tr>
</tbody></table>
<hr>
<h2 id="6-팀-프로젝트-기획">6. 팀 프로젝트 기획</h2>
<p>오늘은 팀 프로젝트 <strong>기획서와 요구사항 명세서</strong>를 작성했다.</p>
<p>프로젝트 주제는 다음과 같다.</p>
<p><strong>태그 기반 여행지 추천 웹 서비스</strong></p>
<p>사용자가 원하는 <strong>여행 테마 태그를 선택하면 해당 테마에 맞는 여행지를 추천</strong>받는 플랫폼이다.</p>
<p>예시 태그</p>
<ul>
<li>힐링</li>
<li>자연</li>
<li>감성 카페</li>
<li>맛집</li>
<li>야경</li>
<li>액티비티</li>
<li>쇼핑</li>
</ul>
<hr>
<h2 id="7-담당-역할">7. 담당 역할</h2>
<p>프로젝트 역할 분배 결과
나는 <strong>회원 시스템 파트</strong>를 맡게 되었다.</p>
<p>구현 예정 기능</p>
<pre><code>회원가입
로그인
이메일 인증
SSO 로그인</code></pre><p>SSO는 소셜 로그인을 의미하며 다음과 같은 방식으로 구현할 예정이다.</p>
<pre><code>Google 로그인
Kakao 로그인</code></pre><p>이후 프로젝트 진행 상황에 따라
<strong>다른 기능 파트도 추가로 맡아 구현할 가능성이 있다.</strong></p>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<p>오늘은 <strong>Supabase CRUD와 RLS 권한 제어 방식</strong>을 학습했다.
특히 인증 정보 조회 방식인 <code>getSession()</code>과 <code>getUser()</code>의 차이를 이해할 수 있었다.</p>
<p>또한 팀 프로젝트 기획을 진행하며 <strong>태그 기반 여행지 추천 플랫폼</strong>을 주제로 선정했고,
나는 <strong>회원가입 / 로그인 (이메일 + SSO)</strong> 파트를 담당하게 되었다.</p>
<p>앞으로는 회원 시스템 구현을 시작으로 프로젝트 기능을 단계적으로 개발해 나갈 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 15 - Supabase]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-15-Supabase</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-15-Supabase</guid>
            <pubDate>Mon, 23 Mar 2026 08:06:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 03월 23일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 <strong>Next.js에서 Supabase를 연동하여 데이터 조회와 인증 기능을
구현하는 방법</strong>을 배웠다.
Supabase를 사용하면 별도의 백엔드 서버 없이 <strong>DB + 인증 기능</strong>을 빠르게
구현할 수 있다.</p>
<hr>
<h2 id="1-supabase-설치-및-환경변수-설정">1. Supabase 설치 및 환경변수 설정</h2>
<p>패키지 설치</p>
<pre><code class="language-bash">npm install @supabase/supabase-js</code></pre>
<p><code>.env.local</code></p>
<pre><code class="language-env">NEXT_PUBLIC_SUPABASE_URL=https://xxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_xxx</code></pre>
<hr>
<h2 id="2-supabase-client-설정">2. Supabase Client 설정</h2>
<p><code>/lib/supabase.ts</code></p>
<pre><code class="language-ts">import { createClient } from &#39;@supabase/supabase-js&#39;

export const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
)</code></pre>
<hr>
<h2 id="3-게시글-목록-조회">3. 게시글 목록 조회</h2>
<pre><code class="language-ts">const { data } = await supabase
  .from(&#39;posts&#39;)
  .select(&#39;*&#39;)</code></pre>
<p>핵심 흐름</p>
<ul>
<li><code>supabase.from(&#39;table&#39;)</code></li>
<li><code>.select()</code> 데이터 조회</li>
<li>React state에 저장 후 렌더링</li>
</ul>
<hr>
<h2 id="4-게시글-상세-조회">4. 게시글 상세 조회</h2>
<p>특정 게시글 조회</p>
<pre><code class="language-ts">const { data } = await supabase
  .from(&#39;posts&#39;)
  .select(&#39;*&#39;)
  .eq(&#39;id&#39;, id)
  .single()</code></pre>
<p>댓글 조회</p>
<pre><code class="language-ts">const { data } = await supabase
  .from(&#39;comments&#39;)
  .select(&#39;*&#39;)
  .eq(&#39;post_id&#39;, id)</code></pre>
<hr>
<h2 id="5-supabase-조회-메서드">5. Supabase 조회 메서드</h2>
<p>  메서드       설명</p>
<hr>
<p>  <code>.eq()</code>      값 일치
  <code>.gt()</code>      초과
  <code>.lt()</code>      미만
  <code>.gte()</code>     이상
  <code>.lte()</code>     이하
  <code>.like()</code>    패턴 검색
  <code>.ilike()</code>   대소문자 무시
  <code>.in()</code>      배열 포함</p>
<p>예시</p>
<pre><code class="language-ts">supabase.from(&#39;posts&#39;).select(&#39;*&#39;).eq(&#39;id&#39;, 1)</code></pre>
<hr>
<h2 id="6-회원가입-구현">6. 회원가입 구현</h2>
<pre><code class="language-ts">await supabase.auth.signUp({
  email,
  password
})</code></pre>
<hr>
<h2 id="7-로그인--로그아웃">7. 로그인 / 로그아웃</h2>
<p>로그인</p>
<pre><code class="language-ts">await supabase.auth.signInWithPassword({
  email,
  password
})</code></pre>
<p>로그아웃</p>
<pre><code class="language-ts">await supabase.auth.signOut()</code></pre>
<p>현재 사용자 조회</p>
<pre><code class="language-ts">const { data: { user } } = await supabase.auth.getUser()</code></pre>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<ul>
<li>Supabase는 <strong>DB + 인증 기능을 제공하는 BaaS</strong></li>
<li><code>@supabase/supabase-js</code>로 Next.js에서 쉽게 연동 가능</li>
<li><code>.from().select()</code> 방식으로 데이터 조회</li>
<li><code>.eq</code>, <code>.like</code> 등으로 SQL처럼 필터링</li>
<li><code>supabase.auth</code>로 <strong>회원가입 / 로그인 / 로그아웃</strong> 구현 가능</li>
</ul>
<p>Next.js와 Supabase를 함께 사용하면 간단한 CRUD 서비스나 개인 프로젝트를
빠르게 만들 수 있다.</p>
<p><strong>수파베이스를 처음 접해봐서 사실 따라가기에 급급했던 수업인 거 같다.
반복해서 학습을 해야 할 필요성을 느꼈다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 14 - Next ?]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-14-Next</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-14-Next</guid>
            <pubDate>Fri, 20 Mar 2026 08:26:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 03월 20일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 <strong>Next.js 기본 구조와 Supabase 설정 기초</strong>를 학습했다.<br>Next.js 프로젝트 생성, 파일 기반 라우팅 규칙, layout 구조,
서버/클라이언트 컴포넌트 차이 그리고 Supabase 연동을 위한 기본 세팅을
실습했다.</p>
<hr>
<h2 id="1-nextjs-프로젝트-생성">1. Next.js 프로젝트 생성</h2>
<p>Next.js 프로젝트는 아래 명령어로 생성할 수 있다.</p>
<pre><code class="language-bash">npx create-next-app@latest</code></pre>
<p>명령어 실행 후 기본 폴더 구조와 함께 Next.js 프로젝트가 생성된다.</p>
<p>대표적으로 생성되는 구조</p>
<pre><code>app/
  layout.jsx
  page.jsx
public/
package.json</code></pre><hr>
<h2 id="2-nextjs-파일-기반-라우팅">2. Next.js 파일 기반 라우팅</h2>
<p>Next.js는 <strong>파일 기반 라우팅(File-based Routing)</strong> 방식을 사용한다.<br>즉 <strong>폴더와 파일 이름이 URL 경로가 된다.</strong></p>
<p>예시 컴포넌트</p>
<pre><code class="language-javascript">export default function PostList() {
  return &lt;&gt;게시글 목록 페이지&lt;/&gt;
}</code></pre>
<pre><code class="language-javascript">export default function PostDetail() {
  return &lt;&gt;게시글 상세 페이지&lt;/&gt;
}</code></pre>
<p>예시 폴더 구조</p>
<pre><code>app
 ├ posts
 │   ├ page.jsx
 │   └ [id]
 │       └ page.jsx</code></pre><p>URL 매핑</p>
<pre><code>/posts
/posts/1
/posts/2</code></pre><hr>
<h2 id="3-layout-구조">3. Layout 구조</h2>
<p>Next.js에서는 <code>layout.jsx</code>를 이용해 <strong>공통 레이아웃</strong>을 구성할 수 있다.</p>
<p>예를 들어 상단 네비게이션과 푸터를 공통으로 만들 수 있다.</p>
<pre><code class="language-jsx">&lt;body&gt;
  &lt;header&gt;
    &lt;nav className=&quot;flex gap-2&quot;&gt;
      &lt;a href=&quot;/&quot; className=&quot;p-2 rounded hover:bg-gray-200&quot;&gt;
        메인
      &lt;/a&gt;

      &lt;a href=&quot;/posts&quot; className=&quot;p-2 rounded hover:bg-gray-200&quot;&gt;
        글 목록
      &lt;/a&gt;
    &lt;/nav&gt;
  &lt;/header&gt;

  {children}

  &lt;footer className=&quot;p-2 text-sm&quot;&gt;
    footer 영역
  &lt;/footer&gt;
&lt;/body&gt;</code></pre>
<p><code>{children}</code> 위치에 각 페이지 컴포넌트가 렌더링된다.</p>
<hr>
<h2 id="4-a-태그-vs-link">4. a 태그 vs Link</h2>
<p>Next.js에서는 내부 페이지 이동 시 <strong>Link 컴포넌트 사용을 권장한다.</strong></p>
<table>
<thead>
<tr>
<th>구분</th>
<th>a 태그</th>
<th>Link</th>
</tr>
</thead>
<tbody><tr>
<td>페이지 이동</td>
<td>전체 새로고침</td>
<td>클라이언트 라우팅</td>
</tr>
<tr>
<td>속도</td>
<td>상대적으로 느림</td>
<td>빠름</td>
</tr>
<tr>
<td>UX</td>
<td>화면 깜빡임 발생</td>
<td>부드러운 전환</td>
</tr>
<tr>
<td>사용 예</td>
<td>외부 링크</td>
<td>내부 페이지 이동</td>
</tr>
</tbody></table>
<p>예시</p>
<pre><code class="language-html">&lt;a href=&quot;/posts&quot;&gt;글 목록&lt;/a&gt;</code></pre>
<pre><code class="language-jsx">&lt;Link href=&quot;/posts&quot;&gt;글 목록&lt;/Link&gt;</code></pre>
<hr>
<h2 id="5-서버-컴포넌트-vs-클라이언트-컴포넌트">5. 서버 컴포넌트 vs 클라이언트 컴포넌트</h2>
<p>Next.js에서는 <strong>Server Component</strong>와 <strong>Client Component</strong> 개념이 존재한다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>서버 컴포넌트</th>
<th>클라이언트 컴포넌트</th>
</tr>
</thead>
<tbody><tr>
<td>실행 환경</td>
<td>서버</td>
<td>브라우저 + 서버</td>
</tr>
<tr>
<td>렌더링</td>
<td>서버에서 렌더링</td>
<td>브라우저 렌더링</td>
</tr>
<tr>
<td>상태 관리</td>
<td>불가능</td>
<td>가능</td>
</tr>
<tr>
<td>이벤트 처리</td>
<td>불가능</td>
<td>가능</td>
</tr>
<tr>
<td>선언 방법</td>
<td>기본값</td>
<td><code>&quot;use client&quot;</code> 선언</td>
</tr>
</tbody></table>
<p>클라이언트 컴포넌트 예시</p>
<pre><code class="language-javascript">&quot;use client&quot;

import { useState, useEffect } from &quot;react&quot;</code></pre>
<hr>
<h2 id="6-더미-데이터-api-연동">6. 더미 데이터 API 연동</h2>
<p>React의 <code>useEffect</code>를 이용해 외부 API 데이터를 가져올 수 있다.</p>
<pre><code class="language-javascript">&quot;use client&quot;

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

export default function PostList() {

  const [posts, setPosts] = useState([])

  useEffect(() =&gt; {
    fetch(&quot;https://dummyjson.com/posts&quot;)
      .then((res) =&gt; res.json())
      .then((data) =&gt; setPosts(data.posts))
  }, [])

  return (
    &lt;ul&gt;
      {posts.map((post) =&gt; (
        &lt;li key={post.id}&gt;
          {post.id} - {post.title}
        &lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}</code></pre>
<hr>
<h2 id="7-동적-라우팅-useparams">7. 동적 라우팅 (useParams)</h2>
<p>Next.js에서는 <code>useParams</code>를 이용해 URL 파라미터 값을 가져올 수 있다.</p>
<pre><code class="language-javascript">&quot;use client&quot;

import { useParams } from &quot;next/navigation&quot;

export default function PostDetail() {

  const { id } = useParams()

  return (
    &lt;div&gt;
      {id}번 게시글 상세 페이지
    &lt;/div&gt;
  )
}</code></pre>
<p>예시 URL</p>
<pre><code>/posts/1
/posts/10</code></pre><hr>
<h2 id="8-supabase-설정">8. Supabase 설정</h2>
<p>Supabase는 <strong>Backend as a Service</strong> 형태의 서비스로 DB와 인증 등을 제공한다.</p>
<p>먼저 패키지를 설치한다.</p>
<pre><code class="language-bash">npm install @supabase/supabase-js</code></pre>
<p>Supabase 클라이언트 설정</p>
<pre><code class="language-javascript">import { createClient } from &quot;@supabase/supabase-js&quot;

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY

export const supabase = createClient(
  supabaseUrl,
  supabaseAnonKey
)</code></pre>
<p>환경 변수에는 Supabase 프로젝트의 URL과 API Key를 설정한다.</p>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<ul>
<li>Next.js는 <strong>파일 기반 라우팅</strong>을 사용한다.</li>
<li><code>layout.jsx</code>로 공통 레이아웃을 만들 수 있다.</li>
<li>내부 페이지 이동은 <strong>Link 컴포넌트</strong>를 사용하는 것이 좋다.</li>
<li>Next.js에는 <strong>서버 컴포넌트와 클라이언트 컴포넌트</strong> 개념이 있다.</li>
<li><code>useEffect</code>를 이용해 외부 API 데이터를 가져올 수 있다.</li>
<li><code>useParams</code>로 <strong>동적 라우팅</strong> 처리가 가능하다.</li>
<li>Supabase 패키지를 통해 <strong>DB 연동 준비</strong>를 할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 13 - API]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-13-API</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-13-API</guid>
            <pubDate>Thu, 19 Mar 2026 08:07:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 03월 19일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 React Todo 프로젝트를 다시 복습하면서 컴포넌트 구조를 정리했고,
외부 API와 연동하여 데이터를 처리하는 방법을 학습했다. 또한 useEffect의
동작 방식을 정리하고, Todo 데이터를 CRUD 방식으로 처리하는 흐름을 학습했다.</p>
<h2 id="1-todoform-컴포넌트-분리">1. TodoForm 컴포넌트 분리</h2>
<p>기존에는 App.jsx 내부에서 Todo 입력과 관련된 로직을 처리하고 있었는데,
이를 별도의 컴포넌트로 분리했다.</p>
<p>기존에는 form submit을 직접 처리하는 <code>handleOnSubmit</code>을 props로
전달했다.</p>
<pre><code class="language-jsx">&lt;TodoForm handleOnSubmit={handleOnSubmit} /&gt;</code></pre>
<p>이 방식을 조금 더 단순하게 만들기 위해 <code>addTodo</code> 함수를 정의하고 props
이름도 명확하게 변경했다.</p>
<pre><code class="language-jsx">&lt;TodoForm addTodo={addTodo} /&gt;</code></pre>
<p>이렇게 하면 컴포넌트의 역할이 더 명확해지고, <strong>App은 상태 관리에
집중하고 TodoForm은 입력 UI에 집중</strong>할 수 있게 된다.</p>
<hr>
<h2 id="2-fetch-api로-더미-데이터-연결">2. Fetch API로 더미 데이터 연결</h2>
<p>Todo 데이터를 테스트하기 위해 더미 API를 사용했다.</p>
<pre><code class="language-javascript">useEffect(() =&gt; {
    fetch(&#39;https://dummyjson.com/todos&#39;)
        .then((res) =&gt; res.json())
        .then((res) =&gt; setTodos(res.todos))
}, [])</code></pre>
<p>흐름은 다음과 같다.</p>
<ol>
<li>서버에 Todo 데이터를 요청한다.</li>
<li>응답을 JSON으로 변환한다.</li>
<li>todos 상태에 데이터를 저장한다.</li>
</ol>
<p>이 코드는 <strong>컴포넌트가 처음 렌더링될 때 한 번만 실행된다.</strong></p>
<hr>
<h2 id="3-useeffect-deps-정리">3. useEffect deps 정리</h2>
<p><code>deps</code>는 <strong>언제 다시 실행할지를 정하는 조건</strong>이다.</p>
<table>
<thead>
<tr>
<th>타입</th>
<th>코드</th>
<th>실행 시점</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>없음</td>
<td><code>useEffect(fn)</code></td>
<td>매 렌더링</td>
<td>거의 사용 ❌</td>
</tr>
<tr>
<td>빈 배열</td>
<td><code>useEffect(fn, [])</code></td>
<td>최초 1번</td>
<td>mount 시 사용</td>
</tr>
<tr>
<td>단일 deps</td>
<td><code>useEffect(fn, [a])</code></td>
<td>a 변경 시</td>
<td>기본 패턴</td>
</tr>
<tr>
<td>복수 deps</td>
<td><code>useEffect(fn, [a, b])</code></td>
<td>하나라도 변경</td>
<td>조건 여러 개</td>
</tr>
</tbody></table>
<p>React에서 <strong>데이터를 처음 불러오는 API 요청은 대부분 <code>[]</code> 형태로
사용한다.</strong></p>
<hr>
<h2 id="4-todo-api-연동-crud">4. Todo API 연동 (CRUD)</h2>
<p>Todo 기능을 API와 연결했다.</p>
<p>사용한 API</p>
<pre><code>https://dummyjson.com/todos</code></pre><p>각 기능별 HTTP Method는 다음과 같다.</p>
<p>  기능   Method   URL</p>
<hr>
<p>  조회   GET      <code>/todos</code>
  생성   POST     <code>/todos/add</code>
  삭제   DELETE   <code>/todos/:id</code>
  수정   PUT      <code>/todos/:id</code></p>
<h3 id="todo-생성-post">Todo 생성 (POST)</h3>
<pre><code class="language-javascript">fetch(&#39;https://dummyjson.com/todos/add&#39;, {
    method: &#39;POST&#39;,
    headers: { &#39;Content-Type&#39;: &#39;application/json&#39; },
    body: JSON.stringify({
        todo: text,
        completed: false,
        userId: randomInt(1, 255),
    }),
})</code></pre>
<p>새로운 Todo 데이터를 서버에 전달한다.</p>
<h3 id="todo-삭제-delete">Todo 삭제 (DELETE)</h3>
<pre><code class="language-javascript">fetch(`https://dummyjson.com/todos/${selectedId}`, {
    method: &#39;DELETE&#39;,
})</code></pre>
<p>선택된 Todo id를 기준으로 데이터를 삭제한다.</p>
<h3 id="todo-수정-put">Todo 수정 (PUT)</h3>
<pre><code class="language-javascript">fetch(`https://dummyjson.com/todos/${selectedId}`, {
    method: &#39;PUT&#39;,
    headers: { &#39;Content-Type&#39;: &#39;application/json&#39; },
    body: JSON.stringify({
        completed: !target.completed,
    }),
})</code></pre>
<p>완료 상태를 토글(toggle)하는 방식으로 업데이트했다.</p>
<hr>
<h2 id="5-목록-갱신을-위한-fetchtodos">5. 목록 갱신을 위한 fetchTodos</h2>
<p>데이터를 다시 불러오기 위해 별도의 <code>fetchTodos</code> 함수를 만들었다.</p>
<pre><code class="language-javascript">const fetchTodos = () =&gt; {
        fetch(&#39;https://dummyjson.com/todos&#39;)
            .then((res) =&gt; res.json())
            .then((res) =&gt; setTodos(res.todos))
    }</code></pre>
<p>이렇게 하면</p>
<ul>
<li>Todo 추가</li>
<li>Todo 삭제</li>
<li>Todo 수정</li>
</ul>
<p>이후 <strong>목록을 다시 가져와 최신 상태를 유지</strong>할 수 있다.</p>
<hr>
<h2 id="6-react-router-dom-설치">6. React Router Dom 설치</h2>
<p>추가로 <strong>라우팅 기능을 위해 React Router Dom을 설치했다.</strong></p>
<p>설치</p>
<pre><code class="language-bash">npm install react-router-dom</code></pre>
<p>이를 통해 여러 페이지에서 데이터를 처리하거나 화면을 분리할 수 있게
된다.</p>
<hr>
<h2 id="7-👥-1차-팀미션-진행">7. 👥 1차 팀미션 진행</h2>
<p>내일까지 <strong>React</strong> 기반의 <strong>Todoapp</strong> 을 강사님 레포에 PR 후
팀원들과 서로 리뷰를 하는 것이 과제였다.
오늘 오전에 PR을 하였는데, 팀원들이 좋은 리뷰를 달아줬고,
내가 Context API를 사용하여 전역 상태 관리를 하려고 하였으나,
일부 컴포넌트에선 props 를 사용하고 있었다. 
팀원분들이 남겨주신 좋은 피드백을 보며 개선했고, 팀원분들의 코드를 보면서도
각기 배울 점들이 많은 것 같다.</p>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<p>오늘 학습한 핵심 내용</p>
<ul>
<li>TodoForm을 컴포넌트로 분리하여 <strong>역할을 명확하게 구성</strong></li>
<li>Fetch API를 사용해 <strong>외부 Todo API와 데이터 연동</strong></li>
<li>useEffect의 <strong>dependency 배열 동작 방식 정리</strong></li>
<li>Todo 데이터를 <strong>CRUD 구조로 API와 연결</strong></li>
<li>데이터 갱신을 위한 <strong>fetch 함수 분리</strong></li>
<li>React Router Dom을 설치하여 <strong>라우팅 구조 준비</strong></li>
</ul>
<p>React에서 <strong>API 연동은 매우 중요한 부분</strong>이기 때문에 CRUD 흐름을 직접
구현해본 것이 의미 있는 연습이었다.</p>
<p>다음 회차부터는 <strong>Next.js</strong> 를 학습할 예정이다.
좀 더 집중해서 학습해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 12 - Review]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-11-Review</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-11-Review</guid>
            <pubDate>Wed, 18 Mar 2026 07:50:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 03월 18일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 바닐라 JavaScript로 만든 <strong>Todo App</strong>을 이어서 구현했고,
이전에 만들었던 <strong>React Todo App</strong>도 처음부터 다시 복습했다.</p>
<p>바닐라 프로젝트에서는 <strong>조건 렌더링</strong>, <strong>컴포넌트 분리</strong>, <strong>LocalStorage 저장</strong>을 구현했고
React 프로젝트에서는 <strong>Vite 프로젝트 생성부터 Todo 등록 / 삭제 / 체크 토글 로직</strong>까지 다시 정리했다.</p>
<hr>
<h2 id="1--연산자로-조건-처리">1. &amp;&amp; 연산자로 조건 처리</h2>
<p>간단한 조건에서는 <strong>삼항 연산자 대신 <code>&amp;&amp;</code> 연산자</strong>를 사용하면 코드가 더 간결해진다.</p>
<pre><code class="language-html">&lt;li style=&quot;${todo.checked &amp;&amp; &#39;text-decoration: line-through&#39;}&quot;&gt;
  &lt;input type=&quot;checkbox&quot; ${todo.checked &amp;&amp; &quot;checked&quot;}&gt;
  &lt;span&gt;${todo.text}&lt;/span&gt;
&lt;/li&gt;</code></pre>
<ul>
<li>조건이 <code>true</code>일 때만 뒤의 값이 적용된다.</li>
<li><code>false</code>면 아무 값도 렌더링되지 않는다.</li>
</ul>
<hr>
<h2 id="2-todolist-생성">2. TodoList 생성</h2>
<p>Todo 목록을 관리하는 컴포넌트.</p>
<pre><code class="language-javascript">import TodoItem from &quot;./TodoItem.js&quot;;

function TodoList({ $target, initialState, onDelete, onToggle }) {
  const $container = document.createElement(&quot;div&quot;);
  const $ul = document.createElement(&quot;ul&quot;);

  $container.appendChild($ul);
  $target.appendChild($container);

  this.state = initialState;

  $container.addEventListener(&quot;click&quot;, (e) =&gt; {
    const id = Number(e.target.dataset.id);

    if (e.target.classList.contains(&quot;del_btn&quot;)) onDelete(id);
    if (e.target.classList.contains(&quot;toggle_btn&quot;)) onToggle(id);
  });

  this.setState = (next) =&gt; {
    this.state = next;
    this.render();
  };

  this.render = () =&gt; {
    $ul.innerHTML = &quot;&quot;;
    this.state.forEach((todo) =&gt;
      new TodoItem({ $target: $ul, todo })
    );
  };

  this.render();
}

export default TodoList;</code></pre>
<p>이벤트 위임을 사용하여 <strong>삭제 / 체크 이벤트를 한 곳에서 처리</strong>했다.</p>
<hr>
<h2 id="3-todoitem-생성">3. TodoItem 생성</h2>
<p>각 Todo 항목을 렌더링하는 컴포넌트.</p>
<pre><code class="language-javascript">function TodoItem({ $target, todo }) {
  const $li = document.createElement(&quot;li&quot;);

  $li.style.textDecoration = todo.checked ? &quot;line-through&quot; : &quot;&quot;;

  $li.innerHTML = `
    &lt;input class=&quot;toggle_btn&quot; type=&quot;checkbox&quot; data-id=&quot;${todo.id}&quot; ${todo.checked ? &quot;checked&quot; : &quot;&quot;}&gt;
    &lt;span&gt;${todo.text}&lt;/span&gt;
    &lt;button class=&quot;del_btn&quot; data-id=&quot;${todo.id}&quot;&gt;X&lt;/button&gt;
  `;

  $target.appendChild($li);
}

export default TodoItem;</code></pre>
<p>체크 상태에 따라 <strong>취소선 스타일을 적용</strong>했다.</p>
<hr>
<h2 id="4-localstorage-유틸-만들기">4. LocalStorage 유틸 만들기</h2>
<p>Todo 데이터를 <strong>새로고침 후에도 유지</strong>하기 위해 LocalStorage를 사용했다.</p>
<pre><code class="language-javascript">export function getStorage(key) {
    try {
        const stored = localStorage.getItem(key); 
        if (stored == null) return null;          
        return JSON.parse(stored);   
    } catch {
        return null;
    }
}

export function setStorage(key, data) {
    try {
        localStorage.setItem(key, JSON.stringify(data)); 
    } catch {
       console.log(&quot;저장 중 오류 발생&quot;)  
    }
}</code></pre>
<ul>
<li><code>setStorage</code> → 데이터 저장</li>
<li><code>getStorage</code> → 데이터 조회 (없으면 기본값 반환)</li>
</ul>
<hr>
<h2 id="5-react-todo-app-복습">5. React Todo App 복습</h2>
<p>이전에 만들었던 <strong>React Todo App</strong>도 처음부터 다시 복습했다.</p>
<h3 id="프로젝트-생성">프로젝트 생성</h3>
<pre><code class="language-bash">npm create vite@latest</code></pre>
<p>프로젝트 생성 후 React 템플릿을 선택하여 기본 환경을 구성했다.</p>
<h3 id="prettier-설정">Prettier 설정</h3>
<p>코드 스타일을 맞추기 위해 <strong>Prettier 플러그인</strong>을 설치하고
프로젝트 루트에 <code>.prettierrc</code> 파일을 생성했다.</p>
<hr>
<h2 id="6-appjsx-todo-로직-구현-복습">6. App.jsx Todo 로직 구현 복습</h2>
<p><code>App.jsx</code>에서 Todo 상태 관리와 함께
<strong>등록(add), 삭제(remove), 체크 토글(toggle)</strong> 기능을 구현했다.</p>
<ul>
<li><code>addTodo</code> → 새로운 Todo 추가</li>
<li><code>removeTodo</code> → Todo 삭제</li>
<li><code>toggleTodo</code> → 체크 상태 변경</li>
</ul>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<ul>
<li><code>&amp;&amp;</code> 연산자를 활용해 조건 처리를 간결하게 작성했다.</li>
<li>TodoList / TodoItem을 분리해 컴포넌트 구조를 만들었다.</li>
<li>LocalStorage를 활용해 Todo 데이터를 저장했다.</li>
<li>React Todo App을 처음부터 다시 복습하며 <strong>Todo 등록 / 삭제 / 체크 토글 로직</strong>을 구현했다.</li>
</ul>
<p>강사님의 강의로 학습한 것이외에도 개인적으로 다시 상기시키며 복습을 해보아야 할 거 같다는 생각이 들어서, 틈틈히 복습을 할 생각이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Day 11 - Router]]></title>
            <link>https://velog.io/@xxxhyxxk__/Day-11-Router</link>
            <guid>https://velog.io/@xxxhyxxk__/Day-11-Router</guid>
            <pubDate>Tue, 17 Mar 2026 08:10:02 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>이 글은 2026년 3월 17일 작성된 글입니다.</p>
</blockquote>
<p>오늘은 React에서 <strong>React Router를 활용한 페이지 라우팅</strong>,
<strong>Dummy JSON API를 활용한 더미 데이터 실습</strong>,
그리고 <strong>REST API 요청 방식(GET / POST / PUT / DELETE)</strong>을 활용하여 Todo 데이터를 관리하는 실습을 진행하였다.</p>
<p>또한 이전에 진행했던 <strong>Vanilla JavaScript 기반 Todo App 구현 과정</strong>도 함께 정리하였다.</p>
<hr>
<h2 id="1-react-router-기본-구조">1. React Router 기본 구조</h2>
<p>React에서는 <strong>React Router</strong>를 사용하여 페이지 이동을 구현한다.</p>
<p>기본적인 라우터 구조는 다음과 같다.</p>
<pre><code class="language-javascript">import { BrowserRouter, Routes, Route } from &quot;react-router-dom&quot;
import Main from &quot;./pages/Main&quot;
import Sub from &quot;./pages/Sub&quot;

function App() {
  return (
    &lt;BrowserRouter&gt;
      &lt;Routes&gt;
        &lt;Route index element={&lt;Main /&gt;} /&gt;
        &lt;Route path=&quot;/sub&quot; element={&lt;Sub /&gt;} /&gt;
      &lt;/Routes&gt;
    &lt;/BrowserRouter&gt;
  )
}

export default App</code></pre>
<p>구성 요소</p>
<table>
<thead>
<tr>
<th>구성요소</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>BrowserRouter</td>
<td>브라우저 URL을 기반으로 라우팅 관리</td>
</tr>
<tr>
<td>Routes</td>
<td>여러 Route를 묶는 컨테이너</td>
</tr>
<tr>
<td>Route</td>
<td>URL 경로와 컴포넌트 연결</td>
</tr>
</tbody></table>
<hr>
<h2 id="2-여러-페이지-라우팅-예제">2. 여러 페이지 라우팅 예제</h2>
<p>Todo 프로젝트에서는 다음과 같이 여러 페이지를 구성하였다.</p>
<pre><code class="language-javascript">import { BrowserRouter, Routes, Route } from &quot;react-router-dom&quot;
import Main from &quot;./pages/Main&quot;
import Sub from &quot;./pages/Sub&quot;
import List from &quot;./pages/List&quot;
import Form from &quot;./pages/Form&quot;

function App() {
  return (
    &lt;BrowserRouter&gt;
      &lt;Routes&gt;
        &lt;Route index element={&lt;Main /&gt;} /&gt;
        &lt;Route path=&quot;/sub&quot; element={&lt;Sub /&gt;} /&gt;
        &lt;Route path=&quot;/list&quot; element={&lt;List /&gt;} /&gt;
        &lt;Route path=&quot;/new&quot; element={&lt;Form /&gt;} /&gt;
      &lt;/Routes&gt;
    &lt;/BrowserRouter&gt;
  )
}

export default App</code></pre>
<p>페이지 구조 예시</p>
<pre><code>/
├ main
├ sub
├ list
└ new</code></pre><p>URL 경로에 따라 다른 컴포넌트가 렌더링된다.</p>
<hr>
<h2 id="3-dummy-json-이란">3. Dummy JSON 이란</h2>
<p><strong>Dummy JSON</strong>은 테스트용 데이터를 제공하는 API 서비스이다.</p>
<p>실제 서버가 없어도 <strong>REST API 요청을 연습할 수 있다.</strong></p>
<p>예시 API</p>
<pre><code>https://dummyjson.com/todos</code></pre><p>응답 데이터 예시</p>
<pre><code class="language-json">{
  &quot;todos&quot;: [
    {
      &quot;id&quot;: 1,
      &quot;todo&quot;: &quot;Do something&quot;,
      &quot;completed&quot;: false
    }
  ]
}</code></pre>
<p>활용 목적</p>
<ul>
<li>API 연동 연습</li>
<li>테스트용 데이터 사용</li>
<li>CRUD 기능 실습</li>
</ul>
<hr>
<h2 id="4-dummy-json으로-todo-데이터-가져오기">4. Dummy JSON으로 Todo 데이터 가져오기</h2>
<p>React에서는 <strong>useEffect + fetch</strong>를 사용하여 데이터를 가져온다.</p>
<pre><code class="language-javascript">import { useEffect } from &quot;react&quot;
import { useTodos } from &quot;../context/TodoContext&quot;
import TodoList from &quot;../components/TodoList&quot;
import TodoWriteForm from &quot;../components/TodoWriteForm&quot;

function Main() {
  const { setTodos } = useTodos()

  useEffect(() =&gt; {
    fetch(&quot;https://dummyjson.com/todos&quot;)
      .then((res) =&gt; res.json())
      .then((data) =&gt; setTodos(data.todos))
  }, [])

  return (
    &lt;&gt;
      &lt;TodoWriteForm /&gt;
      &lt;TodoList /&gt;
    &lt;/&gt;
  )
}

export default Main</code></pre>
<p>동작 흐름</p>
<ol>
<li>컴포넌트 마운트</li>
<li>API 요청 발생</li>
<li>JSON 데이터 변환</li>
<li>상태(state)에 저장</li>
<li>UI 렌더링</li>
</ol>
<hr>
<h2 id="5-todo-데이터-조회-get">5. Todo 데이터 조회 (GET)</h2>
<p>Todo 목록을 가져오는 요청이다.</p>
<pre><code class="language-javascript">useEffect(() =&gt; {
  fetch(&quot;https://dummyjson.com/todos&quot;)
    .then((res) =&gt; res.json())
    .then((data) =&gt; setTodos(data.todos))
}, [])</code></pre>
<p>특징</p>
<ul>
<li>GET 요청</li>
<li>여러 데이터 조회</li>
<li>초기 데이터 로딩에 사용</li>
</ul>
<hr>
<h2 id="6-todo-데이터-추가-post">6. Todo 데이터 추가 (POST)</h2>
<p>새로운 Todo 데이터를 서버에 추가하는 요청이다.</p>
<pre><code class="language-javascript">const handleSubmit = (e) =&gt; {
  e.preventDefault()
  const form = e.target

  fetch(&quot;https://dummyjson.com/todos/add&quot;, {
    method: &quot;POST&quot;,
    headers: {
      &quot;Content-Type&quot;: &quot;application/json&quot;,
    },
    body: JSON.stringify({
      todo: form.todo.value,
      completed: false,
      userId: 5,
    }),
  })
    .then((res) =&gt; res.json())
    .then(console.log)
}</code></pre>
<p>POST 특징</p>
<ul>
<li>서버에 새로운 데이터 생성</li>
<li>body에 JSON 데이터 전달</li>
</ul>
<hr>
<h2 id="7-todo-데이터-삭제-delete">7. Todo 데이터 삭제 (DELETE)</h2>
<p>특정 Todo 데이터를 삭제하는 요청이다.</p>
<pre><code class="language-javascript">const removeTodo = (id) =&gt; {
  fetch(`https://dummyjson.com/todos/${id}`, {
    method: &quot;DELETE&quot;,
  })
    .then((res) =&gt; res.json())
    .then(console.log)
}</code></pre>
<p>특징</p>
<ul>
<li>특정 ID 데이터 삭제</li>
<li>URL 파라미터 사용</li>
</ul>
<hr>
<h2 id="8-todo-데이터-수정-put--patch">8. Todo 데이터 수정 (PUT / PATCH)</h2>
<p>Todo 완료 상태를 변경하는 요청이다.</p>
<pre><code class="language-javascript">const toggleTodo = (id) =&gt; {
  const selected = todos.find((todo) =&gt; todo.id === id)

  fetch(`https://dummyjson.com/todos/${id}`, {
    method: &quot;PUT&quot;,
    headers: {
      &quot;Content-Type&quot;: &quot;application/json&quot;,
    },
    body: JSON.stringify({
      completed: !selected.completed,
    }),
  })
    .then((res) =&gt; res.json())
    .then(console.log)
}</code></pre>
<p>PUT / PATCH 차이</p>
<table>
<thead>
<tr>
<th>방식</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>PUT</td>
<td>전체 데이터 수정</td>
</tr>
<tr>
<td>PATCH</td>
<td>일부 데이터 수정</td>
</tr>
</tbody></table>
<hr>
<h2 id="9-vanilla-javascript-todo-app-구현">9. Vanilla JavaScript Todo App 구현</h2>
<p>Next 실습 이전에 <strong>Vanilla JS 기반 Todo App</strong>도 구현하였다.</p>
<p>구현 과정</p>
<ul>
<li>Todo Header 생성</li>
<li>TodoList 상태 관리</li>
<li>입력창을 통한 Todo 추가</li>
<li>initialState 데이터 구조 변경</li>
<li>삭제 버튼 클릭 시 Todo ID 가져오기</li>
</ul>
<p>이 과정을 통해 <strong>기본적인 상태 관리와 DOM 조작 방식</strong>을 이해할 수 있었다.</p>
<hr>
<h2 id="✅-정리">✅ 정리</h2>
<p>오늘 실습을 통해</p>
<ul>
<li>React Router를 이용한 페이지 라우팅</li>
<li>Dummy JSON API를 활용한 더미 데이터 연동</li>
<li>REST API 요청 방식<ul>
<li>GET (조회)</li>
<li>POST (생성)</li>
<li>PUT / PATCH (수정)</li>
<li>DELETE (삭제)</li>
</ul>
</li>
<li>React에서 API 데이터를 활용하는 방법</li>
<li>Vanilla JavaScript 기반 Todo App 구조</li>
</ul>
<p>를 정리하였다.</p>
<p>React 애플리케이션에서는 <strong>라우팅 구조와 API 연동이 핵심적인 요소</strong>이다.
하지만 아직 익숙치않아 조금 더 복습을 해야 할 것 같다.
이번 실습을 통해 실제 서비스 구조를 이해하는 데 도움이 되었다.</p>
]]></description>
        </item>
    </channel>
</rss>