<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>code를 헤엄치는 인어공쭈</title>
    <link>https://haneui.tistory.com/</link>
    <description>Front-end Developer</description>
    <language>ko</language>
    <pubDate>Tue, 25 Aug 2026 18:53:31 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>인어공쭈</managingEditor>
    <image>
      <title>code를 헤엄치는 인어공쭈</title>
      <url>https://tistory1.daumcdn.net/tistory/6075462/attach/c4eb70c2f6c94a648463f15ce2b02984</url>
      <link>https://haneui.tistory.com</link>
    </image>
    <item>
      <title>&amp;ldquo;오늘 나 뭐 했지?&amp;rdquo;에 데이터로 대답하는 법 (Haneui-Tracker 개발기)</title>
      <link>https://haneui.tistory.com/102</link>
      <description>&lt;h2 data-end=&quot;272&quot; data-start=&quot;265&quot; data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;
&lt;p data-end=&quot;312&quot; data-start=&quot;274&quot; data-ke-size=&quot;size16&quot;&gt;재택근무시엔 개발자로서 한 가지 질문을 자주 받게 됐다.&lt;/p&gt;
&lt;blockquote data-end=&quot;328&quot; data-start=&quot;314&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;328&quot; data-start=&quot;316&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;오늘 뭐 하셨어요?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;374&quot; data-start=&quot;330&quot; data-ke-size=&quot;size16&quot;&gt;문제는,&lt;br /&gt;분명 하루 종일 코딩했는데 말로 설명하려니 늘 추상적이라는 점이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;430&quot; data-start=&quot;376&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;390&quot; data-start=&quot;376&quot;&gt;&amp;ldquo;버그 좀 잡았고요&amp;hellip;&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;410&quot; data-start=&quot;391&quot;&gt;&amp;ldquo;리팩토링이랑 구조 정리 좀&amp;hellip;&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;430&quot; data-start=&quot;411&quot;&gt;&amp;ldquo;코드는 많이 안 늘었는데요&amp;hellip;&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;475&quot; data-start=&quot;432&quot; data-ke-size=&quot;size16&quot;&gt;이쯤 되면 스스로도 의심이 든다.&lt;br /&gt;&lt;b&gt;&amp;ldquo;나 오늘 진짜 일한 거 맞나?&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;486&quot; data-start=&quot;477&quot; data-ke-size=&quot;size16&quot;&gt;그래서 생각했다.&lt;/p&gt;
&lt;blockquote data-end=&quot;507&quot; data-start=&quot;488&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;507&quot; data-start=&quot;490&quot; data-ke-size=&quot;size16&quot;&gt;느낌 말고, 데이터로 말해보자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;597&quot; data-start=&quot;509&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 시작된 프로젝트가&lt;br /&gt;VS Code Extension &lt;b&gt;Haneui-Tracker&lt;/b&gt;다.&lt;br /&gt;내 개발 활동을 감정이 아닌 숫자로 보여주는 도구다.&lt;/p&gt;
&lt;hr data-end=&quot;602&quot; data-start=&quot;599&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;636&quot; data-start=&quot;604&quot; data-ke-size=&quot;size26&quot;&gt;왜 만들었나: 문제 정의 (aka 개발자의 자아성찰)&lt;/h2&gt;
&lt;h3 data-end=&quot;659&quot; data-start=&quot;638&quot; data-ke-size=&quot;size23&quot;&gt;1. 정량적 성과 측정의 어려움&lt;/h3&gt;
&lt;p data-end=&quot;686&quot; data-start=&quot;661&quot; data-ke-size=&quot;size16&quot;&gt;개발자의 성과는 숫자로 표현하기 참 애매하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;778&quot; data-start=&quot;688&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;711&quot; data-start=&quot;688&quot;&gt;커밋 수 &amp;rarr; 리팩토링은 늘 억울하다&lt;/li&gt;
&lt;li data-end=&quot;741&quot; data-start=&quot;712&quot;&gt;코드 라인 수 &amp;rarr; 좋은 코드는 오히려 줄어든다&lt;/li&gt;
&lt;li data-end=&quot;778&quot; data-start=&quot;742&quot;&gt;PR 개수 &amp;rarr; 하루 종일 한 PR이 세상을 구할 수도 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;822&quot; data-start=&quot;780&quot; data-ke-size=&quot;size16&quot;&gt;그럼에도 불구하고,&lt;br /&gt;&lt;b&gt;나의 성장 추이와 실제 활동량은 보고 싶었다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-end=&quot;840&quot; data-start=&quot;824&quot; data-ke-size=&quot;size23&quot;&gt;2. 재택근무의 애매함&lt;/h3&gt;
&lt;p data-end=&quot;864&quot; data-start=&quot;842&quot; data-ke-size=&quot;size16&quot;&gt;재택근무를 하다 보면 이런 순간이 온다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;962&quot; data-start=&quot;866&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;894&quot; data-start=&quot;866&quot;&gt;하루 종일 바빴는데 결과물이 잘 안 보일 때&lt;/li&gt;
&lt;li data-end=&quot;931&quot; data-start=&quot;895&quot;&gt;스탠드업에서 &amp;ldquo;오늘 뭐 했어요?&amp;rdquo;가 가장 어려운 질문일 때&lt;/li&gt;
&lt;li data-end=&quot;962&quot; data-start=&quot;932&quot;&gt;스스로 생산성을 관리하고 싶은데 기준이 없을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;975&quot; data-start=&quot;964&quot; data-ke-size=&quot;size16&quot;&gt;그래서 필요했던 건:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1028&quot; data-start=&quot;977&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;989&quot; data-start=&quot;977&quot;&gt;실제 코딩 시간&lt;/li&gt;
&lt;li data-end=&quot;1008&quot; data-start=&quot;990&quot;&gt;어떤 언어를 얼마나 썼는지&lt;/li&gt;
&lt;li data-end=&quot;1028&quot; data-start=&quot;1009&quot;&gt;오늘 손이 얼마나 움직였는지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1033&quot; data-start=&quot;1030&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1058&quot; data-start=&quot;1035&quot; data-ke-size=&quot;size26&quot;&gt;해결 방향: 그래서 직접 만들었습니다&lt;/h2&gt;
&lt;p data-end=&quot;1095&quot; data-start=&quot;1060&quot; data-ke-size=&quot;size16&quot;&gt;기존 솔루션도 찾아봤다.&lt;br /&gt;하지만 대부분은 다음 중 하나였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1170&quot; data-start=&quot;1097&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1117&quot; data-start=&quot;1097&quot;&gt;서버 기반 &amp;rarr; 프라이버시 부담&lt;/li&gt;
&lt;li data-end=&quot;1140&quot; data-start=&quot;1118&quot;&gt;기능 과다 &amp;rarr; 설정하다 하루 종료&lt;/li&gt;
&lt;li data-end=&quot;1170&quot; data-start=&quot;1141&quot;&gt;VS Code와 미묘한 거리감 &amp;rarr; 결국 안 씀&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1185&quot; data-start=&quot;1172&quot; data-ke-size=&quot;size16&quot;&gt;그래서 결론은 하나였다.&lt;/p&gt;
&lt;blockquote data-end=&quot;1241&quot; data-start=&quot;1187&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1241&quot; data-start=&quot;1189&quot; data-ke-size=&quot;size16&quot;&gt;로컬에서만 동작하고&lt;br /&gt;VS Code에 완전히 녹아들며&lt;br /&gt;켜두면 알아서 기록되는 도구&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1261&quot; data-start=&quot;1243&quot; data-ke-size=&quot;size16&quot;&gt;없으면?&lt;br /&gt;&lt;b&gt;직접 만든다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-end=&quot;1266&quot; data-start=&quot;1263&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1282&quot; data-start=&quot;1268&quot; data-ke-size=&quot;size26&quot;&gt;기술적 아키텍처 설계&lt;/h2&gt;
&lt;h3 data-end=&quot;1293&quot; data-start=&quot;1284&quot; data-ke-size=&quot;size23&quot;&gt;전체 구조&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1768974076749&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Extension Entry
   ├─ ActivityTracker   // 내가 뭘 했는지
   ├─ StatsCalculator   // 그래서 얼마나 했는지
   ├─ StorageManager    // 기억력 담당
   └─ UI
       ├─ StatusBar
       ├─ Sidebar
       └─ Dashboard(Webview)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1543&quot; data-start=&quot;1499&quot; data-ke-size=&quot;size16&quot;&gt;한 줄 요약하면&lt;br /&gt;&lt;b&gt;이벤트는 모으고, 계산은 분리하고, UI는 조용하게&lt;/b&gt;.&lt;/p&gt;
&lt;hr data-end=&quot;1548&quot; data-start=&quot;1545&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1560&quot; data-start=&quot;1550&quot; data-ke-size=&quot;size26&quot;&gt;핵심 컴포넌트&lt;/h2&gt;
&lt;h3 data-end=&quot;1598&quot; data-start=&quot;1562&quot; data-ke-size=&quot;size23&quot;&gt;ActivityTracker &amp;ndash; 개발자의 모든 움직임 추적&lt;/h3&gt;
&lt;p data-end=&quot;1627&quot; data-start=&quot;1600&quot; data-ke-size=&quot;size16&quot;&gt;VS Code는 생각보다 많은 이벤트를 제공한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1768974099414&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;onDidChangeTextDocument
onDidSaveTextDocument
onDidChangeActiveTextEditor
onDidChangeWindowState&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1753&quot; data-start=&quot;1737&quot; data-ke-size=&quot;size16&quot;&gt;이걸 잘 엮으면 알 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1808&quot; data-start=&quot;1755&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1770&quot; data-start=&quot;1755&quot;&gt;키보드를 치고 있는지&lt;/li&gt;
&lt;li data-end=&quot;1786&quot; data-start=&quot;1771&quot;&gt;파일을 옮겨 다니는지&lt;/li&gt;
&lt;li data-end=&quot;1808&quot; data-start=&quot;1787&quot;&gt;아니면 그냥 화면만 보고 있는지&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-end=&quot;1824&quot; data-start=&quot;1810&quot; data-ke-size=&quot;size20&quot;&gt;비활성 시간 감지&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;pre id=&quot;code_1768974118555&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const IDLE_THRESHOLD = 10 * 60 * 1000; // 10분&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1956&quot; data-start=&quot;1883&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1907&quot; data-start=&quot;1883&quot;&gt;커피 마시러 간 시간은 코딩이 아니다&lt;/li&gt;
&lt;li data-end=&quot;1928&quot; data-start=&quot;1908&quot;&gt;멍 때린 시간도 코딩이 아니다&lt;/li&gt;
&lt;li data-end=&quot;1956&quot; data-start=&quot;1929&quot;&gt;10분 이상 조용하면 코딩 타이머는 멈춘다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1971&quot; data-start=&quot;1958&quot; data-ke-size=&quot;size16&quot;&gt;타이머는 정직해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;1971&quot; data-start=&quot;1958&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2003&quot; data-start=&quot;1978&quot; data-ke-size=&quot;size23&quot;&gt;라인 수 계산: 생각보다 까다로운 문제&lt;/h3&gt;
&lt;p data-end=&quot;2018&quot; data-start=&quot;2005&quot; data-ke-size=&quot;size16&quot;&gt;처음엔 이렇게 생각했다.&lt;/p&gt;
&lt;blockquote data-end=&quot;2045&quot; data-start=&quot;2020&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;2045&quot; data-start=&quot;2022&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;라인 수? 그냥 늘어난 줄 세면 되겠지&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;2055&quot; data-start=&quot;2047&quot; data-ke-size=&quot;size16&quot;&gt;완전히 틀렸다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2111&quot; data-start=&quot;2057&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2072&quot; data-start=&quot;2057&quot;&gt;여러 파일 동시 수정&lt;/li&gt;
&lt;li data-end=&quot;2084&quot; data-start=&quot;2073&quot;&gt;대량 붙여넣기&lt;/li&gt;
&lt;li data-end=&quot;2111&quot; data-start=&quot;2085&quot;&gt;삭제 + 추가가 한 이벤트에 섞여 들어옴&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2129&quot; data-start=&quot;2113&quot; data-ke-size=&quot;size16&quot;&gt;그래서 해결책은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2197&quot; data-start=&quot;2131&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2159&quot; data-start=&quot;2131&quot;&gt;파일별 마지막 라인 수를 Map으로 관리&lt;/li&gt;
&lt;li data-end=&quot;2174&quot; data-start=&quot;2160&quot;&gt;변경 전/후를 역산&lt;/li&gt;
&lt;li data-end=&quot;2197&quot; data-start=&quot;2175&quot;&gt;추가 / 삭제 / 수정 구분 집계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2229&quot; data-start=&quot;2199&quot; data-ke-size=&quot;size16&quot;&gt;이제야&lt;br /&gt;&lt;b&gt;진짜로 손이 움직인 만큼만&lt;/b&gt; 기록된다.&lt;/p&gt;
&lt;h3 data-end=&quot;2267&quot; data-start=&quot;2236&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;2267&quot; data-start=&quot;2236&quot; data-ke-size=&quot;size23&quot;&gt;날짜 변경 감지 &amp;ndash; VS Code는 잠들지 않는다&lt;/h3&gt;
&lt;p data-end=&quot;2318&quot; data-start=&quot;2269&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2318&quot; data-start=&quot;2269&quot; data-ke-size=&quot;size16&quot;&gt;VS Code는 며칠이고 켜져 있다.&lt;br /&gt;문제는 날짜는 바뀌는데 앱은 그대로라는 점이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2404&quot; data-start=&quot;2320&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2336&quot; data-start=&quot;2320&quot;&gt;마지막 체크 날짜 저장&lt;/li&gt;
&lt;li data-end=&quot;2361&quot; data-start=&quot;2337&quot;&gt;모든 이벤트에서 날짜 변경 여부 확인&lt;/li&gt;
&lt;li data-end=&quot;2404&quot; data-start=&quot;2362&quot;&gt;날짜가 바뀌면:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2404&quot; data-start=&quot;2375&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2388&quot; data-start=&quot;2375&quot;&gt;어제 데이터 저장&lt;/li&gt;
&lt;li data-end=&quot;2404&quot; data-start=&quot;2391&quot;&gt;오늘 세션 초기화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2434&quot; data-start=&quot;2406&quot; data-ke-size=&quot;size16&quot;&gt;개발자는 자고 일어나도&lt;br /&gt;데이터는 정확해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;2434&quot; data-start=&quot;2406&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2473&quot; data-start=&quot;2441&quot; data-ke-size=&quot;size23&quot;&gt;StorageManager &amp;ndash; 기억은 가볍게, 오래&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2543&quot; data-start=&quot;2475&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2489&quot; data-start=&quot;2475&quot;&gt;일별 데이터만 저장&lt;/li&gt;
&lt;li data-end=&quot;2512&quot; data-start=&quot;2490&quot;&gt;주간 / 월간 통계는 실시간 계산&lt;/li&gt;
&lt;li data-end=&quot;2543&quot; data-start=&quot;2513&quot;&gt;Map / Set &amp;rarr; 직렬화 가능한 구조로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2565&quot; data-start=&quot;2545&quot; data-ke-size=&quot;size16&quot;&gt;오래 써도 무거워지지 않게 설계했다.&lt;/p&gt;
&lt;p data-end=&quot;2565&quot; data-start=&quot;2545&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;2601&quot; data-start=&quot;2572&quot; data-ke-size=&quot;size23&quot;&gt;Dashboard &amp;ndash; 숫자는 시각화되어야 한다&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2669&quot; data-start=&quot;2603&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2633&quot; data-start=&quot;2603&quot;&gt;Chart.js + VS Code Webview&lt;/li&gt;
&lt;li data-end=&quot;2650&quot; data-start=&quot;2634&quot;&gt;언어별 분포 도넛 차트&lt;/li&gt;
&lt;li data-end=&quot;2669&quot; data-start=&quot;2651&quot;&gt;코딩 시간 추이 라인 차트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2700&quot; data-start=&quot;2671&quot; data-ke-size=&quot;size16&quot;&gt;VS Code 테마와 자연스럽게 어울리도록 구성했다.&lt;/p&gt;
&lt;p data-end=&quot;2727&quot; data-start=&quot;2702&quot; data-ke-size=&quot;size16&quot;&gt;데이터는 많아도&lt;br /&gt;보는 사람은 편해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;2727&quot; data-start=&quot;2702&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;2748&quot; data-start=&quot;2734&quot; data-ke-size=&quot;size26&quot;&gt;개발하면서 배운 것들&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2818&quot; data-start=&quot;2750&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2768&quot; data-start=&quot;2750&quot;&gt;정확함은 늘 대가가 따른다&lt;/li&gt;
&lt;li data-end=&quot;2792&quot; data-start=&quot;2769&quot;&gt;이벤트 하나 놓치면 통계가 틀어진다&lt;/li&gt;
&lt;li data-end=&quot;2818&quot; data-start=&quot;2793&quot;&gt;UX는 눈에 안 띄어야 잘 만든 것이다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2829&quot; data-start=&quot;2820&quot; data-ke-size=&quot;size16&quot;&gt;그리고 무엇보다,&lt;/p&gt;
&lt;p data-end=&quot;2891&quot; data-start=&quot;2831&quot; data-ke-size=&quot;size16&quot;&gt;이 Extension을 만들고 나서&lt;br /&gt;&amp;ldquo;오늘 뭐 했어요?&amp;rdquo;에&lt;br /&gt;대시보드 스크린샷으로 답하게 됐다.&lt;/p&gt;
&lt;h4 data-end=&quot;2891&quot; data-start=&quot;2831&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h2 data-end=&quot;2891&quot; data-start=&quot;2831&quot; data-ke-size=&quot;size26&quot;&gt;한계와 개선 계획&lt;/h2&gt;
&lt;p data-end=&quot;2891&quot; data-start=&quot;2831&quot; data-ke-size=&quot;size16&quot;&gt;현재 Haneui-Tracker는 여전히 발전 중인 도구다.&lt;br /&gt;&lt;br /&gt;실사용&amp;nbsp;과정에서&amp;nbsp;발견되는&amp;nbsp;버그와&lt;br /&gt;개선이&amp;nbsp;필요한&amp;nbsp;부분들이&amp;nbsp;분명히&amp;nbsp;존재하며,&lt;br /&gt;이를&amp;nbsp;하나씩&amp;nbsp;해결해&amp;nbsp;나가며&amp;nbsp;디벨롭을&amp;nbsp;이어가고&amp;nbsp;있다.&lt;br /&gt;&lt;br /&gt;이&amp;nbsp;프로젝트는&lt;br /&gt;&amp;ldquo;완성된&amp;nbsp;결과물&amp;rdquo;이라기보다&lt;br /&gt;지속적으로 다듬어 가는 개발 기록에 가깝다.&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2891&quot; data-start=&quot;2831&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;2907&quot; data-start=&quot;2898&quot; data-ke-size=&quot;size26&quot;&gt;성과와 회고&lt;/h2&gt;
&lt;h3 data-end=&quot;2918&quot; data-start=&quot;2909&quot; data-ke-size=&quot;size23&quot;&gt;기술적으로&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3006&quot; data-start=&quot;2920&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2947&quot; data-start=&quot;2920&quot;&gt;VS Code Extension 구조 이해&lt;/li&gt;
&lt;li data-end=&quot;2966&quot; data-start=&quot;2948&quot;&gt;이벤트 기반 아키텍처 설계&lt;/li&gt;
&lt;li data-end=&quot;2987&quot; data-start=&quot;2967&quot;&gt;Webview UI 구현 경험&lt;/li&gt;
&lt;li data-end=&quot;3006&quot; data-start=&quot;2988&quot;&gt;가벼운 도구를 만드는 감각&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;3017&quot; data-start=&quot;3008&quot; data-ke-size=&quot;size23&quot;&gt;개인적으로&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3080&quot; data-start=&quot;3019&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3039&quot; data-start=&quot;3019&quot;&gt;내 개발 패턴을 데이터로 인식&lt;/li&gt;
&lt;li data-end=&quot;3055&quot; data-start=&quot;3040&quot;&gt;재택근무 생산성 관리&lt;/li&gt;
&lt;li data-end=&quot;3080&quot; data-start=&quot;3056&quot;&gt;&amp;ldquo;필요한 도구는 직접 만든다&amp;rdquo;는 경험&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-end=&quot;3093&quot; data-start=&quot;3087&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-end=&quot;3121&quot; data-start=&quot;3095&quot; data-ke-size=&quot;size16&quot;&gt;이 프로젝트는 단순한 사이드 프로젝트가 아니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3170&quot; data-start=&quot;3123&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3135&quot; data-start=&quot;3123&quot;&gt;문제를 정의하고&lt;/li&gt;
&lt;li data-end=&quot;3148&quot; data-start=&quot;3136&quot;&gt;도구를 선택하고&lt;/li&gt;
&lt;li data-end=&quot;3170&quot; data-start=&quot;3149&quot;&gt;실제로 쓰는 제품으로 만든 경험&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3222&quot; data-start=&quot;3172&quot; data-ke-size=&quot;size16&quot;&gt;개발자는 결국&lt;br /&gt;&lt;b&gt;문제를 보고, 귀찮아하고, 결국 직접 만드는 사람&lt;/b&gt;이라고 생각한다.&lt;/p&gt;
&lt;p data-end=&quot;3267&quot; data-start=&quot;3224&quot; data-ke-size=&quot;size16&quot;&gt;Haneui-Tracker는&lt;br /&gt;그 귀찮음에서 시작된, 꽤 정직한 결과물이다.&lt;/p&gt;
&lt;p data-end=&quot;3375&quot; data-start=&quot;3274&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3375&quot; data-start=&quot;3274&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GitHub&lt;/b&gt;: &lt;a href=&quot;https://open-vsx.org/extension/haneui-tracker/Haneui-Tracker&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://open-vsx.org/extension/haneui-tracker/Haneui-Tracker&lt;/a&gt;&lt;br /&gt;&lt;b&gt;Version&lt;/b&gt;: 0.0.6&lt;br /&gt;&lt;b&gt;License&lt;/b&gt;: MIT&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nEj4g/dJMcaia2gS6/UqAUUOcca0S0rb5i0hP7HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nEj4g/dJMcaia2gS6/UqAUUOcca0S0rb5i0hP7HK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nEj4g/dJMcaia2gS6/UqAUUOcca0S0rb5i0hP7HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnEj4g%2FdJMcaia2gS6%2FUqAUUOcca0S0rb5i0hP7HK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1234&quot; height=&quot;750&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>소프트웨어 개발</category>
      <category>code tracker</category>
      <category>CURSOR</category>
      <category>extension</category>
      <category>Tracker</category>
      <category>익스텐션</category>
      <category>커서</category>
      <author>인어공쭈</author>
      <guid isPermaLink="true">https://haneui.tistory.com/102</guid>
      <comments>https://haneui.tistory.com/102#entry102comment</comments>
      <pubDate>Wed, 21 Jan 2026 14:46:58 +0900</pubDate>
    </item>
    <item>
      <title>Cursor / VS Code 개발 활동을 기록하는 나만의 익스텐션 만들기 &amp;ndash; Haneui-Tracker</title>
      <link>https://haneui.tistory.com/101</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다 보면 가끔 이런 생각이 들 때가 있다.&lt;/p&gt;
&lt;blockquote data-end=&quot;232&quot; data-start=&quot;206&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;232&quot; data-start=&quot;208&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;오늘 나&amp;hellip; 열심히 했나?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;304&quot; data-start=&quot;234&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;304&quot; data-start=&quot;234&quot; data-ke-size=&quot;size16&quot;&gt;커밋은 없고, 기능은 조금 진전됐고, 시간은 분명 흘렀는데&lt;br /&gt;&lt;b&gt;내가 실제로 얼마나 열심히 했는지&lt;/b&gt;는 아무도 알수 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;389&quot; data-start=&quot;306&quot; data-ke-size=&quot;size16&quot;&gt;그래서 만들었다.&lt;br /&gt;&lt;b&gt;Cursor / VS Code에서 내 개발 활동을 그대로 기록해주는 익스텐션&lt;/b&gt;,&lt;br /&gt;&lt;a href=&quot;https://marketplace.cursorapi.com/items/?itemName=haneui-tracker.Haneui-Tracker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;=&amp;gt; Haneui-Tracker&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-end=&quot;410&quot; data-start=&quot;396&quot; data-ke-size=&quot;size26&quot;&gt;왜 만들었을까?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;484&quot; data-start=&quot;412&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;430&quot; data-start=&quot;412&quot;&gt;&amp;ldquo;오늘 얼마나 했지?&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;484&quot; data-start=&quot;458&quot;&gt;&amp;ldquo;내 성과가 궁금해&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;530&quot; data-start=&quot;486&quot; data-ke-size=&quot;size16&quot;&gt;이런 궁금증을 &lt;b&gt;Git 커밋 말고, 실제 개발 행위 기준으로&lt;/b&gt; 보고 싶었다.&lt;/p&gt;
&lt;p data-end=&quot;611&quot; data-start=&quot;532&quot; data-ke-size=&quot;size16&quot;&gt;Haneui-Tracker는 커밋 여부와 상관없이 에디터 안에서 실제로 일어난 행동을 그대로 수집하고 시각화한다.&lt;/p&gt;
&lt;p data-end=&quot;611&quot; data-start=&quot;532&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;142&quot; data-start=&quot;120&quot; data-ke-size=&quot;size26&quot;&gt;재택근무할 때 특히 좋은 이유&lt;/h2&gt;
&lt;p data-end=&quot;170&quot; data-start=&quot;144&quot; data-ke-size=&quot;size16&quot;&gt;재택근무를 하다 보면 이런 순간이 은근히 많다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;256&quot; data-start=&quot;172&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;196&quot; data-start=&quot;172&quot;&gt;&amp;ldquo;오늘 뭐 했는지 말로 설명해야 할 때&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;225&quot; data-start=&quot;197&quot;&gt;&amp;ldquo;나 스스로도 오늘 제대로 일했는지 애매할 때&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;256&quot; data-start=&quot;226&quot;&gt;&amp;ldquo;열심히 했는데, 결과물은 아직 정리 안 됐을 때&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;302&quot; data-start=&quot;258&quot; data-ke-size=&quot;size16&quot;&gt;Haneui-Tracker는 이런 상황에서 &lt;b&gt;아주 간단한 증거&lt;/b&gt;가 되어준다.&lt;/p&gt;
&lt;h3 data-end=&quot;330&quot; data-start=&quot;304&quot; data-ke-size=&quot;size23&quot;&gt;&amp;lsquo;활동 증거&amp;rsquo;로 쓰기 좋다&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;391&quot; data-start=&quot;332&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;342&quot; data-start=&quot;332&quot;&gt;하루 코딩 시간&lt;/li&gt;
&lt;li data-end=&quot;360&quot; data-start=&quot;343&quot;&gt;실제 타이핑한 키스트로크 수&lt;/li&gt;
&lt;li data-end=&quot;372&quot; data-start=&quot;361&quot;&gt;수정한 코드 라인&lt;/li&gt;
&lt;li data-end=&quot;391&quot; data-start=&quot;373&quot;&gt;어떤 언어를 얼마나 사용했는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;414&quot; data-start=&quot;393&quot; data-ke-size=&quot;size16&quot;&gt;이 모든 게 &lt;b&gt;자동으로 기록&lt;/b&gt;된다.&lt;/p&gt;
&lt;blockquote data-end=&quot;473&quot; data-start=&quot;416&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;473&quot; data-start=&quot;418&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;오늘은 3시간 정도 코딩했고&lt;br /&gt;React 위주로 작업하면서&lt;br /&gt;실제로 이만큼 수정했어요&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;507&quot; data-start=&quot;475&quot; data-ke-size=&quot;size16&quot;&gt;같은 설명을 &lt;b&gt;숫자와 그래프&lt;/b&gt;로 바로 보여줄 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;558&quot; data-start=&quot;509&quot; data-ke-size=&quot;size16&quot;&gt;보고용 문서를 만들 필요도 없고,&lt;br /&gt;&amp;ldquo;열심히 했습니다&amp;rdquo; 같은 추상적인 말도 필요 없다.&lt;/p&gt;
&lt;h3 data-end=&quot;588&quot; data-start=&quot;565&quot; data-ke-size=&quot;size23&quot;&gt;스스로를 점검하기에도 딱 좋다&lt;/h3&gt;
&lt;p data-end=&quot;647&quot; data-start=&quot;590&quot; data-ke-size=&quot;size16&quot;&gt;이 익스텐션이 진짜 좋은 지점은&lt;br /&gt;&lt;b&gt;남을 설득하기보다, 나 자신을 속이지 않게 해준다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;716&quot; data-start=&quot;649&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;669&quot; data-start=&quot;649&quot;&gt;&amp;ldquo;오늘 생각보다 집중 못 했네?&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;692&quot; data-start=&quot;670&quot;&gt;&amp;ldquo;오히려 짧게 했는데 밀도는 높았네&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;716&quot; data-start=&quot;693&quot;&gt;&amp;ldquo;이번 주는 확실히 JS 비중이 높네&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;747&quot; data-start=&quot;718&quot; data-ke-size=&quot;size16&quot;&gt;이런 걸 &lt;b&gt;하루 단위로 가볍게 확인&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;blockquote data-end=&quot;790&quot; data-start=&quot;749&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;790&quot; data-start=&quot;751&quot; data-ke-size=&quot;size16&quot;&gt;KPI용 도구가 아니라&lt;br /&gt;&lt;b&gt;자기 객관화용 대시보드&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-end=&quot;825&quot; data-start=&quot;797&quot; data-ke-size=&quot;size23&quot;&gt;부담 없는 기록이라 더 자주 보게 된다&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;863&quot; data-start=&quot;827&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;840&quot; data-start=&quot;827&quot;&gt;타이머 켤 필요 없음&lt;/li&gt;
&lt;li data-end=&quot;854&quot; data-start=&quot;841&quot;&gt;버튼 누를 필요 없음&lt;/li&gt;
&lt;li data-end=&quot;863&quot; data-start=&quot;855&quot;&gt;설정도 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;879&quot; data-start=&quot;865&quot; data-ke-size=&quot;size16&quot;&gt;그냥 코딩하면, 기록된다.&lt;/p&gt;
&lt;p data-end=&quot;946&quot; data-start=&quot;881&quot; data-ke-size=&quot;size16&quot;&gt;그래서&lt;br /&gt;&lt;b&gt;재택근무 중에도 부담 없이 켜두고&lt;/b&gt;,&lt;br /&gt;하루 끝나고 &amp;ldquo;오늘은 이 정도 했구나&amp;rdquo; 하고 확인하기 좋다.&lt;/p&gt;
&lt;h2 data-end=&quot;634&quot; data-start=&quot;618&quot; data-ke-size=&quot;size26&quot;&gt;어떤 걸 추적하나?&lt;/h2&gt;
&lt;h3 data-end=&quot;651&quot; data-start=&quot;636&quot; data-ke-size=&quot;size23&quot;&gt;1. 개발 활동 추적&lt;/h3&gt;
&lt;p data-end=&quot;684&quot; data-start=&quot;653&quot; data-ke-size=&quot;size16&quot;&gt;에디터에서 일어나는 거의 모든 &amp;ldquo;손 움직임&amp;rdquo;을 기록한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;894&quot; data-start=&quot;686&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;753&quot; data-start=&quot;686&quot;&gt;⏱ &lt;b&gt;코딩 시간&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;753&quot; data-start=&quot;702&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;712&quot; data-start=&quot;702&quot;&gt;실제 활동 기준&lt;/li&gt;
&lt;li data-end=&quot;753&quot; data-start=&quot;715&quot;&gt;10분 이상 비활성 시 자동 중단 &amp;rarr; 물 마시러 간 시간은 빼준다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;801&quot; data-start=&quot;754&quot;&gt;⌨️ &lt;b&gt;키스트로크 수&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;801&quot; data-start=&quot;773&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;801&quot; data-start=&quot;773&quot;&gt;의미 없는 키 입력이 아니라 라인 단위 기준&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;819&quot; data-start=&quot;802&quot;&gt;  &lt;b&gt;편집한 파일 수&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;856&quot; data-start=&quot;820&quot;&gt;➕➖ &lt;b&gt;코드 라인&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;856&quot; data-start=&quot;837&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;856&quot; data-start=&quot;837&quot;&gt;추가 / 삭제 / 수정 라인 수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;894&quot; data-start=&quot;857&quot;&gt;  &lt;b&gt;언어 분포&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;894&quot; data-start=&quot;874&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;894&quot; data-start=&quot;874&quot;&gt;어떤 언어를 얼마나 썼는지 한눈에&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;941&quot; data-start=&quot;896&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;941&quot; data-start=&quot;898&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;오늘 React만 했네?&amp;rdquo;&lt;br /&gt;&amp;ldquo;아니네, JSON을 제일 많이 썼네&amp;hellip;?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;960&quot; data-start=&quot;943&quot; data-ke-size=&quot;size16&quot;&gt;이런 현실 자각도 가능하다  &lt;/p&gt;
&lt;h2 data-end=&quot;987&quot; data-start=&quot;967&quot; data-ke-size=&quot;size26&quot;&gt;보는 맛이 있어야 기록이지&lt;/h2&gt;
&lt;h3 data-end=&quot;996&quot; data-start=&quot;989&quot; data-ke-size=&quot;size23&quot;&gt;상태바&lt;/h3&gt;
&lt;p data-end=&quot;1018&quot; data-start=&quot;998&quot; data-ke-size=&quot;size16&quot;&gt;코딩하면서 계속 보이는 최소한의 정보&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1040&quot; data-start=&quot;1020&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1030&quot; data-start=&quot;1020&quot;&gt;현재 코딩 시간&lt;/li&gt;
&lt;li data-end=&quot;1040&quot; data-start=&quot;1031&quot;&gt;키스트로크 수&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;1080&quot; data-start=&quot;1042&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1080&quot; data-start=&quot;1044&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;아&amp;hellip; 그래도 2시간은 했구나&amp;rdquo;&lt;br /&gt;이게 은근히 멘탈에 좋다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-end=&quot;1085&quot; data-start=&quot;1082&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1103&quot; data-start=&quot;1087&quot; data-ke-size=&quot;size23&quot;&gt;사이드바 (활동 패널)&lt;/h3&gt;
&lt;p data-end=&quot;1133&quot; data-start=&quot;1105&quot; data-ke-size=&quot;size16&quot;&gt;왼쪽 활동바에 &lt;b&gt;&amp;lsquo;개발 활동&amp;rsquo;&lt;/b&gt; 패널이 생긴다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1199&quot; data-start=&quot;1135&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1158&quot; data-start=&quot;1135&quot;&gt;오늘 / 이번 주 / 이번 달 / 전체&lt;/li&gt;
&lt;li data-end=&quot;1180&quot; data-start=&quot;1159&quot;&gt;트리뷰 형태로 깔끔하게 정리된 통계&lt;/li&gt;
&lt;li data-end=&quot;1199&quot; data-start=&quot;1181&quot;&gt;우클릭으로 통계 초기화도 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1204&quot; data-start=&quot;1201&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1220&quot; data-start=&quot;1206&quot; data-ke-size=&quot;size23&quot;&gt;웹뷰 대시보드&lt;/h3&gt;
&lt;p data-end=&quot;1233&quot; data-start=&quot;1222&quot; data-ke-size=&quot;size16&quot;&gt;이 익스텐션의 핵심.&lt;/p&gt;
&lt;p data-end=&quot;1279&quot; data-start=&quot;1235&quot; data-ke-size=&quot;size16&quot;&gt;명령 팔레트에서&lt;br /&gt;&lt;b&gt;&amp;ldquo;개발 활동 통계 보기&amp;rdquo;&lt;/b&gt; 실행하면 대시보드가 열린다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1339&quot; data-start=&quot;1281&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1310&quot; data-start=&quot;1281&quot;&gt;&lt;b&gt;언어별 키스트로크 분포 (도넛 차트)&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1339&quot; data-start=&quot;1311&quot;&gt;&lt;b&gt;일별 코딩 시간 추이 (라인 차트)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;1394&quot; data-start=&quot;1341&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1394&quot; data-start=&quot;1343&quot; data-ke-size=&quot;size16&quot;&gt;숫자로만 보던 걸&lt;br /&gt;&amp;ldquo;아, 나 진짜 일주일 내내 코딩했네&amp;rdquo;&lt;br /&gt;라고 느끼게 해준다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-end=&quot;1399&quot; data-start=&quot;1396&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1427&quot; data-start=&quot;1401&quot; data-ke-size=&quot;size26&quot;&gt;날짜 바뀌는 거&amp;hellip; 신경 안 써도 된다&lt;/h2&gt;
&lt;p data-end=&quot;1472&quot; data-start=&quot;1429&quot; data-ke-size=&quot;size16&quot;&gt;하루 지나서 통계 꼬이는 거 싫어서&lt;br /&gt;&lt;b&gt;날짜 변경 자동 감지&lt;/b&gt;를 넣었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1569&quot; data-start=&quot;1474&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1517&quot; data-start=&quot;1474&quot;&gt;날짜가 바뀌면
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1517&quot; data-start=&quot;1486&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1503&quot; data-start=&quot;1486&quot;&gt;이전 날짜 데이터 자동 저장&lt;/li&gt;
&lt;li data-end=&quot;1517&quot; data-start=&quot;1506&quot;&gt;새 날짜로 초기화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;1569&quot; data-start=&quot;1518&quot;&gt;단,
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1556&quot; data-start=&quot;1525&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1536&quot; data-start=&quot;1525&quot;&gt;&lt;b&gt;코딩 시간&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1553&quot; data-start=&quot;1539&quot;&gt;&lt;b&gt;수정한 라인 수&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
=&amp;gt;&amp;nbsp; 이 둘은 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1601&quot; data-start=&quot;1571&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;오늘 얼마나 쌓였는지&amp;rdquo; 흐름이 끊기지 않게 설계했다.&lt;/p&gt;
&lt;hr data-end=&quot;1606&quot; data-start=&quot;1603&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1631&quot; data-start=&quot;1608&quot; data-ke-size=&quot;size26&quot;&gt;개발하면서 신경 쓴 포인트&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1736&quot; data-start=&quot;1633&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1662&quot; data-start=&quot;1633&quot;&gt;Cursor / VS Code &lt;b&gt;둘 다 지원&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1685&quot; data-start=&quot;1663&quot;&gt;설정 없이 &lt;b&gt;설치하면 바로 동작&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1711&quot; data-start=&quot;1686&quot;&gt;데이터는 로컬 기준 &amp;rarr; 개인정보 걱정 없음&lt;/li&gt;
&lt;li data-end=&quot;1736&quot; data-start=&quot;1712&quot;&gt;차트는 WebView + Chart.js&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1772&quot; data-start=&quot;1738&quot; data-ke-size=&quot;size16&quot;&gt;아직 Git 커밋 통계는 없지만&lt;br /&gt;그건 &amp;ldquo;의도적으로&amp;rdquo; 뺐다.&lt;/p&gt;
&lt;blockquote data-end=&quot;1822&quot; data-start=&quot;1774&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1822&quot; data-start=&quot;1776&quot; data-ke-size=&quot;size16&quot;&gt;이 익스텐션은&lt;br /&gt;&lt;b&gt;성과를 재는 도구가 아니라, 과정에 집중하는 도구&lt;/b&gt;니까.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1944&quot; data-start=&quot;1899&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;2151&quot; data-start=&quot;2141&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;마무리하며&lt;/h2&gt;
&lt;p data-end=&quot;2212&quot; data-start=&quot;2153&quot; data-ke-size=&quot;size16&quot;&gt;Haneui-Tracker는&lt;br /&gt;&amp;ldquo;열심히 했다&amp;rdquo;는 말을&lt;br /&gt;&lt;b&gt;숫자와 그래프로 증명해주는 작은 도구&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;2266&quot; data-start=&quot;2214&quot; data-ke-size=&quot;size16&quot;&gt;누군가에게 보여주기 위한 통계가 아니라,&lt;br /&gt;&lt;b&gt;스스로를 속이지 않기 위한 기록&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;blockquote data-end=&quot;2302&quot; data-start=&quot;2268&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;2302&quot; data-start=&quot;2270&quot; data-ke-size=&quot;size16&quot;&gt;오늘 커밋이 없어도&lt;br /&gt;오늘의 코딩은 사라지지 않도록.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;2344&quot; data-start=&quot;2304&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2344&quot; data-start=&quot;2304&quot; data-ke-size=&quot;size16&quot;&gt;개발자라면 한 번쯤 써볼 만하다  &lt;br /&gt;아직 부족하지만 앞으로도 조금씩 다듬어갈 예정!&lt;/p&gt;
&lt;p data-end=&quot;2389&quot; data-start=&quot;2346&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Enjoy tracking your coding activity!  &lt;/b&gt;&lt;/p&gt;</description>
      <category>소프트웨어 개발</category>
      <category>CURSOR</category>
      <category>extension</category>
      <category>TRACKING</category>
      <category>tracking tool</category>
      <category>vscode</category>
      <category>익스탠션</category>
      <category>커서</category>
      <author>인어공쭈</author>
      <guid isPermaLink="true">https://haneui.tistory.com/101</guid>
      <comments>https://haneui.tistory.com/101#entry101comment</comments>
      <pubDate>Mon, 22 Dec 2025 09:51:47 +0900</pubDate>
    </item>
    <item>
      <title>React Native CodePush(Hot Updater) 적용기</title>
      <link>https://haneui.tistory.com/100</link>
      <description>&lt;h1 data-end=&quot;284&quot; data-start=&quot;256&quot;&gt;두려워서 시작했는데, 결국 적용해버린 이야기&lt;/h1&gt;
&lt;p data-end=&quot;354&quot; data-start=&quot;326&quot; data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발자로서 가장 무서운 순간이 언제일까.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;435&quot; data-start=&quot;356&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;371&quot; data-start=&quot;356&quot;&gt;배포 버튼을 누를 때&lt;/li&gt;
&lt;li data-end=&quot;396&quot; data-start=&quot;372&quot;&gt;금요일 오후에 긴급 이슈가 터졌을 때&lt;/li&gt;
&lt;li data-end=&quot;435&quot; data-start=&quot;397&quot;&gt;그리고 &lt;b&gt;스토어 심사를 다시 기다려야 한다는 걸 깨달았을 때&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;533&quot; data-start=&quot;437&quot; data-ke-size=&quot;size16&quot;&gt;영끌 앱을 운영하면서 이 모든 순간을 다 겪었다.&lt;br /&gt;특히 모바일 앱은 웹과 달리 &amp;ldquo;한 번 나가면 바로 고치기 어렵다&amp;rdquo;는 점에서&lt;br /&gt;항상 묘한 긴장감을 안고 있었다.&lt;/p&gt;
&lt;p data-end=&quot;556&quot; data-start=&quot;535&quot; data-ke-size=&quot;size16&quot;&gt;그러다 자연스럽게 이런 생각이 들었다.&lt;/p&gt;
&lt;blockquote data-end=&quot;588&quot; data-start=&quot;558&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;588&quot; data-start=&quot;560&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;웹처럼, 조금 고치고 바로 반영할 수는 없을까?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-end=&quot;616&quot; data-start=&quot;595&quot; data-ke-size=&quot;size26&quot;&gt;처음엔 Hot&amp;nbsp;Updater가 아니었다&lt;/h2&gt;
&lt;p data-end=&quot;664&quot; data-start=&quot;618&quot; data-ke-size=&quot;size16&quot;&gt;사실 처음부터 Hot Updater를 쓰려던 건 아니다. 비교적 단순한 revopush라는 서비스를 사용했다.&lt;/p&gt;
&lt;p data-end=&quot;717&quot; data-start=&quot;697&quot; data-ke-size=&quot;size16&quot;&gt;하지만 현실은 예상보다 빠르게 나를 때렸다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;779&quot; data-start=&quot;719&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;747&quot; data-start=&quot;719&quot;&gt;iOS 수정 &amp;rarr; Android에서 문제 발생&lt;/li&gt;
&lt;li data-end=&quot;779&quot; data-start=&quot;748&quot;&gt;Android 수정 &amp;rarr; iOS에서 다시 문제 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;840&quot; data-start=&quot;781&quot; data-ke-size=&quot;size16&quot;&gt;하루는 iOS가 멀쩡했고,&lt;br /&gt;다음 날은 Android가 멀쩡했다.&lt;br /&gt;&lt;b&gt;둘 다 멀쩡한 날은 없었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;850&quot; data-start=&quot;842&quot; data-ke-size=&quot;size16&quot;&gt;그때 깨달았다.&lt;br /&gt;문제를 해결하는 게 아니라 &lt;b&gt;확률 게임&lt;/b&gt;에 가까웠다.&lt;/p&gt;
&lt;h2 data-end=&quot;1021&quot; data-start=&quot;999&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;1021&quot; data-start=&quot;999&quot; data-ke-size=&quot;size26&quot;&gt;그래서 다시 마주한 Hot&amp;nbsp;Updater&lt;/h2&gt;
&lt;p data-end=&quot;1064&quot; data-start=&quot;1023&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1064&quot; data-start=&quot;1023&quot; data-ke-size=&quot;size16&quot;&gt;이전부터 알고는 있었지만 미뤄왔던 이녀석을&lt;br /&gt;다시 꺼내 들었다.&lt;/p&gt;
&lt;p data-end=&quot;1083&quot; data-start=&quot;1066&quot; data-ke-size=&quot;size16&quot;&gt;솔직히 말하면 여전히 무서웠다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1135&quot; data-start=&quot;1085&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1109&quot; data-start=&quot;1085&quot;&gt;잘못 배포하면 모든 유저에게 동시에 영향&lt;/li&gt;
&lt;li data-end=&quot;1123&quot; data-start=&quot;1110&quot;&gt;플랫폼별 미묘한 차이&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1153&quot; data-start=&quot;1137&quot; data-ke-size=&quot;size16&quot;&gt;하지만 한 가지는 분명해졌다.&lt;/p&gt;
&lt;blockquote data-end=&quot;1175&quot; data-start=&quot;1155&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1175&quot; data-start=&quot;1157&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지금 방식이 더 위험하다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1223&quot; data-start=&quot;1177&quot; data-ke-size=&quot;size16&quot;&gt;핫 업데이트가 무서운 게 아니라,&lt;br /&gt;&lt;b&gt;통제할 수 없는 배포가 무서운 거였다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1284&quot; data-start=&quot;1225&quot; data-ke-size=&quot;size16&quot;&gt;그래서 CodePush를 &amp;ldquo;편의 기능&amp;rdquo;이 아니라&lt;br /&gt;&lt;b&gt;통제 가능한 배포 수단&lt;/b&gt;으로 설계하기로 했다.&lt;/p&gt;
&lt;h2 data-end=&quot;1310&quot; data-start=&quot;1291&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;1310&quot; data-start=&quot;1291&quot; data-ke-size=&quot;size26&quot;&gt;적용하면서 가장 신경 쓴 것들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1338&quot; data-start=&quot;1312&quot; data-ke-size=&quot;size23&quot;&gt;1. 핫 업데이트의 범위를 명확히 정하기&lt;/h3&gt;
&lt;p data-end=&quot;1370&quot; data-start=&quot;1339&quot; data-ke-size=&quot;size16&quot;&gt;모든 걸 CodePush로 해결하려는 순간 사고가 난다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1411&quot; data-start=&quot;1372&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1381&quot; data-start=&quot;1372&quot;&gt;UI 수정&lt;/li&gt;
&lt;li data-end=&quot;1392&quot; data-start=&quot;1382&quot;&gt;텍스트 변경&lt;/li&gt;
&lt;li data-end=&quot;1411&quot; data-start=&quot;1393&quot;&gt;경미한 비즈니스 로직 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1452&quot; data-start=&quot;1413&quot; data-ke-size=&quot;size16&quot;&gt;이 정도까지만 허용했다.&lt;br /&gt;네이티브 영역이나 구조 변경은 절대 금지.&lt;/p&gt;
&lt;blockquote data-end=&quot;1475&quot; data-start=&quot;1454&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1475&quot; data-start=&quot;1456&quot; data-ke-size=&quot;size16&quot;&gt;웹처럼 쓰되, 웹처럼 믿지는 말자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;1503&quot; data-start=&quot;1482&quot; data-ke-size=&quot;size23&quot;&gt;2. 플랫폼별 분리와 롤백 전략&lt;/h3&gt;
&lt;p data-end=&quot;1552&quot; data-start=&quot;1504&quot; data-ke-size=&quot;size16&quot;&gt;이전의 가장 큰 문제는&lt;br /&gt;&lt;b&gt;iOS와 Android를 한 번에 묶어버린 것&lt;/b&gt;이었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1608&quot; data-start=&quot;1554&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1566&quot; data-start=&quot;1554&quot;&gt;플랫폼별 배포 분리&lt;/li&gt;
&lt;li data-end=&quot;1583&quot; data-start=&quot;1567&quot;&gt;항상 이전 안정 버전 유지&lt;/li&gt;
&lt;li data-end=&quot;1608&quot; data-start=&quot;1584&quot;&gt;문제 발생 시 즉시 롤백 가능하도록 설계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1654&quot; data-start=&quot;1610&quot; data-ke-size=&quot;size16&quot;&gt;이때 처음으로&lt;br /&gt;&lt;b&gt;프론트엔드도 배포 전략을 설계해야 한다&lt;/b&gt;는 걸 실감했다.&lt;/p&gt;
&lt;h3 data-end=&quot;1680&quot; data-start=&quot;1661&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;1680&quot; data-start=&quot;1661&quot; data-ke-size=&quot;size23&quot;&gt;3. 운영 관점에서의 안정화&lt;/h3&gt;
&lt;p data-end=&quot;1707&quot; data-start=&quot;1681&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1707&quot; data-start=&quot;1681&quot; data-ke-size=&quot;size16&quot;&gt;CodePush를 &amp;ldquo;적용했다&amp;rdquo;로 끝내지 않았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1756&quot; data-start=&quot;1709&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1724&quot; data-start=&quot;1709&quot;&gt;플랫폼별 동작 차이 체크&lt;/li&gt;
&lt;li data-end=&quot;1756&quot; data-start=&quot;1736&quot;&gt;업데이트 타이밍에 따른 UX 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1821&quot; data-start=&quot;1804&quot; data-ke-size=&quot;size26&quot;&gt;적용하고 나서 달라진 것들&lt;/h2&gt;
&lt;p data-end=&quot;1837&quot; data-start=&quot;1823&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1837&quot; data-start=&quot;1823&quot; data-ke-size=&quot;size16&quot;&gt;가장 큰 변화는 이거였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1880&quot; data-start=&quot;1839&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1852&quot; data-start=&quot;1839&quot;&gt;긴급 이슈 대응 속도&lt;/li&gt;
&lt;li data-end=&quot;1863&quot; data-start=&quot;1853&quot;&gt;CS 대응 시간&lt;/li&gt;
&lt;li data-end=&quot;1880&quot; data-start=&quot;1864&quot;&gt;그리고 배포에 대한 두려움&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1946&quot; data-start=&quot;1882&quot; data-ke-size=&quot;size16&quot;&gt;스토어 재배포 없이&lt;br /&gt;&lt;b&gt;그날 안에 문제를 해결할 수 있다는 것&lt;/b&gt;은&lt;br /&gt;운영하는 입장에서 엄청난 안정감을 줬다.&lt;/p&gt;
&lt;p data-end=&quot;1959&quot; data-start=&quot;1948&quot; data-ke-size=&quot;size16&quot;&gt;무엇보다 좋았던 건,&lt;/p&gt;
&lt;blockquote data-end=&quot;2004&quot; data-start=&quot;1961&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;2004&quot; data-start=&quot;1963&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;프론트엔드가 단순 구현자가 아니라&lt;br /&gt;서비스 운영의 한 축이 되었다&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;2015&quot; data-start=&quot;2006&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2015&quot; data-start=&quot;2006&quot; data-ke-size=&quot;size16&quot;&gt;라는 감각이었다.&lt;/p&gt;
&lt;h2 data-end=&quot;2030&quot; data-start=&quot;2022&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;2030&quot; data-start=&quot;2022&quot; data-ke-size=&quot;size26&quot;&gt;마무리하며&lt;/h2&gt;
&lt;p data-end=&quot;2078&quot; data-start=&quot;2032&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2078&quot; data-start=&quot;2032&quot; data-ke-size=&quot;size16&quot;&gt;CodePush는 여전히 조심스럽다.&lt;br /&gt;지금도 배포할 때마다 한 번 더 확인한다.&lt;/p&gt;
&lt;p data-end=&quot;2090&quot; data-start=&quot;2080&quot; data-ke-size=&quot;size16&quot;&gt;하지만 분명한 건,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2165&quot; data-start=&quot;2092&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2112&quot; data-start=&quot;2092&quot;&gt;레포 푸시로 겪은 시행착오 덕분에&lt;/li&gt;
&lt;li data-end=&quot;2134&quot; data-start=&quot;2113&quot;&gt;배포를 더 진지하게 고민하게 되었고&lt;/li&gt;
&lt;li data-end=&quot;2165&quot; data-start=&quot;2135&quot;&gt;그 결과 CodePush를 제대로 설계할 수 있었다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2174&quot; data-start=&quot;2167&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2210&quot; data-start=&quot;2176&quot; data-ke-size=&quot;size16&quot;&gt;두려웠지만,&lt;br /&gt;그래서 더 공부했고&lt;br /&gt;그래서 더 안전해졌다.&lt;/p&gt;
&lt;p data-end=&quot;2278&quot; data-start=&quot;2212&quot; data-ke-size=&quot;size16&quot;&gt;프론트엔드 개발자로서&lt;br /&gt;&lt;b&gt;&amp;ldquo;화면을 만드는 사람&amp;rdquo;을 넘어&lt;br /&gt;&amp;ldquo;서비스를 안전하게 운영하는 사람&amp;rdquo;이 되게 만든 경험&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2297&quot; data-start=&quot;2280&quot; data-ke-size=&quot;size16&quot;&gt;그게 내 핫 업데이트 적용기다.&lt;/p&gt;</description>
      <category>React-Native</category>
      <category>codepush</category>
      <category>레포푸시</category>
      <category>배포</category>
      <category>운영배포</category>
      <category>코드푸시</category>
      <category>핫업데이터</category>
      <author>인어공쭈</author>
      <guid isPermaLink="true">https://haneui.tistory.com/100</guid>
      <comments>https://haneui.tistory.com/100#entry100comment</comments>
      <pubDate>Sat, 20 Dec 2025 20:07:22 +0900</pubDate>
    </item>
    <item>
      <title>HOxy, 프론트 개발자가 경험한 혹시 주도 개발(Feat: Server-Driven UI)</title>
      <link>https://haneui.tistory.com/99</link>
      <description>&lt;h2 data-end=&quot;234&quot; data-start=&quot;190&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빠르게 변하는 서비스에서 Server-Driven UI가 왜 핵심이었나&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;343&quot; data-start=&quot;236&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HOxy(혹시) 개발을 진행하면서 가장 크게 필요했던 것은&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UI 구조를 서버 정책 변화에 의존하게 만들지 않는 것,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉 server-driven UI 기반의 확장 가능 구조였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;412&quot; data-start=&quot;345&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기획 변경이 하루에도 몇 번씩 들어오는 환경에서&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;ldquo;앱을 다시 빌드해야만 반영되는 구조&amp;rdquo;는 더 이상 유지가 불가능했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;481&quot; data-start=&quot;414&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 프론트의 모든 UI 흐름을&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정책 플래그, 서버 응답, 동적 레이아웃 정보로 제어할 수 있도록 만들었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;530&quot; data-start=&quot;483&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래는 실제로 활용했던 server-driven UI의 핵심 개념과 구현 방식 입니다. 간략하게 설명하고자 ~이다 체로 변경할께요 : )&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h1 data-end=&quot;568&quot; data-start=&quot;537&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Server-Driven UI가 필요한 이유&lt;/span&gt;&lt;/h1&gt;
&lt;p data-end=&quot;600&quot; data-start=&quot;569&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적인 모바일 앱에서 UI는 코드에 강하게 묶여 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;678&quot; data-start=&quot;602&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;614&quot; data-start=&quot;602&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;버튼 노출 조건&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;628&quot; data-start=&quot;615&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;화면 이동 플로우&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;645&quot; data-start=&quot;629&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특정 상태의 예외 처리&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;663&quot; data-start=&quot;646&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CTA 기능의 정책 변경&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;678&quot; data-start=&quot;664&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문구/레이아웃 변경&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;714&quot; data-start=&quot;680&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 모든 것이 &amp;ldquo;코드 &amp;rarr; 빌드 &amp;rarr; 배포&amp;rdquo; 과정을 거쳐야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;782&quot; data-start=&quot;716&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 HOxy처럼 정책이 자주 바뀌는 서비스는 이 방식으로는 절대 따라갈 수 없다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 방향을 바꿨다:&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-end=&quot;855&quot; data-start=&quot;784&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;855&quot; data-start=&quot;786&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;lsquo;UI는 서버가 결정하고, 앱은 렌더링만 한다&amp;rsquo;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이게 HOxy에서 적용한 server-driven UI 근간이다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1 data-end=&quot;889&quot; data-start=&quot;862&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Server-Driven UI 구조 설계&lt;/span&gt;&lt;/h1&gt;
&lt;h2 data-end=&quot;929&quot; data-start=&quot;891&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) Policy Layer: 정책을 하나로 묶는 중앙집중 모듈&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;982&quot; data-start=&quot;930&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가장 먼저 한 일은 정책&amp;middot;조건문&amp;middot;분기 로직을 모두 UI 컴포넌트에서 제거하는 것이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1014&quot; data-start=&quot;984&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대신 모든 정책은 아래 형태의 중앙 모듈에서 처리한다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-end=&quot;1363&quot; data-start=&quot;1346&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UI는 정책을 &amp;ldquo;실행&amp;rdquo;만 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1372&quot; data-start=&quot;1365&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 되면:&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1452&quot; data-start=&quot;1374&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1396&quot; data-start=&quot;1374&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정책이 바뀌면 정책 레이어만 수정&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1412&quot; data-start=&quot;1397&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;화면 코드 변경 없음&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1428&quot; data-start=&quot;1413&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 범위 명확해짐&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1452&quot; data-start=&quot;1429&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버 정책 변경 시 즉시 반영 가능&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1479&quot; data-start=&quot;1454&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;= 빌드 없는 정책 변경이 가능해진다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1479&quot; data-start=&quot;1454&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1520&quot; data-start=&quot;1486&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) Server Response 기반 UI 전략 자동화&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;1570&quot; data-start=&quot;1521&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정책 레이어가 방향을 잡아준다면,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제 흐름은 서버 응답이 결정하게 만들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1597&quot; data-start=&quot;1572&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들어 서버 응답이 아래처럼 온다고 하자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763358643310&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;status&quot;: &quot;BLOCKED&quot;,
  &quot;nextAction&quot;: &quot;OPEN_MODAL&quot;,
  &quot;modalType&quot;: &quot;VERIFICATION_REQUIRED&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;앱에서는 아래처럼 자동으로 전략을 조립한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763358656503&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;switch (response.status) {
  case 'BLOCKED':
    if (response.nextAction === 'OPEN_MODAL') {
      openModal(response.modalType);
    }
    break;
  case 'OK':
    navigate('Home');
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉,&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1992&quot; data-start=&quot;1933&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1947&quot; data-start=&quot;1933&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어떤 모달을 띄울지&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1964&quot; data-start=&quot;1948&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어떤 화면으로 이동할지&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1992&quot; data-start=&quot;1965&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CTA 버튼이 어떤 기능을 수행해야 하는지&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2029&quot; data-start=&quot;1994&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전부 서버가 준 instruction대로 렌더링만 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2058&quot; data-start=&quot;2031&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 방식은 기획 변경이 심할수록 진가를 발휘한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2058&quot; data-start=&quot;2031&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;2795&quot; data-start=&quot;2767&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 구조가 결국 &amp;ldquo;주도 개발&amp;rdquo;을 만들어냈다&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;2856&quot; data-start=&quot;2796&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Server-Driven UI는 단순히 UI 기술이 아니라&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서비스 전체 흐름을 설계하는 방식이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;2902&quot; data-start=&quot;2858&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 구조를 프론트에서 설계하면서 자연스럽게 이런 질문이 나에게 모이기 시작했다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3021&quot; data-start=&quot;2904&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2928&quot; data-start=&quot;2904&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어떤 정책이 우선순위를 가져야 하는지&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2961&quot; data-start=&quot;2929&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;API가 어느 범위까지 UI 정책을 책임져야 하는지&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2988&quot; data-start=&quot;2962&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특정 사용자 상태에서 어떤 흐름이 맞는지&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3021&quot; data-start=&quot;2989&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;플래그 조합이 충돌할 때 어떤 전략을 띄워야 하는지&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3109&quot; data-start=&quot;3023&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프론트에서 server-driven 구조를 설계하는 순간,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정책&amp;middot;기획&amp;middot;흐름&amp;middot;데이터 구조까지 책임지는&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;주도 개발의 중심에 서게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3109&quot; data-start=&quot;3023&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3109&quot; data-start=&quot;3023&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HOxy에서의 server-driven UI 도입은 단순히 &amp;ldquo;유연한 UI&amp;rdquo; 문제가 아니었다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3272&quot; data-start=&quot;3179&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3194&quot; data-start=&quot;3179&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빠른 기획 변경 대응&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3211&quot; data-start=&quot;3195&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;재배포 없는 정책 수정&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3225&quot; data-start=&quot;3212&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UI 전략 자동화&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3239&quot; data-start=&quot;3226&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예외 케이스 통일&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3257&quot; data-start=&quot;3240&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모듈 단위 재사용성 증가&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3272&quot; data-start=&quot;3258&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유지보수 비용 절감&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3283&quot; data-start=&quot;3274&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 무엇보다,&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-end=&quot;3332&quot; data-start=&quot;3285&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;3332&quot; data-start=&quot;3287&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;프론트엔드인 내가 서비스의 흐름을 정의하는 주체가 될 수 있게 만든 방식이었다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>잡담</category>
      <author>인어공쭈</author>
      <guid isPermaLink="true">https://haneui.tistory.com/99</guid>
      <comments>https://haneui.tistory.com/99#entry99comment</comments>
      <pubDate>Mon, 17 Nov 2025 14:54:25 +0900</pubDate>
    </item>
    <item>
      <title>&amp;ldquo;React는 게임엔 안 맞다.&amp;rdquo; 그래서 만들어봤습니다.</title>
      <link>https://haneui.tistory.com/98</link>
      <description>&lt;h2 data-end=&quot;244&quot; data-start=&quot;217&quot; data-ke-size=&quot;size26&quot;&gt;React로 만든 &amp;ldquo;하리니 오락실&amp;rdquo; 제작 썰&lt;/h2&gt;
&lt;blockquote data-end=&quot;308&quot; data-start=&quot;246&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;308&quot; data-start=&quot;248&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;아니, 프론트 개발자가 오락실을 만든다고?&amp;rdquo;&lt;br /&gt;네, React로요. 그리고 또 1등했습니다.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;332&quot; data-start=&quot;315&quot; data-ke-size=&quot;size26&quot;&gt;하리니 오락실의 정체&lt;/h2&gt;
&lt;p data-end=&quot;433&quot; data-start=&quot;334&quot; data-ke-size=&quot;size16&quot;&gt;하리니 오락실은 &lt;b&gt;영끌 앱 안의 미니게임 센터&lt;/b&gt;예요.&lt;br /&gt;&amp;ldquo;앱 안에 오락실이 있다고요?&amp;rdquo;&lt;br /&gt;네, 광고도 노출하고, 포인트도 쓰고,&lt;br /&gt;유저도 붙잡는 일석삼조 프로젝트입니다.&lt;/p&gt;
&lt;p data-end=&quot;455&quot; data-start=&quot;435&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표 요약하면 이렇습니다  &lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;608&quot; data-start=&quot;457&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;489&quot; data-start=&quot;457&quot;&gt;&lt;b&gt;리텐션 확보&lt;/b&gt; &amp;mdash; &amp;ldquo;하리니 보러 매일 출근각&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;534&quot; data-start=&quot;490&quot;&gt;&lt;b&gt;광고 구좌 확보&lt;/b&gt; &amp;mdash; &amp;ldquo;죽었을 때 광고 보면 부활 (상술의 미학)&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;570&quot; data-start=&quot;535&quot;&gt;&lt;b&gt;포인트 소진&lt;/b&gt; &amp;mdash; &amp;ldquo;이왕이면 즐겁게 소비하게 하자&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;608&quot; data-start=&quot;571&quot;&gt;&lt;b&gt;체류시간 확보&lt;/b&gt; &amp;mdash; &amp;ldquo;유저가 앱에서 못 나가게 하자  &amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;654&quot; data-start=&quot;610&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;654&quot; data-start=&quot;612&quot; data-ke-size=&quot;size16&quot;&gt;PM님이 한 줄로 정리했습니다.&lt;br /&gt;&amp;ldquo;유저의 시간을 뺏자. 착하게.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-end=&quot;684&quot; data-start=&quot;661&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;684&quot; data-start=&quot;661&quot; data-ke-size=&quot;size26&quot;&gt;  게임 두 개, 미친듯이 단순하게&lt;/h2&gt;
&lt;p data-end=&quot;769&quot; data-start=&quot;686&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;769&quot; data-start=&quot;686&quot; data-ke-size=&quot;size16&quot;&gt;React로 FPS 떨어지는 순간, 그건 게임이 아니라 슬라이드쇼입니다.&lt;br /&gt;그래서 &lt;b&gt;단순함 + 몰입감 + 중독성&lt;/b&gt; 이 세 가지를 목표로 했어요.&lt;/p&gt;
&lt;h3 data-end=&quot;808&quot; data-start=&quot;776&quot; data-ke-size=&quot;size23&quot;&gt;1) 바구니 캐치 (Basket Catch)&lt;/h3&gt;
&lt;blockquote data-end=&quot;875&quot; data-start=&quot;810&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;875&quot; data-start=&quot;812&quot; data-ke-size=&quot;size16&quot;&gt;하늘에서 과일이 떨어집니다.&lt;br /&gt;유저는 바구니를 좌우로 흔듭니다.&lt;br /&gt;그리고&amp;hellip; 폭탄도 떨어집니다.  &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;948&quot; data-start=&quot;877&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;893&quot; data-start=&quot;877&quot;&gt;사과  : +100점&lt;/li&gt;
&lt;li data-end=&quot;910&quot; data-start=&quot;894&quot;&gt;오렌지  : +80점&lt;/li&gt;
&lt;li data-end=&quot;927&quot; data-start=&quot;911&quot;&gt;폭탄  : -1 생명&lt;/li&gt;
&lt;li data-end=&quot;948&quot; data-start=&quot;928&quot;&gt;불  : 속도 미쳤음 (벌받음)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1003&quot; data-start=&quot;950&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1003&quot; data-start=&quot;950&quot; data-ke-size=&quot;size16&quot;&gt;게임이 단순하다고요?&lt;br /&gt;근데 3판만 하면 알 거예요.&lt;br /&gt;이게 왜 멈출 수 없는지.&lt;/p&gt;
&lt;h3 data-end=&quot;1042&quot; data-start=&quot;1010&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;1042&quot; data-start=&quot;1010&quot; data-ke-size=&quot;size23&quot;&gt;2) 하리니 쌓기 (Harini Stack)&lt;/h3&gt;
&lt;blockquote data-end=&quot;1103&quot; data-start=&quot;1044&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1103&quot; data-start=&quot;1046&quot; data-ke-size=&quot;size16&quot;&gt;블록이 좌우로 왔다갔다,&lt;br /&gt;클릭하면 &amp;ldquo;착&amp;rdquo;하고 쌓입니다.&lt;br /&gt;살짝만 삐끗하면 &amp;ldquo;짤림&amp;rdquo;  &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1167&quot; data-start=&quot;1105&quot; data-ke-size=&quot;size16&quot;&gt;Perfect Stack을 성공하면&lt;br /&gt;하리니가 &amp;ldquo;헤헤~ 잘했어&amp;rdquo; 하는데,&lt;br /&gt;그 순간 도파민이 쏟아집니다.&lt;/p&gt;
&lt;p data-end=&quot;1218&quot; data-start=&quot;1169&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이거 React로 만들었다는 게 말이 되냐고요&amp;rdquo;&lt;br /&gt;&amp;rarr; 됩니다. 커서와 함께라면..ㅋㅋ&lt;/p&gt;
&lt;p data-end=&quot;1218&quot; data-start=&quot;1169&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1218&quot; data-start=&quot;1169&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1254&quot; data-start=&quot;1225&quot; data-ke-size=&quot;size26&quot;&gt;Canvas와 React, 그 위험한 동거&lt;/h2&gt;
&lt;p data-end=&quot;1307&quot; data-start=&quot;1256&quot; data-ke-size=&quot;size16&quot;&gt;React는 화면 리렌더링을 사랑하고,&lt;br /&gt;Canvas는 그걸 &lt;b&gt;극도로 싫어합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1325&quot; data-start=&quot;1309&quot; data-ke-size=&quot;size16&quot;&gt;결국 이렇게 결론 내렸죠:&lt;/p&gt;
&lt;blockquote data-end=&quot;1364&quot; data-start=&quot;1326&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1364&quot; data-start=&quot;1328&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;React야, 넌 UI만 해. Canvas는 내가 그릴게.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre id=&quot;code_1762852463746&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export class GameEngine {
  update(dt) {
    this.updatePlayer(dt);
    this.updateItems(dt);
  }

  render() {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.renderPlayer();
    this.renderItems();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1707&quot; data-start=&quot;1688&quot; data-ke-size=&quot;size26&quot;&gt;하루 10판 제한 시스템&lt;/h2&gt;
&lt;p data-end=&quot;1746&quot; data-start=&quot;1709&quot; data-ke-size=&quot;size16&quot;&gt;유저들이 미친 듯이 플레이하면&lt;br /&gt;서버비도, 정신도 타들어가요.&lt;/p&gt;
&lt;p data-end=&quot;1772&quot; data-start=&quot;1748&quot; data-ke-size=&quot;size16&quot;&gt;그래서 하루 10판만 가능하게 했습니다.&lt;/p&gt;
&lt;p data-end=&quot;1834&quot; data-start=&quot;1774&quot; data-ke-size=&quot;size16&quot;&gt;Zustand + localStorage로 간단하게 구현했는데,&lt;br /&gt;이게 또 은근히 UX에 영향을 줘요.&lt;/p&gt;
&lt;p data-end=&quot;1859&quot; data-start=&quot;1836&quot; data-ke-size=&quot;size16&quot;&gt;10판 끝나면 하리니가 나와서 말하죠.&lt;/p&gt;
&lt;blockquote data-end=&quot;1894&quot; data-start=&quot;1860&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1894&quot; data-start=&quot;1862&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이봐요, 쉬었다 하죠 ☕ (친구 초대하면 한 판 더!)&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1910&quot; data-start=&quot;1896&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 사람들은 안 쉽습니다.&lt;/p&gt;
&lt;p data-end=&quot;1910&quot; data-start=&quot;1896&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1948&quot; data-start=&quot;1917&quot; data-ke-size=&quot;size26&quot;&gt;랭킹 시스템: 사람의 경쟁심은 무료가 아닙니다&lt;/h2&gt;
&lt;p data-end=&quot;2019&quot; data-start=&quot;1950&quot; data-ke-size=&quot;size16&quot;&gt;하루 종일 게임하다가 랭킹창 눌러보면&lt;br /&gt;&amp;ldquo;내 점수 2등이네?&amp;rdquo;&lt;br /&gt;&amp;rarr; 5분 후 1등 빼앗김&lt;br /&gt;&amp;rarr; 10분 후 다시 접속&lt;/p&gt;
&lt;p data-end=&quot;2045&quot; data-start=&quot;2021&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Retention 확보 성공&lt;/b&gt;  &lt;/p&gt;
&lt;p data-end=&quot;2105&quot; data-start=&quot;2047&quot; data-ke-size=&quot;size16&quot;&gt;Prisma ORM으로 groupBy 쿼리 날리며,&lt;br /&gt;&amp;ldquo;이게 진짜 DB에서 되는구나&amp;rdquo; 감탄했습니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1762852516300&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const rankings = await prisma.score.groupBy({
  by: [&quot;userId&quot;],
  _max: { score: true },
  orderBy: { _max: { score: &quot;desc&quot; } },
  take: 50,
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;랭킹이 실시간으로 오를 때,&lt;br /&gt;그게 진짜 하리니 오락실의 dopamine trigger입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;2349&quot; data-start=&quot;2331&quot; data-ke-size=&quot;size26&quot;&gt;버그는 게임의 일부였다&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2477&quot; data-start=&quot;2351&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2397&quot; data-start=&quot;2351&quot;&gt;아이템이 너무 많이 떨어져서 FPS 20 &amp;rarr; Object Pooling 도입&lt;/li&gt;
&lt;li data-end=&quot;2437&quot; data-start=&quot;2398&quot;&gt;모바일 터치가 300ms 느림 &amp;rarr; passive: false&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;2543&quot; data-start=&quot;2526&quot; data-ke-size=&quot;size26&quot;&gt;반응형 &amp;amp; 터치 대응&lt;/h2&gt;
&lt;p data-end=&quot;2615&quot; data-start=&quot;2545&quot; data-ke-size=&quot;size16&quot;&gt;Canvas 크기 조정은 지옥이었습니다.&lt;br /&gt;기기마다 픽셀 비율이 달라서&lt;br /&gt;&amp;ldquo;하리니 머리가 길어졌다 짧아졌다&amp;rdquo; 반복  &lt;/p&gt;
&lt;p data-end=&quot;2687&quot; data-start=&quot;2617&quot; data-ke-size=&quot;size16&quot;&gt;결국 devicePixelRatio 기반으로 정규화.&lt;br /&gt;지금은 모든 기기에서&lt;br /&gt;하리니가 일정합니다. (감격)&lt;/p&gt;
&lt;p data-end=&quot;2687&quot; data-start=&quot;2617&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;3109&quot; data-start=&quot;3092&quot; data-ke-size=&quot;size26&quot;&gt;결과: 또 1등 했다&lt;/h2&gt;
&lt;blockquote data-end=&quot;3180&quot; data-start=&quot;3111&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;3135&quot; data-start=&quot;3113&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;부틀리 게임센터 1등 발표합니다.&amp;rdquo;&lt;/p&gt;
&lt;p data-end=&quot;3165&quot; data-start=&quot;3142&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;프로젝트: 하리니 오락실&lt;/p&gt;
&lt;p data-end=&quot;3180&quot; data-start=&quot;3172&quot; data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;3268&quot; data-start=&quot;3231&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <author>인어공쭈</author>
      <guid isPermaLink="true">https://haneui.tistory.com/98</guid>
      <comments>https://haneui.tistory.com/98#entry98comment</comments>
      <pubDate>Wed, 12 Nov 2025 18:06:16 +0900</pubDate>
    </item>
    <item>
      <title>부틀리 시간 몇 시간으로 AI 챗봇을 엔드투엔드 구현한 이야기</title>
      <link>https://haneui.tistory.com/97</link>
      <description>&lt;h1 data-end=&quot;203&quot; data-start=&quot;178&quot;&gt;React로 시작해 인턴을 만들었습니다&lt;/h1&gt;
&lt;p data-end=&quot;223&quot; data-start=&quot;204&quot; data-ke-size=&quot;size16&quot;&gt;AI 챗봇 &amp;lsquo;하리니&amp;rsquo; 개발 회고&lt;/p&gt;
&lt;p data-end=&quot;223&quot; data-start=&quot;204&quot; data-ke-size=&quot;size16&quot;&gt;이미 몇달이나 지나서 앱에 올라갔지만 그냥 넘어가긴 아쉬워서 쓰는 글&lt;/p&gt;
&lt;h2 data-end=&quot;260&quot; data-start=&quot;230&quot; data-ke-size=&quot;size26&quot;&gt;부틀리(Bootly): 하고 싶은 걸 해보는 시간&lt;/h2&gt;
&lt;p data-end=&quot;416&quot; data-start=&quot;262&quot; data-ke-size=&quot;size16&quot;&gt;저희 회사에는 &lt;b&gt;부틀리(Bootly)&lt;/b&gt;라는 문화가 있습니다.&lt;br /&gt;한 달에 한 번, 정해진 토픽 아래 &lt;b&gt;일주일에 몇 시간 정도&lt;/b&gt;를 본업 외의 무언가에 도전해보는 시간이죠.&lt;br /&gt;누군가는 새로운 기능을 실험하고, 누군가는 데이터를 분석하거나 디자인을 다시 뜯어봅니다.&lt;/p&gt;
&lt;p data-end=&quot;446&quot; data-start=&quot;418&quot; data-ke-size=&quot;size16&quot;&gt;저는 이번에, 조금 다른 걸 해보기로 했습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;473&quot; data-start=&quot;447&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;473&quot; data-start=&quot;449&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;CX팀을 도와줄 챗봇을 직접 만들어보자.&amp;rdquo;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-end=&quot;500&quot; data-start=&quot;480&quot; data-ke-size=&quot;size26&quot;&gt;CX의 한숨에서 시작된 프로젝트&lt;/h2&gt;
&lt;p data-end=&quot;607&quot; data-start=&quot;502&quot; data-ke-size=&quot;size16&quot;&gt;시작은 단순했습니다.&lt;br /&gt;옆자리 CX 담당자분이 &amp;ldquo;오늘도 문의가 너무 많아요&amp;hellip;&amp;rdquo;라고 말하던 그날.&lt;br /&gt;유저 수는 빠르게 늘고 있었고, 매일 쏟아지는 문의는 하루를 꽉 채우고 있었습니다.&lt;/p&gt;
&lt;p data-end=&quot;629&quot; data-start=&quot;609&quot; data-ke-size=&quot;size16&quot;&gt;그 말을 듣고 문득 생각했습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;653&quot; data-start=&quot;630&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;653&quot; data-start=&quot;632&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이건 진짜 자동화가 필요하겠다.&amp;rdquo;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;729&quot; data-start=&quot;655&quot; data-ke-size=&quot;size16&quot;&gt;그래서 CX 담당자분과 함께 &lt;b&gt;&amp;lsquo;우리만의 CX 챗봇&amp;rsquo;을 만들어보기로&lt;/b&gt; 했습니다.&lt;br /&gt;기획은 CX팀이, 개발은 제가 맡았습니다.&lt;/p&gt;
&lt;h2 data-end=&quot;751&quot; data-start=&quot;736&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;751&quot; data-start=&quot;736&quot; data-ke-size=&quot;size26&quot;&gt;&amp;ldquo;인턴 하리니&amp;rdquo;의 탄생&lt;/h2&gt;
&lt;p data-end=&quot;800&quot; data-start=&quot;753&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;800&quot; data-start=&quot;753&quot; data-ke-size=&quot;size16&quot;&gt;이름은 &amp;lsquo;인턴 하리니&amp;rsquo;.&lt;br /&gt;처음엔 정말 인턴처럼, 어설프지만 성실했습니다.  &lt;/p&gt;
&lt;p data-end=&quot;870&quot; data-start=&quot;802&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표는 단 하나.&lt;/b&gt;&lt;br /&gt;반복되는 FAQ 문의를 대신 응대해, CX팀이 더 중요한 일에 집중할 수 있도록 하는 것.&lt;/p&gt;
&lt;p data-end=&quot;892&quot; data-start=&quot;872&quot; data-ke-size=&quot;size16&quot;&gt;한 달 동안 다음을 진행했습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;991&quot; data-start=&quot;893&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;920&quot; data-start=&quot;893&quot;&gt;CX 데이터 분석 및 FAQ JSON 구축&lt;/li&gt;
&lt;li data-end=&quot;937&quot; data-start=&quot;921&quot;&gt;사용자 문의 패턴 정리&lt;/li&gt;
&lt;li data-end=&quot;958&quot; data-start=&quot;938&quot;&gt;Gemini 기반 LLM 연동&lt;/li&gt;
&lt;li data-end=&quot;991&quot; data-start=&quot;959&quot;&gt;프론트(React) + 백엔드(Express) 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-end=&quot;1008&quot; data-start=&quot;998&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;1008&quot; data-start=&quot;998&quot; data-ke-size=&quot;size26&quot;&gt;기술적인 구성&lt;/h2&gt;
&lt;p data-end=&quot;1024&quot; data-start=&quot;1010&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1024&quot; data-start=&quot;1010&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Frontend&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1091&quot; data-start=&quot;1025&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1047&quot; data-start=&quot;1025&quot;&gt;React + TypeScript&lt;/li&gt;
&lt;li data-end=&quot;1073&quot; data-start=&quot;1048&quot;&gt;간단한 채팅 UI, 입력/응답 인터랙션&lt;/li&gt;
&lt;li data-end=&quot;1091&quot; data-start=&quot;1074&quot;&gt;상태 관리 및 로딩 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1106&quot; data-start=&quot;1093&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Backend&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1214&quot; data-start=&quot;1107&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1128&quot; data-start=&quot;1107&quot;&gt;Express + Node.js&lt;/li&gt;
&lt;li data-end=&quot;1152&quot; data-start=&quot;1129&quot;&gt;/classify: 문의 내용 분류&lt;/li&gt;
&lt;li data-end=&quot;1181&quot; data-start=&quot;1153&quot;&gt;/response: 카테고리 기반 답변 반환&lt;/li&gt;
&lt;li data-end=&quot;1214&quot; data-start=&quot;1182&quot;&gt;LLM: Google Gemini 2.0 Flash&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1232&quot; data-start=&quot;1216&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Data Layer&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1289&quot; data-start=&quot;1233&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1257&quot; data-start=&quot;1233&quot;&gt;CX팀이 직접 정제한 FAQ JSON&lt;/li&gt;
&lt;li data-end=&quot;1289&quot; data-start=&quot;1258&quot;&gt;각 항목별 카테고리, 키워드, 응답 텍스트 구조화&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1762756108031&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const classifyInquiry = async (text) =&amp;gt; {
  const keywords = await gemini.extractKeywords(text);
  const matched = faq.find((item) =&amp;gt;
    item.keywords.some((k) =&amp;gt; keywords.includes(k))
  );
  return matched?.response || &quot;이건 조금 더 공부 중이에요  &quot;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1553&quot; data-start=&quot;1524&quot; data-ke-size=&quot;size26&quot;&gt;Google ADK로 cs_ai_agent 구현&lt;/h2&gt;
&lt;p data-end=&quot;1685&quot; data-start=&quot;1555&quot; data-ke-size=&quot;size16&quot;&gt;챗봇을 한 단계 업그레이드하기 위해 **Google ADK(Agent Development Kit)**를 사용했습니다.&lt;br /&gt;FAQ와 VOCS 데이터를 instruction에 넣어, 반복되는 문의를 &lt;b&gt;자동으로 처리&lt;/b&gt;하도록 설계했죠.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1858&quot; data-start=&quot;1687&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1744&quot; data-start=&quot;1687&quot;&gt;&lt;b&gt;구현 방식&lt;/b&gt;: instruction embedding &amp;rarr; RAG/벡터 검색 없이 단순 구현&lt;/li&gt;
&lt;li data-end=&quot;1773&quot; data-start=&quot;1745&quot;&gt;&lt;b&gt;모델&lt;/b&gt;: Gemini 2.0 Flash&lt;/li&gt;
&lt;li data-end=&quot;1808&quot; data-start=&quot;1774&quot;&gt;&lt;b&gt;데이터&lt;/b&gt;: FAQ 40개+, VOCS 1800개+&lt;/li&gt;
&lt;li data-end=&quot;1858&quot; data-start=&quot;1809&quot;&gt;&lt;b&gt;실행&lt;/b&gt;: InMemoryRunner를 사용, 비동기 스트리밍으로 실시간 응답&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;1920&quot; data-start=&quot;1860&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1920&quot; data-start=&quot;1862&quot; data-ke-size=&quot;size16&quot;&gt;장점: 단순, 빠른 프로토타입 가능&lt;br /&gt;한계: 데이터가 늘어나면 토큰 사용량 증가, RAG 전환 필요&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-end=&quot;1575&quot; data-start=&quot;1553&quot; data-ke-size=&quot;size26&quot;&gt;결과: 부틀리 1등, 그리고 상용화&lt;/h2&gt;
&lt;p data-end=&quot;1645&quot; data-start=&quot;1577&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1645&quot; data-start=&quot;1577&quot; data-ke-size=&quot;size16&quot;&gt;한 달 뒤, 이 프로젝트는 &lt;b&gt;부틀리 1등&lt;/b&gt;으로 선정되었습니다.&lt;br /&gt;그리고 다른 기획자, 백엔드, AI 담당자분들이 합류해&lt;br /&gt;&amp;lsquo;인턴 하리니&amp;rsquo;는 &lt;b&gt;정식 앱 기능으로 상용화&lt;/b&gt;되었죠.&lt;/p&gt;
&lt;p data-end=&quot;1766&quot; data-start=&quot;1711&quot; data-ke-size=&quot;size16&quot;&gt;지금은 유저의 문의를 자동으로 처리하고,&lt;br /&gt;CX팀의 하루를 조금 더 여유롭게 만들어주고 있습니다.&lt;/p&gt;
&lt;h2 data-end=&quot;1795&quot; data-start=&quot;1773&quot; data-ke-size=&quot;size26&quot;&gt;회고: 중요한 건 &amp;lsquo;관찰&amp;rsquo;&lt;/h2&gt;
&lt;p data-end=&quot;1858&quot; data-start=&quot;1797&quot; data-ke-size=&quot;size16&quot;&gt;이 프로젝트를 하면서 가장 크게 느낀 건,&lt;br /&gt;좋은 아이디어는 꼭 회의실에서 나오지 않는다는 점이었습니다.&lt;/p&gt;
&lt;p data-end=&quot;1918&quot; data-start=&quot;1860&quot; data-ke-size=&quot;size16&quot;&gt;옆자리의 한숨, &amp;ldquo;이거 좀 불편해요&amp;rdquo; 같은 말 한마디가&lt;br /&gt;가장 강력한 문제 정의가 될 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;1963&quot; data-start=&quot;1920&quot; data-ke-size=&quot;size16&quot;&gt;그걸 &lt;b&gt;기술로 해결해보는 시도&lt;/b&gt;&lt;br /&gt;그게 결국 개발자의 재미 아닐까요?&lt;/p&gt;
&lt;h2 data-end=&quot;1980&quot; data-start=&quot;1970&quot; data-ke-size=&quot;size26&quot;&gt;마무리하며&lt;/h2&gt;
&lt;p data-end=&quot;2054&quot; data-start=&quot;1982&quot; data-ke-size=&quot;size16&quot;&gt;React로 시작해 인턴을 만들 줄은 몰랐습니다.&lt;br /&gt;하지만 덕분에, 기술이 사람을 돕는 방식에 대해 다시 생각하게 됐습니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;2087&quot; data-start=&quot;2056&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;2087&quot; data-start=&quot;2058&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;코드는 결국, 옆자리 사람을 도와주는 도구다.&amp;rdquo;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;2128&quot; data-start=&quot;2089&quot; data-ke-size=&quot;size16&quot;&gt;다음 부틀리에서는 또 어떤 실험이 탄생할지, 기대가 됩니다 :)&lt;/p&gt;
&lt;p data-end=&quot;2128&quot; data-start=&quot;2089&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;1942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n3xTO/dJMcahQhvC0/8XMMgEZyJtup7KVDqTZJHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n3xTO/dJMcahQhvC0/8XMMgEZyJtup7KVDqTZJHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n3xTO/dJMcahQhvC0/8XMMgEZyJtup7KVDqTZJHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn3xTO%2FdJMcahQhvC0%2F8XMMgEZyJtup7KVDqTZJHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;346&quot; height=&quot;896&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;1942&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>React-Native</category>
      <category>AI</category>
      <category>AIChatbot</category>
      <category>Bootly</category>
      <category>chatbot</category>
      <category>frontend</category>
      <category>llm</category>
      <category>REmited</category>
      <category>TeamCulture</category>
      <author>인어공쭈</author>
      <guid isPermaLink="true">https://haneui.tistory.com/97</guid>
      <comments>https://haneui.tistory.com/97#entry97comment</comments>
      <pubDate>Mon, 10 Nov 2025 15:38:39 +0900</pubDate>
    </item>
    <item>
      <title>혼자서 상위 1% 앱을 살려냈다? 프론트엔드 개발자의 피 땀 눈물 마이그레이션 생존 보고서!</title>
      <link>https://haneui.tistory.com/96</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dduzzi/btsPlM58AON/ocFX75TF4hQOHKYclgy3SK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dduzzi/btsPlM58AON/ocFX75TF4hQOHKYclgy3SK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dduzzi/btsPlM58AON/ocFX75TF4hQOHKYclgy3SK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdduzzi%2FbtsPlM58AON%2FocFX75TF4hQOHKYclgy3SK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;548&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;웹 개발자가 앱 마이그레이션을 맡으면 생기는 일 .zip&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;안녕하세요, 5년차 웹 프론트엔드 개발자입니다.&amp;rdquo;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;아, 그럼 앱도 할 줄 아시겠네요?&amp;rdquo;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rdquo;(??!?!!)&amp;rdquo;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 말을 들은 날, 제 React Native 설치 창에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;npm install과 함께&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;신호 없이 찾아온 여정&lt;/b&gt;이 열렸습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;React Native 처음 써봤습니다. 말 그대로 처음&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음엔 그냥 웹처럼&lt;span&gt;&amp;nbsp;&lt;/span&gt;div,&lt;span&gt;&amp;nbsp;&lt;/span&gt;className,&lt;span&gt;&amp;nbsp;&lt;/span&gt;px&lt;span&gt;&amp;nbsp;&lt;/span&gt;단위 쓰면 되겠지~ 싶었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;네, 바로 뚝배기 맞았습니다. React Native에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;div도 없고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;className도 없고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;px도 그냥 무시당합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;대신&lt;span&gt;&amp;nbsp;&lt;/span&gt;View,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Text,&lt;span&gt;&amp;nbsp;&lt;/span&gt;StyleSheet.create()&lt;span&gt;&amp;nbsp;&lt;/span&gt;같은 낯선 친구들과 친해져야 했고, 어느샌가 저는 &amp;ldquo;Metro 번들러가 터졌을 때는 캐시부터 지워보자&amp;rdquo;는 신념을 가진 개발자가 되어 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  구조 파악부터 컴포넌트 분해까지&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존 앱은 Flutter 기반이라 구조가 전혀 달랐고,&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이를 RN으로 옮기기 위해선 먼저 전체 플로우와 UI 구조를 세세하게 분석해야 했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333d4b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라우팅 방식 다름 &amp;rarr; React Navigation으로 재구성&lt;/li&gt;
&lt;li&gt;스크롤/페이지 전환 UX &amp;rarr; Flutter처럼 구현하려면 커스텀 작업 필요&lt;/li&gt;
&lt;li&gt;컴포넌트 분리 기준 &amp;rarr; 재사용성 vs platform specific 분기 처리&lt;/li&gt;
&lt;li&gt;WebView ..ㅠㅠ&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이걸 혼자 하려니, 하루에 폴더 구조만 3번 뒤엎은 날도 있었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;내가 만든 코드인데 내가 이해 못하는 느낌...&amp;rdquo;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;크로스 플랫폼 = 같은 코드, 다른 버그 (참 쉽죠?)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RN은 하나의 코드베이스로 Android와 iOS를 모두 지원합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;hellip;라는 말은 이론이고요. 실제로는:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333d4b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iOS에선 잘 되는데 Android에선 깜빡임&lt;/li&gt;
&lt;li&gt;Android에선 터지는데 iOS에선 멀쩡&lt;/li&gt;
&lt;li&gt;시뮬레이터에선 되는데 실기기에선 죽음&lt;/li&gt;
&lt;li&gt;됐다가 갑자기 안됌&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;어디서 분명 된다는데, 왜 나는 안 되지?&amp;rdquo;라는 자문을 하루 3번씩 하게 됐고, 결국&lt;span&gt;&amp;nbsp;&lt;/span&gt;Platform.OS&lt;span&gt;&amp;nbsp;&lt;/span&gt;조건문을 코드 곳곳에 심으며 종교처럼 외웠습니다:&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이건 Android 때문이야. 분명히 그래. (iOS일 수도 있지만&amp;hellip;, 도 아닐 수도 있지만...)&amp;rdquo;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;디바이스 반응형 이슈: 내 앱이 왜 씹히죠...?&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;웹에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;media query&lt;span&gt;&amp;nbsp;&lt;/span&gt;한 줄이면 끝나던 반응형.&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;앱에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;아이폰 미니&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;삼성 폴드&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;플립 등&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;다 따로 놀며 SafeArea가 아니라&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Danger Zone&lt;/b&gt;이었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333d4b; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iPhone 13 mini: 버튼이 잘려서 못 누름&lt;/li&gt;
&lt;li&gt;폴드, 플립: 텍스트가 뭉개짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그제서야 깨달았습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;앱 개발자들이 테스트폰을 5대씩 쌓아두는 이유는 그냥 멋이 아니었다는 걸.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;useWindowDimensions,&lt;span&gt;&amp;nbsp;&lt;/span&gt;percentage-based layout,&lt;span&gt;&amp;nbsp;&lt;/span&gt;SafeAreaView,&lt;span&gt;&amp;nbsp;&lt;/span&gt;StatusBar,&lt;span&gt;&amp;nbsp;&lt;/span&gt;FlatList&lt;span&gt;&amp;nbsp;&lt;/span&gt;등 온갖 키워드를 던지며, 결국은 화면 비율별 조건 분기라는 폭풍 속으로 빠져들었습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;디버깅은 고행이었고, 캐시는 배신자였다 (feat. 삽질의 연속)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;웹에서는 새로고침이면 되던 걸, 앱에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;npm start --reset-cache,&lt;span&gt;&amp;nbsp;&lt;/span&gt;npx pod-install,&lt;span&gt;&amp;nbsp;&lt;/span&gt;gradlew clean&lt;span&gt;&amp;nbsp;&lt;/span&gt;등&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;주술급 명령어&lt;/b&gt;를 외워야만 했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;iOS는 Xcode가 삐지면 30분 날아가고, Android는 에뮬레이터 하나 띄우는 데도 메모리와 싸워야 했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하루에 앱을 10번은 지웠다 깔고, &amp;ldquo;도대체 뭐가 문제야&amp;hellip;&amp;rdquo;라는 말을 매일 중얼거렸습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러다 보니깐 주술급 명령어들을 어느순간 다 외워지는... SUPER MAGIC&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;그럼에도 앱스토어에 올라갔습니다. 나름(?) 당당하게.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 모든 던전 클리어 끝에 앱스토어에 배포 완료.&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그때 깨달았죠.&lt;/p&gt;
&lt;blockquote style=&quot;color: #333d4b; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;개발자란, 코드를 잘 짜는 사람이 아니라 끝까지 버티는 사람이다.&amp;rsquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &amp;zwj; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;웹 개발자의 앱 도전, 그 이후&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 경험 덕분에 React Native라는 새로운 영역에서 한 뼘 더 성장할 수 있었고, 무엇보다 &amp;ldquo;나도 앱 만든 사람이다&amp;rdquo;라는 자신감이 생겼습니다. 아직도 앱을 열 때마다 감회가 새롭습니다. &amp;ldquo;와 이걸 내가 만들었어?&amp;rdquo; 하면서요.&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;끝으로 한 마디만&amp;hellip;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;웹 개발자라고 앱 못 만드는 거 아니잖아요. 다만, 약간의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;멘탈 관리 능력&lt;/b&gt;은 필수입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;고군분투 했던 저의 앱이 궁금하신 분들은 아래를 보고 다양한 피드백 부탁드립니다~!&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333d4b; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.FhTArrLMPEaw.natively&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://play.google.com/store/apps/details?id=com.FhTArrLMPEaw.natively&amp;amp;hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1752721326950&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;영끌 - 영수증 끌어모아 포인트 적립 - Google Play 앱&quot; data-og-description=&quot;영끌, 영수증 끌어오면 돈이 된다!&quot; data-og-host=&quot;play.google.com&quot; data-og-source-url=&quot;https://play.google.com/store/apps/details?id=com.FhTArrLMPEaw.natively&amp;amp;hl=ko&quot; data-og-url=&quot;https://play.google.com/store/apps/details?id=com.FhTArrLMPEaw.natively&amp;amp;hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bjSPCU/hyZjAhgxCK/wwz6GKrhqNTCVh7WoXGKL1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/A0KzT/hyZnbtocxV/XDMQOmqfBTGofJKg1kakek/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/c49mLH/hyZjcHpti1/koPrU6xYcqjk3kvE4BHCd1/img.png?width=500&amp;amp;height=296&amp;amp;face=0_0_500_296&quot;&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.FhTArrLMPEaw.natively&amp;amp;hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://play.google.com/store/apps/details?id=com.FhTArrLMPEaw.natively&amp;amp;hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bjSPCU/hyZjAhgxCK/wwz6GKrhqNTCVh7WoXGKL1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512,https://scrap.kakaocdn.net/dn/A0KzT/hyZnbtocxV/XDMQOmqfBTGofJKg1kakek/img.png?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/c49mLH/hyZjcHpti1/koPrU6xYcqjk3kvE4BHCd1/img.png?width=500&amp;amp;height=296&amp;amp;face=0_0_500_296');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;영끌 - 영수증 끌어모아 포인트 적립 - Google Play 앱&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;영끌, 영수증 끌어오면 돈이 된다!&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;play.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React-Native</category>
      <category>React-native</category>
      <category>rn</category>
      <category>리엑트네이티브</category>
      <category>앱</category>
      <category>앱개발</category>
      <category>앱개발자</category>
      <category>영끌</category>
      <author>인어공쭈</author>
      <guid isPermaLink="true">https://haneui.tistory.com/96</guid>
      <comments>https://haneui.tistory.com/96#entry96comment</comments>
      <pubDate>Thu, 17 Jul 2025 12:03:20 +0900</pubDate>
    </item>
    <item>
      <title>RN - 프론트엔드 개발자를 위한 모바일 앱 개발 가이드  </title>
      <link>https://haneui.tistory.com/95</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuRntc/btsMwsvsHOu/iBh37LGtv8ExKeMZp4yRZ1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuRntc/btsMwsvsHOu/iBh37LGtv8ExKeMZp4yRZ1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuRntc/btsMwsvsHOu/iBh37LGtv8ExKeMZp4yRZ1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuRntc%2FbtsMwsvsHOu%2FiBh37LGtv8ExKeMZp4yRZ1%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;342&quot; height=&quot;342&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;br /&gt;1. React Native란?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;React Native(RN)는 &lt;/span&gt;&lt;span&gt;&lt;b&gt;React 기반의 크로스플랫폼 모바일 앱 개발 프레임워크&lt;/b&gt;&lt;/span&gt;&lt;span&gt;이다. JavaScript 및 TypeScript를 사용하여 iOS와 Android 앱을 동시에 개발할 수 있도록 해준다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;주요 특징&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;크로스플랫폼 개발&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 하나의 코드베이스로 iOS, Android 동시 개발 가능&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;네이티브 성능&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: React와 네이티브 코드를 결합하여 최적화된 성능 제공&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;핫 리로드(Hot Reload)&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 코드 변경 시 앱을 새로 실행하지 않고 바로 반영&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;풍부한 라이브러리 지원&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: Expo, React Navigation, Redux 등과 호환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;광범위한 커뮤니티&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: Meta 및 오픈소스 커뮤니티에서 활발히 유지보수됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;2. React Native의 기본 구조&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;React Native 앱의 주요 구조는 다음과 같다:&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740554759679&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  MyReactNativeApp
 ┣ android  // 안드로이드 네이티브 코드
 ┣ ios       // iOS 네이티브 코드
 ┣ src       // 앱의 주요 React Native 코드
 ┣ assets    // 이미지, 폰트 등의 정적 파일
 ┣ components // 재사용 가능한 UI 컴포넌트
 ┣ screens   // 각 페이지별 화면 구성
 ┣ navigation // 앱 내비게이션 설정
 ┣ App.js    // 앱의 진입점
 ┗ package.json  // 프로젝트 설정 파일&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;3. React Native 설치 및 환경 설정&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;React Native는 &lt;/span&gt;&lt;span&gt;&lt;b&gt;Expo&lt;/b&gt;&lt;/span&gt;&lt;span&gt;와 &lt;/span&gt;&lt;span&gt;&lt;b&gt;React Native CLI&lt;/b&gt;&lt;/span&gt;&lt;span&gt; 두 가지 방식으로 설치할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;3.1 Expo를 사용한 설치 (쉬운 방법)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Expo는 빠르게 React Native 프로젝트를 시작할 수 있는 도구로, 네이티브 빌드를 직접 하지 않아도 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740554827196&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx create-expo-app MyApp
cd MyApp
npm start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;3.2 React Native CLI를 사용한 설치 (커스텀 가능)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;네이티브 코드를 직접 다룰 필요가 있다면 React Native CLI를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740554851318&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx react-native init MyApp
cd MyApp
npx react-native run-android # 안드로이드 실행
npx react-native run-ios # iOS 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;4. React Native의 주요 개념&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;4.1 JSX 및 컴포넌트 기반 개발&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;React Native는 &lt;/span&gt;&lt;span&gt;&lt;b&gt;JSX 문법&lt;/b&gt;&lt;/span&gt;&lt;span&gt;을 사용하며, 컴포넌트 기반 개발을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740554957599&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const App = () =&amp;gt; {
  return (
    &amp;lt;View style={styles.container}&amp;gt;
      &amp;lt;Text style={styles.text}&amp;gt;Hello, React Native!  &amp;lt;/Text&amp;gt;
    &amp;lt;/View&amp;gt;
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#f0f0f0' },
  text: { fontSize: 20, color: '#333' }
});

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;4.2 React Navigation (내비게이션)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;React Native에서 화면 이동을 관리하려면 &lt;/span&gt;&lt;span&gt;react-navigation&lt;/span&gt;&lt;span&gt;을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740554992795&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();

const App = () =&amp;gt; {
  return (
    &amp;lt;NavigationContainer&amp;gt;
      &amp;lt;Stack.Navigator&amp;gt;
        &amp;lt;Stack.Screen name=&quot;Home&quot; component={HomeScreen} /&amp;gt;
      &amp;lt;/Stack.Navigator&amp;gt;
    &amp;lt;/NavigationContainer&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;</description>
      <category>React-Native</category>
      <category>native</category>
      <category>react</category>
      <category>react native</category>
      <category>rn</category>
      <category>리엑트</category>
      <category>리엑트네이티브</category>
      <author>인어공쭈</author>
      <guid isPermaLink="true">https://haneui.tistory.com/95</guid>
      <comments>https://haneui.tistory.com/95#entry95comment</comments>
      <pubDate>Wed, 26 Feb 2025 16:31:09 +0900</pubDate>
    </item>
    <item>
      <title>JavaScript - 리스트 매핑시 여러가지 방법</title>
      <link>https://haneui.tistory.com/94</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;주요 이슈 요약&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; 금액이 0보다 큰 금액이 있는 결제 SEQ 를 취소를 해야 하는 데, 0인 SEQ 는 제외해서 매핑&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; TASF 빼고 RV의 합산금액을 초과할수 없다. 취소가능금액 해당 payment 에 가능금액으로 RV 합산금액과 비교해서 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 리스트를 매핑하는 과정에서 다양한 방법이 존재하길래 한번 정리해보았다.&lt;/p&gt;
&lt;pre id=&quot;code_1733894839448&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;onClickSave() {
    if (this.putForm.invalid || this._checkCancelAmountExceeds()) {
      this._checkValid = true;
      return;
    }

    const cancelItems = this.payments.value.flatMap((item) =&amp;gt;
      item.transactions
        .filter((transaction) =&amp;gt; transaction.cancelAmount &amp;gt; 0 &amp;amp;&amp;amp; transaction.cancelAmount != null) // 값이 없거나 0인 경우 제외
        .map((transaction) =&amp;gt; ({
          test: transaction.test,
        })),
    );
    this.submitEvent.emit({ cancelItems });
  }

  private _checkCancelAmountExceeds() {
    const exceededPayments = this.payments.value.filter((payment) =&amp;gt; {
      const airrvTransactions = payment.transactions.filter(
        (transaction) =&amp;gt; transaction.typeCode === 'OK',
      );
      const totalCancelAmount = airrvTransactions.reduce(
        (sum, transaction) =&amp;gt; sum + (transaction.cancelAmount || 0),
        0,
      );
      return totalCancelAmount &amp;gt; payment.cancelableAmount;
    });
    return exceededPayments.length &amp;gt; 0;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;대안 1: reduce를 사용한 방식&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const cancelItems = this.payments.value.reduce((acc, item) =&amp;gt; {
  const transactions = item.transactions.map((transaction) =&amp;gt; ({
    test: transaction.test,
  }));
  return acc.concat(transactions);
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;대안 2: for...of 반복문을 사용한 방식&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const cancelItems = [];
for (const item of this.payments.value) {
  for (const transaction of item.transactions) {
    cancelItems.push({
          test: transaction.test,
    });
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;대안 3: map과 concat 조합 방식&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const cancelItems = [].concat(
  ...this.payments.value.map((item) =&amp;gt;
    item.transactions.map((transaction) =&amp;gt; ({
        test: transaction.test,
    }))
  )
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 테스트 코드를 작성해보았는데 여러가지 방법이 있지만 가독성 및 효율을 따져서 나의 픽은 flatMap 이었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flatMap 과 reduce 는 잘 안쓰는 메서드 였는데 이번기회에 잘알게 되어서 좋은 경험이었다. 굳굳&lt;/p&gt;</description>
      <category>JavaScript</category>
      <category>concat</category>
      <category>flatMap</category>
      <category>map</category>
      <category>reduce</category>
      <author>인어공쭈</author>
      <guid isPermaLink="true">https://haneui.tistory.com/94</guid>
      <comments>https://haneui.tistory.com/94#entry94comment</comments>
      <pubDate>Wed, 11 Dec 2024 14:31:33 +0900</pubDate>
    </item>
    <item>
      <title>JavaScript - About 비동기, AJAX란?</title>
      <link>https://haneui.tistory.com/93</link>
      <description>&lt;h1 data-pm-slice=&quot;0 0 []&quot;&gt;&lt;span&gt;AJAX란 무엇인가?&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;AJAX&lt;/b&gt;&lt;/span&gt;&lt;span&gt;는 &quot;Asynchronous JavaScript And XML&quot;의 약자로, JavaScript를 사용하여 &lt;/span&gt;&lt;span&gt;&lt;b&gt;서버와 비동기적으로 데이터를 주고받는 기술&lt;/b&gt;&lt;/span&gt;&lt;span&gt;이다. 비동기 처리를 통해 웹페이지 전체를 새로고침하지 않고도 필요한 데이터를 서버로부터 받아올 수 있다. 이로 인해 사용자 경험(UX)이 크게 향상된다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;AJAX의 동작 원리&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AJAX는 클라이언트(브라우저)와 서버 간의 비동기 데이터 통신을 관리하는 메커니즘으로 다음의 단계로 동작한다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;클라이언트 요청&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JavaScript가 서버로 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;요청은 비동기적으로 수행되며, 웹페이지가 새로고침되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 처리&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버는 클라이언트 요청을 처리하고, 데이터(예: JSON, XML)를 생성.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 응답&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버로부터 응답 데이터를 받아 웹페이지의 특정 부분을 동적으로 업데이트.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 과정에서 전체 페이지가 다시 로드되지 않기 때문에 사용자는 더 빠르고 유연한 경험을 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;AJAX 구현을 위한 주요 도구&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AJAX는 특정 기술이 아니라 여러 도구와 개념의 조합이다. 다음은 AJAX를 구현하는 데 사용되는 주요 도구들이다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;1. XMLHttpRequest&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AJAX 구현을 위한 초기 기술이다. 비동기적으로 서버와 통신할 수 있도록 설계되었다. 하지만 사용법이 복잡하고, 코드가 길어질수록 &quot;콜백 지옥&quot; 문제를 초래할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);

xhr.onload = function () {
  if (xhr.status === 200) {
    console.log('Response:', xhr.responseText);
  }
};

xhr.send();&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;2. Fetch API&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;Fetch API&lt;/b&gt;&lt;/span&gt;&lt;span&gt;는 XMLHttpRequest의 현대적인 대안으로, &lt;/span&gt;&lt;span&gt;&lt;b&gt;Promise&lt;/b&gt;&lt;/span&gt;&lt;span&gt; 기반으로 설계되어 비동기 작업을 더 간결하게 처리할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;fetch('https://api.example.com/data')
  .then((response) =&amp;gt; response.json())
  .then((data) =&amp;gt; console.log('Data:', data))
  .catch((error) =&amp;gt; console.error('Error:', error));&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;3. Promise&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Promise는 비동기 작업의 결과를 나타내는 객체이다. AJAX 요청 후 서버 응답을 처리하거나 에러를 처리하는 등의 작업을 체계적으로 관리할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;const getData = () =&amp;gt;
  new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve('Data received');
    }, 1000);
  });

getData()
  .then((data) =&amp;gt; console.log(data))
  .catch((error) =&amp;gt; console.error(error));&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;4. Async/Await&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Async/Await은 Promise를 기반으로 하여 비동기 작업을 동기 코드처럼 읽기 쉽게 작성할 수 있도록 도와준다. Fetch API와 함께 사용하면 더욱 간결하게 AJAX 요청을 처리할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;const fetchData = async () =&amp;gt; {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log('Data:', data);
  } catch (error) {
    console.error('Error:', error);
  }
};

fetchData();&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;AJAX의 장점과 단점&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;장점&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;새로고침 없는 데이터 업데이트&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 필요한 부분만 업데이트하므로 빠르고 유연한 사용자 경험을 제공한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;효율적인 데이터 전송&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 전체 페이지를 다시 로드하지 않으므로 데이터 전송량이 줄어든다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;다양한 데이터 형식 지원&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: JSON, XML, HTML, 텍스트 등 다양한 데이터 형식을 주고받을 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;단점&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Cross-Origin 제한&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 기본적으로 동일 출처(Same-Origin) 정책을 따르므로, 다른 도메인과 통신하려면 CORS 설정이 필요하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;SEO 문제&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 클라이언트에서 동적으로 데이터를 로드하므로, 검색 엔진이 콘텐츠를 인덱싱하지 못할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;구형 브라우저 호환성&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: Fetch API와 같은 최신 기술은 구형 브라우저에서 지원되지 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;AJAX와 함께 사용되는 데이터 형식: JSON&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AJAX에서 가장 많이 사용되는 데이터 형식은 **JSON(JavaScript Object Notation)**이다. 가볍고 구조화된 데이터를 처리하기에 적합하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;John Doe&quot;,
  &quot;age&quot;: 30,
  &quot;email&quot;: &quot;john.doe@example.com&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;JSON 데이터를 처리하는 JavaScript 코드 예제:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;fetch('https://api.example.com/user')
  .then((response) =&amp;gt; response.json())
  .then((data) =&amp;gt; {
    console.log(data.name); // John Doe
  });&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;비유로 이해하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AJAX를 &lt;b&gt;비동기 통신의 철학&lt;/b&gt;이라고 생각하고, 그 위에 쌓인 도구들을 기술 스택으로 이해해보자&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;AJAX는 비동기 데이터 통신의 &lt;b&gt;기본 철학&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;XMLHttpRequest&lt;/b&gt;는 AJAX를 구현하기 위해 처음 만들어진 기술.&lt;/li&gt;
&lt;li&gt;이후 등장한 &lt;b&gt;Fetch API&lt;/b&gt;는 AJAX 구현을 더 간단하게 만든 현대적 도구.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Promise&lt;/b&gt;는 AJAX 요청을 관리하고 비동기 작업을 체계적으로 처리하기 위한 메커니즘.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Async/Await&lt;/b&gt;은 Promise를 쉽게 사용하기 위한 더 간단한 문법.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>JavaScript</category>
      <category>Ajax</category>
      <category>Angular</category>
      <category>API</category>
      <category>async</category>
      <category>await</category>
      <category>fetch</category>
      <category>Promise</category>
      <author>인어공쭈</author>
      <guid isPermaLink="true">https://haneui.tistory.com/93</guid>
      <comments>https://haneui.tistory.com/93#entry93comment</comments>
      <pubDate>Tue, 10 Dec 2024 13:53:49 +0900</pubDate>
    </item>
  </channel>
</rss>