<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>F로 시작하고 R로 끝나는 것은?</title>
        <link>https://velog.io/</link>
        <description>항상 Frame 안에서 일하고, 끝날 땐 Realese 한다.</description>
        <lastBuildDate>Wed, 22 Jul 2026 00:09:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>F로 시작하고 R로 끝나는 것은?</title>
            <url>https://velog.velcdn.com/images/hyphen_/profile/486b8a90-3b38-4b1e-a502-d0df4da4abc9/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. F로 시작하고 R로 끝나는 것은?. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hyphen_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[AI는 어디까지 왔을까? Build with AI Seoul 2026 참관기]]></title>
            <link>https://velog.io/@hyphen_/AI%EB%8A%94-%EC%96%B4%EB%94%94%EA%B9%8C%EC%A7%80-%EC%99%94%EC%9D%84%EA%B9%8C-Build-with-AI-Seoul-2026-%EC%B0%B8%EA%B4%80%EA%B8%B0</link>
            <guid>https://velog.io/@hyphen_/AI%EB%8A%94-%EC%96%B4%EB%94%94%EA%B9%8C%EC%A7%80-%EC%99%94%EC%9D%84%EA%B9%8C-Build-with-AI-Seoul-2026-%EC%B0%B8%EA%B4%80%EA%B8%B0</guid>
            <pubDate>Wed, 22 Jul 2026 00:09:58 GMT</pubDate>
            <description><![CDATA[<p>지난 5월, <strong>Build with AI Seoul 2026 with Google DeepMind</strong> 컨퍼런스에 참가하며 인상 깊었던 세션들을 정리해 보았습니다.</p>
<p>AI가 개발 생태계 전반에 빠르게 자리 잡으면서 이제는 단순히 새로운 기술을 접하는 것을 넘어, 이를 실제 업무에 어떻게 활용할 것인지가 중요한 화두가 되고 있습니다. 이번 컨퍼런스에서는 AI를 적극적으로 도입하고 있는 다양한 기업의 개발자들이 실제 경험과 노하우를 공유했으며, AI를 어떻게 받아들이고 개발 프로세스에 자연스럽게 접목시키는지 배울 수 있는 뜻깊은 시간이었습니다.</p>
<p>이 글에서는 그중 특히 인상 깊었던 세션과, 실무 개발자의 관점에서 얻은 인사이트를 정리해 보려고 합니다.</p>
<h1 id="keynote-🤖-코딩이-다시-즐거워졌다-구글-엔지니어가-전하는-ai-개발의-신세계">[Keynote] 🤖 “코딩이 다시 즐거워졌다”: 구글 엔지니어가 전하는 AI 개발의 신세계</h1>
<p>최근 구글 엔지니어링 팀에서 20년 넘게 근무한 한 베테랑 개발자가 서울을 찾았습니다. 그는 최근 몇 달 사이 AI(Gemini)와 함께 작업하며 &quot;지난 수년간 느꼈던 것보다 훨씬 더 큰 재미를 코딩에서 느끼고 있다&quot;고 고백했는데요.</p>
<p>아이디어가 시제품(Prototype)이 되고, 실제 구현까지 걸리는 시간이 &#39;며칠&#39;에서 &#39;몇 분&#39;으로 단축되는 이 마법 같은 변화 속에서 우리는 무엇을 준비해야 할까요?</p>
<h2 id="1-변하지-않는-것-기본기는-배신하지-않는다">1. 변하지 않는 것: “기본기는 배신하지 않는다”</h2>
<p>세상은 변하지만, 소프트웨어 개발의 본질은 여전히 중요합니다. AI를 도구로 쓰더라도 다음의 5가지는 절대 놓쳐선 안 됩니다.</p>
<ul>
<li><strong>철저한 계획과 설계:</strong> 무작정 코드부터 짜지 마세요. 아키텍처가 부실하면 AI도 답을 줄 수 없습니다.</li>
<li><strong>문제의 공통점 찾기:</strong> 세상에 완전히 새로운 문제는 드뭅니다. 다른 산업이나 과거의 사례에서 해결책을 찾는 안목이 필요합니다.</li>
<li><strong>팀 스포츠로서의 개발:</strong> 소프트웨어는 개발자 혼자 만드는 게 아닙니다. 기획, 디자인, 비즈니스가 함께하는 &#39;문화&#39;가 핵심입니다.</li>
<li><strong>데이터의 질 (GIGO):</strong> &quot;쓰레기가 들어가면 쓰레기가 나온다(Garbage In, Garbage Out)&quot;는 원칙은 AI 시대에 더 강력하게 적용됩니다.</li>
<li><strong>인프라의 신뢰성:</strong> 아무리 뛰어난 알고리즘도 인프라가 불안정하면 무용지물입니다.</li>
</ul>
<h2 id="2-변하는-것-개발자-이제는-에이전트를-관리하는-매니저">2. 변하는 것: &quot;개발자, 이제는 에이전트를 관리하는 매니저”</h2>
<p>AI가 도입되면서 개발자의 역할은 근본적으로 재정의되고 있습니다.</p>
<ul>
<li><strong>엔지니어 = 매니저:</strong> 이제 개발자는 코드를 한 줄씩 짜는 사람이 아니라, 여러 AI 에이전트(Agents)를 지휘하고 우선순위를 정하는 &#39;관리자&#39;가 되어야 합니다.</li>
<li><strong>컨텍스트(Context)가 곧 실력:</strong> 좋은 데이터를 제공하고, 해결해야 할 문제의 제약 조건을 명확히 전달하는 &#39;컨텍스트 구축&#39; 능력이 가장 중요해졌습니다.</li>
<li><strong>언어의 장벽 붕괴:</strong> 자바스크립트 개발자도 AI의 도움을 받으면 단숨에 Go 언어로 고성능 서버를 짤 수 있습니다. 도구에 얽매이지 않고 문제 해결에 집중하는 시대입니다.</li>
<li><strong>회의실에서의 실시간 프로토타이핑:</strong> &quot;나중에 만들어 올게요&quot;라는 말은 옛말입니다. 회의 중에 AI로 바로 코드를 짜서 노트북을 돌려 보여주는 &#39;초고속 피드백&#39;이 가능해졌습니다.</li>
</ul>
<h2 id="3-⚠️-ai-시대의-4가지-함정-하지-말아야-할-것">3. ⚠️ AI 시대의 4가지 함정 (하지 말아야 할 것)</h2>
<table>
<thead>
<tr>
<th>함정</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>변화 무시하기</td>
<td>&quot;나는 엔지니어가 아니니까&quot;라며 AI를 멀리하는 것. 이제 AI는 모든 직군을 위한 도구입니다.</td>
</tr>
<tr>
<td>데이터 무시하기</td>
<td>검증되지 않은 데이터를 AI에 넣는 것. 결과물의 신뢰성을 완전히 파괴합니다.</td>
</tr>
<tr>
<td>교육의 불균형</td>
<td>팀원 중 일부만 AI를 쓸 줄 아는 상황. 공동체의 성장을 저해합니다.</td>
</tr>
<tr>
<td>프로세스 생략</td>
<td>AI가 빠르다고 해서 리더십, 소통, 문화적 가치를 건너뛰면 결국 프로젝트는 실패합니다.</td>
</tr>
</tbody></table>
<h2 id="4-결론-우리의-본질은-가치-창출">4. 결론: 우리의 본질은 ‘가치 창출’</h2>
<p>AI가 수많은 &#39;작업&#39;을 대신해주고 있습니다. 덕분에 우리는 오탈자 수정이나 반복적인 코드 작성 같은 지루한 일에서 해방되고 있습니다. 하지만 그만큼 &quot;내가 고객에게 줄 수 있는 본질적인 가치는 무엇인가?&quot;라는 질문에 답할 책임은 더 커졌습니다.</p>
<p>서울은 전 세계 어느 곳보다 AI 기술을 수용하는 데 열정적이고 빠릅니다. 이 커뮤니티 안에서 서로 배우고, 실험하고, 실패하며 여러분만의 &#39;AI 워크플로우&#39;를 만들어보세요.</p>
<p><strong>&quot;AI는 여러분의 자리를 뺏는 것이 아니라, 여러분을 가장 창의적인 일로 복귀시켜 줄 것입니다.”</strong></p>
<hr>
<p><strong>💡 바로 실천해볼 수 있는 다음 단계:</strong></p>
<ol>
<li>해결하고 싶은 작은 문제를 하나 정한다.</li>
<li>AI(Gemini 등)를 연구 파트너로 삼아 문제의 본질을 파헤친다.</li>
<li>5분 안에 돌아가는 프로토타입을 만들어본다.</li>
<li>이 과정을 동료들과 공유한다.</li>
</ol>
<h1 id="session-1-gemini-cli로-구현하는-스타트업-전-직군-위크플로우-자동화-가이드">[Session 1] Gemini CLI로 구현하는 스타트업 전 직군 위크플로우 자동화 가이드</h1>
<p><code>정명훈</code> <code>Jerry Jeong</code> <code>Google Cloud</code> </p>
<blockquote>
<p>Gemini CLI와 Skill, MCP 기반 멀티 에이전트 아키텍처를 통해 스타트업 전 직군의 워크플로우를 자동화하고, 오케스트레이터 중심 디자인 패턴으로 적은 인력으로도 생산성 극대화와 비용 절감을 동시에 달성하는 ‘Agentic Startup’ 전환 방법을 제시하는 세션입니다.</p>
</blockquote>
<p>많은 분이 AI를 사용할 때 브라우저 창 하나에서 프롬프트를 주고받는 것에 그칩니다. 하지만 업무가 복잡해지고 하루에 날리는 프롬프트가 100개가 넘어가면 한계에 부딪히죠. 이번 세션에서는 이를 해결할 강력한 대안으로 <strong>Gemini CLI</strong>와 <strong>에이전트 시스템</strong>을 제시합니다.</p>
<h2 id="1-왜-gemini-cli인가-가볍고-빠르고-강력하다">1. 왜 Gemini CLI인가? “가볍고, 빠르고, 강력하다”</h2>
<p>터미널 환경(CLI)에서 Gemini를 쓰는 이유는 단순히 멋있어 보이기 위해서가 아닙니다.</p>
<ul>
<li><strong>가용성(Lightweight):</strong> 웹 브라우저는 무겁고 창이 많아지면 관리가 힘들지만, CLI는 10개를 띄워도 시스템에 무리가 없습니다.</li>
<li><strong>멀티태스킹:</strong> 여러 프로젝트를 동시에 돌릴 때, 각 터미널 창을 하나의 &#39;에이전트&#39;처럼 독립적으로 활용할 수 있습니다.</li>
<li><strong>파일 중심 워크플로우:</strong> 로컬 폴더에 파일을 던져 넣는 것만으로 AI의 컨텍스트(맥락)를 업데이트할 수 있습니다. 마크다운(.md)이나 PDF를 표준 문서로 활용하면 관리가 매우 쉬워집니다.</li>
</ul>
<h2 id="2-나만의-스킬skill-자산화하기">2. 나만의 ‘스킬(Skill)’ 자산화하기</h2>
<p>연사는 “남이 만든 유명한 스킬보다 <strong>나에게 맞는 스킬</strong>이 가장 중요하다”고 강조합니다.</p>
<h3 id="실전-사례-동영상-스토리라인-추출-스킬">실전 사례: 동영상 스토리라인 추출 스킬</h3>
<ol>
<li>미팅 영상이나 긴 동영상을 <code>ffmpeg</code>를 이용해 주요 장면별로 자릅니다.</li>
<li>이 이미지들을 <strong>4x4 그리드</strong> 형태의 작은 이미지로 만듭니다.</li>
<li>Gemini에게 &quot;변화가 심한 장면만 인덱스를 뽑고 스토리라인을 마크다운으로 써줘&quot;라고 시킵니다.</li>
<li>이 과정이 만족스러우면 이를 <code>.md</code> 파일 형태의 &#39;스킬&#39;로 저장해 자동화합니다.</li>
</ol>
<h2 id="3-멀티-에이전트와-미들-매니저cli-래퍼">3. 멀티 에이전트와 미들 매니저(CLI 래퍼)</h2>
<p>혼자서 모든 AI 도구(Claude, NotebookLM, 이미지 생성 등)를 제어하는 것은 불가능합니다. 이때 CLI가 <strong>&#39;중간 관리자&#39;</strong> 역할을 수행합니다.</p>
<ul>
<li><strong>에이전트 간 통신:</strong> 프로젝트 A의 성과를 프로젝트 B로 넘길 때, 마크다운 파일을 공유 폴더(인박스)에 넣어두면 에이전트가 이를 감시하다가 자동으로 작업을 이어받습니다.</li>
<li><strong>보안과 효율의 균형:</strong> 회사의 민감한 정보는 엔터프라이즈 환경에서 조회하고, 그 결과값만 로컬 CLI로 가져와 2차 가공하는 방식으로 보안을 유지하며 생산성을 높입니다.</li>
</ul>
<h2 id="4-실전-팁-geminimd-룰-파일-구성법">4. 실전 팁: <code>Gemini.md</code> 룰 파일 구성법</h2>
<p>AI가 내 의도대로 움직이게 하려면 명확한 &#39;규칙&#39;이 필요합니다. 연사가 제안하는 룰 파일의 핵심은 다음과 같습니다.</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>AI의 &#39;과잉 충성(할루시네이션)&#39;을 막기 위해 반드시 구분 요청</td>
</tr>
<tr>
<td>분량</td>
<td>10~15개 항목 제한</td>
<td>한 파일에 룰이 너무 많으면 AI가 인지 부하를 느껴 정보를 누락함</td>
</tr>
<tr>
<td>전문성</td>
<td>페르소나 지정</td>
<td>리뷰어, 디벨로퍼, 오케스트레이터 등으로 역할을 명확히 분담</td>
</tr>
</tbody></table>
<h2 id="5-결론-본질적인-가치value에-집중하라">5. 결론: “본질적인 가치(Value)에 집중하라”</h2>
<p>우리가 하루 8시간 중 4시간 동안 매달리는 &#39;복잡해 보이는 작업들&#39;이 사실은 AI가 더 잘할 수 있는 일일 수 있습니다.</p>
<blockquote>
<p><strong>&quot;AI가 내 일의 80%를 가져가면 나는 놀 줄 알았습니다. 하지만 오히려 일이 더 많아졌어요. 이제는 &#39;문서 오탈자 수정&#39;이 아니라 &#39;이 서비스가 고객에게 진짜 가치가 있는가?&#39;라는 본질적인 고민에 더 많은 시간을 쓰게 되었기 때문입니다.&quot;</strong></p>
</blockquote>
<p>AI로 불필요한 작업(잡무)을 쳐내고 얻은 20%의 여유. 그 시간을 <strong>창의와 혁신</strong>에 쏟는 것, 그것이 바로 이 세션이 전달하고자 하는 핵심 메시지입니다.</p>
<hr>
<h3 id="🛠️-활용해-볼-만한-도구들">🛠️ 활용해 볼 만한 도구들</h3>
<ul>
<li><strong>Gemini CLI Wrapper:</strong> 터미널이 불편한 분들을 위한 웹 기반 래핑 툴.</li>
<li><strong>MCP (Model Context Protocol):</strong> GitLab, GitHub 등을 연결해 실전 데이터를 분석하는 기술.</li>
<li><strong>Markdown(.md):</strong> 모든 AI 통신과 문서의 표준 포맷으로 강력 추천.</li>
</ul>
<p>이제 여러분도 자신만의 <code>.md</code> 룰 파일을 만들고, AI 에이전트와 함께 협업하는 &#39;매니저&#39;로서의 첫발을 내디뎌 보세요!</p>
<h1 id="session-2-harness-engineering-진화하는-ai-설계">[Session 2] Harness Engineering: 진화하는 AI 설계</h1>
<p><code>이경록</code> <code>Teddy Lee</code>  <code>브레인크루</code> </p>
<blockquote>
<p>LLM 성능이 상향 평준화된 환경에서 모델보다 이를 제어하고 연결하는 Harness Engineering의 중요성을 짚고, 오케스트레이션･MCP 기반 도구 연결･상태･관리･안전장치 등 핵심 요소와 함께 Single Agent에서 Multi-Agent, Agentic Workflow, Agent Mesh로 확장되는 아키텍처와 실전 적용 포인트를 다루는 세션입니다.</p>
</blockquote>
<h3 id="-프롬프트-엔지니어링을-넘어-자가-진화-시스템으로-가는-길">: 프롬프트 엔지니어링을 넘어 &#39;자가 진화 시스템&#39;으로 가는 길</h3>
<p>최근 AI 모델은 하루가 다르게 업데이트되고 있습니다. 하지만 현업에서는 이런 의문이 끊이지 않습니다. <strong>&quot;모델은 좋아지는데, 왜 우리 서비스의 답변 품질은 그대로인가?&quot;</strong> 구글과 엔트로픽 등 글로벌 빅테크가 주목하는 해답은 모델 자체가 아니라, 모델을 둘러싼 운영 체계인 &#39;하네스(Harness)&#39;에 있습니다. 2만 명 이상의 임직원이 사용하는 대규모 시스템을 구축하며 얻은 인사이트를 공유합니다.</p>
<h2 id="1-ai-엔지니어링의-3단계-변천사">1. AI 엔지니어링의 3단계 변천사</h2>
<p>우리가 AI를 활용하는 방식은 크게 세 단계를 거쳐 진화해 왔습니다. 각 단계의 특징을 이해해야 왜 지금 &#39;하네스&#39;가 필요한지 알 수 있습니다.</p>
<table>
<thead>
<tr>
<th><strong>단계</strong></th>
<th><strong>핵심 개념</strong></th>
<th><strong>특징</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>1단계</strong></td>
<td><strong>프롬프트 엔지니어링</strong></td>
<td>&quot;AI는 죄가 없다, 못 쓰면 니 탓.&quot; 유저가 직접 프롬프트를 깎던 시절. 유저 영역이 가장 넓음.</td>
</tr>
<tr>
<td><strong>2단계</strong></td>
<td><strong>컨텍스트 엔지니어링</strong></td>
<td>RAG(검색 증강 생성), 메모리, 툴 콜링 도입. 시스템이 유저의 질문을 보강해 줌. 방식이 정적(Static)임.</td>
</tr>
<tr>
<td><strong>3단계</strong></td>
<td><strong>에이전트 하네스</strong></td>
<td><strong>자가 수정(Self-correction)</strong>과 <strong>피드백 루프</strong>의 도입. 비결정론적인 AI를 결정론적인 시스템으로 통제.</td>
</tr>
</tbody></table>
<h2 id="2-컨텍스트-엔지니어링의-한계와-하네스의-등장">2. &#39;컨텍스트 엔지니어링&#39;의 한계와 &#39;하네스&#39;의 등장</h2>
<p>기존의 RAG나 컨텍스트 엔지니어링은 &#39;정적&#39;이라는 치명적인 단점이 있습니다. 시스템 프롬프트와 툴이 고정되어 있어, 다양한 상황에 유연하게 대처하기 어렵습니다. 특히 롱 러닝 테스크(Long-running Task)에서 문제가 발생합니다.</p>
<aside>
💡

<p><strong>95%의 함정</strong> &gt; 단일 태스크 정확도가 95%인 모델도 이를 10번 반복하면 정확도가 60%로 급락합니다. 에러가 쌓이기 때문입니다. 이를 해결하기 위해 필요한 것이 바로 <strong>&#39;하네스 시스템&#39;</strong>입니다.</p>
</aside>

<h2 id="3-하네스-시스템의-핵심-피드백과-말랑말랑한-컨텍스트">3. 하네스 시스템의 핵심: &#39;피드백&#39;과 &#39;말랑말랑한 컨텍스트’</h2>
<p>하네스 이전의 시스템이 정보를 넣어주기만 하는 &#39;피드 비포워드(Feed-forward)&#39;였다면, 하네스는 결과물을 다시 시스템에 반영하는 &#39;피드백(Feed-back)&#39;이 핵심입니다.</p>
<hr>
<h3 id="동적-컨텍스트-관리-markdown-활용">동적 컨텍스트 관리 (Markdown 활용)</h3>
<p>하네스 시스템에서는 딱딱하게 굳어있던 시스템 프롬프트를 <strong>마크다운(Markdown)</strong> 형식의 스킬, 룰, 위키로 변환합니다.</p>
<ul>
<li><strong>이유:</strong> AI도 수정하기 쉽고, 사람도 읽기 쉽기 때문입니다.</li>
<li><strong>자가 수정:</strong> AI가 실수를 하면, 엔지니어가 답변을 고치는 대신 &#39;룰(Rule)&#39;이나 &#39;위키&#39;를 수정합니다. 그러면 다음부터는 같은 실수를 반복하지 않습니다.</li>
</ul>
<h3 id="에이전트-오케스트레이션-4인-체제">에이전트 오케스트레이션 (4인 체제)</h3>
<p>복잡한 일을 처리하기 위해 하네스는 역할을 나눕니다.</p>
<ol>
<li><strong>슈퍼바이저(Supervisor):</strong> 전체 진행 상황 관리 및 완성 여부 결정.</li>
<li><strong>스트래티지(Strategy):</strong> 실무적인 두뇌 역할, 전략 수립.</li>
<li><strong>옵저버(Observer):</strong> 상황 관측 및 리포트 작성.</li>
<li><strong>액터(Actor):</strong> 실제 키보드 조작이나 코드 실행.</li>
</ol>
<h2 id="4-실전-하네스-구축-시-주의할-점">4. 실전! 하네스 구축 시 주의할 점</h2>
<h3 id="프로그레시브-디스클로저-progressive-disclosure">프로그레시브 디스클로저 (Progressive Disclosure)</h3>
<p>100만 토큰을 쓸 수 있다고 해서 다 채우면 정확도가 떨어집니다.</p>
<ul>
<li><strong>해법:</strong> 필요한 순간에만 정보를 여는 &#39;온디맨드(On-demand)&#39; 방식을 쓰세요. 스킬이나 메모리를 처음부터 다 로드하지 말고, 필요할 때만 불러와야 합니다.</li>
</ul>
<h3 id="조직-차원의-스킬-관리">조직 차원의 스킬 관리</h3>
<p>개인의 노하우를 팀 전체의 생산성으로 바꾸려면 영역을 나누어야 합니다.</p>
<ul>
<li><strong>개인 영역:</strong> 나만의 작업 방식, 자유로운 스킬 사용.</li>
<li><strong>팀/부서 영역:</strong> 합의된 룰과 공통 스킬.</li>
<li><strong>회사 영역:</strong> 자산화된 지식 위키.</li>
</ul>
<h2 id="5-결론-하네스-엔지니어의-시대가-온다">5. 결론: 하네스 엔지니어의 시대가 온다</h2>
<p>앞으로 좋은 AI 모델은 누구나 돈만 내면 쓰는 &#39;상품&#39;이 될 것입니다. 진짜 차이는 그 모델을 어떻게 통제하고 운영하느냐, 즉 <strong>하네스를 얼마나 정교하게 설계하느냐</strong>에서 결정됩니다.</p>
<p><strong>지금 당장 시작할 리소스:</strong></p>
<ul>
<li><strong>마틴 파울러의 &#39;에이전트 하네스&#39; 블로그:</strong> 개발자라면 필독해야 할 아키텍처 가이드.</li>
<li><strong>MCP (Model Context Protocol):</strong> 모델과 툴 간의 표준화된 호출 규약.</li>
<li><strong>LLM 위키:</strong> 레슨런(Lesson Learned)을 지속적으로 축적하고 AI가 참조하게 만드는 시스템 구축.</li>
</ul>
<blockquote>
<p><strong>&quot;누구나 완벽한 계획을 가지고 있다, 실패하기 전까지는. 코딩도 마찬가지다. 실패했을 때 그 경험을 시스템(Rule)에 녹여내는 것이 하네스의 본질이다.&quot;</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] React Native 디버깅과 Navigation 흐름 정리]]></title>
            <link>https://velog.io/@hyphen_/WIL-React-Navigation-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@hyphen_/WIL-React-Navigation-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Fri, 27 Feb 2026 06:27:25 GMT</pubDate>
            <description><![CDATA[<p>이번 주 학습 주제는 크게 두 가지 였습니다.</p>
<ol>
<li>디버깅과 에러 유형 정리</li>
<li>React Navigation 세팅 및 주요 Navigator 이해</li>
</ol>
<p>완전히 새로운 개념을 배운 주차는 아니었습니다.
하지만 React Native에서 Navigation을 구현할 때 어떤 방식이 있는지, 그리고 이를 사용하기 위해 어떤 서드파티 라이브러리들이 필요한지 전체적으로 훑어보았다는 점에서 의미가 있었습니다.</p>
<h2 id="디버깅과-그-중요성">디버깅과 그 중요성</h2>
<p>개발을 하면서 느끼는 건,
기능 구현 속도 자체보다 문제 해결 능력과 해결 속도에서 실력 차이가 난다는 것입니다.</p>
<p>이 강의 파트는 새로운 개념을 배우기보다는,
디버깅 기본기를 다시 점검하는 시간에 가까웠습니다.</p>
<h3 id="잘못된-import로-인해-발생하는-문제">잘못된 import로 인해 발생하는 문제</h3>
<p>의외로 가장 많이 발생하는 문제입니다.</p>
<ul>
<li>잘못된 경로</li>
<li>export default / named export 혼동</li>
<li>존재하지 않는 파일 import</li>
</ul>
<p>초기 프로젝트 세팅 단계에서 alias 설정과 ESLint 규칙을 잘 정의해두면
위와 같은 실수는 웬만하면 방지할 수 있습니다.</p>
<p>다만 리팩토링 과정에서 파일 경로가 변경되었는데 import를 수정하지 못한 경우,
또는 불필요한 import가 남아있는 경우 빌드 오류로 이어질 수 있습니다.</p>
<p>현재는 배포 전 빌드 체크를 통해 사전 검증하는 방식으로 관리하고 있어,
이와 같은 이슈는 자주 발생하지 않습니다.</p>
<h3 id="useeffect와-관련된-에러">useEffect와 관련된 에러</h3>
<ol>
<li>Maximum update depth exceeded
무한 루프가 발생했을 때 나타나는 대표적인 에러입니다.<br/></li>
</ol>
<p><strong>대표적인 원인</strong>
    1. useEffect 안에서 setState 호출
    2. dependency 배열에 해당 state가 포함되어 있음 </p>
<pre><code>state 변경 -&gt; 재랜더 -&gt; useEffect 재실행 -&gt; 무한 반복</code></pre><br/>
    이 문제는 React를 어느 정도 사용해본 개발자라면 반드시 한 번은 겪어보는 이슈입니다.
<br/><br/>
    체크 포인트:

<pre><code>  - dependency 배열이 정확한가?
 - 불필요한 값이 포함되어 있지는 않은가?
 - 콜백을 useCallback으로 감싸야 하는 상황은 아닌가?</code></pre><br/>

<ol start="2">
<li><p>Unable to resolve module
 존재하지 않는 라이브러리를 import 했을 때 발생하는 에러입니다.</p>
</li>
<li><p>Each child in a list should have a unique key prop
 배열을 순회하며 렌더링할 때 key prop을 지정하지 않으면 발생하는 경고입니다.</p>
<p> 최근 테이블 페이지네이션을 구현하면서 실제로 겪은 이슈가 있었습니다.</p>
<p> row마다 id값을 <code>index</code> 기준으로 설정했는데,
 페이지를 뒤로 갔다가 다시 앞으로 돌아올 때
 마지막에 있어야 할 요소가 맨 위로 올라오는 현상이 발생했습니다.</p>
<p> 원인은 다음과 같았습니다:</p>
<ul>
<li><p>페이지가 변경되면서 index 값이 재계산됨</p>
</li>
<li><p>기존 렌더링 구조와 key 값이 충돌</p>
</li>
<li><p>React가 기존 요소를 재사용하면서 DOM 구조가 의도와 다르게 유지됨</p>
<p>해결 방법은 <code>index</code>와 <code>현재 페이지 번호</code>를 조합하여 고유한 key를 생성하는 것이었습니다.</p>
<blockquote>
<p>이 경험을 통해 느낀 점은 index를 key로 사용하는 것은 최후의 수단이어야 한다는 것 입니다.</p>
</blockquote>
<p>가능하다면 항상 <strong>고유 식별자(id)</strong>를 사용하는 것이 안전합니다.</p>
</li>
</ul>
</li>
</ol>
<br/>

<h3 id="논리적-오류-logical-error">논리적 오류 (Logical Error)</h3>
<p>논리적 오류는 터미널에 에러로 표시되지 않습니다.</p>
<p>코드는 문법적으로 맞습니다.
하지만 기대한 결과가 나오지 않습니다.</p>
<p>이 경우를 논리적 오류라고 볼 수 있습니다.</p>
<h4 id="해결-접근-방식">해결 접근 방식</h4>
<ol>
<li>console.log 적극 활용<ul>
<li>값이 언제 바뀌는지</li>
<li>어디까지 실행되는지</li>
<li>조건문이 통과하는지</li>
</ul>
</li>
<li>코드 부분 주석 처리<ul>
<li>특정 컴포넌트를 통째로 주석 처리</li>
<li>실행 후 이상 동작이 사라졌는지 확인</li>
<li>사라졌다면 해당 컴포넌트가 원인</li>
<li>그 안에서 점점 범위를 줄여가며 원인 탐색</li>
</ul>
</li>
</ol>
<br/>

<h2 id="react-navigation">React Navigation</h2>
<p>이제 본격적으로 Navigation 세팅을 진행했습니다.</p>
<p>React Native에서 화면 전환은 거의 필수이고,
사실상 표준 라이브러리는 <strong>React Navigation</strong>인 것 같습니다.</p>
<p>설치는 <a href="https://reactnavigation.org/docs/getting-started">공식 문서</a>를 참고하여 진행했습니다.</p>
<h3 id="stack-navigation">Stack Navigation</h3>
<p><a href="https://reactnavigation.org/docs/stack-navigator/">Stack Navigation</a>은 페이지를 위로 차곡차곡 쌓는 구조입니다.
기본적으로는</p>
<ul>
<li>push</li>
<li>pop</li>
</ul>
<p>동작을 지원하고, 새로운 화면을 push하면 스택의 맨 위에 배치 됩니다.</p>
<blockquote>
<p>Tip: navigation 폴더를 분리하여 <code>routes.ts</code> 파일에 라우트 경로를 상수로 관리하면 유지보수가 수월합니다. </p>
</blockquote>
<h3 id="drawer-navigation">Drawer Navigation</h3>
<p><a href="https://reactnavigation.org/docs/drawer-navigator/">Drawer Navigator</a>는 좌측에서 슬라이드되는 메뉴를 제공합니다.</p>
<ul>
<li>기능이 많은 앱</li>
<li>카테고리가 많은 앱</li>
<li>설정 / 프로필 / 기타 화면 분리</li>
</ul>
<p>에 주로 사용됩니다.</p>
<h3 id="material-top-tabs-navigator">Material Top Tabs Navigator</h3>
<p><a href="https://reactnavigation.org/docs/material-top-tab-navigator/">Material Top Tabs Navigator</a>는 상단 탭 형태의 네비게이션입니다.</p>
<ul>
<li>카테고리 전환</li>
<li>콘텐츠 필터링</li>
<li>스와이프 기반 UX</li>
</ul>
<p>에서 주로 사용됩니다.</p>
<h3 id="bottom-tabs-navigator">Bottom Tabs Navigator</h3>
<p><a href="https://reactnavigation.org/docs/bottom-tab-navigator">Bottom Tabs Navigator</a>는 하단 탭 형태의 네비게이션입니다.</p>
<ul>
<li>주요 기능 간 빠른 이동</li>
<li>앱의 핵심 섹션을 상시 노출</li>
</ul>
<h2 id="마무리">마무리</h2>
<p>이번 주차는 난이도 자체는 높지 않았습니다.
React 경험이 있다면 충분히 알고 있는 내용입니다.</p>
<p>하지만 앱 개발에서는 화면이 많아질수록 <strong>구조 설계 능력</strong>이 중요해집니다.</p>
<ul>
<li>Stack, Drawer, Tabs를 언제 어떻게 조합할 것인가</li>
<li>Stack이 깊어질 때 구조를 어떻게 관리할 것인가</li>
<li>Navigation 흐름을 얼마나 명확히 이해하고 있는가</li>
</ul>
<p>이 부분이 단순 구현 능력보다 더 중요해진다고 생각합니다.</p>
<p>다음 단계는 배포 과정을 진행하려고 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] React Native 개발의 기본: Core Components]]></title>
            <link>https://velog.io/@hyphen_/WIL-React-Native-Core-Component%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C</link>
            <guid>https://velog.io/@hyphen_/WIL-React-Native-Core-Component%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C</guid>
            <pubDate>Tue, 10 Feb 2026 07:38:31 GMT</pubDate>
            <description><![CDATA[<p>2주차 React Native CLI 스터디 주제는 <strong>Core Components</strong>였습니다. 
강의 내용을 기반으로 학습하되, 부족하다고 느낀 부분은 React Native <strong>공식 문서</strong>를 참고하여 보완하며 정리하였습니다.</p>
<p>React Native에서 사용하는 컴포넌트 구조 자체는 웹(React)과 크게 다르지 않았지만,
<strong>HTML 태그 대신 전용 컴포넌트를 사용</strong>하고 <strong>style 및 props 네이밍이 다르기 때문에 한 번씩 보고 넘어가는 것이 유익</strong>하다고 느꼈습니다.</p>
<h2 id="react--react-native-basic">React &amp; React Native Basic</h2>
<h3 id="custom-components">Custom Components</h3>
<p>React Native에서도 React와 동일하게 컴포넌트를 분리하여 재사용할 수 있습니다.
다만 HTML 태그 대신 <code>View</code>, <code>Text</code>, <code>Image</code>와 같은 Core Component를 사용합니다.</p>
<pre><code class="language-tsx">const MyComponent = () =&gt; {
  return (
    &lt;View&gt;
      &lt;Text&gt;Hello React Native&lt;/Text&gt;
    &lt;/View&gt;
  );
};</code></pre>
<p>웹에서는 부모 태그만 존재하면 <code>&lt;p&gt;</code>, <code>&lt;h1&gt;</code> 같은 텍스트 전용 태그를 사용하지 않아도 문자열을 바로 렌더링할 수 있습니다.
반면 React Native에서는 모든 텍스트 노드가 반드시 <code>&lt;Text&gt;</code> 컴포넌트로 감싸져 있어야 하며, 그렇지 않으면 오류가 발생합니다. <a href="https://reactnative.dev/docs/text?utm_source=chatgpt.com#limited-style-inheritance">공식문서 참고</a></p>
<br />

<h3 id="style-sheets">Style Sheets</h3>
<p>React Native에서는 CSS 파일을 사용하지 않고,
JavaScript 객체 기반의 스타일 시스템을 사용합니다.</p>
<pre><code class="language-typescript">const style = StyleSheet.create({
    text: { fontSize: 20, color: &#39;red&#39; },
});

const MyText = () =&gt; {
    return &lt;Text style={style.text}&gt;Hello World!&lt;/Text&gt;;
}</code></pre>
<p><strong>StyleSheet를 사용하는 이유</strong> </p>
<ul>
<li>스타일 객체를 미리 생성하여 성능 최적화</li>
<li>오타나 잘못된 속성 사용 시 개발 단계에서 경고</li>
<li>스타일 관리의 일관성 확보</li>
</ul>
<br />

<h3 id="view-component">View Component</h3>
<p><code>View</code>는 React Native에서 가장 기본적인 <strong>레이아웃 컨테이너</strong>입니다.
웹의 <code>div</code>와 유사한 역할을 하지만, 레이아웃은 기본적으로 Flexbox 기반으로 동작합니다.</p>
<pre><code>&lt;View&gt;
  &lt;Text&gt;Hello World!!&lt;/Text&gt;
&lt;/View&gt;</code></pre><ul>
<li>화면 레이아웃 구성</li>
<li>다른 컴포넌트를 감싸는 용도</li>
<li>자체적으로는 UI를 표현하지 않음</li>
</ul>
<br/>

<h3 id="props">Props</h3>
<p>React Native 컴포넌트도 React와 동일하게 props를 통해 데이터를 전달합니다.</p>
<pre><code class="language-tsx">const Greeting = ({ name }: { name: string }) =&gt; {
  return &lt;Text&gt;Hello {name}&lt;/Text&gt;;
};</code></pre>
<br/>


<h3 id="event-handling">Event Handling</h3>
<p>React Native에서는 터치 기반 이벤트를 사용합니다.</p>
<ul>
<li>onPress: 가장 기본적인 클릭(터치) 이벤트</li>
<li>웹의 onClick과 유사하지만, 마우스 이벤트는 존재하지 않음</li>
</ul>
<pre><code class="language-tsx">&lt;Pressable onPress={() =&gt; console.log(&#39;pressed&#39;)}&gt;
  &lt;Text&gt;Press me&lt;/Text&gt;
&lt;/Pressable&gt;</code></pre>
<p>이 외에도 텍스트 입력, 스와이프, 롱프레스 등
모바일 환경에 특화된 다양한 인터랙션 이벤트가 존재합니다.</p>
<br/>

<h2 id="basic-components">Basic Components</h2>
<h3 id="image-component">Image Component</h3>
<pre><code class="language-tsx">&lt;Image  
    source={required(&#39;./cake.png&#39;)} 
    style={{ width: 100, heigth: 100 }}
    resizeMode={&#39;contain&#39;}
/&gt;</code></pre>
<blockquote>
<p>⚠️ width, height는 반드시 명시해야 합니다.</p>
</blockquote>
<ul>
<li><strong>resizeMode</strong><ul>
<li><code>contain</code>: 이미지 비율을 유지한 채 컨테이너 안에 전부 보이도록 맞춤</li>
<li><code>stretch</code>: 이미지 비율을 무시하고 컨테이너 크기에 강제로 늘림</li>
<li><code>repeat</code>: 이미지를 원래 크기 그대로 컨테이너가 찰 때까지 반복해서 타일링</li>
<li><code>center</code>: 이미지 비율을 유지하며 확대/축소 없이 컨테이너 정중앙에 배치</li>
</ul>
</li>
</ul>
<br/>

<h3 id="text-input-component">Text Input Component</h3>
<pre><code class="language-tsx">const [textValue, setTextValue] = useState(&#39;&#39;);

return (
    &lt;TextInput 
        style={{ borderWidth: 1, padding: 10, borderRadius: 4 }} 
        value={textValue}
        onChangeText={(value) =&gt; setTextValue(value)}
        autoFocus={true}
        placeholder={&#39;Please enter your name&#39;}
    /&gt;
)</code></pre>
<p><strong>주요 Props</strong></p>
<ul>
<li><code>autoFocus</code>: 화면이 로드되면 자동으로 포커스된다.</li>
<li><code>secureTextEntry</code>: 입력 값을 보이지 않게 한다. (ex. 비밀번호)</li>
<li><code>keyboardType</code><ul>
<li><code>default</code></li>
<li><code>numeric</code>: 숫자 키보드, <code>.</code></li>
<li><code>phone-pad</code>: 숫자 키보드, <code>+</code>, <code>*</code>, <code>#</code></li>
<li><code>email-address</code>: 이메일 입력 키보드</li>
</ul>
</li>
<li><code>returnKeyType</code>: 키보드에서 완료되는 버튼의 텍스트를 변경할 수 있다.<ul>
<li><code>done</code>: 입력이 끝났을 때 (완료)</li>
<li><code>go</code>: 화면을 이동할 때</li>
<li><code>next</code>: 다음 입력으로 넘어갈때</li>
<li><code>search</code>: 검색할 때</li>
</ul>
</li>
</ul>
<br/>

<h3 id="scrollview-component">ScrollView Component</h3>
<pre><code class="language-tsx">&lt;ScrollView 
    onScroll={() =&gt; {}}
    contentContainerStyle={{ backgroundColor: &#39;red&#39;, height: 600 }}
    showsVerticalScrollIndicator={false} 
    horizontal={true}
    showsHorizontalScrollIndicator={false}
&gt;
    {/* contents */}
&lt;/ScrollView&gt;</code></pre>
<ul>
<li>모든 자식을 한 번에 렌더링</li>
<li>데이터가 많아질 경우 성능 저하 발생 가능</li>
</ul>
<br/>

<h3 id="button-component--form-submission">Button Component &amp; Form Submission</h3>
<p>React Native의 기본 Button은 커스터마이징이 어렵기 때문에
실무에서는 주로 Pressable 또는 TouchableOpacity를 사용합니다.</p>
<pre><code class="language-tsx">&lt;Pressable disabled={false} onPress={() =&gt; {}}&gt;
    &lt;Text&gt;Submit&lt;/Text&gt;
&lt;/Pressable&gt;</code></pre>
<br/>

<h3 id="switch-component">Switch Component</h3>
<pre><code class="language-tsx">const [shouldKeepLoggedIn, setShouldKeepLoggedIn] = useState(true);

return (
    &lt;View&gt;
        &lt;Switch 
            value={shouldKeepLoggedIn} 
            onValueChange={value =&gt; setShouldKeepLoggedIn(value)}
        /&gt;
        &lt;Text&gt;Keep me logged in&lt;/Text&gt;
    &lt;/View&gt;
);</code></pre>
<br/>

<h3 id="deep-dive-in-flexbox">Deep Dive in Flexbox</h3>
<pre><code class="language-tsx">&lt;View style={{ flex: 1, backgroundColor: &#39;red&#39; }}&gt;
    &lt;View style={{ flex: 1, backgroundColor: &#39;yellow&#39; }} /&gt;
    &lt;View style={{ flex: 2, backgroundColor: &#39;geeen&#39; }} /&gt;
    &lt;View style={{ flex: 3, backgroundColor: &#39;black&#39; }} /&gt;
&lt;/View&gt;</code></pre>
<p>웹에서의 <code>flex</code> 속성 사용 방법과 크게 다르지 않았습니다.</p>
<br/>


<h2 id="그외-정리">그외 정리</h2>
<h3 id="flatlist">FlatList</h3>
<ul>
<li>대량의 리스트 렌더링을 위한 컴포넌트</li>
<li>가상화(Virtualization) 적용</li>
<li>ScrollView와 함께 사용 ❌</li>
</ul>
<pre><code class="language-tsx">&lt;FlatList
  data={data}
  renderItem={({ item }) =&gt; &lt;Text&gt;{item.title}&lt;/Text&gt;}
  keyExtractor={(item) =&gt; item.id}
/&gt;</code></pre>
<h3 id="image-렌더링에-대한-고민">Image 렌더링에 대한 고민</h3>
<p>React Native에서는 이미지의 크기를 명시하지 않으면 렌더링되지 않습니다.
인스타그램 사이드 프로젝트를 진행하면서
다양한 비율의 이미지를 어떻게 효율적으로 렌더링할 수 있을지에 대해 고민하게 되었습니다.</p>
<ul>
<li>고정된 비율 틀을 기반으로 렌더링 (인스타그램 서비스도 이처럼 사용하는 것 같습니다.)</li>
<li>실제 서비스에서도 동적 이미지 계산은 성능 이슈로 인해 제한적으로 사용</li>
<li>React Native에서도 동적 계산은 가능하지만, 성능 저하가 명확히 체감됨</li>
</ul>
<h3 id="patch-package">patch package</h3>
<p>React Native에서 서드파티 라이브러리의 native 코드 수정이 필요한 경우가 종종 발생합니다.</p>
<ul>
<li>node_modules 직접 수정 ❌</li>
<li>patch-package 사용 ⭕</li>
</ul>
<pre><code>npx patch-package package-name</code></pre><ul>
<li>수정 사항을 patch 파일로 관리</li>
<li>npm install 이후에도 유지됨</li>
<li>React Native에서 특히 중요한 도구</li>
</ul>
<blockquote>
<p>React 웹 환경에서는 드물지만,
React Native에서는 실무에서 자주 마주치는 문제를 해결해주는 한 줄기의 빛 같은 존재라고 느꼈습니다.</p>
</blockquote>
<h2 id="마무리">마무리</h2>
<p>이번 주차에서는 React Native의 기본이 되는 Core Component를 전반적으로 정리할 수 있었습니다.
웹 React와 비슷한 구조 덕분에 진입 장벽은 낮았지만,
모바일 환경에 맞는 컴포넌트와 제약 사항을 이해하는 것이 중요하다는 점을 다시 한 번 느꼈습니다.</p>
<p>다음 주차에는 Social Media Application을 구현하려고 합니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] React Native CLI 개발 환경 세팅과 프로젝트 생성 해보기]]></title>
            <link>https://velog.io/@hyphen_/WIL-React-Native-CLI-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85%EA%B3%BC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1-%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@hyphen_/WIL-React-Native-CLI-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85%EA%B3%BC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1-%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 24 Jan 2026 07:42:27 GMT</pubDate>
            <description><![CDATA[<p>이번 주부터 <strong>React Native CLI 스터디</strong>를 시작했습니다.<br>학습은 <strong>Udemy 강의</strong>를 기반으로 진행하고 있으며, React Native CLI 강좌 자체가 많지 않아 선택지가 제한적이었던 점도 있었습니다.</p>
<p>이번 주차에서는 주로 <strong>개발 환경 세팅</strong>과 <strong>React Native CLI 프로젝트 생성 및 실행</strong>에 대해 다루었습니다.<br>이미 관련 경험이 있어 환경 세팅과 기본 실행 과정은 비교적 빠르게 수강할 수 있었습니다.</p>
<blockquote>
<p style="font-weight: 700">🔗 Udemy React Native CLI </p>
<span style="color: gray">첫 구매시 80% 할인 된 가격에 구매할 수 있었습니다.</span>
<a>https://www.udemy.com/course/react-native-mobile-app-development/</a>
</blockquote>
<blockquote>
<p style="font-weight: 700"> 🔗 Udemy 한글 자막 - Google, DeepL 번역 </p>
<span style="color: gray">강의 내에서 사용되는 자막 보다 더 좋은 번역본을 얻을 수 있었습니다.</span>
<a>https://chromewebstore.google.com/detail/deobfkgpcfnaelcfeiihpahnejpakgjj?utm_source=item-share-cb</a>
</blockquote>
<br/>

<h2 id="react-native란-무엇인가">React Native란 무엇인가?</h2>
<p><strong>React Native</strong>는 React를 기반으로 한 <strong>크로스 플랫폼 모바일 애플리케이션 프레임워크</strong>입니다.<br>하나의 JavaScript 코드베이스로 <strong>Android와 iOS 앱을 동시에 개발</strong>할 수 있도록 도와줍니다.</p>
<blockquote>
<p style="font-weight: 700">크로스 플랫폼이란?<p/>
<span>하나의 소스 코드나 개발 방식으로 여러 운영체제(OS)나 기기에서 동작하는 소프트웨어를 만드는 것을 의미합니다.</span>
</blockquote>
<h3 id="주요-장점">주요 장점</h3>
<ul>
<li><p><strong>React 기반</strong></p>
<ul>
<li>기존 React 및 JavaScript 지식을 그대로 활용할 수 있어 학습 비용이 낮습니다.</li>
<li>새로운 언어를 배우지 않아도 되므로 개발 속도가 빠릅니다.</li>
</ul>
</li>
<li><p><strong>높은 생산성</strong></p>
<ul>
<li>공통 코드 기반으로 Android / iOS 개발을 동시에 진행할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>네이티브 컴포넌트 사용</strong></p>
<ul>
<li>실제 네이티브 UI 컴포넌트를 사용하여 성능과 사용자 경험을 유지할 수 있습니다.</li>
</ul>
</li>
</ul>
<br/>

<h2 id="expo와-react-native-cli의-차이">Expo와 React Native CLI의 차이</h2>
<p>React Native로 앱을 시작할 때 크게 <strong>Expo</strong>와 <strong>React Native CLI</strong> 두 가지 방식 중 하나를 선택하게 됩니다.</p>
<h3 id="expo">Expo</h3>
<ul>
<li>React Native를 위해 설계된 <strong>사용자 친화적인 프레임워크</strong></li>
<li>초기 설정이 매우 간단함</li>
<li>사전 구성된 API와 라이브러리 제공</li>
<li>네이티브 코드를 직접 다루지 않고도 빠르게 앱 개발 가능</li>
</ul>
<p>=&gt; <strong>프로토타이핑, MVP, 개인 프로젝트에 적합</strong></p>
<h3 id="react-native-cli">React Native CLI</h3>
<ul>
<li>React Native의 공식 <strong>명령줄 인터페이스(CLI)</strong></li>
<li>네이티브 코드(Android / iOS) 직접 수정 가능</li>
<li>초기 환경 세팅이 상대적으로 복잡하고 시간이 오래 걸림</li>
<li>프로젝트 구조와 설정을 완전히 제어할 수 있음</li>
</ul>
<p>=&gt; <strong>장기 프로젝트, 성능 최적화, 커스터마이징이 필요한 서비스에 적합</strong></p>
<br/>

<h2 id="개발-환경-세팅">개발 환경 세팅</h2>
<p>운영체제에 따라 개발 환경 설정 방식이 다르며,<br>저는 <strong>macOS 환경</strong>에서 작업을 진행했습니다.</p>
<blockquote>
<p>iOS 앱 개발을 위해서는 <strong>Mac + Xcode</strong>가 필수입니다.</p>
</blockquote>
<h3 id="필수-설치-항목">필수 설치 항목</h3>
<ul>
<li>Homebrew</li>
<li>Xcode (iOS)</li>
<li>Android Studio (Android)</li>
<li>Node.js</li>
<li>CocoaPods (iOS)</li>
<li>Java (Android)</li>
</ul>
<br/>
설치가 완료된 후 아래 명령어로 환경이 정상적으로 준비되었는지 확인할 수 있습니다.

<pre><code class="language-bash">npx react-native doctor</code></pre>
<h2 id="프로젝트-생성-및-실행">프로젝트 생성 및 실행</h2>
<h3 id="프로젝트-생성">프로젝트 생성</h3>
<pre><code class="language-bash"># 프로젝트 생성 명령어
npx @react-native-community/cli init [프로젝트명]</code></pre>
<h3 id="안정-버전">안정 버전</h3>
<p>25년 6월 기준으로 안정 버전은 <strong>v0.80.0</strong>을 사용하길 React Native에서 권장하고 있습니다.<br>현재는 <strong>v0.83.0</strong> 버전이 최신 안정화 버전이지만, React Native는 릴리즈 주기가 빠르고 네이티브 의존성이 강하기 때문에 <code>latest</code>가 항상 가장 안전한 선택은 아닙니다.</p>
<p>특히 React Native는 iOS, Android, 그리고 각종 네이티브 모듈과 강하게 연결되어 있어 주요 라이브러리들이 최신 버전을 완전히 지원하기까지 일정 시간이 필요합니다.<br>따라서 실무에서는 단순히 최신 버전을 선택하기보다, <strong>공식적으로 Active 상태이며 생태계 전반에서 충분히 검증된 버전</strong>을 기준으로 사용하는 것이 일반적입니다. </p>
<p>Metro Bundler 터미널에서 J 키를 누르면 React Native DevTools가 바로 열립니다.</p>
<h3 id="react-native-화면-설계-tip">React Native 화면 설계 Tip</h3>
<ul>
<li>Provider 내부를 SafeAreaView로 감싸는 것을 권장<ul>
<li>Status Bar</li>
<li>iOS 노치 / 다이나믹 아일랜드</li>
</ul>
</li>
</ul>
<p>등을 침범하지 않고, 모든 디바이스에서 안전한 UI를 구성할 수 있습니다.</p>
<h2 id="마무리">마무리</h2>
<p>이번 주차 강의는 분량 자체는 짧았지만,
React Native CLI의 기본 개념과 개발 방식에 대해 정리할 수 있는 시간이었습니다.</p>
<p>특히 CLI 환경에서만 가능한 네이티브 커스터마이징과
장기 프로젝트 관점에서의 장점을 다시 한 번 체감할 수 있었습니다.</p>
<h3 id="이번-주-학습-내용-정리">이번 주 학습 내용 정리</h3>
<ul>
<li>React Native란 무엇인가</li>
<li>Expo와 React Native CLI의 차이</li>
<li>개발 환경 세팅</li>
<li>CLI 프로젝트 생성 및 실행</li>
</ul>
<p>다음 주차에는 Component와 기본 UI 구성에 대해 학습할 예정입니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RN] 인스타그램 클론,  피드 스크롤 구현기]]></title>
            <link>https://velog.io/@hyphen_/RN-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0-Step-1-%ED%94%BC%EB%93%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EA%B5%AC%ED%98%84%EA%B8%B0</link>
            <guid>https://velog.io/@hyphen_/RN-%EC%9D%B8%EC%8A%A4%ED%83%80%EA%B7%B8%EB%9E%A8-%ED%81%B4%EB%A1%A0-Step-1-%ED%94%BC%EB%93%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EA%B5%AC%ED%98%84%EA%B8%B0</guid>
            <pubDate>Wed, 07 Jan 2026 06:11:13 GMT</pubDate>
            <description><![CDATA[<p>이번 스텝에서는 Mock API 서버를 구성하고, 이를 기반으로 인스타그램 피드 리스트를 세로 스크롤로 출력하는 작업을 진행했습니다.
아직 좋아요, 댓글 등의 인터랙션 기능은 구현하지 않았고, 레이아웃과 리스트 렌더링에 집중한 단계입니다.!</p>
<h2 id="1-인스타그램-피드-화면-레이아웃-구현">1. 인스타그램 피드 화면 레이아웃 구현</h2>
<p>
<Image src="https://velog.velcdn.com/images/hyphen_/post/eb228c12-2817-4c67-ad52-a2a3a121bb09/image.png" width="250" />
</p>

<h3 id="1-1-레이아웃-참고-방식">1-1. 레이아웃 참고 방식</h3>
<p>처음에는 웹 인스타그램의 모바일 뷰어를 기준으로 레이아웃을 구현하려 했습니다.
하지만 실제 모바일 앱과 웹 모바일 뷰의 UI 차이가 생각보다 커서,
실제 휴대폰에서 인스타그램 피드를 캡처하여 이를 기준으로 레이아웃을 구성하였습니다.</p>
<h3 id="1-2-navigation-구조">1-2. Navigation 구조</h3>
<p>이전 단계에서 학습했던 <code>TabNavigation</code>을 활용해 Bottom Navigation을 구현했습니다.
인스타 그램을 그대로 따라하지 않고 핵심 기능을 토대로 정리했어요.</p>
<ul>
<li>Header<ul>
<li>인스타그램 로고</li>
<li>주요 액션 버튼 일부만 선택적으로 구현</li>
</ul>
</li>
<li>사용 빈도가 낮은 액션 아이콘 제외</li>
<li>로고 타이포그래피는 추후 이미지 리소스로 교체 예정</li>
</ul>
<br/>

<h2 id="2-라이트다크-모드-테마-설계">2. 라이트/다크 모드 테마 설계</h2>
<p>인스타그램의 라이트/다크 모드를 구현하기 위해
컬러셋을 config 형태로 분리하고, 전역에서 관리하도록 구성했습니다.</p>
<h3 id="2-2-컬러셋-정의">2-2. 컬러셋 정의</h3>
<pre><code class="language-typescript">// colors.ts
export const colors = {
  background: &#39;#FAFAFA&#39;,
  textPrimary: &#39;#1C1C1E&#39;,
  textSecondary: &#39;#6E6E73&#39;,
  divider: &#39;#E5E5EA&#39;,
  primary: &#39;#007AFF&#39;,
};

export const darkColors = {
  background: &#39;#0F0F10&#39;,
  surface: &#39;#1A1A1D&#39;,
  textPrimary: &#39;#FAFAFA&#39;,
  textSecondary: &#39;#B3B3B3&#39;,
  divider: &#39;#2A2A2E&#39;,
  primary: &#39;#007AFF&#39;,
};

export type ThemeColors = typeof colors;
</code></pre>
<p>현 단계에서는 자주 사용하는 컬러만 최소한으로 정의했습니다.</p>
<h3 id="2-2-themecontext-구현">2-2. ThemeContext 구현</h3>
<p>전역 테마 상태 관리를 위해 <code>ThemeContext</code>를 작성했습니다.</p>
<pre><code class="language-typescript">import { createContext, useContext, useMemo, useState } from &#39;react&#39;;
import { colors, darkColors } from &#39;../shared/style/colors&#39;;

type ThemeContextType = {
  isDark: boolean;
  theme: typeof colors | typeof darkColors;
  toggleTheme: () =&gt; void;
};

const ThemeContext = createContext&lt;ThemeContextType&gt;({
  isDark: false,
  theme: colors,
  toggleTheme: () =&gt; {},
});

export const useTheme = () =&gt; useContext(ThemeContext);

export const ThemeProvider = ({ children }: { children: React.ReactNode }) =&gt; {
  const [isDark, setIsDark] = useState(false);

  const toggleTheme = () =&gt; setIsDark(prev =&gt; !prev);

  const theme = useMemo(() =&gt; (isDark ? darkColors : colors), [isDark]);

  return (
    &lt;ThemeContext.Provider value={{ isDark, theme, toggleTheme }}&gt;
      {children}
    &lt;/ThemeContext.Provider&gt;
  );
};
</code></pre>
<h3 id="2-3-app-전체에-theme-적용">2-3. App 전체에 Theme 적용</h3>
<pre><code class="language-typescript">import { QueryClient, QueryClientProvider } from &#39;@tanstack/react-query&#39;;
import { NavigationContainer } from &#39;@react-navigation/native&#39;;
import TabNavigation from &#39;./src/navigation/TabNavigation&#39;;
import { ThemeProvider } from &#39;./src/context/ThemeContext&#39;;

const queryClient = new QueryClient();

export default function App() {
  return (
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;ThemeProvider&gt;
        &lt;NavigationContainer&gt;
          &lt;TabNavigation /&gt;
        &lt;/NavigationContainer&gt;
      &lt;/ThemeProvider&gt;
    &lt;/QueryClientProvider&gt;
  );
}
</code></pre>
<h3 id="2-4-테마-토글-ui">2-4. 테마 토글 UI</h3>
<pre><code class="language-typesciprt">import React from &#39;react&#39;;
import { View, Text, Switch } from &#39;react-native&#39;;
import { useTheme } from &#39;../context/ThemeContext&#39;;

const ThemeToggle = () =&gt; {
  const { isDark, toggleTheme } = useTheme();
  const textColor = isDark ? &#39;#fff&#39; : &#39;#222&#39;;

  return (
    &lt;View style={{ flexDirection: &#39;row&#39;, alignItems: &#39;center&#39; }}&gt;
      &lt;Text style={{ color: textColor, marginRight: 8 }}&gt;
        {isDark ? &#39;다크 모드&#39; : &#39;화이트 모드&#39;}
      &lt;/Text&gt;
      &lt;Switch value={isDark} onValueChange={toggleTheme} /&gt;
    &lt;/View&gt;
  );
};

export default ThemeToggle;</code></pre>
<p><strong>이후 개선 예정</strong></p>
<ul>
<li>AsyncStorage에 테마 저장</li>
<li>시스템 테마(Appearance) 기반 초기값 적용</li>
</ul>
<h2 id="3-postman을-활용한-mock-api-서버-구성">3. Postman을 활용한 Mock API 서버 구성</h2>
<p>피드 리스트 구현을 위해 <strong>Postman Mock Server</strong>를 사용했습니다.</p>
<ul>
<li>무료 플랜 기준<ul>
<li>월 1,000회 호출</li>
<li>GET 요청 위주로 간단한 테스트에 적합</li>
</ul>
</li>
<li>POST / 상태 변경 로직에는 한계가 있음</li>
</ul>
<h3 id="3-1-collection-생성">3-1. Collection 생성</h3>
<p><img src="https://velog.velcdn.com/images/hyphen_/post/36135785-4767-47f6-9b81-86a006eff723/image.png" alt=""></p>
<p>Postman의 워크스페이스 옆 New 버튼을 클릭하여 Collection을 추가합니다. </p>
<h3 id="3-2-mock-server-생성">3-2. Mock Server 생성</h3>
<p>다음으로 왼쪽 사이드바에서 Mock servers를 클릭한 다음 Plus 버튼으로 새로운 mock server를 추가해줍니다. 
<img src="https://velog.velcdn.com/images/hyphen_/post/b48611b5-8251-40df-aa9c-176900f26448/image.png" alt="">
혼선을 방지하고자 Mock server와 Collection 이름을 통일하였습니다. Collection 옵션에 방금 만들었던 Collection을 꼭 등록해줍니다.</p>
<p>그렇게 만들어진 mock server의 도메인 주소를 복사하여 생성한 Collection으로 돌아갑니다.</p>
<p><img src="https://velog.velcdn.com/images/hyphen_/post/e7b50ba5-db6d-4e84-9674-d48584de6caa/image.png" alt=""></p>
<p>variables탭에서 url 변수를 추가합니다. 빨간 테두리에 복사한 도메인 주소를 넣어줍니다.</p>
<h3 id="3-4-feed-api-추가">3-4. Feed API 추가<img src="https://velog.velcdn.com/images/hyphen_/post/d1c4e02f-4641-42af-8124-8d5686a43e90/image.png" alt=""></h3>
<p>마지막 단계입니다. collection에서 Plus 버튼으로 HTTP 요청을 추가합니다.</p>
<p><img src="https://velog.velcdn.com/images/hyphen_/post/eb1522c3-5469-424f-a1b7-4922dc2aad51/image.png" alt="">
추가된 HTTP는 GET 요청이 default입니다. 경로를 <code>{{url}}/feeds</code>로 설정해줍니다. 이때 url에는 방금 설정한 api 도메인 주소가 할당되게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/hyphen_/post/5f41bf5b-c0d1-4988-a13e-44095e711777/image.png" alt="">
생성된 HTTP에서 Example을 추가하여 반환받고 싶은 Reponse를 JSON 형태로 Body &gt; Raw에 작성해 줍니다. 간단한 mock 데이터는 chatgpt를 활용하여 리스트를 만들어 주었습니다. 이미지는 매번 랜덤으로 생성해주는 <a href="https://picsum.photos/">Lorem Picsum</a>을 활용하였습니다.</p>
<pre><code class="language-bash"># 참고용 데이터 구조
{
    &quot;id&quot;: 1,
    &quot;user&quot;: {
        &quot;id&quot;: 101,
        &quot;username&quot;: &quot;una&quot;,
        &quot;profileImage&quot;: &quot;https://picsum.photos/50/50?random=1&quot;
    },
    &quot;images&quot;: [
           &quot;https://picsum.photos/500/200?random=11&quot;,
           &quot;https://picsum.photos/500/500?random=12&quot;
       ],
       &quot;caption&quot;: &quot;인스타 클론 코딩 중 📱🔥&quot;,
    &quot;likesCount&quot;: 142397401,
    &quot;sharedCount&quot;: 502440,
    &quot;isLiked&quot;: true,
    &quot;comments&quot;: [
        { &quot;id&quot;: 1, &quot;username&quot;: &quot;frontend_lover&quot;, &quot;text&quot;: &quot;와 퀄리티 대박이에요!&quot; },
        { &quot;id&quot;: 2, &quot;username&quot;: &quot;react_master&quot;, &quot;text&quot;: &quot;이거 리액트로 만드신 거죠?&quot; }
    ],
    &quot;createdAt&quot;: &quot;2026-01-04T10:30:00Z&quot;
},</code></pre>
<p>Postman으로 모든 기능을 mock API로 처리하다 보니 요청 횟수 제한에 자주 걸리고, POST 요청 등은 API를 생성하는 과정 자체가 다소 복잡하다는 한계가 있었습니다. 또한 요청 횟수 초기화 시점이 매월 1일 기준이 아닌, 사용 시점을 기준으로 하는 것처럼 보여 기준이 모호하게 느껴졌습니다. 이로 인해 Postman은 정말 간단한 테스트 용도로는 적합했지만, 향후 기능 개발까지 고려했을 때 확장성 측면에서는 아쉬움이 있었습니다. 따라서 프론트엔드 개발자도 비교적 쉽게 다룰 수 있는 Supabase나 Firebase를 고려하고 있습니다.</p>
<h2 id="4-피드-리스트-구현">4. 피드 리스트 구현</h2>
<p>피드 UI는 다음과 같이 구현되었습니다.</p>
<p>
  <img
    src="https://velog.velcdn.com/images/hyphen_/post/c0c84999-5cc9-40a9-b0dc-1dd847036d08/image.png"
    width="250"
    alt="image-1"
  />
  <img
    src="https://velog.velcdn.com/images/hyphen_/post/02da6800-6e98-4af7-8de0-061a20ef6cff/image.gif"
    width="250"
    alt="image-2"
  />
</p>



<ul>
<li>아이콘: <code>material-community</code></li>
<li>동일한 아이콘이 없는 경우 유사 아이콘으로 대체</li>
<li>이 과정에서 다음과 같은 내용들을 학습할 수 있었습니다:<ul>
<li>React Native 컴포넌트 구조</li>
<li>리스트 렌더링 방식</li>
<li>API fetch 로직</li>
<li>환경 변수 관리</li>
</ul>
</li>
</ul>
<h2 id="react-native-환경-변수--디버깅-삽질">React Native 환경 변수 &amp; 디버깅 삽질</h2>
<p><img src="https://velog.velcdn.com/images/hyphen_/post/adae1e74-e82a-4cf7-a052-033fdef93874/image.gif" alt=""></p>
<h3 id="환경-변수-이슈">환경 변수 이슈</h3>
<p>웹에서 사용하던 <code>process.env</code> 방식은 React Native에서 동작하지 않아
<code>react-native-config</code>를 사용해 환경별 API 도메인을 분리했습니다.</p>
<p>처음 API 응답이 오지 않아 헤맸는데, env 설정 문제였습니다.</p>
<h3 id="react-native-075-디버깅">React Native 0.75 디버깅</h3>
<p>웹에서 크롬 개발자 도구를 통해 network를 보는 것 처럼 앱에서도 api응답을 보기 위해 설정을 추가하였습니다.</p>
<ul>
<li>Flipper를 최초로 brew를 사용에 전역 설치</li>
<li>Flipper 설정이 번거로워 제거</li>
<li>React Native Debugger 사용 시도 → flipper를 계속 찾는 문제 발생</li>
</ul>
<p><a href="https://reactnative-archive-august-2025.netlify.app/docs/0.75/debugging">React Native 0.75 공식 문서</a>를 참고하여 다음 명령어로 해결했습니다.</p>
<pre><code class="language-bash">npx react-native start --experimental-debugger
</code></pre>
<p>이후:</p>
<ul>
<li><code>cmd + D -&gt; Open Debugger</code></li>
<li>Flipper 없이 DevTools 정상 실행</li>
</ul>
<h3 id="하지만-network-탭이-없다">하지만... Network 탭이 없다</h3>
<p>가장 중요한 Network 탭이 DevTools에 없다
결록적으로 React Native 0.75의 Hermes DevTools에는 Network탭이 없는 것이 정상이었습니다.</p>
<p>결국 DevTools 오픈에 만족하며 이번 스텝을 마무리했습니다.</p>
<h2 id="마무리">마무리</h2>
<p>올해 중반기까지 앱 프로젝트를 끝내는 것이 목표입니다.
그 전에 클론 코딩을 마무리할 수 있을지 살짝 걱정이 됩니다.</p>
<p>7월까지는 SI 프로젝트 일정으로 야근 파티가 예정되어 있지만,
다음 포스트에서는:</p>
<ul>
<li>새로운 서버 환경 구축</li>
<li>피드 기능 확장 (좋아요, 댓글 등)</li>
</ul>
<p>을 이어서 작업해보려고 합니다.</p>
<p>긴 글 읽어주셔서 감사합니다.
다음 포스트에서 계속 이어가겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RN] 웹에서 앱으로, React Native 개발 환경 구축기]]></title>
            <link>https://velog.io/@hyphen_/RN-%EC%9B%B9%EC%97%90%EC%84%9C-%EC%95%B1%EC%9C%BC%EB%A1%9C-React-Native-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95%EA%B8%B0</link>
            <guid>https://velog.io/@hyphen_/RN-%EC%9B%B9%EC%97%90%EC%84%9C-%EC%95%B1%EC%9C%BC%EB%A1%9C-React-Native-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95%EA%B8%B0</guid>
            <pubDate>Mon, 05 Jan 2026 06:16:47 GMT</pubDate>
            <description><![CDATA[<p>이전에 <strong>Flutter 기반 앱 프로젝트에 초기 단계부터 참여해 약 반년 정도 투입</strong>된 경험이 있습니다. Flutter는 러닝 커브가 낮고, 실무에 빠르게 적용할 수 있다는 점이 큰 장점이었습니다. 물론 고급 기능이나 세밀한 커스터마이징이 필요해질수록 지속적인 학습이 필요하지만, 기본적인 앱 개발에 있어서는 누구나 비교적 빠르게 접근할 수 있는 프레임워크라는 인상을 받았어요.</p>
<p>특히 구글 스타일의 다양한 위젯과 레이아웃 시스템은 생산성을 크게 높여주었습니다.</p>
<p>하지만 <strong>앱의 개성과 디테일을 끝까지 살리기 위해서는</strong>, A부터 Z까지 직접 조립하듯 다룰 수 있는 도구가 더 적합하다고 느꼈습니다. 그런 관점에서 볼 때, <strong>React Native는 앱 개발에 있어 가장 메리트 있고 경쟁력 있는 선택지</strong>라고 판단했어요.</p>
<p>그래서 이번에는 <strong>React Native를 학습하며 실제 프로젝트를 직접 만들어보는 것</strong>을 목표로 하였습니다.</p>
<h2 id="react-native-cli-vs-expo">React Native: CLI vs Expo</h2>
<p>React Native는 크게 두 가지 방식으로 프로젝트를 생성할 수 있습니다.</p>
<ul>
<li><strong>Expo</strong><ul>
<li>프로젝트 생성, 실행, 배포까지 매우 빠르고 간편</li>
<li>CLI에 비해 진입 장벽이 낮고 생산성이 높음</li>
</ul>
</li>
<li><strong>React Native CLI</strong><ul>
<li>초기 설정은 복잡하지만 네이티브 레벨까지 자유로운 커스터마이징 가능</li>
<li>구조와 동작을 깊이 이해해야 함</li>
</ul>
</li>
</ul>
<p>사실 <em>“쉽게 앱을 만들고 싶었다면 Flutter를 계속 사용했을 것”</em> 입니다.<br>이번에는 <strong>완전히 커스텀 가능한 앱 개발 경험</strong>을 목표로 하고 있기 때문에, <strong>React Native CLI 기반으로 프로젝트를 구성</strong>하기로 하였습니다.</p>
<h2 id="환경-구축">환경 구축</h2>
<p>React Native 앱 프로젝트를 실행하기 위해서는 아래와 같은 기본 환경 세팅이 필요합니다.</p>
<ol>
<li>Homebrew 설치  </li>
<li>Java, Android Studio 설치  </li>
<li>Xcode 및 Apple Developer 계정 설정  </li>
<li>brew를 이용한 Node, Watchman, CocoaPods 설치  </li>
</ol>
<p>환경 구축이 정상적으로 되었는지는 아래 명령어로 확인할 수 있습니다.</p>
<pre><code class="language-bash">npx react-native doctor</code></pre>
<h2 id="프로젝트-생성-및-버전-선택">프로젝트 생성 및 버전 선택</h2>
<p>React Native는 서드파티 라이브러리의 버전에 매우 민감합니다.
따라서 사용 중인 React Native 버전에 맞는 라이브러리 조합을 신중하게 선택해야 해요.
저도 서드파티를 설정하면서 버전 이슈를 겪었었습니다.</p>
<p>공식 문서에서는 <a href="https://reactnative.dev/blog/2025/02/19/react-native-0.78#upgrade-to-078">React Native 0.78 버전</a>을 권장하고 있지만,
나는 <strong>팀원들과의 개발 환경을 맞추기 위해 0.75 버전</strong>을 사용했습니다.
(다만, 곧 지원 종료 예정이라는 점은 인지하고 있다.)</p>
<h3 id="네비게이션-구조-설계">네비게이션 구조 설계</h3>
<p>이번 포스트에서는 <strong>Stack + Bottom Tab</strong> 구조의 네비게이션을 구성합니다.</p>
<h4 id="구현-목표">구현 목표</h4>
<ul>
<li>Bottom Tab Navigation 구현</li>
<li>Stack 기반 화면 이동 구조 설계</li>
</ul>
<blockquote>
<p><strong>서드파티란?</strong>
프로그래밍에서의 서드파티(Third-party)란, 제3자가 제공하는 라이브러리나 프레임워크로,
개발자가 특정 기능을 더 쉽게 구현할 수 있도록 도와주는 도구를 의미합니다.</p>
</blockquote>
<h3 id="react-native-075-환경의-서드파티-설정">React Native 0.75 환경의 서드파티 설정</h3>
<p>아래는 React Native 0.75 환경에서 네비게이션 구성을 위해 사용한 주요 라이브러리들입니다.</p>
<ul>
<li><code>@react-navigation/native</code></li>
<li><code>@react-navigation/native-stack</code></li>
<li><code>@react-navigation/bottom-tabs</code></li>
<li><code>react-native-gesture-handler</code></li>
<li><code>react-native-safe-area-context</code></li>
<li><code>react-native-screens</code></li>
<li><code>react-native-vector-icons</code> (Bottom Tab 아이콘용)</li>
</ul>
<p><strong>참고 자료</strong></p>
<ul>
<li><a href="https://www.npmjs.com/package/react-native-screens">https://www.npmjs.com/package/react-native-screens</a></li>
<li><a href="https://github.com/software-mansion/react-native-gesture-handler">https://github.com/software-mansion/react-native-gesture-handler</a></li>
<li><a href="https://appandflow.github.io/react-native-safe-area-context/">https://appandflow.github.io/react-native-safe-area-context/</a></li>
</ul>
<p>일부 패키지는 React Native 버전에 따라 버전을 명시해야 했고,
나머지는 최신 버전을 그대로 설치하여 사용했습니다.</p>
<h3 id="적용-결과-packagejson">적용 결과 (package.json)</h3>
<pre><code class="language-bash">&quot;@react-navigation/bottom-tabs&quot;: &quot;^7.4.5&quot;,
&quot;@react-navigation/native&quot;: &quot;^7.1.17&quot;,
&quot;@react-navigation/native-stack&quot;: &quot;^7.3.24&quot;,
&quot;react&quot;: &quot;18.3.1&quot;,
&quot;react-native&quot;: &quot;0.75.5&quot;,
&quot;react-native-gesture-handler&quot;: &quot;2.24.0&quot;,
&quot;react-native-safe-area-context&quot;: &quot;^5.5.2&quot;,
&quot;react-native-screens&quot;: &quot;4.5.0&quot;,
&quot;react-native-vector-icons&quot;: &quot;^10.3.0&quot;</code></pre>
<h3 id="화면-이동-방식-정리">화면 이동 방식 정리</h3>
<p>React Navigation에서 자주 사용되는 화면 이동 메서드들을 정리해보면 다음과 같습니다.</p>
<ul>
<li><code>replace</code>: 현재 경로를 새로운 경로로 교체합니다. (스택이 유지됨)</li>
<li><code>reset</code>: 기존 스택을 모두 제거하고 새로운 경로로 이동합니다.</li>
<li><code>push</code>: 새로운 화면을 스택 최상단에 추가하고 이동합니다.</li>
<li><code>pop</code>:
스택에서 지정한 수만큼 뒤로 이동합니다. 
매개변수를 넘기지 않으면 기본값은 1입니다.</li>
<li><code>popTo</code>:
특정 화면으로 이동합니다.
replace처럼 동작하기 때문에, 해당 화면이 스택에 없어도 앱이 중단되지 않습니다.</li>
<li><code>popToTop</code>:
스택의 첫 번째 화면으로 이동하고 나머지는 모두 제거합니다.
단, 현재 화면이 이미 최상단인 경우 오류 로그가 발생할 수 있습니다.</li>
<li><code>goBack</code>:
현재 화면 기준으로 이전 화면으로 이동합니다.
이전 화면이 없을 경우 오류가 발생합니다.</li>
</ul>
<p>이 외에도 Stack Navigator를 사용할때 쓰이는 Props들이 더 있지만 화면을 구성하면서 정리하기로 하였습니다. 다음 포스트에서는 클론코딩을 진행해보며 프로젝트를 완성하는 과정을 차근차근 적어보려고 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React에서 S3 이미지 URL로 다운로드 구현하기 (Blob 처리 + CORS 해결 + PDF 변환)]]></title>
            <link>https://velog.io/@hyphen_/React%EC%97%90%EC%84%9C-S3-%EC%9D%B4%EB%AF%B8%EC%A7%80-URL%EB%A1%9C-%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-Blob-%EC%B2%98%EB%A6%AC-CORS-%ED%95%B4%EA%B2%B0-PDF-%EB%B3%80%ED%99%98</link>
            <guid>https://velog.io/@hyphen_/React%EC%97%90%EC%84%9C-S3-%EC%9D%B4%EB%AF%B8%EC%A7%80-URL%EB%A1%9C-%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-Blob-%EC%B2%98%EB%A6%AC-CORS-%ED%95%B4%EA%B2%B0-PDF-%EB%B3%80%ED%99%98</guid>
            <pubDate>Mon, 24 Nov 2025 07:40:41 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 진행하던 중, <strong>S3에 업로드된 이미지를 클라이언트에서 직접 다운로드할 수 있게 구현해야하는 요구사항</strong>이 있었습니다.
일반적으로 S3에 올린 이미지는 URL로 접근하면 화면에 렌더링하는 데에는 문제가 없지만, 아래와 같은 문제가 발생할 수 있습니다.</p>
<ul>
<li>브라우저가 자동 다운로드를 허용하지 않음</li>
<li>CORS 정책 때문에 fetch 요청이 차단됨</li>
<li>S3에 <code>Content-Disposition</code>이 설정되어 있지 않음</li>
<li>Blob 변환 과정에서 에러 발생</li>
</ul>
<p>서버에서 presigned URL을 만들어 내려주거나, S3 CORS 정책을 수정하는 방식도 있지만
이번 요구사항은 클라이언트에서 간단하게 처리할 수 있는 수준이었기 때문에 React 단에서 직접 구현했습니다.</p>
<p>이 글에서는 S3 이미지 URL을 이용해 다운로드 기능을 구현하는 방법과
제가 겪은 CORS 이슈 트러블슈팅 과정,
그리고 이미지를 PDF로 변환하여 다운로드하는 방법까지 정리해보았습니다.</p>
<h2 id="1-이미지-url-→-blob-→-object-url-변환">1. 이미지 URL → Blob → Object URL 변환</h2>
<p>이미지 URL은 바로 다운로드 링크로 사용할 수 있지만,
CORS 또는 Content-Disposition 설정 문제 때문에 정상적인 다운로드가 안 될 수 있습니다.</p>
<p><code>fetch</code>를 이용해 URL을 Blob으로 받아온 뒤, 이를 <code>URL.createObjectURL()</code>로 변환하면
브라우저가 안전하게 다운로드 가능한 가짜 링크(blob URL)를 생성할 수 있습니다.</p>
<pre><code>export const toDataURL = (url: string) =&gt; {
  return fetch(url)
    .then((response) =&gt; {
      return response.blob();
    })
    .then((blob) =&gt; {
      return URL.createObjectURL(blob);
    });
};
</code></pre><p>ㄴ&gt; 이미지 URL을 fetch -&gt; blob -&gt; 브라우저 내부 전용 URL로 만드는 과정</p>
<h3 id="트러블슈팅-💣--이미지-fetch-시-cors-오류-발생">트러블슈팅 💣 — 이미지 fetch 시 CORS 오류 발생</h3>
<p>하지만 여기에서 예상치 못한 문제가 있었습니다.
이미지 fetch 자체가 CORS 오류로 막혀 버린 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/hyphen_/post/9ad30b16-0d67-418a-8e09-c16c5bccf01c/image.png" alt="CORS_오류"></p>
<p>처음에는 프로젝트 초기에 CORS 설정을 하면서
테스트 도메인(<code>localhost:5176</code>)을 허용했던 기억이 있어
서버 측에 CORS 정책 재확인을 요청했습니다.</p>
<p>그러나 S3의 CORS 설정을 수정해도 오류가 해결되지 않는 상황이 지속되었습니다.</p>
<h4 id="원인을-찾는-과정">원인을 찾는 과정</h4>
<p>동료와 함께 확인하던 중, 특정 글에서
“origin 헤더를 강제로 넣어 요청하는 방식”을 발견했지만
fetch는 브라우저 환경에서 origin 헤더를 직접 변경할 수 없기 때문에 불가능한 방법이었습니다.</p>
<p>그러던 중, 아래 옵션을 추가하면 되는 경우가 있다는 내용을 발견했습니다:</p>
<pre><code> fetch(url, { method: &#39;GET&#39;, mode: &#39;cors&#39;, cache: &#39;no-store&#39; })</code></pre><h4 id="🤔-어떻게-cache-no-store가-해결에-도움이-되었을까">🤔 어떻게 cache: &#39;no-store&#39;가 해결에 도움이 되었을까?</h4>
<p>일반적으로 cache: &#39;no-store&#39;는 리소스 캐시를 무효화할 때 사용합니다.
하지만 일부 환경(S3 + CDN)에서는 아래 상황이 발생할 수 있습니다.</p>
<ul>
<li>S3의 CORS 정책이 변경되었지만</li>
<li>브라우저가 이전의 캐싱된 응답 헤더(특히 이미지 요청 결과)를 계속 사용</li>
<li>이로 인해 fetch가 여전히 CORS 에러를 유발</li>
</ul>
<p>즉, S3가 반환하는 오래된 캐시 응답이
CORS 헤더를 포함하지 않고 있었던 것입니다.</p>
<p>cache: &#39;no-store&#39;를 설정하여 항상 실제 S3에 새 요청을 보내도록 하자,
최신 CORS 정책이 적용된 응답을 받게 되었고
문제가 해결되었습니다.</p>
<hr>
<h2 id="2-blob-url을-활용해-실제-다운로드-트리거">2. Blob URL을 활용해 실제 다운로드 트리거</h2>
<p>생성한 blob URL을 a 태그에 할당하고,
동적으로 클릭 이벤트를 발생시키면 브라우저는 해당 리소스를 다운로드하게 됩니다.</p>
<pre><code>export const downloadFile = async (url: string, fileName?: string) =&gt; {
  const a = document.createElement(&#39;a&#39;);
  a.href = await toDataURL(url);
  a.download = fileName ?? &#39;download&#39;;

  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
};</code></pre><p>이 방식은 특히 아래와 같은 상황에서 유용하게 사용할 수 있습니다.</p>
<ul>
<li>S3에서 <code>Content-Disposition: attachment</code> 헤더가 설정되어 있지 않은 경우</li>
<li>이미지 URL을 바로 다운로드할 때 CORS가 막히는 경우</li>
<li>React 환경에서 직접 다운로드 버튼을 제공해야 할 때</li>
</ul>
<hr>
<h2 id="3-이미지---pdf-변환-후-다운로드-jspdf-사용">3. 이미지 -&gt; PDF 변환 후 다운로드 (jsPDF 사용)</h2>
<p>특정 요구사항에서는 이미지 파일을 그대로 저장하는 것이 아니라
PDF로 변환해서 저장해야 하는 경우가 있습니다.
대표적으로:</p>
<ul>
<li>사업자등록증 이미지</li>
<li>계약서 사진</li>
<li>스캔본 이미지</li>
</ul>
<p>이런 경우 jsPDF를 이용하면
클라이언트에서 이미지를 읽어 PDF에 삽입하여 다운로드할 수 있습니다.</p>
<pre><code>export const downloadImageAsPDF = async (
  imageUrl: string,
  fileName = &#39;image.pdf&#39;,
) =&gt; {
  try {
    // 1. 이미지 Blob 다운로드
    const response = await fetch(imageUrl, {
      method: &#39;GET&#39;,
      mode: &#39;cors&#39;,
      cache: &#39;no-store&#39;,
    });
    const blob = await response.blob();

    // 2. Blob → Base64 변환
    const reader = new FileReader();
    const base64 = await new Promise&lt;string&gt;((resolve) =&gt; {
      reader.onloadend = () =&gt; resolve(reader.result as string);
      reader.readAsDataURL(blob);
    });

    // 3. PDF 생성
    const pdf = new jsPDF({
      orientation: &#39;portrait&#39;,
      unit: &#39;px&#39;,
      format: &#39;a4&#39;,
    });

    // 4. PDF 크기에 맞게 이미지 추가
    const imgProps = pdf.getImageProperties(base64);
    const pageWidth = pdf.internal.pageSize.getWidth();
    const ratio = imgProps.height / imgProps.width;
    const imgHeight = pageWidth * ratio;

    pdf.addImage(base64, &#39;PNG&#39;, 0, 0, pageWidth, imgHeight);

    // 5. 다운로드
    pdf.save(fileName);
  } catch (error) {
    console.error(&#39;PDF 생성 중 오류:&#39;, error);
  }
};</code></pre><p>위 로직은 아래와 같은 과정으로 진행됩니다.</p>
<ul>
<li>이미지 URL을 fetch하여 Blob으로 가져옴</li>
<li>Blob을 Base64 문자열로 변환</li>
<li>jsPDF에서 이미지의 width/height 비율에 맞게 자동 리사이징</li>
<li>PDF 파일로 저장 후 다운로드</li>
</ul>
<p>특히 사업자등록증, 계약서, 인증서 이미지 등을 PDF로 변환해 저장하는 UI에서 많이 사용됩니다.</p>
<hr>
<h3 id="마무리">마무리</h3>
<p>React에서 S3 이미지 URL을 다운로드하는 방법은 생각보다 단순해 보이지만,
CORS, Blob 처리, PDF 변환, 파일명 처리 등 디테일이 필요한 기능입니다.</p>
<p>이번 글에서 소개한 방식들을 적절히 조합하면,</p>
<ul>
<li>단순 이미지 다운로드</li>
<li>파일명 지정 다운로드</li>
<li>이미지 -&gt; PDF 변환 다운로드</li>
<li>CORS 제약 우회</li>
</ul>
<p>까지 모두 구현할 수 있습니다.</p>
<h3 id="참고-자료">참고 자료</h3>
<ul>
<li>이미지 다운시 CORS 에러 해결하기 <a href="https://parkseohyun.com/develop-cors/">https://parkseohyun.com/develop-cors/</a></li>
<li>StackOverflow 사례: S3 이미지 fetch 시 CORS 오류 발생 + 어떤 헤더가 필요한지. <a href="https://stackoverflow.com/questions/57136480/fetching-an-image-from-s3-bucket-is-not-working-because-of-cors?utm_source=chatgpt.com">Stack Overflow</a></li>
<li>React - Image URL을 다운로드 할 수 있는 방법 <a href="https://velog.io/@moolbum/React-Image-URL%EC%9D%84-%EB%8B%A4%EC%9A%B4%EB%A1%9C%EB%93%9C-%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-%EB%B0%A9%EB%B2%95">velog.io</a></li>
<li><a href="https://www.npmjs.com/package/jspdf">https://www.npmjs.com/package/jspdf</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js란 무엇인가? ]]></title>
            <link>https://velog.io/@hyphen_/Next.js%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80</link>
            <guid>https://velog.io/@hyphen_/Next.js%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80</guid>
            <pubDate>Thu, 26 Jun 2025 14:52:53 GMT</pubDate>
            <description><![CDATA[<h2 id="nextjs는-서버-사이드-렌더링을-지원한다">Next.js는 서버 사이드 렌더링을 지원한다</h2>
<p>리액트와 같은 자바스크립트 기반의 애플리케이션은 JavaScript로 동작하기 때문에,<br>JavaScript를 다운로드한 후 브라우저에서 실행되어야만 UI가 화면에 표시된다.<br>이런 방식은 클라이언트 쪽에서 렌더링한다고 하여 <strong>클라이언트 사이드 렌더링(CSR)</strong>이라고 한다.</p>
<br/>

<hr>
<h3 id="클라이언트-사이드-렌더링의-단점">클라이언트 사이드 렌더링의 단점</h3>
<ol>
<li>자바스크립트가 동작하지 않는 환경에서는 화면이 표시되지 않아, 검색엔진 등의 로봇이 컨텐츠를 읽지 못하는 치명적인 문제가 있다.</li>
<li>자바스크립트를 다운로드하고 실행하기 전까지는 화면이 보이지 않아 사용자 경험이 떨어질 수 있다.</li>
</ol>
<br/>

<hr>
<h3 id="그렇다면-서버-사이드-렌더링이란">그렇다면 서버 사이드 렌더링이란?</h3>
<p><strong>서버 사이드 렌더링(SSR)</strong>은 JavaScript가 <strong>서버에서 실행</strong>되어 <strong>완성된 HTML</strong>을 브라우저에 전달한다.<br>이 방식은 자바스크립트를 실행할 수 없는 환경에서도 페이지가 잘 작동하며 <strong>SEO</strong>, <strong>빠른 첫 화면 로딩</strong>에 강점을 갖는다.</p>
<blockquote>
<p>다운로드 받는 즉시 실행되기 때문에 사용자 입장에서는<br>눈 깜빡할 사이에 웹페이지가 표시되는 느낌을 받게 됩니다.</p>
</blockquote>
<hr>
<p><img src="https://velog.velcdn.com/images/hyphen_/post/978760e3-5a87-4917-8b4f-d56216f08add/image.png" alt="Next.js 기능 이미지"></p>
<hr>
<h3 id="nextjs에서-제공하는-주요-기능">Next.js에서 제공하는 주요 기능</h3>
<h4 id="⭐-내장-최적화-기능-built-in-optimizations">⭐ 내장 최적화 기능 (Built-in Optimizations)</h4>
<p>코드 스플리팅, 이미지 최적화, 폰트 최적화, lazy loading 등을 자동으로 처리하여 성능을 향상시킨다.</p>
<h4 id="동적-html-스트리밍-dynamic-html-streaming">동적 HTML 스트리밍 (Dynamic HTML Streaming)</h4>
<p>React 18의 Streaming SSR 기반으로, 서버에서 점진적으로 HTML을 전송하여 초기 로딩 속도를 개선한다.</p>
<h4 id="⭐-리액트-서버-컴포넌트-react-server-components">⭐ 리액트 서버 컴포넌트 (React Server Components)</h4>
<p>서버에서만 렌더링되는 컴포넌트로, 불필요한 JavaScript 전송을 줄여 성능을 향상시키며 App Router에서 기본적으로 사용된다.</p>
<h4 id="⭐-데이터-패칭-data-fetching">⭐ 데이터 패칭 (Data Fetching)</h4>
<p><code>getStaticProps</code>, <code>getServerSideProps</code>, <code>fetch</code>, <code>cache</code>, <code>use</code> 등 다양한 방법으로 클라이언트와 서버에서 데이터를 유연하게 가져올 수 있다.</p>
<h4 id="css-지원-css-support">CSS 지원 (CSS Support)</h4>
<p>CSS Modules, 전역 CSS, Tailwind CSS, Sass 등 다양한 스타일링 방식을 유연하게 지원한다.</p>
<h4 id="⭐-클라이언트--서버-렌더링-client-and-server-rendering">⭐ 클라이언트 &amp; 서버 렌더링 (Client and Server Rendering)</h4>
<p>CSR과 SSR을 페이지 또는 컴포넌트 단위로 유연하게 선택해 구현할 수 있다.</p>
<h4 id="서버-액션-server-actions">서버 액션 (Server Actions)</h4>
<p><code>use server</code> 지시어를 활용해 클라이언트에서 직접 서버 함수를 실행할 수 있으며, App Router에서 실험적으로 제공된다.</p>
<h4 id="라우트-핸들러-route-handlers">라우트 핸들러 (Route Handlers)</h4>
<p><code>/app/api/route.ts</code> 형식으로 API 요청을 처리하는 핸들러를 구성할 수 있어 기존 API Routes를 대체한다.</p>
<h4 id="⭐-고급-라우팅--중첩-레이아웃-advanced-routing--nested-layouts">⭐ 고급 라우팅 &amp; 중첩 레이아웃 (Advanced Routing &amp; Nested Layouts)</h4>
<p>App Router 기반의 폴더 구조를 활용하여 중첩 레이아웃, 병렬 라우트, 인터셉팅 라우트 등 고급 라우팅 패턴을 구현할 수 있다.</p>
<h4 id="미들웨어-middleware">미들웨어 (Middleware)</h4>
<p>요청이 처리되기 전에 인증, 리디렉션, 로깅 등의 작업을 수행할 수 있으며, Edge Runtime 환경에서 실행된다.</p>
<br/>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] React DevTools를 활용한 성능 최적화 경험 공유]]></title>
            <link>https://velog.io/@hyphen_/WIL-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@hyphen_/WIL-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Wed, 11 Jun 2025 15:15:40 GMT</pubDate>
            <description><![CDATA[<p>React DevTools를 활용하여 더 심도 있는 성능 최적화를 진행해보았습니다.
이 도구는 <a href="https://chromewebstore.google.com/detail/fmkadmapgofadopljbjfkapdkoienihi?utm_source=item-share-cb">Chrome 확장 프로그램</a>으로 설치할 수 있으며, React 앱이 로드된 페이지에서 Chrome 개발자 도구 내에서 <strong>Components</strong>와 <strong>Profiler</strong> 탭을 통해 사용할 수 있습니다.</p>
<br/>


<h2 id="react-devtools-소개">React DevTools 소개</h2>
<p>React DevTools는 React 컴포넌트 구조를 시각적으로 분석하고, 상태와 props를 편집하거나 성능 병목을 파악할 수 있게 도와주는 도구입니다.</p>
<h3 id="하이라이팅">하이라이팅</h3>
<ul>
<li>어떤 컴포넌트가 리렌더링되었는지 확인하는 하이라이팅 기능을 제공합니다.</li>
<li>설정에서 <code>Highlight updates when components render</code> 옵션을 활성화하면, 리렌더링 시 컴포넌트 테두리에 색상이 표시되어 렌더링 빈도를 시각적으로 파악할 수 있습니다.</li>
<li><strong>색상 단계</strong>
오른쪽으로 갈 수록 높은 렌더링 빈도를 나타낸다.
<img src="https://velog.velcdn.com/images/hyphen_/post/62fed4b3-2028-4731-8275-7645f8d53a5b/image.png" alt=""></li>
</ul>
<br/>

<h3 id="components-tab">Components Tab</h3>
<ul>
<li>React 컴포넌트 트리를 시각적으로 보여줍니다.</li>
<li>각 컴포넌트의 props, state, hooks, context 정보를 확인하고 실시간으로 수정할 수 있습니다.</li>
</ul>
<br/>

<h3 id="profiler-tab">Profiler Tab</h3>
<ul>
<li>성능 측정을 위한 탭으로, 앱을 실제로 조작하면서 <strong>컴포넌트별 렌더링 시간, 렌더링 순서, 렌더링 원인</strong> 등을 시각화하여 확인할 수 있습니다.</li>
<li><strong>렌더링 트리, 플레임 차트, 상세 정보 뷰</strong>를 통해 병목이 발생한 컴포넌트를 찾을 수 있습니다.</li>
<li><code>Commit</code> 단위로 기록되어 성능 개선 전/후 비교에 용이합니다.</li>
</ul>
<br/>
<br/>

<h2 id="성능-최적화-적용-사례">성능 최적화 적용 사례</h2>
<h4 id="적용-대상-시간표-애플리케이션">적용 대상: 시간표 애플리케이션</h4>
<p>DropDown으로 일정을 이동할 수 있는 시간표 앱에 성능 이슈가 발생하고 있어 최적화를 진행하였습니다.
문제의 원인을 React DevTools를 통해 분석하고, 아래와 같은 3가지 주요 개선을 적용했습니다.</p>
<h3 id="1-api-호출-방식-개선">1. API 호출 방식 개선</h3>
<ul>
<li><strong>문제점</strong>: API 호출이 직렬로 실행되어 전체 로딩 시간이 길어졌습니다.</li>
<li><strong>개선점</strong>: 병렬 호출과 캐싱 전략을 도입하여 응답 시간을 단축했습니다.</li>
</ul>
<table border="1" cellspacing="0" cellpadding="8" >
  <thead>
    <tr style="background-color: white;">
      <th>항목</th>
      <th>개선 전</th>
      <th>개선 후</th>
    </tr>
  </thead>
  <tbody>
    <tr style="background-color: white;">
      <td>전체 API 호출 시간</td>
      <td>약 233.1ms</td>
      <td>약 192.1ms</td>
    </tr>
    <tr style="background-color: white;">
      <td>개별 API 최대 응답 시간</td>
      <td>최대 1473.4ms</td>
      <td>최대 369.6ms</td>
    </tr>
  </tbody>
</table>

<br/>


<h3 id="2-불필요한-연산-및-렌더링-제거">2. 불필요한 연산 및 렌더링 제거</h3>
<ul>
<li><strong>문제점</strong>: 무한 스크롤 시 리스트가 처음부터 재렌더링되는 문제가 있었습니다.</li>
<li><strong>개선점</strong>: 초기 렌더링 이후 동일 데이터를 재사용하도록 개선했습니다.<ul>
<li>React의 <code>useMemo</code>, <code>memo</code>를 적극 활용해 동일 결과를 재계산하지 않도록 했습니다.</li>
<li>렌더링 대상 변경이 없는 컴포넌트는 재렌더링 되지 않도록 최적화했습니다.</li>
</ul>
</li>
</ul>
<br/>


<h3 id="3-드래그-시-렌더링-성능-개선">3. 드래그 시 렌더링 성능 개선</h3>
<ul>
<li><strong>문제점</strong>: 일정을 드래그할 때 전체 컴포넌트가 매번 리렌더링되었습니다.</li>
<li><strong>개선점</strong>:<ul>
<li>드래그된 항목만 리렌더링되도록 컴포넌트를 분리했습니다.</li>
<li>전역 상태를 분리하여 데이터 상태 Context와 액션 상태 Context로 역할을 분담시켰습니다.</li>
<li>관심사 분리를 통해 불필요한 리렌더링을 방지할 수 있었습니다.</li>
</ul>
</li>
</ul>
<p>이 과정에서 React DevTools의 하이라이팅 기능을 활용하니, 리렌더링이 발생하는 영역을 직관적으로 파악할 수 있어 매우 유용했습니다.</p>
<br />
<br />


<h2 id="성능-최적화-작업에서-배운-점">성능 최적화 작업에서 배운 점</h2>
<p>React 앱에서 성능 최적화는 단순히 &quot;빠르게 만드는 것&quot;이 아니라, <strong>불필요한 렌더링과 연산을 줄이고, 앱의 구조를 정돈</strong>하며, <strong>유지보수성을 높이는 작업</strong>입니다.</p>
<p>그 과정에서 아래의 원칙들을 반드시 지켜야 함을 깨달았습니다</p>
<ul>
<li><strong>기존 기능이 정상적으로 동작해야 한다.</strong><ul>
<li>기능 손상이 없는 상태에서 성능을 개선해야 합니다.</li>
</ul>
</li>
<li><strong>개선 전과 후의 차이를 명확하게 문서화한다.</strong><ul>
<li>&quot;AS-IS&quot; / &quot;TO-BE&quot; 방식으로 수치 기반의 보고서를 남기는 것이 중요합니다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] 프론트엔드 개발자 인프라 첫걸음]]></title>
            <link>https://velog.io/@hyphen_/WIL-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%9D%B8%ED%94%84%EB%9D%BC-%EC%B2%AB%EA%B1%B8%EC%9D%8C</link>
            <guid>https://velog.io/@hyphen_/WIL-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%9D%B8%ED%94%84%EB%9D%BC-%EC%B2%AB%EA%B1%B8%EC%9D%8C</guid>
            <pubDate>Sun, 01 Jun 2025 13:58:14 GMT</pubDate>
            <description><![CDATA[<p>인프라란 무엇일까? 그동안은 필요할 때마다 얼렁뚱땅 찾아서 사용하는 정도에 그쳤고, 깊이 있게 고민해본 적은 없었다.</p>
<blockquote>
<p>개발에서 <strong>인프라(Infra, Infrastructure)</strong>란 소프트웨어나 서비스를 안정적으로 운영하기 위해 필요한 기반 시스템 전체를 의미한다.
즉, 개발한 코드가 실제로 실행되고 동작하는 운영 환경의 모든 구성 요소를 포함한다.</p>
</blockquote>
<p>GitHub Actions, CI/CD, Vercel, GCP App Engine 등을 사용해 간단한 배포 테스트를 해본 경험은 있었지만, 인프라를 직접 구축하거나 성능을 개선해 본 경험은 없었다.</p>
<p>이번에 처음으로 경험한 인프라 구성 방식은 AWS S3와 CloudFront를 활용한 정적 사이트 배포였다.
직접 S3에 정적 파일을 업로드하고, CloudFront를 통해 전 세계 사용자에게 빠르게 콘텐츠를 전달하는 구조를 이해하고 적용해볼 수 있었다.</p>
<h2 id="s3와-스토리지">S3와 스토리지</h2>
<h3 id="🧊-s3-amazon-simple-storage-service">🧊 S3 (Amazon Simple Storage Service)</h3>
<h4 id="정의">정의</h4>
<p>S3는 Amazon Web Services(AWS)가 제공하는 객체 스토리지 서비스로, 정적 파일(HTML, CSS, JS, 이미지 등)을 안전하고 확장 가능하게 저장할 수 있는 공간입니다.</p>
<h4 id="버킷bucket-특징">버킷(bucket) 특징</h4>
<p>기본 단위: &quot;버킷&quot;은 S3에서 데이터를 저장할 때 가장 먼저 생성하는 단위로, 파일이 저장되는 &#39;컨테이너&#39; 혹은 폴더처럼 생각할 수 있습니다.</p>
<ul>
<li>버킷의 이름은 유일해야 됩니다.</li>
<li>전 세계에서 유일한 ID만 사용할 수 있습니다.</li>
<li>버킷을 만들기 위해서는 리전을 선택해야 합니다.</li>
<li>버킷 주소는 https://{bucketname}.s3.{region}.amazon.com 형태로 이루어 집니다.</li>
<li>버전관리 기능을 통해서 사용자에 의한 실수도 복원이 가능합니다.</li>
</ul>
<br/>

<h4 id="객체object-특징">객체(object) 특징</h4>
<p>기본 저장 단위: &quot;객체&quot;는 S3에서 저장되는 파일 자제를 의미합니다.</p>
<ul>
<li>파일과 유사<ul>
<li>객체는 컴퓨터의 파일과 유사한 개념. 이미지, 비디오, 문서 등 어떤 종류의 파일이든 객체가 될 수 있습니다.</li>
</ul>
</li>
<li>구성 요소<ul>
<li>데이터: 실제 저장하려는 콘텐츠</li>
<li>키: 버킷 내에서 객체를 고유하게 식별하는 이름</li>
<li>메타데이터: 객체에 대한 추가 정보 (예: 생성 날짜, 크기 등)</li>
</ul>
</li>
<li>크기<ul>
<li>한 객체는 최대 5TB까지 저장할 수 있습니다.</li>
</ul>
</li>
<li>불변성<ul>
<li>객체를 수정할 수 없습니다.</li>
<li>변경하려면 새 객체를 만들어 덮어써야 함.</li>
</ul>
</li>
<li>URL 접근<ul>
<li>각 객체는 고유한 URL을 가지며, 이를 통해 접근할 수 있습니다.</li>
</ul>
</li>
<li>버킷 내 저장<ul>
<li>모든 객체는 반드시 버킷이라는 컨테이너 내에 저장됩니다.</li>
</ul>
</li>
</ul>
<br/>

<h2 id="cloudfront와-cdn">CloudFront와 CDN</h2>
<h3 id="🧊-cloudfront-amazon-cdn">🧊 CloudFront (Amazon CDN)</h3>
<h4 id="정의-1">정의</h4>
<p>CloudFront는 AWS가 제공하는 콘텐츠 전송 네트워크(Content Delivery Network) 서비스로, 전 세계의 사용자에게 S3의 정적 자산을 가장 가까운 엣지 로케이션(Edge Location)에서 빠르게 제공한다.</p>
<h4 id="주요-기능">주요 기능</h4>
<ul>
<li>전 세계에 분산된 캐시 서버를 통해 지연 시간 감소</li>
<li>HTTPS 및 SSL 지원</li>
<li>캐시 정책, 만료 정책 설정 가능</li>
<li>S3, EC2, API Gateway 등 다양한 오리진과 연결 가능</li>
<li>캐시 무효화(invalidation) 기능으로 빠른 반영 가능</li>
</ul>
<h4 id="사용-사례">사용 사례</h4>
<ul>
<li>글로벌 정적 웹사이트 성능 최적화</li>
<li>대용량 이미지, 동영상 콘텐츠 전송</li>
<li>고속 API 응답 전송 (정적 또는 동적)</li>
</ul>
<br/>

<h2 id="정리">정리</h2>
<ol>
<li>코드에 push 이벤트가 발생하면, GitHub Actions를 통해 자동으로 <strong>빌드 및 배포 파이프라인</strong>이 실행됩니다.</li>
<li>빌드된 정적 파일은 AWS S3에 업로드되며, S3는 이를 <strong>정적 웹 호스팅 서버</strong>로서 저장합니다.</li>
<li>사용자 요청은 AWS CloudFront CDN을 통해 가장 가까운 엣지 서버에서 전달되어, <strong>전 세계 어디서든 빠르고 안정적인 서비스 제공</strong>이 가능해집니다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] 테스트 코드 삽질 끝에 결국에는 해냈다 (+ 사이드 프로젝트 발표)]]></title>
            <link>https://velog.io/@hyphen_/WIL-8%EC%A3%BC%EC%B0%A8-%EC%82%BD%EC%A7%88-%EB%81%9D%EC%97%90-%EA%B2%B0%EA%B5%AD%EC%97%90%EB%8A%94-%ED%95%B4%EB%83%88%EB%8B%A4-%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%9C%ED%91%9C</link>
            <guid>https://velog.io/@hyphen_/WIL-8%EC%A3%BC%EC%B0%A8-%EC%82%BD%EC%A7%88-%EB%81%9D%EC%97%90-%EA%B2%B0%EA%B5%AD%EC%97%90%EB%8A%94-%ED%95%B4%EB%83%88%EB%8B%A4-%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%9C%ED%91%9C</guid>
            <pubDate>Sat, 24 May 2025 06:35:33 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hyphen_/post/954b285c-42d6-44d6-84a3-0f481b61f97b/image.jpeg" alt=""></p>
<p>3번째 챕터가 끝이 났습니다. 이번 챕터는 유난히 길고 끝나지 않은 싸움의 연속이었죠. 어째서 과제 통과 100%를 달성하기 어려운가, 그것을 이번에 제대로 깨달았다. 테스트 코드는 할 것도 많고, 기능 구현과 리팩토링까지 같이해야 해서 진짜 힘들었기 때문이었다...</p>
<br/>

<hr>
<br/>

<p>저번 주에 치욕스럽게도 two Fail을 적립했다. 다시 돌아보자면 테스트 코드 지식 zero, 충분히 해낼 거라는 자만심, 마지막으로 시간 분배 대폭망!</p>
<p>이번에는 발제가 되고 프로젝트 세팅을 마친 후 일요일부터 기본 과제를 본격적으로 시작했다. 기본 과제의 30% 정도를 완료하고 다른 일정 때문에 하루를 비운 뒤, 내일 하루에 마무리하고 수요일 전까지 끝내자고 다짐했다. (심화 과제는 팀 과제라 코어타임을 기다림)</p>
<p>그러나! 저번 주에 어느 정도 실마리를 얻었다고는 해도 테스트 코드를 짜는 건 여전히 버거웠다.
<span style="color: grey">버거 하니까 롯데리아에 나폴리 맛피아 햄버거가 상시 출시되었는데 진짜 맛있으니까 꼭 먹어보면 좋겠다. 하지만 내 마음속 1위는 아직도 핫 크리스피 치킨 버거.</span></p>
<p>결국 테스트 코드와의 싸움 끝에 목요일 밤까지 작업해서 기본 과제를 모두 마무리했고, 그 와중에 틈틈이 팀원들과 심화 과제도 작성해서 함께 제출했다. 확실히 이번 주엔 초심으로 돌아가 미리미리 작성하니까 CI도 통과하고, 제출도 수월했고, 뿌듯하게 잠들 수 있었다.</p>
<br/>

<hr>
<br/>


<p><img src="https://velog.velcdn.com/images/hyphen_/post/6b03e65c-6f87-46ff-9571-2378fef01764/image.jpg" alt=""></p>
<p>우리 팀은 사이드 프로젝트도 하나 항해와 병행해서 진행했다. 기획부터 디자인 컨펌, 역할 분담까지! 시간이 날 때마다 짬짬이 준비해서 발표 전날엔 다 같이 모여 남은 기능을 최대한 마무리하고, 발표 준비까지 함께 했다.</p>
<p>나는 프로젝트 세팅과 메인 페이지 개발을 맡았다. 그동안 프로젝트를 시작할 때마다 세팅을 대충 넘긴 경우가 많아서 이번엔 제대로 경험해보자는 마음으로 자원했다.
프로젝트 세팅에는 총 5시간이 걸렸다. prettier, eslint, husky 설정은 물론이고, vite, react, typescript, zustand, tailwindcss 기반의 팀 스펙을 반영해서 바로 사용 가능하게끔 설정했다. 중간에 eslint가 적용 안 되는 문제나 버전 충돌 이슈 같은 건 있었지만, 전체적으로는 큰 문제 없이 잘 마무리했다.</p>
<p>세팅이 어느 정도 마무리될 즈음, 작업 방식에 대한 고민도 있었다. 따로 Jira나 Notion을 쓰지 않고 있어서, 설정 없이 간단하게 현황을 공유할 수 있는 방법이 뭐가 있을까 고민하다가 issue, PR 템플릿을 커스텀해서 적용할 수 있다는 걸 알게 됐다. 여러 레포를 참고해서 템플릿을 만들었고, 덕분에 누가 어떤 작업을 하는지 한눈에 보기 쉬워졌다.
<span style="color: grey">물론 배포 임박해서 급하게 PR 올릴 땐 템플릿 내용 채울 여유도 없었지만...</span></p>
<p>심화 과제를 진행할 때도, 사이드 프로젝트 발표 준비 날도, 우리 팀은 오랜 시간 함께 모여 끝까지 참여해줬다. 진짜 감동이었고, 역시 우리 팀 최고 🙌 </p>
<br/>

<hr>
<br/>

<p><img src="https://velog.velcdn.com/images/hyphen_/post/21149390-087b-443a-bb64-fcd2ef68d4f8/image.png" alt=""></p>
<p>zep에서 멋지게 발표하는 우리 팀.</p>
<p>이번 사이드 프로젝트 주제는 항해 롤링페이퍼였다. 메인 페이지에선 팀별 리스트를 출력하고, 각 팀의 상세 페이지로 이동할 수 있도록 구성하는 간단한 구조였다.
메인 페이지에서는 헤더와 카드형 그리드 레이아웃을 구현했고, PC와 모바일 모두에서 유연하게 작동하도록 반응형 디자인도 적용했다.</p>
<p>프로젝트 세팅은 눈에 잘 안 보이지만, 생각보다 손이 많이 가는 작업이었다. 개발을 시작하기 전에 꼭 해야 하는 일이다 보니, 일정이 빠듯하게 느껴지기도 했다.
특히 레이아웃 작업은 늘 그렇듯 처음보다 나중이 더 어렵다.</p>
<p>기능 구현이 끝난 뒤 테스트하면서 수정할 일이 생겼는데, 이미 짜여진 구조 안에서 변경을 하려다 보니 아예 새로 만드는 것보다 더 고민이 많았다.
그럼에도 프로젝트의 기반을 다지는 역할을 맡아, 팀원들이 안정적으로 개발을 이어갈 수 있도록 준비한 건 나름 의미 있었던 시간이었다.</p>
<p>마지막으로 발표 전에 우당탕탕 사소한 이슈들도 있었지만, 재미있게 잘 마무리해서 너무 좋았다.
다들 항해하면서 이런 팀 프로젝트를 꼭 한번쯤 해볼 기회가 생겼으면 좋겠다.
힘들지만, 진짜 유익하고 오래 기억에 남을 경험이었다.</p>
<p><span style="color: grey">GitHub Repository는 비밀 입니다 ㅎㅎ 미숙한 부분을 보여주고 싶지 않았던 우리 팀 귀엽다.</span></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] 테스트 코드 그거 어떻게 하는건데?]]></title>
            <link>https://velog.io/@hyphen_/WIL-7%EC%A3%BC%EC%B0%A8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EA%B7%B8%EA%B1%B0-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%98%EB%8A%94%EA%B1%B4%EB%8D%B0</link>
            <guid>https://velog.io/@hyphen_/WIL-7%EC%A3%BC%EC%B0%A8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EA%B7%B8%EA%B1%B0-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%98%EB%8A%94%EA%B1%B4%EB%8D%B0</guid>
            <pubDate>Sat, 17 May 2025 06:00:34 GMT</pubDate>
            <description><![CDATA[<p style="color: grey">팀원분의 블로그를 보다가 마음에 들어 나도 사진 한장 넣어보았다. 📸</p><br/>
드디어! 기다리고 기다리던 대망의 테스트 코드 작성 챕터가 도착했다. 
내가 그동안 작성했던 테스트 코드라고는... 함수에 대한 간단한 단위 테스트 몇 줄이 전부였기에, 비동기 처리? 테스트 코드 그거 어떻게 하는건데? 상태였다. 

<p>그럼에도 불구하고 처음부터 Hard 난이도에 도전!
하지만… 시간 분배를 제대로 못 해서 수요일쯤에는 Medium 난이도로 급하게 이사를 갔다. 솔직히 나에겐 Easy가 가장 잘 맞는 난이도이긴 하지만, &quot;하면 된다!&quot; 는 마음으로 밀어붙였다가 결국 두 가지 이슈 때문에 Medium조차 완벽하게 마무리하지 못했다. <span style="color: grey">이번에는 한 개도 통과 못 할지도... 😢</span></p>
<br/>
<br/>

<p>** 성공적으로 마무리하지 못한 두 가지 이유 **</p>
<li>무엇을 해야 하는지 전부 파악하지 않고 시작했다. </li>
<li>너무 높은 난이도를 설정한 것에 비해 러닝 타임이 부족했다. </li>

<br/>
<br/>

<p>제출기간이 끝나고 나니 “아... 이렇게 했으면 완벽하게 끝낼 수 있었겠다...” 하는 생각이 들었다. 과제가 발제되고 나서 일요일~화요일까지는 util, hook 테스트만 했는데, 도대체 무슨 자신감으로 그렇게 여유를 부렸는지 모르겠다. </p>
<p>아마 속으로는, &quot;단위 테스트 코드 작성도 쉬웠으니까 통합테스트도 어렵지 않겠지~&quot;라고 생각했던 것 같다... (절대 아니야..!ㅠㅠ) 결국 심화 과제는 손도 못 대고, 기본 과제에서만 삽질을 왕창 했다.</p>
<br/>
<br/>

<p>처음 repository를 fork 하고 VSCode에서 세팅할 때부터 시련이 시작됐다. <code>npm install</code>을 하자마자 버전 충돌 오류가 떴고, 그걸 해결하려고 안 쓰는 라이브러리를 굳이 지워가며 기묘한 방식으로 진행했다. 겉보기엔 해결된 것 같았지만, 나중에 보니 그 탓에 test 로그에 <code>warning</code>이 계속 떴다. 결국엔 Notion에 있는 프로젝트 세팅 가이드를 보고 안 쓰던 <code>pnpm</code>을 설치해서 사용했더니 문제는 눈녹듯이 사라졌다. </p>
<p>이래서 가라는 길로 가야 해...
요리도 결국 레시피를 따라가지 않으면 망하는 것처럼. 🍳</p>
<p><img src="https://velog.velcdn.com/images/hyphen_/post/79bf22c6-402d-4c46-97b4-7556719eba71/image.gif" alt=""></p>
<p>기본 과제라도 마무리하려 했지만 끝까지 발목을 잡았던 치명적인 에러가 있었으니... 바로 timeout 에러, CI에서 확인한 로그와는 다르게 렌더링 결과가 나오면서 문제가 계속됐다.</p>
<p>“다른 사람들은 어떻게 했을까?” 하고 여기저기 코드를 구경했지만, 내 구조와 큰 차이는 없었다.</p>
<p>그러다 코드 리뷰 시간을 통해 실마리를 찾을 수 있었다. 나처럼 테스트 코드가 처음인 분이었는데도, Hard 난이도를 완벽하게 수행한 팀원분의 코드리뷰를 통해 많이 배웠다. </p>
<p>그 분이 알려주시길, &quot;캘린더를 사용하는 부분이라면 fakeTime을 설정해야 하고, waitFor에서 timeout 에러가 난다는 건 내부 테스트 평가가 통과하지 않았기 때문이에요.&quot; 그 말을 듣고, &quot;아… 나도 정말 시간 분배만 잘했더라면…&quot; 하는 후회와 함께 이번 과제 과정을 되돌아볼 수 있었다. 테스트 코드 작성은 처음이라, 오류가 나도 무슨 오류인지 감조차 못 잡을 때가 많았다.</p>
<br/>

<p><strong>다음 과제를 시작하기 전에 꼭!</strong>
msw, 자주 쓰이는 테스트 메서드들의 동작 원리를 확실히 이해하고 넘어가야겠다고 다짐했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] 클린 코드의 마지막]]></title>
            <link>https://velog.io/@hyphen_/WIL-6%EC%A3%BC%EC%B0%A8-2%EB%B2%88%EC%A7%B8-%EC%B1%95%ED%84%B0-%ED%81%B4%EB%A6%B0-%EC%BD%94%EB%93%9C%EC%9D%98-%EB%A7%88%EC%A7%80%EB%A7%89-%EB%8B%A8%EA%B3%84</link>
            <guid>https://velog.io/@hyphen_/WIL-6%EC%A3%BC%EC%B0%A8-2%EB%B2%88%EC%A7%B8-%EC%B1%95%ED%84%B0-%ED%81%B4%EB%A6%B0-%EC%BD%94%EB%93%9C%EC%9D%98-%EB%A7%88%EC%A7%80%EB%A7%89-%EB%8B%A8%EA%B3%84</guid>
            <pubDate>Sun, 04 May 2025 09:04:36 GMT</pubDate>
            <description><![CDATA[<p>글을 시작하기 전, 이번 주의 소감을 짧게 요약하자면 딱 이 말이 떠오른다.
<strong>“모래사장에서 바늘 찾기.”</strong></p>
<p><img width="400" src="https://velog.velcdn.com/images/hyphen_/post/f2c34bb7-1733-4b4b-aa85-8e22efc82115/image.jpg"></img></p>
<p>4~5주차부터 클린코드 과제를 빌드업하면서, 마침내 가장 힘들다는 난관에 직면해버렸다.
FSD 아키텍처를 적용해 의존성을 분리하는 건 기본이고, Tanstack Query까지 도입해 서버 통신 로직도 따로 관리해야 했다. 한 파일에 700줄이 넘는 코드를 분리하면서 “이건 잘 구현했다” 싶으면서도, 동시에 “이게 과연 좋은 코드일까?” 하는 의문이 리팩토링 내내 머릿속에 맴돌았다.</p>
<p>컴포넌트를 분리하고, 의존성 맞춰서 폴더 구조도 재정비하고, 공통으로 쓰이는 함수들은 재사용 가능하게 따로 분리하고, 그런 공용 로직들은 전부 shared 폴더로 옮기고...
이런 작업들을 3~4일 동안 반복했다. 일요일부터 수요일까지 — 물론 하루 종일 붙잡고 있었던 건 아니고, 짬짬이 하다 보니 더 오래 걸린 것처럼 느껴졌던 것 같기도. 아무튼, 아키텍처를 적용하면서 구조를 정리한 것만 해도 꽤 오랜 시간이 걸렸고, 마치 끝없이 펼쳐진 바다를 헤엄치는 기분이었다. 정말 첫 실무에서 내가 짠 코드를 다시 보는 듯한, 그런 느낌.
<br/></p>
<p>과제를 하면서 여러 고민이 들었다.</p>
<p> “Tanstack Query...? 이게 대체 뭐지?”</p>
<p>...</p>
<p>“러닝타임이 너무 길어서 PASS는 어렵겠는데... 제출은 가능할까?”</p>
<br/>
이렇게 우왕좌왕하며 tanstack-query에 대해 좀 더 알아보니, 예전에 사용한 react-query가 다양한 프레임워크에서도 쓸 수 있도록 리디자인되면서 이름까지 바뀐 것이더라.
다행히도 사용 방식은 예전과 크게 다르지 않아서, 기본적인 개념만 다시 익힌 뒤에는 큰 무리 없이 작업을 이어갈 수 있었다.


<p><img width="400" src="https://velog.velcdn.com/images/hyphen_/post/4a87ad18-f860-44e3-95f0-e54ba4700e7d/image.jpg
" ></img></p>
<p>이번 과제를 통해 &quot;관심사 분리&quot;라는 개념을 다시 한 번 몸으로 체감하게 되었다. 단순히 폴더만 나누는 게 아니라, 각 레이어가 어떤 역할을 가져야 하는지, 어디서 어디까지 책임져야 하는지를 계속 고민하면서 작업했기 때문에 분명 이전보다 더 의식적인 구조 설계를 할 수 있었던 것 같다.</p>
<p>다만 여전히 features 레이어를 어떻게 정의하고, 어디까지 끌어와야 할지에 대한 감은 잘 잡히지 않았다. 구조적으로는 분명히 필요하고 유용한 개념인데, 막상 구현하려고 하면 애매하게 겹치는 영역들이 많아서 손을 대기가 망설여졌던 것도 사실이다. 이번에는 시간상의 이유로 features는 과감히 생략했지만, 다음 과제에서는 꼭 한 번 실전에서 적용해보며 흐름을 잡아보고 싶다.</p>
<p>또한 성능적인 측면에 대해서는 아직 갈 길이 멀다고 느꼈다. 이번에는 구조 분리에 대부분의 시간을 투자하면서 최적화는 거의 손대지 못했는데, 다음에는 불필요한 렌더링을 줄이기 위한 memoization이나 context 분리, tanstack-query의 캐싱 전략을 보다 효과적으로 활용하는 방향까지도 고민해보며 한층 나은 코드로 발전시켜보고 싶다.</p>
<p><img src="https://velog.velcdn.com/images/hyphen_/post/4781d38c-069e-439e-ae3a-f61fe614342e/image.png" alt=""></p>
<p>그렇게 과제 마감일 전날, 배포까지 마무리하느라 두 시간 넘게 진이 빠졌고, PR까지 작성한 뒤에서야 겨우 마음이 놓여 눈을 감을 수 있었다. 사실 ALL PASS는 기대도 안 했는데, 아직까지는 다행히 과제 통과율 100%...!!</p>
<p>다음 챕터도 무사히, 헤엄쳐 지나갈 수 있기를 🙏</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] props가 많은 컴포넌트]]></title>
            <link>https://velog.io/@hyphen_/WIL-5%EC%A3%BC%EC%B0%A8-</link>
            <guid>https://velog.io/@hyphen_/WIL-5%EC%A3%BC%EC%B0%A8-</guid>
            <pubDate>Sat, 26 Apr 2025 03:56:21 GMT</pubDate>
            <description><![CDATA[<h3 id="1-문제">1. 문제</h3>
<p>컴포넌트를 분리하고 커스텀 훅과 유틸 함수를 도입하면서 props 개수가 많아졌다. 그 결과 가독성이 떨어지고, 테스트 코드에서도 반복적으로 많은 props를 일일이 넘겨줘야 하는 번거로움이 생겼다.</p>
<h3 id="2-시도"><strong>2. 시도</strong></h3>
<p>관련된 로직끼리 묶어서 부모 컴포넌트에 상태와 함수를 모은 뒤, 하위 컴포넌트에 props로 전달하는 구조를 유지했다. 동시에 기능별 책임을 커스텀 훅과 유틸 함수로 분리하면서 구조를 조금 더 깔끔하게 만들려고 했다.</p>
<h3 id="3-해결"><strong>3. 해결</strong></h3>
<p>모든 상태와 함수를 직접 props로 넘기는 대신, 일부 컴포넌트를 더 세분화하거나 공통 로직을 커스텀 훅으로 옮겨서 복잡도를 줄이려고 했다. 시간이 허락하는 범위 내에서 테스트 코드도 새로운 구조에 맞게 수정했다.</p>
<h3 id="4-알게된-것"><strong>4. 알게된 것</strong></h3>
<p>props 전달만으로 구조를 유지하는 데에는 한계가 있다는 걸 느꼈고, 상태와 로직 관리를 더 유연하게 할 수 있는 방법(Context나 Provider 패턴 등)이 필요하다는 걸 알게 됐다.</p>
<p>기능 구현과 테스트 사이에서 균형을 잡는 게 쉽지 않다는 걸 체감했고, 테스트를 고려한 컴포넌트 구조 설계가 얼마나 중요한지 새삼 느꼈다.</p>
<h3 id="keep--현재-만족하고-계속-유지할-부분"><strong>Keep : 현재 만족하고 계속 유지할 부분</strong></h3>
<ul>
<li>기능을 추가하기 전에 구조와 책임 분리를 먼저 고민한 점</li>
<li>테스트 코드 작성에 대한 부담감을 줄이고 직접 작성해본 경험</li>
<li>멘토링에서 받은 피드백을 토대로 개선 방향을 찾아 적용한 점</li>
</ul>
<h3 id="problem--개선이-필요하다고-생각하는-문제점"><strong>Problem : 개선이 필요하다고 생각하는 문제점</strong></h3>
<p>P1. props가 많아지면서 컴포넌트 간 결합도가 높아졌고, 테스트 코드에서도 많은 데이터를 반복적으로 넘겨줘야 해서 유지보수가 번거로워졌다.</p>
<h3 id="try--문제점을-해결하기-위해-시도해야-할-것"><strong>Try : 문제점을 해결하기 위해 시도해야 할 것</strong></h3>
<p>P1. 관련 props를 묶어 객체로 전달하거나, 상태 관리를 Context로 위임해서 구조를 간결하게 만들고 싶다.
또한 props drilling을 줄일 수 있는 구조나 패턴(Context API, Presenter-Container 패턴 등)을 더 학습하고 적용해보면 좋을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] 코드 개선 시작 (with. 리팩토링)]]></title>
            <link>https://velog.io/@hyphen_/WIL-4%EC%A3%BC%EC%B0%A8-%EC%BD%94%EB%93%9C-%EA%B0%9C%EC%84%A0-%EC%8B%9C%EC%9E%91</link>
            <guid>https://velog.io/@hyphen_/WIL-4%EC%A3%BC%EC%B0%A8-%EC%BD%94%EB%93%9C-%EA%B0%9C%EC%84%A0-%EC%8B%9C%EC%9E%91</guid>
            <pubDate>Mon, 21 Apr 2025 12:31:08 GMT</pubDate>
            <description><![CDATA[<h3 id="1-문제">1. 문제</h3>
<p>과제를 하면서 가장 어려웠던 건 리팩토링 중 기존 로직이 망가지지 않도록 유지하는 것이었다. 코드가 처음부터 모듈화가 안 되어 있고, 한 파일에 기능이 덩어리처럼 뭉쳐 있어서, 뭔가 수정하면 어디서 오류가 나는지 파악하기가 힘들었다. 특히 calcCart() 같은 거대한 함수는 처음 봤을 때 눈앞이 깜깜할 정도였다.</p>
<h3 id="2-시도">2. 시도</h3>
<p>예전처럼 코드를 한꺼번에 고치다가 고생한 적이 있어서, 이번엔 처음부터 리팩토링 전략을 세우고 차근차근 구조 파악부터 시작했다. 기능 단위로 잘게 쪼개서, 하나의 파일에는 하나의 역할만 주도록 의식하면서 정리했다. 관련 기능들은 도메인 기준으로 묶어서 디렉토리를 나눴고, 네이밍도 의미를 명확하게 바꾸려고 노력했다.</p>
<h3 id="3-해결">3. 해결</h3>
<p>큰 기능들을 잘게 나누고 모듈화하면서, 코드가 훨씬 명확해졌고 기존 로직도 안정적으로 유지할 수 있었다. 예를 들어 calcCart()는 calculateCartTotals.js로 옮기고, 내부 로직도 할인 계산, 수량 파싱 같은 걸 따로 분리했다. 덕분에 코드가 훨씬 읽기 좋아졌고, 테스트나 유지보수도 쉬워졌다.</p>
<h3 id="4-알게된-것">4. 알게된 것</h3>
<p>이번에 느낀 건 모듈화는 단순히 파일을 나누는 게 아니라, 코드의 책임을 명확히 나누고 결합도를 줄이는 설계 방식이라는 거였다. 또, 기능 단위로 나누는 게 얼마나 중요한지도 실감했다. 이후 React로 전환하면서도 이 구조가 그대로 이어졌고, 컴포넌트 분리나 상태 관리처럼 JS에서의 모듈화와 React 설계가 이어지는 지점을 자연스럽게 체감할 수 있었다.</p>
<h3 id="✅-keep--잘했다고-생각한-부분">✅ Keep : 잘했다고 생각한 부분</h3>
<ul>
<li>리팩토링 전에 전체 구조를 먼저 파악하고 방향을 잡은 점</li>
<li>기능 단위로 모듈화하고 도메인 기준으로 디렉토리를 정리한 점</li>
<li>함수와 파일에 명확한 책임을 부여한 점</li>
<li>React 전환 과정에서도 구조 고민을 계속 이어간 점</li>
</ul>
<h3 id="❗️problem--아쉬웠던-점">❗️Problem : 아쉬웠던 점</h3>
<p>P1. 커밋 단위를 너무 크게 잡은 것
리팩토링은 나눠서 했는데, 커밋은 덩어리로 해버린 경우가 많았다. 그래서 결과만 보면 어떤 변경을 왜 했는지 흐름이 잘 안 드러나는 게 아쉬웠다. 히스토리를 설명하기에 부족했다는 느낌이 들었다.</p>
<h3 id="🔧-try--다음-주에-개선할-점">🔧 Try : 다음 주에 개선할 점</h3>
<p>P1. 커밋 단위를 더 작고 명확하게 나누기</p>
<ul>
<li>하나의 수정에는 하나의 커밋만 하기</li>
<li>커밋 메시지에 무엇을, 왜 바꿨는지 간단하게라도 설명하기</li>
<li>리팩토링의 흐름이 자연스럽게 보이도록 기록하는 습관 들이기</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] 항해 플러스 프론트엔드 직접 경험한 첫번째 챕터]]></title>
            <link>https://velog.io/@hyphen_/WIL-%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%A7%81%EC%A0%91-%EA%B2%BD%ED%97%98%ED%95%9C-%EC%B2%AB%EB%B2%88%EC%A7%B8-%EC%B1%95%ED%84%B0</link>
            <guid>https://velog.io/@hyphen_/WIL-%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%A7%81%EC%A0%91-%EA%B2%BD%ED%97%98%ED%95%9C-%EC%B2%AB%EB%B2%88%EC%A7%B8-%EC%B1%95%ED%84%B0</guid>
            <pubDate>Sat, 12 Apr 2025 06:21:38 GMT</pubDate>
            <description><![CDATA[<p>웹과 모바일 분야에서 3년 정도 실무 경험이 있다. 다양한 프로젝트를 경험했지만, 아직 지식적으로 부족한 부분이 많다고 느껴 항해 플러스 프론트엔드에 승선하게 되었다!</p>
<br/>

<p>아래는 우리팀 코어 타임 사진 ⛴️
코어 타임에 모여서 모각코를 진행한다. 모르는 것이 있을때 언제든지 도움 요청 가능 🚨
<img src="https://velog.velcdn.com/images/hyphen_/post/5c58d11f-b607-42b4-bb49-cdbfdac20521/image.png" alt=""></p>
<p>이번 챕터를 시작하면서 스터디를 진행하면서의 목표와 프론트엔드 개발자 공부 목표를 나누어 설정했다.</p>
<p><strong>스터디 목표</strong></p>
<ul>
<li>과제 PASS 100% 달성</li>
<li>모르는 부분은 넘어가지 않고 찾아보고 정리하기</li>
</ul>
<p><strong>지식 목표</strong></p>
<ul>
<li>JavaScript Deep Dive</li>
<li>VanillaJS로 과제를 하면서 React 관련 핵심 지식 공부</li>
</ul>
<br/>

<p>이번 챕터를 마무리하면서 <strong>기록</strong>의 중요성과 <strong>코드 리뷰</strong>를 왜 해야 하는지 다시 한번 느꼈다.
여러 과제를 진행하면서 시간이 지나면 점점 잊혀지는 부분들이 생겼는데, 과제가 끝날 때마다 기록을 남기니까 ‘내가 이런 걸 했었구나’ 하고 확인할 수 있었고,
모르는 게 생기거나 문제가 발생했을 때 어떻게 해결했는지를 적어둔 덕분에 같은 상황이 다시 와도 빠르게 대응할 수 있었다.</p>
<p>그리고 다양한 사람들과 코드 리뷰를 하면서 내가 어떤 부분을 잘 모르고 있었는지, 어떻게 하면 더 좋은 코드를 짤 수 있을지를 감 잡을 수 있었다.
이런 경험들이 앞으로 더 크게 성장하는 데 정말 많은 도움이 될 거라는 생각이 들었다.</p>
<p><br/><br/></p>
<p>이번 챕터를 하면서 얻은 <strong>성취</strong>는 크게 두 가지였다.
하나는 시간 분배를 어떻게 해야 할지 조금씩 감을 잡게 된 거고, 다른 하나는 ‘내가 뭘 모르고 있는지’를 더 분명하게 알게 됐다는 점이다.</p>
<p>처음 과제를 받았을 땐 여유롭게 시작했다가 막판에 시간에 쫓겨 겨우 제출하곤 했는데, 점점 과제 제출을 미리 끝내고 PR도 더 꼼꼼히 작성하거나 부족한 부분을 복습할 수 있을 만큼 시간 분배를 효율적으로 할 수 있게 됐다. 물론 개인적인 약속이나 일 때문에 모든 시간을 여기에 쓸 수는 없지만 말이다.</p>
<p>스터디를 시작하기 전엔 JavaScript Deep Dive 책을 혼자 스터디하면서 그냥 ‘기초를 좀 더 깊게 공부하자’는 막연한 생각뿐이었는데, 이번 스터디를 진행하면서부터는 명확한 목표가 생겼고, 어떤 부분에서 내가 약한지도 훨씬 잘 보이게 됐다. 물론 지금 당장 모든 걸 완벽히 학습할 수는 없지만, 어떤 부분부터 보완해 나가야 할지 단계별로 머릿속에 정리된다는 점에서 꽤 만족스럽다.</p>
<br/>
<br/>

<p>React Deep Dive를 두번째 목표로 잡았었는데 이번 챕터로만 끝내기에는 양도 많았고 시간도 부족하여 VirtualDOM과 React Hook에 대해서 나마 학습할 수 있었다. 두가지를 공부하면서 React의 렌더링 방식 등 배워야 할 것들이 산더미지만 이 목표를 항해가 마무리 될 때까지 달성할 수 있도록 노력할 예정이다. 지금도 노션으로 작성한 학습 내용을 <a href="https://github.com/ph-1dnjs/Programming-Learning">GitHub</a>에 연동하여 올리고 있다.</p>
<br/>
<br/>

<p>목표를 크게 잡다 보니 세세한 목표들은 머릿속으로만 정리되어 있는 경우가 많다. 지금까지는 배운 내용을 중심으로만 PR에 정리해왔는데, 앞으로는 과제를 진행하면서 겪은 트러블슈팅 과정을 좀 더 자세히 기록하는 연습을 해보려고 한다.</p>
<p>현업에서도 특정 부분에서 문제가 발생했을 때, 어떤 지점에서 시간을 썼고 어떤 시도를 해봤는지를 정리하는 게 중요한데, 아직 그런 경험이 많지 않다 보니 나도 모르게 그런 기록에 소홀했던 것 같다. 만약 코드를 구현하면서 뚜렷한 문제를 겪지 않았다면, 완성된 코드에 이것저것 새로운 걸 시도해보면서 생긴 경험들을 중심으로 적어보려고 한다.</p>
<br/>
<br/>

<p>나의 강점 중 하나는 <strong>기록</strong>을 보기 좋게 잘한다는 점이다. 평소에도 부족한 기억력을 보완하기 위해 기록을 중요하게 생각하고, 잊었을 때 다시 보며 리마인드하는 용도로도 자주 활용한다. 무엇보다 기록하는 과정에서 머릿속으로 한 번 더 정리되기 때문에 더 오래 기억에 남는다. 이런 강점을 살려서, 앞으로는 내가 배운 내용이나 깊이 있게 파고들었던 것들을 블로그에 정리해보는 활동을 해보면 좋을 것 같다.</p>
<br/>
<br/>

<p>과제를 진행하면서 항상 테스트도 함께 병행해왔다. 대부분 이미 구현되어 있는 테스트 로직을 통과시키는 수준이었지만, 이전까지는 간단한 단위 테스트만 작성해봤던 터라 새로운 경험이었다. 아직 테스트 코드 내부 로직을 깊게 들여다보진 못했지만, 앞으로는 <strong>TDD 방식의 개발</strong>과 <strong>성능 최적화</strong> 같은 부분에서도 실력을 키워야겠다는 필요성을 많이 느꼈다. 이런 영역은 실제 서비스에서의 코드 품질과 직결되는 만큼, 경험을 차근차근 쌓아나가야 할 것 같다.</p>
<p>단순히 ‘돌아가기만 하는 코드’에서 벗어나, ‘가치 있는 코드’를 작성하는 것이 이번 스터디에 참여한 이유 중 하나였고, 테스트와 최적화 같은 부분에서의 성장만으로도 그 목적에 더 가까워질 수 있을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] SPA 구현 잘 하고 있는걸까?]]></title>
            <link>https://velog.io/@hyphen_/WIL-2%EC%A3%BC%EC%B0%A8-%EC%9E%98-%ED%95%98%EA%B3%A0-%EC%9E%88%EB%8A%94%EA%B1%B8%EA%B9%8C</link>
            <guid>https://velog.io/@hyphen_/WIL-2%EC%A3%BC%EC%B0%A8-%EC%9E%98-%ED%95%98%EA%B3%A0-%EC%9E%88%EB%8A%94%EA%B1%B8%EA%B9%8C</guid>
            <pubDate>Fri, 04 Apr 2025 16:23:19 GMT</pubDate>
            <description><![CDATA[<h3 id="1-문제-과제-프로젝트를-진행하면서-부딪혔던-기술적인-문제">1. 문제 <strong>(과제, 프로젝트를 진행하면서 부딪혔던 기술적인 문제)</strong></h3>
<p>이전 과제에서는 라우팅을 직접 구현하고 이벤트 관련하여 작업을 진행 하였는데, 이번에는 <code>VirtualDOM</code>을 직접 구현해야했다. DOM이란 것이 무엇인지는 알고 있지만 VirtualDOM에 대해서는 공부가 필요했다. VirtualDOM이 무엇인지, 어떻게 사용되는 것 인지 동작하는 프로세스를 이해해야 구현할 수 있을 것 같았다. </p>
<h3 id="2-시도"><strong>2. 시도</strong></h3>
<p>과제를 구현하기 위해 제일 처음 VirtualDOM이 무엇인지에 대해 알아보았다.</p>
<p><strong>VirtualDOM이란?</strong></p>
<blockquote>
<p>DOM에 대한 내용은 자세히 알고 있지만 VirtualDOM이란 정확히 무엇이고 왜 사용하는 것일까 찾아보았다.</p>
</blockquote>
<p> VirtualDOM은 실제 DOM에 직접적인 변경을 가하기 전에 메모리 상에서 가상의 DOM을 먼저 변경하는 방식이다. DOM에 변화가 있을 때, 렌더 트리를 재생성하고 리플로우 및 리페인트 과정을 거치며, 이로 인해 모든 요소에 대한 연산을 반복하게 되어 전체적인 프로세스가 비효율적이다. 하지만 VirtualDOM은 이러한 문제를 해결하는 방법으로, 실제 DOM을 조작하는 대신, 메모리 내에 존재하는 가상의 DOM을 업데이트한다. 이후, 이전 Virtual DOM과 새로 업데이트된 VirtualDOM을 비교하여 실제로 변경이 필요한 부분만을 찾아 실제 DOM에 적용한다.</p>
<p>💡 즉, Virtual DOM은 실제 DOM을 변경하기 전에 <strong>가상 DOM에서 먼저 변화를 처리</strong>하여 <strong>효율성을 높이는 방식</strong>이다.</p>
<br/>

<p><strong>Diff 알고리즘</strong></p>
<blockquote>
<p>Diff 알고리즘이란 무엇일까? 어디에, 왜 사용하는지 찾아보았다.</p>
</blockquote>
<p> Virtual DOM을 설명하면서 실제 DOM과 Virtual DOM을 비교해 변경된 부분만 실제 DOM에 반영한다고 했는데, 이때 변경된 부분을 찾아내는 알고리즘이 바로 Diff 알고리즘이다.</p>
<p> Diff 알고리즘은 변경된 부분을 찾아내어 updateElement에서 실제 DOM을 수정, 추가, 삭제하는 역할을 한다. renderElement와 updateElement에 Diff 알고리즘을 적용하면, oldNode와 newNode를 비교하여 변경된 부분만 반영할 수 있다. 변경 사항을 비교하는 로직은 다음과 같다.</p>
<ul>
<li>기존 노드가 없는 경우: appendChild</li>
<li>업데이트 될 노드가 없는 경우: removeChild</li>
<li>텍스트 노드인 경우: replaceChild</li>
<li>태그 이름(type)이 다른 경우: textContent 업데이트</li>
<li>태그 이름(type)이 같은 경우: props 및 children 비교</li>
</ul>
<p>변경 사항을 모두 확인한 후, 자식 요소들을 비교하기 위해 maxLength를 구하고 이를 기반으로 반복문을 실행하여 모든 자식 요소를 비교한다.</p>
<p> Diff 알고리즘은 변경된 노드만 업데이트하기 때문에, 속성(attribute)도 비교하여 변경된 부분만 수정한다. 기존 속성과 새로운 속성을 순회하면서 이벤트 속성인 경우 기존 이벤트를 제거하고, 새로운 속성에서 해당 이벤트를 찾아 업데이트한다. 그 외 다른 속성에 대해서도 변경 사항을 업데이트하며, 새로운 속성에 대해서 추가 작업을 한다.</p>
<p> 구현하기 전에는 막막했지만, 알고리즘을 이해하고 차근차근 구현해보니 Virtual DOM이 어떻게 동작하는지 더 깊이 알 수 있었다. 이를 통해 Virtual DOM을 사용하는 이유와, Browser DOM과의 차이점 및 동작 방식에 대해 배울 수 있었다.</p>
<br/>

<p>VirtualDOM을 구현하면서 updateElement 관련 로직에는 Diff 알고리즘이 적용되는 것을 알게 되어 추가로 학습을 진행했다.</p>
<h3 id="3-해결"><strong>3. 해결</strong></h3>
<p>VirtualDOM을 이해하는 것과 별개로 구현하는 것은 쉽지 않았다. 어떤 방식으로 구현해야 하는 지 처음에 감이 잡히지 않았던 것과 지식 베이스가 거의 0에 수렴하던 부분에 대해 작업하려니 압도되는 느낌도 없지않아 있었다. 몰랐던 부분은 학습하며 진행했지만 과제를 진행하면서 기본 과제, 심화 과제의 구현 순서를 참고해서 진행하니 순조롭게 마무리 할 수 있었다.</p>
<h3 id="4-알게된-것"><strong>4. 알게된 것</strong></h3>
<p>과제를 구현하면서 오류가 발생하거나 이 코드는 어떤 역할을 하는 것인지 궁금했던 것이 있었다.</p>
<h4 id="📍-commit-오류">📍 commit 오류</h4>
<p> 이전 과제부터 첫 작업 완료 후 commit을 할 때 마다 아래 오류가 발생했었다.
<code>npm error code ETARGET</code></p>
<p> 이 문제를 해결하려고 30분 씩 씨름했었는데 Error Log를 확인해보면 마지막 줄에 husky 관련해서 문제가 있는 것을 알 수 있었다.
<code>husky - pre-commit script failed (code 1)</code></p>
<p><a href="https://github.com/typicode/husky/issues/1014">https://github.com/typicode/husky/issues/1014</a> 결과적으로는 해당 사이트를 참고해서 문제를 해결하였는데 husky 설정이 잘 안되어 있어서 그런 것 같다. 다음에도 이런 문제가 생기면 바로 해결할 수 있을 것 같다.</p>
<br/>

<h4 id="📍-jsx-createvnode-">📍<code>/** @jsx createVNode */</code></h4>
<p>프로젝트의 구조를 살펴보던 중, 컴포넌트들의 첫 번째 줄에 일관되게 작성된 코드가 눈에 띄었다. /** @jsx createVNode */가 무엇을 위해 작성된 것인지 궁금해졌다.</p>
<p>조사해 보니, 해당 코드는 <strong>JSX 변환을 사용자 정의 함수 <code>createVNode</code>로 처리하도록 지정하는 역할</strong>을 한다고 한다.
이 프로젝트에서는 VirtualDOM을 직접 구현하면서 React를 사용하지 않고 자체적인 렌더링 방식을 도입했다.
그렇기 때문에 JSX를 해석하고 변환할 함수가 필요한데, Babel이 JSX를 변환할 때 기본적으로 <code>React.createElement</code> 대신 <code>createVNode</code>를 사용하도록 지정하기 위해 이 주석을 추가하는 것이다.</p>
<p>📝 <strong>정리</strong>
<code>React.createElement</code> 대신 <code>createVNode</code>를 이용하여 <strong>JSX를 VirtualDOM으로 변환하는 역할</strong>을 하도록 지정하는 것!</p>
<h4 id="📍-weakmap을-이해한-후-코드-리팩토링">📍 WeakMap을 이해한 후 코드 리팩토링</h4>
<p><code>WeakMap</code>을 학습하면서 <strong>키값으로 객체만 사용할 수 있다</strong>는 점을 알게 되었다. 하지만 기존 코드에서는 <code>WeakMap</code>의 키값으로 문자열(<code>eventType</code>)을 사용하고 있었고, 단순히 메모리상의 이점이 있다고 생각하여 그대로 유지했었다.
👉 그런데 객체만 키로 사용할 수 있는 <code>WeakMap</code>에서 문자열 키를 사용했는데도 오류가 발생하지 않는 이유는 무엇일까?</p>
<p><strong>기존 코드 문제점</strong></p>
<pre><code>const eventListenersMap = new WeakMap();</code></pre><ul>
<li><code>eventListenersMap</code>의 키값으로 <code>eventType</code>(문자열)을 사용했지만, <code>WeakMap</code>은 <strong>객체만 키로 사용할 수 있다.</strong></li>
<li>즉, 문자열을 키값으로 사용하면 정상적으로 동작하지 않으며, 사실상 의미 없는 코드가 된다.</li>
<li>그런데도 오류가 발생하지 않은 이유는, <code>WeakMap[eventType]</code>과 같이 배열 표기법을 사용했기 때문이다.<ul>
<li>이 방식은 <code>WeakMap</code>의 내부 메서드가 아니라 일반적인 객체의 속성을 추가하는 방법이다.</li>
<li>따라서 <code>WeakMap</code>의 기능이 아니라, 그냥 일반 객체처럼 동작했던 것. (🚨즉, <code>WeakMap</code>을 사용한 의미가 없었다!)</li>
</ul>
</li>
</ul>
<br/>

<p><strong>리팩토링 과정</strong></p>
<p>🪄 문제 해결 방법:</p>
<ul>
<li><code>WeakMap</code> 대신 <code>Map</code>을 사용하여 <code>eventType</code>을 키로 저장</li>
<li>각 <code>eventType</code>에 대해 <code>WeakMap</code>을 사용하여 해당 이벤트에 등록된 핸들러들을 저장</li>
<li><code>for...in</code> 대신 <code>for...of</code>를 사용하여 <code>Map</code>을 올바르게 순회</li>
<li><code>get()</code>, <code>set()</code> 등의 <code>Map</code> 메서드를 활용하여 코드 가독성을 개선</li>
</ul>
<p>✅ 리팩토링 된 코드</p>
<pre><code>const eventListenersMap = new Map();

export function setupEventListeners(root) {
  for (const eventType of eventListenersMap.keys()) {
    root.removeEventListener(eventType, handleEvents);
    root.addEventListener(eventType, handleEvents);
  }
}

export function addEvent(element, eventType, handler) {
  if (!element || typeof handler !== &quot;function&quot;) return;

  if (!eventListenersMap.has(eventType)) {
    eventListenersMap.set(eventType, new WeakMap());
  }

  eventListenersMap.get(eventType).set(element, handler);
}

export function removeEvent(element, eventType, handler) {
  const handlers = eventListenersMap.get(eventType);

  if (handlers &amp;&amp; handlers.get(element) === handler) {
    handlers.delete(element);
  }
}

export function handleEvents(e) {
  const handlers = eventListenersMap.get(e.type);

  if (handlers &amp;&amp; handlers.has(e.target)) {
    handlers.get(e.target)(e);
  }
}</code></pre><p>💡 결과적으로 코드의 길이는 조금 늘어났지만, 기존 코드의 오류 가능성을 제거하고 더 안전하게 동작하도록 개선되었다.</p>
<h3 id="keep--현재-만족하고-계속-유지할-부분"><strong>Keep : 현재 만족하고 계속 유지할 부분</strong></h3>
<p>이번 주 과제를 진행하면서 저번 주 보다 기록에 대해 좀 더 신경쓰려고 했었다. 지난 과제와 비교해서 PR을 더 꼼꼼하게 작성한 부분이 마음에 들었다. 또한 작업하면서 궁금했던 점을 발견했을때 기억해 두고 있다가 나중에 찾아보곤 했는데 이제는 코드에 대해 의도를 알 수 없을 때 메모를 해두고 잊지 않게 해야겠다.</p>
<h3 id="problem--개선이-필요하다고-생각하는-문제점"><strong>Problem : 개선이 필요하다고 생각하는 문제점</strong></h3>
<p>P1. 테스트를 전부 통과하고 리팩토링을 진행하였는데 정말 내가 이 과제의 의도를 파악하고 모든 테스트에 대한 이론, 구현 방식을 잘 이해하고 있는지 의문이 들었다. 어찌저찌 통과하긴 했지만 가장 큰 틀은 알고 있는데 그 사이의 세부적인 요소에 대해 신경을 쓰지 못해서 다음 과제에 똑같은 것이 있었을 때 다시 찾아보거나 &quot;어떻게 하는거지?&quot; 하게 되는 것 같다.</p>
<h3 id="try--문제점을-해결하기-위해-시도해야-할-것"><strong>Try : 문제점을 해결하기 위해 시도해야 할 것</strong></h3>
<p>P1. 구현이 필요한 부분을 자세하게 검토하고 모르는 부분을 정리할 수도 있겠지만 테스트를 통과만 하는 것이 아니라 어떤 테스트를 통과했고 왜 통과할 수 있었는지 이해하고 넘어갈 수 있게 해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] 프레임워크 없이 SPA 구현하기 회고]]></title>
            <link>https://velog.io/@hyphen_/WIL-1%EC%A3%BC%EC%B0%A8-%EC%B2%AB-%EA%B3%BC%EC%A0%9C%EB%A5%BC-%EB%A7%88%EC%B9%98%EA%B3%A0</link>
            <guid>https://velog.io/@hyphen_/WIL-1%EC%A3%BC%EC%B0%A8-%EC%B2%AB-%EA%B3%BC%EC%A0%9C%EB%A5%BC-%EB%A7%88%EC%B9%98%EA%B3%A0</guid>
            <pubDate>Fri, 28 Mar 2025 16:15:38 GMT</pubDate>
            <description><![CDATA[<h3 id="1-문제-과제-프로젝트를-진행하면서-부딪혔던-기술적인-문제">1. 문제 <strong>(과제, 프로젝트를 진행하면서 부딪혔던 기술적인 문제)</strong></h3>
<p>첫 과제는 프레임워크 없이 SPA를 구현하는 것이었습니다. 기존에는 항상 프레임워크를 활용해 개발했기 때문에, 처음부터 &quot;라우팅을 어떻게 구현하지?&quot;라는 고민이 들었습니다. 과제와 함께 제공된 참고 자료를 활용했지만, 세부적인 구현 방식까지는 다루지 않아 &quot;이렇게 사용하는 게 맞을까?&quot; 하는 불확실함이 있었습니다.</p>
<p>기본 과제 테스트를 통과한 후 심화 과제로 넘어가면서, 심화 테스트를 성공하면 기본 테스트가 실패하거나, 특정 테스트 오류를 해결하면 나머지 테스트가 전부 실패하는 등 예상치 못한 문제가 발생했습니다. 이러한 오류의 원인을 파악하는 것이 쉽지 않았습니다.</p>
<h3 id="2-시도"><strong>2. 시도</strong></h3>
<p>라우팅과 해시 처리 방식 등 처음 구현한 내용은 부족한 지식의 결합체였기 때문에, 관련 정보를 다시 찾아보며 코드의 구조를 보완해 나갔습니다. 또한, 테스트가 계속 실패하는 원인을 파악하기 위해 오류가 발생하는 상황을 분석하며, 각 테스트의 의미와 해결 방식을 익혔습니다. 이를 통해 현재 구현된 코드가 테스트를 정확히 반영하고 있는지 지속적으로 확인할 수 있었습니다.</p>
<h3 id="3-해결"><strong>3. 해결</strong></h3>
<p>결국 모르는 부분에 대해 시간을 투자하고, 반복적인 테스트를 수행하며 문제 해결 방식을 익혔습니다. 또한, 같은 팀 멤버 및 멘토링 시간을 적극적으로 활용해 막히는 부분에서 도움을 받을 수 있었습니다.</p>
<h3 id="4-알게된-것"><strong>4. 알게된 것</strong></h3>
<p>SPA를 직접 구현하면서, 내가 어떤 부분을 모르는지를 명확하게 파악할 수 있었습니다. 또한, Vanilla JS만으로 프로젝트를 개발하는 것은 예상보다 어려웠으며, 세세한 부분까지 깊이 이해하지 않으면 성능이나 기술적 이슈가 발생할 수 있음을 깨달았습니다. 이 과정에서 프레임워크가 개발을 얼마나 쉽게 만들어주는지도 다시금 실감할 수 있었습니다.</p>
<hr>
<h3 id="keep--현재-만족하고-계속-유지할-부분"><strong>Keep : 현재 만족하고 계속 유지할 부분</strong></h3>
<ul>
<li>하루에 목표치를 정해 꾸준히 과제를 진행했다.</li>
<li>마감 기한 전에 제출을 완료하고 충분한 검토 과정을 거쳤다.</li>
</ul>
<h3 id="problem--개선이-필요하다고-생각하는-문제점"><strong>Problem : 개선이 필요하다고 생각하는 문제점</strong></h3>
<p>P1. GitHub 커밋 메시지 관리가 미흡함
P2. 리팩토링을 위한 충분한 시간을 확보하지 못함
P3. 과제를 진행하는 데 필요한 지식이 부족함</p>
<h3 id="try--문제점을-해결하기-위해-시도해야-할-것"><strong>Try : 문제점을 해결하기 위해 시도해야 할 것</strong></h3>
<p>P1. commit message 규칙 적용 - 의미 있는 커밋 메시지를 작성하는 습관을 들인다.
P2. 금주에는 일정이 많아 충분한 리팩토링 시간을 확보하지 못했으나, 앞으로는 시간을 철저히 분배해 미리미리 과제를 완수하고 개선 시간을 확보할 예정입니다.
P3. 과제는 제출했지만 여전히 애매한 부분이 남아 있습니다. 이를 개인 스터디 주제로 선정해 더 깊이 공부할 계획입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] 시작하는 마음]]></title>
            <link>https://velog.io/@hyphen_/WIL-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-%EB%A7%88%EC%9D%8C</link>
            <guid>https://velog.io/@hyphen_/WIL-%EC%8B%9C%EC%9E%91%ED%95%98%EB%8A%94-%EB%A7%88%EC%9D%8C</guid>
            <pubDate>Sat, 22 Mar 2025 08:15:22 GMT</pubDate>
            <description><![CDATA[<h3 id="1-시작하는-마음">1. 시작하는 마음</h3>
<p>지금까지의 회고
개강 후 커리큘럼과 학습 과정을 들으며 오랜만에 본격적으로 공부하게 되어 두근거리는 동시에, 끝까지 잘 해낼 수 있을지 걱정도 되었습니다. 첫 번째 발제를 들으며 ‘어떻게 과제를 해결해야 할까?’ 하는 고민이 들었지만, 관련 자료가 잘 정리되어 있어 충분히 해결할 수 있겠다는 확신이 생겼습니다. 앞으로의 과제들도 기대됩니다.</p>
<h3 id="2-항해-플러스-참여-계기">2. 항해 플러스 참여 계기</h3>
<p>프론트엔드 개발자로 3년간 일하면서 Vue, React, Flutter 등 다양한 기술을 활용해 프로젝트를 진행해왔습니다. 새로운 기술을 익히고 실무 경험을 쌓을 수 있었지만, 프론트엔드 개발자로서 근본적인 성장에는 한계를 느꼈습니다. 이에 대한 부족함을 채우고 싶었고, 딥다이브, 코드 테스트, 성능 최적화 등 핵심 영역을 더욱 깊이 탐구하며 같은 목표를 가진 동료들과 함께 성장하고자 항해 플러스에 참여하게 되었습니다.</p>
<h3 id="3-향후-5년-뒤-커리어-방향성">3. 향후 5년 뒤 커리어 방향성</h3>
<p>항해를 통해 시니어 개발자로 한 걸음 더 가까워질 수 있다고 기대하고 있습니다. 실무에서 1인분 그 이상을 하는 개발자로 더욱 성장하고 싶습니다. (누군가에게 도움이 되는 코드를 짜는 그날까지..! 👊)</p>
<h3 id="4-10주간의-목표">4. 10주간의 목표</h3>
<p>평소 할 일을 미리 끝내고 쉬는 것을 좋아하는 성향이라, 계획적으로 과제를 완수하는 것을 목표로 삼고 있습니다. 과제를 일찍 끝냈을 때는 내 코드에 개선할 부분이 없는지 다시 점검하며, ‘정말 최선을 다했다’ 라는 느낌이 들 수 있도록 하나하나 꼼꼼하게 마무리하고 싶습니다.</p>
<h3 id="5-최종-목표-배지">5. 최종 목표 배지</h3>
<p>목표는 클수록 좋다고 생각하기에, 블랙 배지를 획득할 수 있도록 모든 과제를 100% 완수하는 것을 목표로 삼았습니다. 항해인 만큼 최선을 다해 노를 저으며 끝까지 완주하겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[주스탠드(Zustand) 맛보기]]></title>
            <link>https://velog.io/@hyphen_/%EC%A3%BC%EC%8A%A4%ED%83%A0%EB%93%9CZustand-%EB%BF%8C%EC%8B%9C%EA%B8%B0</link>
            <guid>https://velog.io/@hyphen_/%EC%A3%BC%EC%8A%A4%ED%83%A0%EB%93%9CZustand-%EB%BF%8C%EC%8B%9C%EA%B8%B0</guid>
            <pubDate>Tue, 09 Apr 2024 08:47:16 GMT</pubDate>
            <description><![CDATA[<h3 id="zustand란">Zustand란?</h3>
<p>작년부터 사용률이 늘어나고 있는 클라이언트 상태 관리 라이브러리이다. 주스탠드는 특정 라이브러리에 종속되어 만들어진 도구는 아니기 때문에 바닐라 자바스크립트에서도 사용이 가능하다. </p>
<h3 id="장점">장점</h3>
<ul>
<li>간단하고 직관적이다<ul>
<li>초기 설정이 쉽고 간단하다.</li>
<li>Zustand의 사용법은 store를 만들고 type, object, function을 정의하면 되기 때문에 정말 간단하다.</li>
</ul>
</li>
<li>라이브러리의 크기가 작고 가볍다.</li>
<li>성능에 최적화 되어 있다.</li>
<li>활발한 커뮤니티를 가지고 있다.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>간단한 상태 관리에는 적합하지만, 복잡한 상태 관리에는 적합하지 않을 수 있다.</li>
<li>다른 라이브러리와 네이밍 충돌이 발생할 수 있다.</li>
<li>테스트와 디버깅이 어려울 수 있다.</li>
</ul>
<h3 id="사용-방법">사용 방법</h3>
<p><strong>라이브러리 설치</strong>
npm 또는 yarn을 이용해 라이브러리를 설치한다.</p>
<blockquote>
<p>npm install zustand
yarn add zustand</p>
</blockquote>
<p><a href="https://docs.pmnd.rs/zustand/getting-started/introduction">공식 문서</a>를 참조해서 Store를 생성하고 사용하자.</p>
<h3 id="사용-후기">사용 후기</h3>
<p>원래는 jotai를 사용했었는데 다른 상태 관리 라이브러리를 사용해 보고 싶어서 사용량이 많은 Zustand를 선택해서 프로젝트에 적용해보았다. 간단한 상태 관리와 테스트, 디버깅에는 jotai가 더 유용하지만 성능에서는 Zustand가 더 적합하다. 커뮤니티도 활발하다는 점도 있지만 선호하는 부분에 초점을 맞춰 라이브러리를 선택하면 좋을 것 같다.</p>
]]></description>
        </item>
    </channel>
</rss>